Qual è il modo più semplice per analizzare un file INI in Java?


104

Sto scrivendo una sostituzione immediata per un'applicazione legacy in Java. Uno dei requisiti è che i file ini utilizzati dalla vecchia applicazione debbano essere letti così come sono nella nuova applicazione Java. Il formato di questo file ini è lo stile comune di Windows, con sezioni di intestazione e coppie chiave = valore, utilizzando # come carattere per i commenti.

Ho provato a utilizzare la classe Properties da Java, ma ovviamente non funzionerà se ci sono conflitti di nome tra intestazioni diverse.

Quindi la domanda è: quale sarebbe il modo più semplice per leggere questo file INI e accedere alle chiavi?

Risposte:


121

La libreria che ho usato è ini4j . È leggero e analizza facilmente i file ini. Inoltre non utilizza dipendenze esoteriche per altri 10.000 file jar, poiché uno degli obiettivi di progettazione era utilizzare solo l'API Java standard

Questo è un esempio di come viene utilizzata la libreria:

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));

2
non funziona, l'errore dice "IniFile non può essere risolto in un tipo"
Caballero

@Caballero sì, sembra che la IniFilelezione sia stata eliminata, provaIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly

2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html probabilmente rimarrà aggiornato anche se cambiano di nuovo il nome della classe.
Lokathor

Questa cosa funziona ancora? Ho scaricato il sorgente 0.5.4 e non è stato nemmeno compilato, e non era una dipendenza mancante .. non valeva la pena dedicarsi di più. Inoltre ini4j ha un sacco di altre schifezze che non ci servono, la modifica del registro di Windoze ... dai. #LinuxMasterRace ... Ma immagino che se funziona per te, mettiti al tappeto.
Utente

Per il file INI che ho scritto, ho dovuto usare la Winiclasse, come illustrato nel tutorial "One Minute"; il prefs.node("something").get("val", null)non ha funzionato il modo in cui mi aspettavo.
Agi Hammerthief

65

Come accennato , ini4j può essere utilizzato per ottenere questo risultato. Lasciatemi mostrare un altro esempio.

Se abbiamo un file INI come questo:

[header]
key = value

Il seguente dovrebbe essere visualizzato valuein STDOUT:

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

Controlla i tutorial per ulteriori esempi.


2
! Neat Ho sempre usato BufferedReader e un po 'di codice di analisi delle stringhe copia / incolla per non dover aggiungere ancora un'altra dipendenza alle mie applicazioni (che può esplodere di proporzioni quando inizi ad aggiungere API di terze parti anche per le attività più semplici ). Ma non posso ignorare questo tipo di semplicità.
Gimby

30

Semplice come 80 righe:

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 ));
   }
}

+1 Semplicemente per l'uso di regex Pattern / Matcher. Funziona come un fascino
kalelien

Non è una soluzione perfetta ma un buon punto di partenza, ad esempio, la mancanza di getSection () e getString () restituisce defaultValue solo se manca l'intera sezione.
Jack Miller

qual è la differenza di prestazioni tra un tale regx e lavorare con l'implementazione di stringhe?
Ewoks,

Le prestazioni durante la lettura di un piccolo file di configurazione non sono un problema. aprire e chiudere un file è molto più faticoso, credo.
Aerospaziale

Sì, questo è tanto semplice quanto dovrebbe essere per semplici casi d'uso. Non sono sicuro del motivo per cui le persone vogliono complicarlo. Se sei preoccupato per le prestazioni (o altre preoccupazioni come la segnalazione degli errori), sì, probabilmente vorrai usare qualcos'altro (probabilmente un altro formato completamente).
Utente

16

Ecco un esempio semplice ma potente, utilizzando la configurazione HierarchicalINIC della classe apache :

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");
}

Commons Configuration ha una serie di dipendenze di runtime . Come minimo, commons-lang e commons-logging sono necessari. A seconda di cosa stai facendo con esso, potresti aver bisogno di librerie aggiuntive (vedi il link precedente per i dettagli).


1
Questa sarebbe la mia risposta corretta. Molto semplice da usare e versatile.
marcolopes

configurazioni comuni non raccolte.
jantox

13

Oppure con l'API Java standard puoi utilizzare java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}

12
Il problema è che, con i file ini, la struttura ha intestazioni. La classe Property non sa come gestire le intestazioni e potrebbero verificarsi conflitti di nome
Mario Ortegón

2
Inoltre, la Propertiesclasse non riceve correttamente i valori che contengono \
rds

3
+1 per la soluzione semplice, ma si adatta solo ai file di configurazione semplici, come hanno notato Mario Ortegon e rds.
Benj

1
Il file INI contiene [sezione], il file delle proprietà contiene le assegnazioni.
Aerospaziale


10

In 18 righe, estendendo il java.util.Propertiesper analizzare in più sezioni:

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;
}

2

Un'altra opzione è Apache Commons Config ha anche una classe per il caricamento da file INI . Ha alcune dipendenze di runtime , ma per i file INI dovrebbe richiedere solo raccolte Commons, lang e registrazione.

Ho usato Commons Config su progetti con le loro proprietà e configurazioni XML. È molto facile da usare e supporta alcune funzionalità piuttosto potenti.



2

Personalmente preferisco Confucious .

È bello, poiché non richiede dipendenze esterne, è piccolo - solo 16K e carica automaticamente il file ini durante l'inizializzazione. Per esempio

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);

3 anni dopo, Mark e l'OP sono probabilmente morti di vecchiaia ... ma questa è davvero una buona scoperta.
Utente

6
Uso un bastone per andare in giro, ma sono ancora vivo e vegeto
Mario Ortegón

@ MarioOrtegón: fantastico sentirlo!
ישו אוהב אותך

0

La soluzione di hoat4 è molto elegante e semplice. Funziona con tutti i file ini sani . Tuttavia, ne ho visti molti che hanno caratteri di spazio non di escape nella chiave .
Per risolvere questo problema, ho scaricato e modificato una copia di java.util.Properties. Anche se questo è un po 'poco ortodosso ea breve termine, le modifiche effettive erano solo poche righe e piuttosto semplici. Presenterò una proposta alla comunità JDK per includere le modifiche.

Aggiungendo una variabile di classe interna:

private boolean _spaceCharOn = false;

Controllo l'elaborazione relativa alla scansione del punto di separazione chiave / valore. Ho sostituito il codice di ricerca dei caratteri spazio con un piccolo metodo privato che restituisce un valore booleano a seconda dello stato della variabile sopra.

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

Questo metodo viene utilizzato in due punti all'interno del metodo privato load0(...).
C'è anche un metodo pubblico per attivarlo, ma sarebbe meglio usare la versione originale di Propertiesse il separatore di spazio non è un problema per la tua applicazione.

Se c'è interesse, sarei disposto a inserire il codice nel mio IniFile.javafile. Funziona con entrambe le versioni di Properties.


0

Utilizzando la risposta di @Aerospace, mi sono reso conto che è legittimo che i file INI abbiano sezioni senza valori-chiave. In questo caso, l'aggiunta alla mappa di primo livello dovrebbe avvenire prima che vengano trovati i valori-chiave, ad es. (Aggiornamento minimo per Java 8):

            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);
            }

-1

È proprio così semplice .....

//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");

1
Questo non gestisce le sezioni INI
Igor Melnichenko
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.