需求:.properties 文件包含節點(Section)與變量引用(%key%),如:
[Default]
team = happy team
[Project]
Dev = Harry
From = %team%
age = 20
Des = %Dev%, from %From%, %age% years old
調用 getString("Project", "Des") 返回 "Harry, from happy team, 20 years old"。
[Section],構建 Map> 結構,存儲原始值。%([A-Za-z]+)% 匹配引用,遞歸查找並替換,直到無引用。getString(section, key)、getInteger(section, key) 便捷方法。public class PropertiesParser {
private final Map> data = new LinkedHashMap<>();
private final Pattern refPattern = Pattern.compile("%([A-Za-z]+)%");
public void parse(String filePath) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line, currentSection = null;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
if (line.startsWith("[") && line.endsWith("]")) {
currentSection = line.substring(1, line.length() - 1);
data.putIfAbsent(currentSection, new LinkedHashMap<>());
} else if (currentSection != null) {
int eq = line.indexOf('=');
if (eq > 0) {
String key = line.substring(0, eq).trim();
String val = line.substring(eq + 1).trim();
data.get(currentSection).put(key, val);
}
}
}
}
// 第二遍:解析引用
resolveReferences();
}
private void resolveReferences() {
boolean changed;
do {
changed = false;
for (Map section : data.values()) {
for (Map.Entry entry : section.entrySet()) {
String val = entry.getValue();
Matcher m = refPattern.matcher(val);
StringBuffer sb = new StringBuffer();
while (m.find()) {
String refKey = m.group(1);
String refVal = findValueInAllSections(refKey);
if (refVal != null) {
m.appendReplacement(sb, Matcher.quoteReplacement(refVal));
changed = true;
}
}
m.appendTail(sb);
entry.setValue(sb.toString());
}
}
} while (changed); // 支持鏈式引用 %a%→%b%→value
}
private String findValueInAllSections(String key) {
for (Map sec : data.values()) {
if (sec.containsKey(key)) return sec.get(key);
}
return null;
}
public String getString(String section, String key) {
return data.getOrDefault(section, Collections.emptyMap()).get(key);
}
public int getInteger(String section, String key) {
String v = getString(section, key);
return v != null ? Integer.parseInt(v) : -1;
}
}
PropertiesParser parser = new PropertiesParser();
parser.parse("pro.properties");
System.out.println(parser.getString("Project", "Des"));
// 輸出: Harry, from happy team, 20 years old
System.out.println(parser.getInteger("Project", "age")); // 20
${key} 語法(Spring 風格),只需改正則。%a% → %b% → %a%),防死循環。PropertySource 體系,實現 @Value("${Project.Des}") 直接注入。這份解析器展示了「配置文件預處理」的通用模式:**分層存儲 → 延遲解析 → 正則替換 → 緩存結果**,可直接用於項目配置管理模塊。