概述

大多数Java应用在某个阶段都需要使用属性文件,通常以键值对的形式存储一些简单的参数,这些数据不包含在编译后的代码中。为此,Java语言提供了对属性文件的内置支持,通过java.util.Properties工具类来处理这类配置文件。

本文将重点介绍如何使用Properties

加载属性

2.1. 从属性文件加载

我们先来看一个从属性文件中加载键值对的例子。我们将加载两个放在类路径上的文件:

  • app.properties:
version=1.0
name=TestApp
date=2016-11-12
  • catalog:
c1=files
c2=images
c3=videos

注意虽然推荐使用带有.properties后缀的文件,但这并不是必需的。

现在我们可以简单地将它们加载到Properties实例中:

String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
String appConfigPath = rootPath + "app.properties";
String catalogConfigPath = rootPath + "catalog";

Properties appProps = new Properties();
appProps.load(new FileInputStream(appConfigPath));

Properties catalogProps = new Properties();
catalogProps.load(new FileInputStream(catalogConfigPath));

  
String appVersion = appProps.getProperty("version");
assertEquals("1.0", appVersion);
        
assertEquals("files", catalogProps.getProperty("c1"));

只要文件内容符合属性文件的格式要求,Properties类就能正确解析。更多关于属性文件格式的细节请参阅。

2.2. 从XML文件加载

除了属性文件,Properties类还可以加载XML文件,遵循特定的DTD规范。

以下是来自icons.xml文件的键值对加载示例:

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>xml example</comment>
    <entry key="fileIcon">icon1.jpg</entry>
    <entry key="imageIcon">icon2.jpg</entry>
    <entry key="videoIcon">icon3.jpg</entry>
</properties>

现在让我们加载它:

String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
String iconConfigPath = rootPath + "icons.xml";
Properties iconProps = new Properties();
iconProps.loadFromXML(new FileInputStream(iconConfigPath));

assertEquals("icon1.jpg", iconProps.getProperty("fileIcon"));

获取属性

我们可以使用getProperty(String key)getProperty(String key, String defaultValue)方法根据键获取值。

如果存在对应的键值对,两种方法都会返回相应的值。但如果找不到这样的键值对,前者会返回null,而后者会返回defaultValue

String appVersion = appProps.getProperty("version");
String appName = appProps.getProperty("name", "defaultName");
String appGroup = appProps.getProperty("group", "baeldung");
String appDownloadAddr = appProps.getProperty("downloadAddr");

assertEquals("1.0", appVersion);
assertEquals("TestApp", appName);
assertEquals("baeldung", appGroup);
assertNull(appDownloadAddr);

尽管Properties类继承了Hashtable类的get()方法,但我们不建议使用它获取值。get()方法会返回一个Object值,只能强制转换为String,而getProperty()方法已经为我们妥善处理了原始的Object值。

下面的代码会抛出异常:

float appVerFloat = (float) appProps.get("version");

设置属性

我们可以使用setProperty()方法更新已有的键值对或添加新的键值对:

appProps.setProperty("name", "NewAppName"); // update an old value
appProps.setProperty("downloadAddr", "www.baeldung.com/downloads"); // add new key-value pair

String newAppName = appProps.getProperty("name");
assertEquals("NewAppName", newAppName);
        
String newAppDownloadAddr = appProps.getProperty("downloadAddr");
assertEquals("www.baeldung.com/downloads", newAppDownloadAddr);

同样,虽然Properties类继承了Hashtable类的put()putAll()方法,出于与get()方法相同的理由,我们也不建议使用它们;Properties只支持String类型的值。

下面的代码不能按预期工作,当我们使用getProperty()获取其值时,它会返回null

appProps.put("version", 2);

删除属性

如果要删除键值对,可以使用remove()方法:

String versionBeforeRemoval = appProps.getProperty("version");
assertEquals("1.0", versionBeforeRemoval);

appProps.remove("version");    
String versionAfterRemoval = appProps.getProperty("version");
assertNull(versionAfterRemoval);

存储

6.1. 存储到属性文件

Properties类提供了一个store()方法来输出键值对:

String newAppConfigPropertiesFile = rootPath + "newApp.properties";
appProps.store(new FileWriter(newAppConfigPropertiesFile), "store to properties file");

第二个参数用于添加注释。如果我们不需要写任何注释,可以将其设置为null

6.2. 存储到XML文件

Properties类还提供了storeToXML()方法,以XML格式输出键值对:

String newAppConfigXmlFile = rootPath + "newApp.xml";
appProps.storeToXML(new FileOutputStream(newAppConfigXmlFile), "store to xml file");

第二个参数与store()方法相同。

其他常见操作

Properties类还提供了其他操作属性的方法:

appProps.list(System.out); // list all key-value pairs

Enumeration<Object> valueEnumeration = appProps.elements();
while (valueEnumeration.hasMoreElements()) {
    System.out.println(valueEnumeration.nextElement());
}

Enumeration<Object> keyEnumeration = appProps.keys();
while (keyEnumeration.hasMoreElements()) {
    System.out.println(keyEnumeration.nextElement());
}

int size = appProps.size();
assertEquals(3, size);

默认属性列表

Properties对象可以包含另一个Properties对象作为其默认属性列表。如果原始属性中找不到键,将搜索默认列表。

除了app.properties,我们的类路径上还有一个default.properties文件:

default.properties:

site=www.google.com
name=DefaultAppName
topic=Properties
category=core-java

示例代码:

String rootPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();

String defaultConfigPath = rootPath + "default.properties";
Properties defaultProps = new Properties();
defaultProps.load(new FileInputStream(defaultConfigPath));

String appConfigPath = rootPath + "app.properties";
Properties appProps = new Properties(defaultProps);
appProps.load(new FileInputStream(appConfigPath));

assertEquals("1.0", appVersion);
assertEquals("TestApp", appName);
assertEquals("www.google.com", defaultSite);

9. 属性和编码

默认情况下,属性文件期望是ISO-8859-1(拉丁-1)编码,因此不应使用包含ISO-8859-1编码范围以外字符的属性。

我们可以通过工具(如JDK的native2ascii工具)或在文件上显式指定编码来解决这一限制。

对于XML文件,loadFromXML()方法和storeToXML()方法默认使用UTF-8字符编码。然而,在读取编码不同的XML文件时,可以在DOCTYPE声明中指定。写入也足够灵活,我们可以在storeToXML()API的第三个参数中指定编码。

结论

在这篇文章中,我们讨论了基本的Properties类用法。我们学习了如何使用Properties,加载和存储键值对(属性和XML格式),操作Properties对象中的键值对(如获取值、更新值和获取大小),以及如何为Properties对象使用默认列表。

示例的完整源代码可以在这个GitHub项目中找到。