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

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

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

Таким образом, вопрос в том, что было бы самым простым способом прочитать в этом файле INI и получить доступ к ключам?

13 ответов

Решение

Библиотека, которую я использовал - 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));

Как уже упоминалось, ini4j может быть использован для достижения этой цели. Позвольте мне показать еще один пример.

Если у нас есть файл INI, как это:

[header]
key = value

Должно отображаться следующее value чтобы вывести:

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

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

Так же просто, как 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 ));
   }
}

Вот простой, но мощный пример, использующий класс 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 требуются. В зависимости от того, что вы делаете с ним, вам могут потребоваться дополнительные библиотеки (подробности см. В предыдущей ссылке).

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

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

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

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

Я использовал Commons Config для проектов с их свойствами и конфигурациями XML. Он очень прост в использовании и поддерживает некоторые довольно мощные функции.

Я лично предпочитаю Конфуция.

Это хорошо, так как не требует никаких внешних зависимостей, он крошечный - всего 16 КБ и автоматически загружает ваш INI-файл при инициализации. Например

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

Вы можете попробовать JINIFile. Это перевод TIniFile из Delphi, но для Java

https://github.com/SubZane/JIniFile

Используя ответ @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);
            }

Решение 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.

Вы можете использовать ini4j для преобразования INI в свойства

          Properties properties = new Properties();
    Ini ini = new Ini(new File("path/to/file"));
    ini.forEach((header, map) -> {
      map.forEach((subKey, value) -> {
        StringBuilder key = new StringBuilder(header);
        key.append("." + subKey);
        properties.put(key.toString(), value);
      });
    });

Это так же просто, как это.....

//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");
Другие вопросы по тегам