在Java中解析INI文件的最简单方法是什么?

111

我正在编写一个Java的遗留应用程序替代品。其中一个要求是必须将旧应用程序使用的ini文件按原样读入新的Java应用程序中。这些ini文件的格式是常见的Windows格式,包含标题部分和键值对,使用#作为注释字符。

我尝试使用Java的Properties类,但是如果不同标题之间存在名称冲突,那么它当然无法工作。

因此问题是,最简单的方法是什么,以便读取这个INI文件并访问键?

13个回答

134

我使用的库是ini4j。它轻量级且易于解析ini文件。此外,它不使用奇怪的依赖关系来连接其他1万个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
1
@Caballero 是的,看起来 IniFile 类已经被移除了,可以尝试使用 Ini ini = new Ini(new File("/path/to/file")); - Mehdi Karamosly
2
即使他们再次更改类名,http://ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html也可能保持最新状态。 - Lokathor
这个东西还能用吗?下载了0.5.4的源代码,甚至都没编译成功,也不是缺少依赖...浪费时间去折腾它不值得。另外ini4j里面有很多我们不需要的垃圾,比如Windows注册表编辑...拜托了。#LinuxMasterRace ...但如果它对你有用,那就自行决定吧。 - User
我写的INI文件需要使用"Wini"类,就像"One Minute"教程中所示;而prefs.node("something").get("val", null)并没有按照我预期的方式工作。 - Agi Hammerthief
2
浪费了很多时间,但这个解决方案并没有成功。它有漏洞。这个项目最后更新于2015年,很多3年前提交的问题仍然没有解决。对我来说,什么都没用。我尝试了WINI、prefs.node和许多其他变化,但都失败了。最好使用维护良好的代码。 - Vikas Piprade

69

正如提到的那样,可以使用ini4j来实现这一点。下面是另一个示例。

如果我们有这样一个INI文件:

[header]
key = value

以下代码应该会将 value 显示在标准输出(STDOUT)中:

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

查看教程以获取更多示例。


2
太好了!我一直只是使用BufferedReader和一些复制/粘贴的字符串解析代码,以免在我的应用程序中添加另一个依赖项(当您开始为最简单的任务添加第三方API时,这可能会变得非常庞大)。但我不能忽略这种简单性。 - Gimby

34

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

仅使用正则表达式Pattern/Matcher,可以轻松实现。工作非常顺利。 - kalelien
这不是一个完美的解决方案,但是一个很好的起点,例如,缺少getSection()和getString()只有在整个部分缺失时才返回defaultValue。 - Jack Miller
这种正则表达式与使用字符串实现相比,性能差异有多大? - Ewoks
读取小配置文件时性能并不是一个问题。我认为打开和关闭文件会更加耗费资源。 - Aerospace
1
是的,对于简单的用例来说,这就是应该的简单。不确定为什么人们想要把它复杂化。如果你关心性能(或其他问题,如错误报告),是的,你可能想使用其他东西(可能是完全不同的格式)。 - User
2
不要重复造轮子。这是一个非常标准的问题,建议在每个需要解决它的代码库中维护其解决方案,而不是构建和使用公共库,这是反进步的。 - Daddy32

19

这是一个简单而强大的示例,使用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-langcommons-logging。根据你的使用情况,可能需要其他库(详见上面的链接)。


1
这将是我的正确答案。非常简单易用且多功能。 - marcolopes
通用配置而非集合。 - jantox
1
commons-configurations2 已经发布。该类现在被称为 INIConfiguration - mihca
看起来你指的是 iniConfObj,而不是 iniObj - Jean Spector

17

或者您可以使用标准的Java API java.util.Properties

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

17
问题是,使用 ini 文件时会有一些头部结构。Property 类不知道如何处理这些头部,并且可能会出现名称冲突。 - Mario Ortegón
3
好的,我会尽力为您翻译。以下是需要翻译的内容:另外,“Properties”类无法正确获取包含\的值。 - rds
3
简单的解决方案很好,但只适用于简单的配置文件,正如Mario Ortegon和rds所指出的那样。 - Benj
1
INI文件包含[节],属性文件包含赋值。 - Aerospace
1
文件格式:1 / 简单的面向行 或 2 / 简单的 XML 格式 或 3 / 简单的面向行,使用 ISO 8859-1(带有Unicode 转义 + 对于其他编码使用 native2ascii)。 - n611x007
显示剩余3条评论

15

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

4
另一个选择是使用Apache Commons Config,它也有一个从INI文件中加载的类。虽然它有一些运行时依赖项,但对于INI文件,它只需要Commons collections、lang和logging。
我曾在使用其属性和XML配置的项目中使用过Commons Config。它非常易于使用,并支持一些非常强大的功能。

2

2
我个人更喜欢孔子语录
它很好,因为它不需要任何外部依赖,只有16K,而且在初始化时自动加载您的ini文件。例如:
Configurable config = Configuration.getInstance();  
String host = config.getStringValue("host");   
int port = config.getIntValue("port"); 
new Connection(host, port);

三年后,Mark和OP可能已经因为年迈而去世了...但这真的是一个很好的发现。 - User
8
我使用拐杖走路,但我仍然健在并精力充沛。 - Mario Ortegón
@MarioOrtegón:太好听了! - ישו אוהב אותך

0

你可以使用ini4j将INI文件转换为Properties格式

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接