概述

INI文件是Windows或MS-DOS系统中的初始化或配置文件,本质是包含分节键值对的纯文本文件。虽然我们更习惯用Java原生的.properties文件或其他格式配置应用,但有时仍需处理现有的INI文件。

本文将介绍两个实用的解析库,并演示如何将INI文件数据填充到POJO对象中。踩过坑的都知道,直接处理INI文件比想象中麻烦,但用对工具就能简单粗暴解决。

创建示例INI文件

先看个示例文件sample.ini

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

[background]
color=white

[RequestResult]
RequestCode=1

[ResponseResult]
ResultCode=0

这个文件包含四个节,命名混用了小写、kebab-case和大驼峰法,值类型包括字符串和数字。

使用ini4j解析INI文件

ini4j是个轻量级INI文件解析库,但要注意它自2015年后就没更新过。

安装ini4j

pom.xml中添加依赖:

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

在ini4j中打开INI文件

直接构造Ini对象即可打开文件

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

这个对象现在包含了所有节和键。

读取节中的键

通过Ini类的get()方法读取键值:

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

转换为Map

将整个INI文件转为Map<String, Map<String, String>>(天然匹配INI结构的Java原生数据结构):

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

Ini对象的entrySet本质是StringMap<String, String>的键值对,其内部实现接近Map,用流式处理轻松转换:

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

虽然Ini类开箱即用,转Map操作可能多余,但后面会用到它的价值。不过要注意,ini4j是个老库,维护状况堪忧,我们看看更现代的替代方案。

使用Apache Commons解析INI文件

Apache Commons提供了更成熟的INI文件处理方案,支持完整的读写操作,本文只关注解析功能。

安装Commons Configuration

pom.xml添加依赖:

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

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

打开INI文件

创建INIConfiguration对象并传入Reader

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

这里用了try-with-resources模式打开FileReader,再调用read方法读取内容。

读取节中的键

通过getSection()获取节,再用getProperty()读取键值:

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

注意getProperty()返回Object而非String,需手动转换类型。

转换为Map

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

操作步骤:

  1. getSections()获取所有节名
  2. 通过getSection()获取每个节对象
  3. Iterator遍历节内所有键
  4. 调用getProperty()获取键值对

虽然转Map稍麻烦,但这样能隐藏INI解析细节,方便后续转换为POJO。

将INI文件转换为POJO

Jackson将Map结构转为POJO。通过注解处理INI文件中的各种命名风格,比直接操作Map更优雅。

引入Jackson

添加Jackson依赖到pom.xml

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

定义POJO类

示例文件中的fonts节使用kebab-case命名:

@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
}

从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();

注意textSizerequestCode等数值属性已自动转为数字类型。

库和方法的比较

对比维度 ini4j Apache Commons Configuration
易用性 极简,类似Map操作 功能完整但稍复杂
维护状态 停滞(2015年后未更新) 活跃(2022年有更新)
转换Map复杂度 一行流式处理搞定 需手动迭代处理
适用场景 简单配置快速解析 需要完整配置管理能力的场景

结论

本文通过两个开源库演示了INI文件的解析方法:

  • 基本操作:读取单个键值和遍历全文件生成Map
  • 进阶技巧:用Jackson将Map转换为强类型POJO

代码示例已上传至GitHub,实际使用时根据项目需求选库即可——简单需求用ini4j,长期维护的项目优先考虑Apache Commons。


原始标题:How to Parse an INI File in Java