Каков самый простой способ разобрать файл INI на Java?

104

Я пишу замену устаревшему приложению на Java. Одно из требований состоит в том, что файлы ini, которые использовало старое приложение, должны читаться как есть в новом приложении Java. Формат этих файлов ini является обычным стилем Windows, с разделами заголовка и парами ключ = значение, используя # в качестве символа для комментариев.

Я пробовал использовать класс Properties из Java, но, конечно, это не сработает, если между разными заголовками есть конфликты имен.

Итак, вопрос в том, как проще всего прочитать этот INI-файл и получить доступ к ключам?

Марио Ортегон
источник

Ответы:

121

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

Это пример использования библиотеки:

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 да, похоже, что IniFileурок был исключен, попробуйтеIni ini = new Ini(new File("/path/to/file"));
Мехди Карамослы
2
ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html , вероятно, будет оставаться в актуальном состоянии, даже если они снова изменят имя класса.
Lokathor
Эта штука вообще работает? Загрузил исходный код 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"));

Проверьте учебники для большего количества примеров.

tshepang
источник
2
Аккуратно! Я всегда просто использовал BufferedReader и немного кода синтаксического анализа строки копирования / вставки, чтобы мне не приходилось добавлять еще одну зависимость к моим приложениям (которые могут выйти из-под контроля, когда вы начнете добавлять сторонние API даже для самых простых задач. ). Но я не могу игнорировать такую ​​простоту.
Gimby 08
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 Просто для использования регулярных выражений Pattern / Matcher. Работает как шарм
kalelien
Не идеальное решение, но хорошая отправная точка, например, отсутствие getSection () и getString () возвращает значение defaultValue, только если отсутствует весь раздел.
Джек Миллер
Какая разница в производительности между таким regx и работой со строковой реализацией?
Ewoks
Производительность при чтении небольшого файла конфигурации не имеет значения. Я считаю, что открытие и закрытие файла намного труднее.
Aerospace
Да, это примерно так же просто, как и должно быть для простых случаев использования. Не уверен, почему люди хотят это усложнять. Если вас беспокоит производительность (или другие проблемы, например, сообщение об ошибках), да, вы, вероятно, захотите использовать что-то еще (возможно, совершенно другой формат).
Пользователь
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 имеет ряд зависимостей во время выполнения . Как минимум, требуются commons-lang и commons-logging . В зависимости от того, что вы с ним делаете, вам могут потребоваться дополнительные библиотеки (подробности см. В предыдущей ссылке).

user50217
источник
1
Это был бы мой правильный ответ. Очень прост в использовании и универсален.
marcolopes
общие конфигурации, а не коллекции.
jantox
13

Или со стандартным API Java вы можете использовать java.util.Properties :

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
    props.load(in);
}
Питер
источник
12
Проблема в том, что у ini-файлов в структуре есть заголовки. Класс Property не знает, как обрабатывать заголовки, и могут возникнуть конфликты имен
Марио Ортегон,
2
Кроме того, Propertiesкласс неправильно получает значения, содержащие \
rds
3
+1 за простое решение, но подходит только для простых файлов конфигурации, как заметили Марио Ортегон и rds.
Бендж
1
Файл INI содержит [раздел], файл свойств содержит назначения.
Aerospace
1
формат файла: 1 / простой строчно-ориентированный или 2 / простой формат XML или 3 / простойnative2ascii
строчно
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;
}
hoat4
источник
2

Другой вариант - Apache Commons Config также имеет класс для загрузки из файлов INI . У него есть некоторые зависимости во время выполнения , но для файлов INI он должен требовать только коллекции Commons, язык и ведение журнала.

Я использовал 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);
            }
Денис Баранов
источник
-1

Это так просто ...

//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
Игорь Мельниченко