如何在Java中解析INI文件

2025/03/29

1. 概述

INI文件是Windows或MS-DOS的初始化或配置文件,它们具有纯文本内容,其中包含部分中的键值对。虽然我们可能更喜欢使用Java的原生.properties文件或其他格式来配置我们的应用程序,但有时我们可能需要使用现有INI文件中的数据。

在本教程中,我们将介绍几个可以帮助我们的库。我们还将介绍一种使用INI文件中的数据填充POJO的方法。

2. 创建示例INI文件

让我们从示例INI文件sample.ini开始:

; for 16-bit app support
[fonts]
letter=bold
text-size=28

[background]
color=white

[RequestResult]
RequestCode=1

[ResponseResult]
ResultCode=0

该文件有4个部分,使用小写、短横线命名法和大写驼峰命名法混合命名,其值可以是字符串或数字。

3. 使用ini4j解析INI文件

ini4j是一个轻量级库,用于从INI文件中读取配置。自2015年以来,它一直没有更新。

3.1 安装ini4j

为了能够使用ini4j库,首先我们应该在pom.xml中添加它的依赖

<dependency>
    <groupId>org.ini4j</groupId>
    <artifactId>ini4j</artifactId>
    <version>0.5.4</version>
</dependency>

3.2 在ini4j中打开INI文件

我们可以通过构造一个Ini对象在ini4j中打开一个INI文件

File fileToParse = new File("sample.ini");
IniINI= new Ini(fileToParse);

该对象现在包含部分和键。

3.3 读取章节关键字

我们可以使用Ini类上的get()函数从INI文件的一个部分中读取一个键:

assertThat(ini.get("fonts", "letter"))
    .isEqualTo("bold");

3.4 转换为Map

让我们看看将整个INI文件转换为Map<String, Map<String, String>>有多么容易,这是一个表示INI文件层次结构的Java原生数据结构:

public static Map<String, Map<String, String>> parseIniFile(File fileToParse) throws IOException {
    INIini = new Ini(fileToParse);
    return ini.entrySet().stream()
        .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}

这里,Ini对象的entrySet本质上是String和Map<String, String>的键值对。Ini的内部表示几乎是一个Map,因此可以使用stream()和toMap()收集器轻松将其转换为普通Map。

我们现在可以用get()从这个Map中读取各个部分:

assertThat(result.get("fonts").get("letter"))
    .isEqualTo("bold");

Ini类开箱即用,非常容易,并且可能不需要转换为Map,但我们稍后会找到它的用途。

但是,ini4j是一个老库,而且看起来维护得不是很好,让我们考虑另一种选择。

4. 使用Apache Commons解析INI文件

Apache Commons有一个更复杂的工具来处理INI文件,它能够对整个文件进行建模以供读写,但我们只关注它的解析功能。

4.1 安装通用配置

让我们首先在pom.xml中添加所需的依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-configuration2</artifactId>
    <version>2.8.0</version>
</dependency>

2.8.0版本于2022年更新,比ini4j更新。

4.2 打开INI文件

我们可以通过声明一个INIConfiguration对象并传递一个Reader来打开一个INI文件:

INIConfiguration iniConfiguration = new INIConfiguration();
try (FileReader fileReader = new FileReader(fileToParse)) {
    iniConfiguration.read(fileReader);
}

这里我们使用了try-with-resources模式来打开一个FileReader,然后要求INIConfiguration对象使用read函数来读取它。

4.3 读取章节键

INIConfiguration类有一个getSection()函数来读取一个部分,还有一个getProperty()函数在返回的对象上读取一个键:

String value = iniConfiguration.getSection("fonts")
    .getProperty("letter")
    .toString();
assertThat(value)
    .isEqualTo("bold");

我们应该注意getProperty()返回的是Object而不是String,因此需要转换为String。

4.4 转换为Map

我们也可以像前面一样将INIConfiguration转换为Map,这比ini4j稍微复杂一些:

Map<String, Map<String, String>> iniFileContents = new HashMap<>();

for (String section : iniConfiguration.getSections()) {
    Map<String, String> subSectionMap = new HashMap<>();
    SubnodeConfiguration confSection = iniConfiguration.getSection(section);
    Iterator<String> keyIterator = confSection.getKeys();
    while (keyIterator.hasNext()) {
        String key = keyIterator.next();
        String value = confSection.getProperty(key).toString();
        subSectionMap.put(key, value);
    }
    iniFileContents.put(section, subSectionMap);
}

要获取所有部分,我们需要使用getSections()查找它们的名称。然后getSection()可以为我们提供每个部分。

然后,我们可以使用为该部分提供所有键的迭代器,并使用每个getProperty()来获取键值对。

虽然这里很难生成Map,但更简单的数据结构的优点是我们可以向系统的其他部分隐藏INI文件解析。或者,我们可以将配置转换为POJO。

5. 将INI文件转换为POJO

我们可以使用Jackson将Map结构转换为POJO。我们可以使用反序列化注解来修饰POJO,以帮助Jackson理解原始INI文件中的各种命名约定,POJO比我们迄今为止见过的任何数据结构都更易于使用。

5.1 导入Jackson

我们需要将Jackson添加到pom.xml中:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.17.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.17.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.2</version>
</dependency>

5.2 定义一些POJO

我们示例文件的字体部分使用短横线命名作为其属性,让我们定义一个类来表示该部分:

@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public static class Fonts {
    private String letter;
    private int textSize;

    // getters and setters
}

这里我们使用了JsonNaming注解来描述属性中使用的大小写。

类似地,RequestResult部分具有使用大写驼峰命名的属性:

@JsonNaming(PropertyNamingStrategies.UpperCamelCaseStrategy.class)
public static class RequestResult {
    private int requestCode;

    // getters and setters
}

部分名称本身有多种情况,因此我们可以在父对象中声明每个部分,并使用JsonProperty注解来显示与默认小写驼峰命名的偏差:

public class MyConfiguration {
    private Fonts fonts;
    private Background background;

    @JsonProperty("RequestResult")
    private RequestResult requestResult;

    @JsonProperty("ResponseResult")
    private ResponseResult responseResult;

    // getters and setters
}

5.3 从Map转换为POJO

现在我们可以使用我们的任一库将INI文件读取为Map,并将文件内容建模为POJO,我们可以使用Jackson ObjectMapper执行转换:

ObjectMapper objectMapper = new ObjectMapper();
Map<String, Map<String, String>> iniKeys = parseIniFile(TEST_FILE);
MyConfiguration config = objectMapper.convertValue(iniKeys, MyConfiguration.class);

让我们检查整个文件是否已正确加载:

assertThat(config.getFonts().getLetter()).isEqualTo("bold");
assertThat(config.getFonts().getTextSize()).isEqualTo(28);
assertThat(config.getBackground().getColor()).isEqualTo("white");
assertThat(config.getRequestResult().getRequestCode()).isEqualTo(1);
assertThat(config.getResponseResult().getResultCode()).isZero();

我们应该注意到,数字属性,例如textSize和requestCode已经作为数字加载到我们的POJO中。

6. 库和方法的比较

ini4j库使用起来非常简单,本质上是一个简单的Map类结构。但是,这是一个较旧的库,没有定期更新。

Apache Commons解决方案功能更全面,并定期更新,但使用时需要做更多的工作。

7. 总结

在本文中,我们了解了如何使用几个开源库读取INI文件,我们了解了如何读取单个键以及如何遍历整个文件以生成Map。

然后我们看到了如何使用Jackson将Map转换为POJO。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章