Was ist der einfachste Weg, eine INI-Datei in Java zu analysieren?

104

Ich schreibe einen Drop-In-Ersatz für eine Legacy-Anwendung in Java. Eine der Anforderungen ist, dass die INI-Dateien, die die ältere Anwendung verwendet, unverändert in die neue Java-Anwendung eingelesen werden müssen. Das Format dieser INI-Dateien ist der übliche Windows-Stil mit Kopfzeilenabschnitten und Schlüssel-Wert-Paaren, wobei # als Zeichen für das Kommentieren verwendet wird.

Ich habe versucht, die Properties-Klasse aus Java zu verwenden, aber das funktioniert natürlich nicht, wenn zwischen verschiedenen Headern Namenskonflikte auftreten.

Die Frage ist also, wie Sie diese INI-Datei am einfachsten einlesen und auf die Schlüssel zugreifen können.

Mario Ortegón
quelle

Antworten:

121

Die Bibliothek, die ich benutzt habe, ist ini4j . Es ist leicht und analysiert die INI-Dateien mit Leichtigkeit. Außerdem werden keine esoterischen Abhängigkeiten zu 10.000 anderen JAR-Dateien verwendet, da eines der Entwurfsziele darin bestand, nur die Standard-Java-API zu verwenden

Dies ist ein Beispiel für die Verwendung der Bibliothek:

Ini ini = new Ini(new File(filename));
java.util.prefs.Preferences prefs = new IniPreferences(ini);
System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));
Mario Ortegón
quelle
2
funktioniert nicht, Fehler sagt "IniFile kann nicht in einen Typ aufgelöst werden"
Caballero
@ Caballero ja es scheint, dass die IniFileKlasse herausgenommen wurde, versuchen SieIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html wird wahrscheinlich auf dem neuesten Stand bleiben, auch wenn der Klassenname erneut geändert wird.
Lokathor
Funktioniert das Ding überhaupt noch? Die 0.5.4-Quelle wurde heruntergeladen und nicht einmal erstellt, und es fehlte keine Abhängigkeit. Es lohnt sich nicht, sich mehr damit zu beschäftigen. Auch ini4j hat eine Menge anderen Mist drin, den wir nicht brauchen, Windoze Registry Editing ... komm schon. #LinuxMasterRace ... Aber ich denke, wenn es für Sie funktioniert, schlagen Sie sich aus.
Benutzer
Für die INI-Datei, die ich geschrieben habe, musste ich die WiniKlasse verwenden, wie im "One Minute" -Tutorial dargestellt. Das prefs.node("something").get("val", null)hat nicht so funktioniert, wie ich es erwartet hatte.
Agi Hammerthief
65

Wie bereits erwähnt , kann ini4j verwendet werden, um dies zu erreichen. Lassen Sie mich ein weiteres Beispiel zeigen.

Wenn wir eine INI-Datei wie diese haben:

[header]
key = value

Für valueSTDOUT sollte Folgendes angezeigt werden:

Ini ini = new Ini(new File("/path/to/file"));
System.out.println(ini.get("header", "key"));

Weitere Beispiele finden Sie in den Tutorials .

Tshepang
quelle
2
Ordentlich! Ich habe immer nur BufferedReader und ein bisschen String / Parsing-Code zum Kopieren / Einfügen verwendet, um meinen Anwendungen keine weitere Abhängigkeit hinzufügen zu müssen (die möglicherweise überproportional wird, wenn Sie APIs von Drittanbietern für die einfachsten Aufgaben hinzufügen ). Aber ich kann diese Art von Einfachheit nicht ignorieren.
Gimby
29

So einfach wie 80 Zeilen:

package windows.prefs;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class IniFile {

   private Pattern  _section  = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*" );
   private Pattern  _keyValue = Pattern.compile( "\\s*([^=]*)=(.*)" );
   private Map< String,
      Map< String,
         String >>  _entries  = new HashMap<>();

   public IniFile( String path ) throws IOException {
      load( path );
   }

   public void load( String path ) throws IOException {
      try( BufferedReader br = new BufferedReader( new FileReader( path ))) {
         String line;
         String section = null;
         while(( line = br.readLine()) != null ) {
            Matcher m = _section.matcher( line );
            if( m.matches()) {
               section = m.group( 1 ).trim();
            }
            else if( section != null ) {
               m = _keyValue.matcher( line );
               if( m.matches()) {
                  String key   = m.group( 1 ).trim();
                  String value = m.group( 2 ).trim();
                  Map< String, String > kv = _entries.get( section );
                  if( kv == null ) {
                     _entries.put( section, kv = new HashMap<>());   
                  }
                  kv.put( key, value );
               }
            }
         }
      }
   }

   public String getString( String section, String key, String defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return kv.get( key );
   }

   public int getInt( String section, String key, int defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Integer.parseInt( kv.get( key ));
   }

   public float getFloat( String section, String key, float defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Float.parseFloat( kv.get( key ));
   }

   public double getDouble( String section, String key, double defaultvalue ) {
      Map< String, String > kv = _entries.get( section );
      if( kv == null ) {
         return defaultvalue;
      }
      return Double.parseDouble( kv.get( key ));
   }
}
Luft- und Raumfahrt
quelle
+1 Einfach zur Verwendung von Regex Pattern / Matcher. Funktioniert wie ein Zauber
Kalelien
Keine perfekte Lösung, aber ein guter Ausgangspunkt, z. B. fehlende getSection () und getString () geben nur dann defaultValue zurück, wenn der gesamte Abschnitt fehlt.
Jack Miller
Was ist der Leistungsunterschied zwischen einem solchen Regx und der Arbeit mit der String-Implementierung?
Ewoks
Die Leistung beim Lesen einer kleinen Konfigurationsdatei spielt keine Rolle. Das Öffnen und Schließen einer Datei ist meiner Meinung nach viel aufwendiger.
Luft- und Raumfahrt
Ja, dies ist ungefähr so ​​einfach, wie es für einfache Anwendungsfälle sein sollte. Ich bin mir nicht sicher, warum die Leute es komplizieren wollen. Wenn Sie sich Gedanken über die Leistung machen (oder andere Bedenken wie Fehlerberichte), möchten Sie wahrscheinlich etwas anderes verwenden (wahrscheinlich ein ganz anderes Format).
Benutzer
16

Hier ist ein einfaches, aber leistungsstarkes Beispiel mit der Apache-Klasse HierarchicalINIConfiguration :

HierarchicalINIConfiguration iniConfObj = new HierarchicalINIConfiguration(iniFile); 

// Get Section names in ini file     
Set setOfSections = iniConfObj.getSections();
Iterator sectionNames = setOfSections.iterator();

while(sectionNames.hasNext()){

 String sectionName = sectionNames.next().toString();

 SubnodeConfiguration sObj = iniObj.getSection(sectionName);
 Iterator it1 =   sObj.getKeys();

    while (it1.hasNext()) {
    // Get element
    Object key = it1.next();
    System.out.print("Key " + key.toString() +  " Value " +  
                     sObj.getString(key.toString()) + "\n");
}

Die Commons-Konfiguration weist eine Reihe von Laufzeitabhängigkeiten auf . Zumindest sind commons-lang und commons-logging erforderlich. Je nachdem, was Sie damit machen, benötigen Sie möglicherweise zusätzliche Bibliotheken (Details siehe vorherigen Link).

user50217
quelle
1
Dies wäre meine richtige Antwort. Sehr einfach zu bedienen und vielseitig.
marcolopes
Commons-Konfigurationen, keine Sammlungen.
Jantox
13

Oder mit der Standard-Java-API können Sie java.util.Properties verwenden :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Peter
quelle
12
Problem ist, dass bei INI-Dateien die Struktur Header hat. Die Property-Klasse weiß nicht, wie sie mit den Headern umgehen soll, und es kann zu Namenskonflikten kommen
Mario Ortegón,
2
Außerdem Propertieserhält die Klasse nicht richtig Werte, die \
rds
3
+1 für die einfache Lösung, passt aber nur für einfache Konfigurationsdateien, wie Mario Ortegon und rds es bemerkt haben.
Benj
1
Die INI-Datei enthält [Abschnitt], die Eigenschaftendatei enthält Zuweisungen.
Luft- und Raumfahrt
10

In 18 Zeilen wird das java.util.PropertiesParsen in mehrere Abschnitte erweitert:

public static Map<String, Properties> parseINI(Reader reader) throws IOException {
    Map<String, Properties> result = new HashMap();
    new Properties() {

        private Properties section;

        @Override
        public Object put(Object key, Object value) {
            String header = (((String) key) + " " + value).trim();
            if (header.startsWith("[") && header.endsWith("]"))
                return result.put(header.substring(1, header.length() - 1), 
                        section = new Properties());
            else
                return section.put(key, value);
        }

    }.load(reader);
    return result;
}
hoat4
quelle
2

Eine weitere Option ist, dass Apache Commons Config auch eine Klasse zum Laden aus INI-Dateien hat . Es gibt einige Laufzeitabhängigkeiten , aber für INI-Dateien sollten nur Commons-Sammlungen, lang und Protokollierung erforderlich sein.

Ich habe Commons Config für Projekte mit ihren Eigenschaften und XML-Konfigurationen verwendet. Es ist sehr einfach zu bedienen und unterstützt einige ziemlich mächtige Funktionen.

John Meagher
quelle
2

Sie könnten JINIFile versuchen. Ist eine Übersetzung des TIniFile aus Delphi, aber für Java

https://github.com/SubZane/JIniFile

Andreas Norman
quelle
2

Ich persönlich bevorzuge Konfuzious .

Es ist schön, da es keine externen Abhängigkeiten erfordert, es ist winzig - nur 16 KB, und lädt Ihre INI-Datei bei der Initialisierung automatisch. Z.B

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
Kennzeichen
quelle
3 Jahre später sind Mark und das OP wahrscheinlich an Altersschwäche gestorben ... aber das ist ein wirklich guter Fund.
Benutzer
6
Ich benutze einen Stock, um herumzukommen, aber noch am Leben und kickend
Mario Ortegón
@ MarioOrtegón: Schön das zu hören!
3 אוהב אותך
0

Die Lösung von hoat4 ist sehr elegant und einfach. Es funktioniert für alle vernünftigen INI-Dateien. Ich habe jedoch viele gesehen, die nicht maskierte Leerzeichen im Schlüssel haben .
Um dies zu lösen, habe ich eine Kopie von heruntergeladen und geändert java.util.Properties. Obwohl dies ein wenig unorthodox und kurzfristig ist, waren die eigentlichen Mods nur ein paar Zeilen und recht einfach. Ich werde der JDK-Community einen Vorschlag unterbreiten, um die Änderungen aufzunehmen.

Durch Hinzufügen einer internen Klassenvariablen:

private boolean _spaceCharOn = false;

Ich steuere die Verarbeitung im Zusammenhang mit dem Scannen nach dem Schlüssel / Wert-Trennungspunkt. Ich habe den Suchcode für Leerzeichen durch eine kleine private Methode ersetzt, die abhängig vom Status der obigen Variablen einen Booleschen Wert zurückgibt.

private boolean isSpaceSeparator(char c) {
    if (_spaceCharOn) {
        return (c == ' ' || c == '\t' || c == '\f');
    } else {
        return (c == '\t' || c == '\f');
    }
}

Diese Methode wird an zwei Stellen innerhalb der privaten Methode verwendet load0(...).
Es gibt auch eine öffentliche Methode zum Einschalten. Es ist jedoch besser, die Originalversion von zu verwenden, Propertieswenn das Leerzeichen für Ihre Anwendung kein Problem darstellt.

Bei Interesse bin ich bereit, den Code in meine IniFile.javaDatei aufzunehmen. Es funktioniert mit beiden Versionen von Properties.

Bradley Willcott
quelle
0

Durch die Antwort von @Aerospace wurde mir klar, dass es für INI-Dateien legitim ist, Abschnitte ohne Schlüsselwerte zu haben. In diesem Fall sollte das Hinzufügen zur Top-Level-Map erfolgen, bevor Schlüsselwerte gefunden werden, z. B. (für Java 8 minimal aktualisiert):

            Path location = ...;
            try (BufferedReader br = new BufferedReader(new FileReader(location.toFile()))) {
                String line;
                String section = null;
                while ((line = br.readLine()) != null) {
                    Matcher m = this.section.matcher(line);
                    if (m.matches()) {
                        section = m.group(1).trim();
                        entries.computeIfAbsent(section, k -> new HashMap<>());
                    } else if (section != null) {
                        m = keyValue.matcher(line);
                        if (m.matches()) {
                            String key = m.group(1).trim();
                            String value = m.group(2).trim();
                            entries.get(section).put(key, value);
                        }
                    }
                }
            } catch (IOException ex) {
                System.err.println("Failed to read and parse INI file '" + location + "', " + ex.getMessage());
                ex.printStackTrace(System.err);
            }
Denis Baranov
quelle
-2

So einfach ist das .....

//import java.io.FileInputStream;
//import java.io.FileInputStream;

Properties prop = new Properties();
//c:\\myapp\\config.ini is the location of the ini file
//ini file should look like host=localhost
prop.load(new FileInputStream("c:\\myapp\\config.ini"));
String host = prop.getProperty("host");
Desmond
quelle
1
Dies behandelt keine INI-Abschnitte
Igor Melnichenko