Який найпростіший спосіб розібрати INI-файл на Java?


104

Я пишу заміну заміни для застарілої програми на Java. Однією з вимог є те, щоб ini-файли, які використовували старіші програми, повинні бути прочитані як нові в додатку Java. Формат цих ini-файлів є загальним стилем Windows, із розділами заголовків та парами key = value, використовуючи # як символ для коментарів.

Я спробував використовувати клас Властивості від Java, але, звичайно, це не спрацює, якщо між різними заголовками є зіткнення імен.

Отже, питання полягає в тому, що було б найпростішим способом прочитати цей файл INI та отримати доступ до клавіш?

Відповіді:


121

Я використовував бібліотеку ini4j . Він легкий і легко розбирає файли ini. Крім того, він не використовує езотеричних залежностей від 10 000 інших файлів jar, оскільки однією з цілей проектування було використання лише стандартного Java API

Це приклад того, як використовується бібліотека:

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
не працює, помилка каже: "IniFile неможливо вирішити до типу"
Caballero

@Caballero так, схоже, IniFileклас вивели, спробуйтеIni ini = new Ini(new File("/path/to/file"));
Мехді Карамослі

2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html , ймовірно, буде в курсі останніх , навіть якщо вони знову змінить назву класу.
Локатор

Це річ навіть більше не працює? Завантажили джерело 0.5.4, і його навіть не створили, і це не було відсутнім залежністю. Не варто витрачати час на це більше. Крім того, у ini4j є багато інших лайнів, які нам там не потрібні, редагування реєстру Windoze ... давай. #LinuxMasterRace ... Але я здогадуюсь, якщо це працює для вас, вибийте себе.
Користувач

Для файлу INI, який я написав, мені довелося використовувати Winiклас, як це проілюстровано у підручнику «Одна хвилина»; prefs.node("something").get("val", null)не працює так , як я очікував.
Agi Hammerthief

65

Як вже було сказано , ini4j може бути використаний для досягнення цього. Дозвольте показати ще один приклад.

Якщо у нас є такий файл INI:

[header]
key = value

У valueSTDOUT має відображатися наступне :

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

Перевірте підручники, щоб отримати більше прикладів.


2
Акуратно! Я завжди тільки використовував BufferedReader і трохи копіювати / вставляти код String для розбору, щоб не потрібно додавати ще одну залежність до моїх додатків (це може вибухнути з пропорцій, коли ви починаєте додавати в API сторонніх розробників навіть для найпростіших завдань ). Але я не можу ігнорувати подібну простоту.
Гімбі

30

Настільки ж просто 80 рядків:

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 Просто для використання схеми регексу / відповідника. Працює як шарм
kalelien

Не ідеальне рішення, але хороша відправна точка, наприклад, відсутні файли getSection () та getString () повертають лише defaultValue, якщо не вистачає цілого розділу.
Джек Міллер

Яка різниця в продуктивності між таким regx і роботою з реалізацією рядка?
Евокс

Продуктивність при читанні невеликого файлу конфігурації не викликає занепокоєння. відкриття та закриття файлу набагато затратніше, я вважаю.
Аерокосмічний

Так, це приблизно так просто, як і для випадків простого використання. Не впевнений, чому люди хочуть це ускладнити. Якщо ви стурбовані ефективністю (або іншими проблемами, такими як повідомлення про помилки), так, ви, ймовірно, хочете використовувати щось інше (можливо, інший формат цілком).
Користувач

16

Ось простий, але потужний приклад із використанням класу 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");
}

Commons Configuration має ряд залежностей від часу виконання . Як мінімум, потрібні commons-lang та commons-logging . Залежно від того, що ви робите з цим, вам можуть знадобитися додаткові бібліотеки (детальніше див. Попереднє посилання).


1
Це була б моя правильна відповідь. Дуже простий у використанні та універсальний.
marcolopes

commons конфигурації, а не колекції.
jantox

13

Або зі стандартним Java API ви можете використовувати java.util.Properties :

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

12
Проблема полягає в тому, що у файлах ini структура має заголовки. Клас "Властивості" не знає, як поводитись із заголовками, і можуть виникнути сутички з назвою
Mario Ortegón

2
Крім того, Propertiesклас неправильно отримує значення, які містять \
rds

3
+1 для простого рішення, але підходить лише для простих конфігураційних файлів, як це помітили Mario Ortegon і rds.
Бендж

1
Файл INI містить [розділ], файл властивостей містить завдання.
Aerospace


10

У 18 рядках, розширивши java.util.Propertiesсинтаксичний аналіз на кілька розділів:

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

Інший варіант - Apache Commons Config також має клас для завантаження з INI-файлів . Він має деякі залежності від часу виконання , але для INI-файлів він повинен вимагати лише колекцій Commons, lang та журналів.

Я використовував Commons Config для проектів із їх властивостями та конфігураціями XML. Він дуже простий у використанні та підтримує деякі досить потужні функції.



2

Я особисто віддаю перевагу Confucious .

Це приємно, оскільки не потребує зовнішніх залежностей, він крихітний - лише 16 К, і автоматично завантажує ваш файл ini при ініціалізації. Напр

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

Через 3 роки Марк та ОП, мабуть, померли від старості ... але це справді гарна знахідка.
Користувач

6
Я використовую тростину, щоб об'їхати, але все ще живий і
кикаючий

@ MarioOrtegón: Чудово це чути!
ישו אוהב אותך

0

рішення hoat4 дуже елегантне і просте. Він працює для всіх здорових ini-файлів. Однак я бачив багатьох, у яких у ключі не залишилися символи пробілу .
Щоб вирішити це, я завантажив і змінив копію java.util.Properties. Хоча це трохи неортодоксально і короткостроково, фактичні моди були лише декількома рядками та досить простими. Я буду висувати пропозицію громаді JDK про включення змін.

Додавши внутрішню змінну класу:

private boolean _spaceCharOn = false;

Я контролюю обробку, пов’язану зі скануванням на точку поділу ключа / значення. Я замінив код пошуку пробілів символів невеликим приватним методом, який повертає булеву залежність від стану наведеної вище змінної.

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

Цей метод використовується в двох місцях в рамках приватного методу load0(...).
Існує також загальнодоступний метод його включення, але було б краще використовувати оригінальну версію, Propertiesякщо роздільник простору не є проблемою для вашої програми.

Якщо є інтерес, я хотів би опублікувати код у своєму IniFile.javaфайлі. Він працює з будь-якою версією Properties.


0

Використовуючи відповідь @Aerospace, я зрозумів, що у файлах INI законно мати розділи без будь-яких ключових значень. У цьому випадку додавання до карти верхнього рівня має відбуватися до того, як будуть знайдені будь-які ключові значення, наприклад (мінімально оновлені для 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);
            }

-2

Це так само просто, як це .....

//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
Це не стосується розділів INI
Ігор Мельниченко
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.