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

Posted

技术标签:

【中文标题】在 Java 中解析 INI 文件的最简单方法是啥?【英文标题】:What is the easiest way to parse an INI file in Java?在 Java 中解析 INI 文件的最简单方法是什么? 【发布时间】:2010-09-16 11:30:16 【问题描述】:

我正在用 Java 编写一个替代旧应用程序的插件。要求之一是旧应用程序使用的 ini 文件必须按原样读取到新的 Java 应用程序中。这个ini文件的格式是常见的windows风格,有header部分和key=value对,使用#作为注释字符。

我尝试使用 Java 中的 Properties 类,但当然,如果不同标头之间存在名称冲突,那将无法正常工作。

所以问题是,读取此 INI 文件并访问密钥的最简单方法是什么?

【问题讨论】:

【参考方案1】:

我使用的库是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));

【讨论】:

不起作用,错误提示“IniFile 无法解析为类型” @Caballero 是的,IniFile 的课似乎被淘汰了,试试Ini ini = new Ini(new File("/path/to/file")); ini4j.sourceforge.net/tutorial/OneMinuteTutorial.java.html 可能会保持最新,即使他们再次更改类名。 这东西还能用吗?下载了 0.5.4 源代码,它甚至没有构建,而且它不是缺少的依赖项.. 不值得花时间去打扰它。此外 ini4j 还有很多其他我们不需要的废话,Windoze 注册表编辑......来吧。 #LinuxMasterRace ...但我想如果它对你有用,那就把自己打晕吧。 对于我写的INI文件,我不得不使用Wini类,如“一分钟”教程中所示; prefs.node("something").get("val", null) 没有按我预期的方式工作。【参考方案2】:

作为mentioned,ini4j可以用来实现这一点。让我再举一个例子。

如果我们有这样的 INI 文件:

[header]
key = value

下面应该显示value到STDOUT:

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

查看the tutorials 了解更多示例。

【讨论】:

整洁!我一直在使用 BufferedReader 和一些复制/粘贴字符串解析代码,而不必向我的应用程序添加另一个依赖项(当您开始为最简单的任务添加第三方 API 时,这可能会变得不成比例)。但我不能忽视这种简单性。【参考方案3】:

简单到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 仅用于正则表达式模式/匹配器。像魅力一样工作 不是一个完美的解决方案,而是一个好的起点,例如,缺少 getSection() 和 getString() 仅在缺少整个部分时返回 defaultValue。 这样的 regx 与使用字符串实现之间的性能差异是什么? 读取小配置文件时的性能不是问题。我相信,打开和关闭文件要耗费更多时间。 不要重新发明***。这是一个标准问题,因为它曾经建议在每个需要解决它的代码库中维护其解决方案,而不是构建和使用公共库是反进步的。【参考方案4】:

这是一个简单但功能强大的示例,使用 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 配置有多个runtime dependencies。至少需要commons-lang 和commons-logging。根据您使用它的目的,您可能需要其他库(有关详细信息,请参阅上一个链接)。

【讨论】:

这将是我的正确答案。非常简单易用且用途广泛。 公用配置不是集合。【参考方案5】:

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

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

【讨论】:

问题是,对于 ini 文件,结构有标题。 Property 类不知道如何处理标题,并且可能存在名称冲突 另外,Properties 类无法正确获取包含 \ 的值 +1 用于简单的解决方案,但仅适用于简单的配置文件,正如 Mario Ortegon 和 rds 注意到的那样。 INI 文件包含 [section],属性文件包含分配。 文件格式:1/ a simple line-oriented or 2/ a simple XML format or 3/ a simple line-oriented, using ISO 8859-1 (with Unicode escapes + use native2ascii for other encodings)【参考方案6】:

在 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;

【讨论】:

【参考方案7】:

另一个选项是Apache Commons Config 也有一个用于从INI files 加载的类。它确实有一些runtime dependencies,但对于 INI 文件,它应该只需要 Commons 集合、语言和日志记录。

我在项目中使用了 Commons Config 及其属性和 XML 配置。它非常易于使用并支持一些非常强大的功能。

【讨论】:

【参考方案8】:

你可以试试 JINIFile。是 Delphi 的 TIniFile 的翻译,但用于 java

https://github.com/SubZane/JIniFile

【讨论】:

【参考方案9】:

我个人更喜欢Confucious。

很好,因为它不需要任何外部依赖项,它很小 - 只有 16K,并且在初始化时自动加载您的 ini 文件。例如

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

【讨论】:

3 年后,Mark 和 OP 可能已经因年老去世了……但这是一个非常好的发现。 我拄着拐杖四处走动,但我还活着,还在踢球 @MarioOrtegón:很高兴听到这个消息!【参考方案10】:

hoat4 的解决方案非常优雅和简单。它适用于所有 sane ini 文件。但是,我看到许多在 key 中有未转义的空格字符。 为了解决这个问题,我下载并修改了一份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 的任一版本。

【讨论】:

【参考方案11】:

使用@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);
            

【讨论】:

【参考方案12】:

您可以使用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);
      );
    );

【讨论】:

【参考方案13】:

就这么简单.....

//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");

【讨论】:

这不处理 INI 部分

以上是关于在 Java 中解析 INI 文件的最简单方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 C++ 中从文件末尾读取的最有效方法是啥? (解析文件中的最后 128 位)

从 C++ 调用 Java 方法的最简单方法是啥?

在文件中存储对象/充满对象的地图的最简单方法是啥?

将hdf5文件中的uint32数据写入java中的数组的最简单方法是啥?

在java中并行化任务的最简单方法是啥?

在 Java SE 中拥有 CDI 和 JPA 的最简单方法是啥?