Apa cara termudah untuk mengurai file INI di Java?

104

Saya menulis pengganti drop-in untuk aplikasi lama di Java. Salah satu persyaratannya adalah bahwa file ini yang digunakan aplikasi lama harus dibaca apa adanya ke dalam Aplikasi Java yang baru. Format file ini adalah gaya windows umum, dengan bagian header dan pasangan kunci = nilai, menggunakan # sebagai karakter untuk berkomentar.

Saya mencoba menggunakan kelas Properties dari Java, tetapi tentu saja itu tidak akan berhasil jika ada bentrokan nama antara header yang berbeda.

Jadi pertanyaannya adalah, apa cara termudah untuk membaca di file INI dan mengakses kuncinya?

Mario Ortegón
sumber

Jawaban:

121

Perpustakaan yang saya gunakan adalah ini4j . Ini ringan dan mem-parsing file ini dengan mudah. Juga tidak menggunakan dependensi esoterik ke 10.000 file jar lainnya, karena salah satu tujuan desainnya adalah hanya menggunakan Java API standar

Ini adalah contoh bagaimana perpustakaan digunakan:

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
sumber
2
tidak berfungsi, kesalahan mengatakan "IniFile tidak dapat diselesaikan ke jenis"
Caballero
@Caballero ya tampaknya IniFilekelas itu diambil, cobaIni ini = new Ini(new File("/path/to/file"));
Mehdi Karamosly
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html mungkin akan tetap up to date meskipun mereka mengubah nama kelas lagi.
Lokathor
Apa benda ini bisa berfungsi lagi? Mengunduh 0.5.4 sumber dan itu bahkan tidak membangun, dan itu bukan ketergantungan yang hilang .. tidak sepadan dengan waktu untuk repot-repot lagi. Juga ini4j memiliki banyak sampah lain di sana yang tidak kita butuhkan, penyuntingan registri Windoze ... ayolah. #LinuxMasterRace ... Tapi saya rasa jika itu berhasil untuk Anda, jatuhkan diri Anda sendiri.
Pengguna
Untuk file INI yang saya tulis, saya harus menggunakan Winikelas, seperti yang diilustrasikan dalam tutorial "Satu Menit"; yang prefs.node("something").get("val", null)tidak bekerja dengan cara yang saya harapkan.
Agi Hammerthief
65

Seperti yang disebutkan , ini4j dapat digunakan untuk mencapai ini. Izinkan saya menunjukkan satu contoh lainnya.

Jika kami memiliki file INI seperti ini:

[header]
key = value

Berikut ini akan ditampilkan valueke STDOUT:

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

Lihat tutorial untuk lebih banyak contoh.

tshepang
sumber
2
Rapi! Saya selalu baru saja menggunakan BufferedReader dan sedikit salin / tempel kode parsing String agar tidak perlu menambahkan ketergantungan lain ke aplikasi saya (yang dapat meledak dari proporsi ketika Anda mulai menambahkan API pihak ketiga bahkan untuk tugas yang paling sederhana ). Tapi saya tidak bisa mengabaikan kesederhanaan semacam ini.
Gimby
30

Sesederhana 80 baris:

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 ));
   }
}
Dirgantara
sumber
+1 Cukup untuk penggunaan Pola regex / Matcher. Bekerja seperti pesona
kalelien
Bukan solusi sempurna tetapi titik awal yang baik, misalnya, getSection () dan getString () yang hilang hanya mengembalikan defaultValue jika seluruh bagian hilang.
Jack Miller
apa perbedaan kinerja antara regx vs bekerja dengan implementasi string?
Ewoks
Performa saat membaca file konfigurasi kecil bukanlah masalah. Saya yakin membuka dan menutup file jauh lebih memakan waktu.
Dirgantara
Ya, ini sesederhana yang seharusnya untuk kasus penggunaan sederhana. Tidak yakin mengapa orang ingin memperumitnya. Jika Anda khawatir tentang kinerja (atau masalah lain seperti pelaporan kesalahan), ya, Anda mungkin ingin menggunakan sesuatu yang lain (mungkin format lain seluruhnya).
Pengguna
16

Berikut adalah contoh sederhana namun kuat, menggunakan kelas apache 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");
}

Konfigurasi Commons memiliki sejumlah dependensi runtime . Minimal, diperlukan commons-lang dan commons-logging . Bergantung pada apa yang Anda lakukan dengannya, Anda mungkin memerlukan pustaka tambahan (lihat tautan sebelumnya untuk detailnya).

pengguna50217
sumber
1
Ini akan menjadi jawaban saya yang benar. Sangat mudah digunakan dan serbaguna.
marcolopes
konfigurasi commons bukan koleksi.
jantox
13

Atau dengan Java API standar Anda dapat menggunakan java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Peter
sumber
12
Masalahnya adalah, dengan file ini, struktur memiliki header. Kelas Properti tidak tahu cara menangani header, dan mungkin ada bentrokan nama
Mario Ortegón
2
Selain itu, Propertieskelas tidak mendapatkan nilai yang berisi \
rds
3
1 untuk solusi sederhana, tetapi hanya cocok untuk file konfigurasi sederhana, seperti yang diketahui Mario Ortegon dan rds.
Benj
1
File INI berisi [bagian], file properti berisi tugas.
Dirgantara
1
format file: 1 / berorientasi garis sederhana atau 2 / format XML sederhana atau 3 / berorientasi garis sederhana, menggunakan ISO 8859-1 (dengan native2ascii
pelolosan
10

Dalam 18 baris, perpanjang java.util.Propertiesto parse menjadi beberapa bagian:

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
sumber
2

Pilihan lainnya adalah Apache Commons Config juga memiliki kelas untuk memuat dari file INI . Itu memang memiliki beberapa dependensi runtime , tetapi untuk file INI hanya memerlukan koleksi Commons, lang, dan logging.

Saya telah menggunakan Commons Config pada proyek dengan properti dan konfigurasi XML-nya. Ini sangat mudah digunakan dan mendukung beberapa fitur yang cukup kuat.

John Meagher
sumber
2

Saya pribadi lebih suka Confucious .

Ini bagus, karena tidak memerlukan dependensi eksternal, ukurannya kecil - hanya 16K, dan secara otomatis memuat file ini saat inisialisasi. Misalnya

Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);
Menandai
sumber
3 tahun kemudian, Mark dan OP mungkin meninggal karena usia tua ... tapi ini penemuan yang sangat bagus.
Pengguna
6
Saya menggunakan tongkat untuk berkeliling, tetapi masih hidup dan
menendang
@ MarioOrtegón: Senang mendengarnya!
ישו אוהב אותך
0

solusi hoat4 sangat elegan dan sederhana. Ia bekerja untuk semua file ini waras . Namun, saya telah melihat banyak yang memiliki karakter spasi yang tidak lolos di kuncinya .
Untuk mengatasi ini, saya telah mengunduh dan memodifikasi salinan java.util.Properties. Meskipun ini sedikit tidak ortodoks, dan jangka pendek, mod sebenarnya hanyalah beberapa baris dan cukup sederhana. Saya akan ajukan proposal ke komunitas JDK untuk memasukkan perubahan tersebut.

Dengan menambahkan variabel kelas internal:

private boolean _spaceCharOn = false;

Saya mengontrol pemrosesan yang terkait dengan pemindaian untuk titik pemisahan kunci / nilai. Saya mengganti kode pencarian karakter spasi dengan metode privat kecil yang mengembalikan boolean tergantung pada status variabel di atas.

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

Metode ini digunakan di dua tempat dalam metode privat load0(...).
Ada juga metode publik untuk mengaktifkannya, tetapi akan lebih baik menggunakan versi asli Propertiesjika pemisah spasi tidak menjadi masalah untuk aplikasi Anda.

Jika ada minat, saya bersedia memposting kode ke IniFile.javafile saya . Ia bekerja dengan salah satu versi Properties.

Bradley Willcott
sumber
0

Menggunakan jawaban oleh @Aerospace, saya menyadari bahwa sah untuk file INI memiliki bagian tanpa nilai kunci apa pun. Dalam kasus ini, penambahan ke peta tingkat atas harus terjadi sebelum nilai kunci apa pun ditemukan, misalnya (diperbarui minimal untuk 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);
            }
Denis Baranov
sumber
-1

Sesederhana ini .....

//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
sumber
1
Ini tidak menangani bagian INI
Igor Melnichenko