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該文件包含四個部分,使用了混合的命名方式,包括小寫、kebab-case和大駝峯命名法。它包含字符串或數字值。
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");
Ini ini = new Ini(fileToParse);這個對象現在包含 sections 和 keys。
3.3. 讀取節區鍵
可以使用 Ini 類中的 <em get()</em> 函數,從 INI 文件中讀取節區鍵。
assertThat(ini.get("fonts", "letter"))
.isEqualTo("bold");3.4. 轉換為 Map
讓我們看看如何輕鬆地將整個 INI 文件轉換為 `<em Map<String, Map<String, String>>,這是一個 Java 原生數據結構,它代表了 INI 文件的層次結構:
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));
}在這裏,entrySet 是 Ini 對象的鍵值對,本質上是 <em style="font-style: italic;">String</em> 和 <em style="font-style: italic;">Map<String, String></em>》。Ini對象的內部表示實際上就是一個Map,因此可以通過使用stream()和toMap()收集器輕鬆轉換為一個普通的Map`。
現在,我們可以使用 get() 方法讀取這個 map 中的 section:
assertThat(result.get("fonts").get("letter"))
.isEqualTo("bold");這個 Ini 類非常容易直接使用,雖然轉換為 Map 可能並不必要,但稍後我們會找到它的用途。
然而,ini4j 是一箇舊庫,目前來看似乎沒有得到很好的維護。我們考慮另一個選項。
4. 使用 Apache Commons 解析 INI 文件
Apache Commons 提供了一個更強大的工具來處理 INI 文件。該工具能夠對整個文件進行建模,支持讀寫操作,儘管我們將重點關注其解析功能。
4.1. 安裝 Commons 配置
讓我們首先在我們的 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. 讀取節標題
<em>INIConfiguration</em> 類包含一個 <em>getSection()</em> 函數用於讀取節標題,以及在返回的對象的 <em>getProperty()</em> 函數用於讀取節標題中的鍵:
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()可以為我們提供每個部分。
我們可以使用Iterator,它提供所有部分的鍵,並使用getProperty()與每個鍵值對進行操作。
雖然Map在這裏更難生成,但更簡單的數據結構允許我們隱藏INI文件解析與其他系統部分。
5. 將 INI 文件轉換為 POJO
我們可以使用 Jackson 將我們的 Map 結構轉換為 POJO。 我們可以使用反序列化註解來裝飾我們的 POJO,以幫助 Jackson 理解原始 INI 文件中各種命名約定。 相對於我們之前見過的任何數據結構,POJO 更易於使用。
5.1. 導入 Jackson
我們需要將 Jackson 添加到我們的 `pom.xml</em/> 中:
<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
該 <em font="fonts"</em> 部分在我們的示例文件中使用 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
}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。