1. 概述
INI 文件是 Windows 或 MS-DOS 的初始化或配置文件。它們包含純文本內容,該內容由鍵值對組成,並按部分進行組織。雖然我們可能更喜歡使用 Java 的原生 .properties 文件或其他格式來配置我們的應用程序,但在某些情況下,我們可能需要從現有 INI 文件中消費數據。
在本教程中,我們將探討一些可以幫助我們使用的庫。我們還將研究如何使用這些庫將數據填充到 POJO 中。
2. 創建一個示例 INI 文件
讓我們從一個示例 INI 文件,sample.ini:
; 用於 16 位應用程序支持
[fonts]
letter=bold
text-size=28
[background]
color=white
[RequestResult]
RequestCode=1
[ResponseResult]
ResultCode=0
此文件包含四個部分,使用混合大小寫(小寫、kebab-case 和大駝峯命名法)進行命名。 它包含字符串或數字值。
3. 解析 INI 文件使用 ini4j
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);
這個對象現在包含節和鍵。
3.3. 讀取節中的鍵
我們可以使用 get() 函數從 INI 文件中的節中讀取一個鍵:
assertThat(ini.get("fonts", "letter"))
.isEqualTo("bold");
3.4. 轉換為 Map
讓我們看看將整個 INI 文件轉換為 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 對象本質上是一個 與 Map<String, String> 的鍵值對。 Ini 對象的內部表示實際上是一個 Map,因此可以使用 stream() 和 toMap() 收集器輕鬆轉換為一個普通的 Map。
現在我們可以使用 get() 函數從這個映射中讀取節:
assertThat(result.get("fonts").get("letter"))
.isEqualTo("bold");
Ini 類易於使用,無需轉換為 Map,儘管稍後我們會找到它的用途。
但是,ini4j 是一箇舊庫,並且看起來沒有得到積極維護。讓我們考慮另一個選項。
4. 解析 INI 文件使用 Apache Commons
Apache Commons 具有用於處理 INI 文件的更高級工具。它能夠對整個文件進行建模,用於讀取和寫入,儘管我們將僅關注其解析功能。
4.1. 安裝 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 更新。
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. 讀取一個 Section Key
INIConfiguration 類具有一個 getSection() 函數來讀取一個 section,以及一個 getProperty() 函數用於讀取一個 key:
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);
}
為了獲取所有 section,我們需要使用 getSections() 來找到它們的名稱。然後 getSection() 可以為我們提供每個 section。
然後我們可以使用 Iterator,該 iterator 提供所有 section 的 key,並使用 getProperty() 與每個 key 組合來獲取 key-value 對。
雖然一個 Map 更難生成,但更平坦的數據結構的好處是我們可以隱藏 INI 文件解析從其他系統部分中。或者,我們可以將配置轉換為 POJOs。
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
我們的樣本文件中 fonts 部分使用 kebab-case 格式存儲其屬性。讓我們定義一個類來表示該部分:
@JsonNaming(PropertyNamingStrategies.KebabCaseStrategy.class)
public static class Fonts {
private String letter;
private int textSize;
// getters and setters
}
在這裏,我們使用了 JsonNaming 註解來描述屬性使用的格式。
同樣,RequestResult 部分的屬性使用 upper-camel-case:
@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
現在,我們有了使用 Jackson 的庫之一讀取 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。
然後我們學習瞭如何將 Map 轉換為 POJO,使用 Jackson。