在 Java 中解析 INI 文件最简单的方法是什么?

我正在为 Java 中的遗留应用程序编写一个插件替换。其中一个要求是,旧应用程序使用的 ini 文件必须按原样读入新的 Java 应用程序。这个 ini 文件的格式是常见的 windows 样式,带有头部分和 key = value 对,使用 # 作为注释字符。

我尝试使用 Java 中的 Properties 类,但是当然,如果不同头之间存在名称冲突,那么这种方法就无法工作。

所以问题是,读取 INI 文件并访问密钥的最简单方法是什么?

168028 次浏览

The library I've used is ini4j. It is lightweight and parses the ini files with ease. Also it uses no esoteric dependencies to 10,000 other jar files, as one of the design goals was to use only the standard Java API

This is an example on how the library is used:

Ini ini = new Ini(new File(filename));
java.util.prefs.Preferences prefs = new IniPreferences(ini);
System.out.println("grumpy/homePage: " + prefs.node("grumpy").get("homePage", null));

Another option is Apache Commons Config also has a class for loading from INI files. It does have some runtime dependencies, but for INI files it should only require Commons collections, lang, and logging.

I've used Commons Config on projects with their properties and XML configurations. It is very easy to use and supports some pretty powerful features.

Or with standard Java API you can use java.util.Properties:

Properties props = new Properties();
try (FileInputStream in = new FileInputStream(path)) {
props.load(in);
}

Here's a simple, yet powerful example, using the apache class HierarchicalINIConfiguration:

HierarchicalINIConfiguration iniConfObj = new HierarchicalINIConfiguration(iniFile);


// Get Section names in ini file
Set setOfSections = iniConfObj.getSections();
Iterator sectionNames = setOfSections.iterator();


while(sectionNames.hasNext()){


String sectionName = sectionNames.next().toString();


SubnodeConfiguration sObj = iniObj.getSection(sectionName);
Iterator it1 =   sObj.getKeys();


while (it1.hasNext()) {
// Get element
Object key = it1.next();
System.out.print("Key " + key.toString() +  " Value " +
sObj.getString(key.toString()) + "\n");
}

Commons Configuration has a number of runtime dependencies. At a minimum, commons-lang and commons-logging are required. Depending on what you're doing with it, you may require additional libraries (see previous link for details).

As mentioned, ini4j can be used to achieve this. Let me show one other example.

If we have an INI file like this:

[header]
key = value

The following should display value to STDOUT:

Ini ini = new Ini(new File("/path/to/file"));
System.out.println(ini.get("header", "key"));

Check the tutorials for more examples.

You could try JINIFile. Is a translation of the TIniFile from Delphi, but for java

https://github.com/SubZane/JIniFile

As simple as 80 lines:

package windows.prefs;


import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class IniFile {


private Pattern  _section  = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*" );
private Pattern  _keyValue = Pattern.compile( "\\s*([^=]*)=(.*)" );
private Map< String,
Map< String,
String >>  _entries  = new HashMap<>();


public IniFile( String path ) throws IOException {
load( path );
}


public void load( String path ) throws IOException {
try( BufferedReader br = new BufferedReader( new FileReader( path ))) {
String line;
String section = null;
while(( line = br.readLine()) != null ) {
Matcher m = _section.matcher( line );
if( m.matches()) {
section = m.group( 1 ).trim();
}
else if( section != null ) {
m = _keyValue.matcher( line );
if( m.matches()) {
String key   = m.group( 1 ).trim();
String value = m.group( 2 ).trim();
Map< String, String > kv = _entries.get( section );
if( kv == null ) {
_entries.put( section, kv = new HashMap<>());
}
kv.put( key, value );
}
}
}
}
}


public String getString( String section, String key, String defaultvalue ) {
Map< String, String > kv = _entries.get( section );
if( kv == null ) {
return defaultvalue;
}
return kv.get( key );
}


public int getInt( String section, String key, int defaultvalue ) {
Map< String, String > kv = _entries.get( section );
if( kv == null ) {
return defaultvalue;
}
return Integer.parseInt( kv.get( key ));
}


public float getFloat( String section, String key, float defaultvalue ) {
Map< String, String > kv = _entries.get( section );
if( kv == null ) {
return defaultvalue;
}
return Float.parseFloat( kv.get( key ));
}


public double getDouble( String section, String key, double defaultvalue ) {
Map< String, String > kv = _entries.get( section );
if( kv == null ) {
return defaultvalue;
}
return Double.parseDouble( kv.get( key ));
}
}

I personally prefer Confucious.

It is nice, as it doesn't require any external dependencies, it's tiny - only 16K, and automatically loads your ini file on initialization. E.g.

Configurable config = Configuration.getInstance();
String host = config.getStringValue("host");
int port = config.getIntValue("port");
new Connection(host, port);

In 18 lines, extending the java.util.Properties to parse into multiple sections:

public static Map<String, Properties> parseINI(Reader reader) throws IOException {
Map<String, Properties> result = new HashMap();
new Properties() {


private Properties section;


@Override
public Object put(Object key, Object value) {
String header = (((String) key) + " " + value).trim();
if (header.startsWith("[") && header.endsWith("]"))
return result.put(header.substring(1, header.length() - 1),
section = new Properties());
else
return section.put(key, value);
}


}.load(reader);
return result;
}

It is just as simple as this.....

//import java.io.FileInputStream;
//import java.io.FileInputStream;


Properties prop = new Properties();
//c:\\myapp\\config.ini is the location of the ini file
//ini file should look like host=localhost
prop.load(new FileInputStream("c:\\myapp\\config.ini"));
String host = prop.getProperty("host");

hoat4's solution is very elegant and simple. It works for all sane ini files. However, I have seen many that have un-escaped space characters in the key.
To solve this, I have downloaded and modified a copy of java.util.Properties. Though this is a little unorthodox, and short-term, the actual mods were but a few lines and quite simple. I will be puting forward a proposal to the JDK community to include the changes.

By adding an internal class variable:

private boolean _spaceCharOn = false;

I control the processing related to scanning for the key/value separation point. I replaced the space characters search code with a small private method that returns a boolean depending on the state of the above variable.

private boolean isSpaceSeparator(char c) {
if (_spaceCharOn) {
return (c == ' ' || c == '\t' || c == '\f');
} else {
return (c == '\t' || c == '\f');
}
}

This method is used in two places within the private method load0(...).
There is also a public method to switch it on, but it would be better to use the original version of Properties if the space separator is not an issue for your application.

If there is interest, I would be willing to post the code to my IniFile.java file. It works with either version of Properties.

Using answer by @Aerospace, I realized that it is legitimate for INI files to have sections without any key-values. In this case, addition to the top-level map should happen before any key-values are found, ex (minimally updated for Java 8):

            Path location = ...;
try (BufferedReader br = new BufferedReader(new FileReader(location.toFile()))) {
String line;
String section = null;
while ((line = br.readLine()) != null) {
Matcher m = this.section.matcher(line);
if (m.matches()) {
section = m.group(1).trim();
entries.computeIfAbsent(section, k -> new HashMap<>());
} else if (section != null) {
m = keyValue.matcher(line);
if (m.matches()) {
String key = m.group(1).trim();
String value = m.group(2).trim();
entries.get(section).put(key, value);
}
}
}
} catch (IOException ex) {
System.err.println("Failed to read and parse INI file '" + location + "', " + ex.getMessage());
ex.printStackTrace(System.err);
}


You can use ini4j to convert INI to Properties

    Properties properties = new Properties();
Ini ini = new Ini(new File("path/to/file"));
ini.forEach((header, map) -> {
map.forEach((subKey, value) -> {
StringBuilder key = new StringBuilder(header);
key.append("." + subKey);
properties.put(key.toString(), value);
});
});