使用 Java 解析配置文件

当您使用 Java 编写软件时,实现持久设置。
52 位读者喜欢这篇文章。

当您编写应用程序时,您通常希望用户能够配置他们与应用程序的交互方式以及应用程序与他们系统的交互方式。这些通常被称为“首选项”或“设置”,它们存储在“首选项文件”或“配置文件”中,或者简称为“配置”。配置文件有很多不同的格式,包括 INI、JSON、YAML 和 XML,每种语言解析这些语言的方式都不同。本文讨论了当您使用 Java 编程语言编写软件时,可以实现持久设置的一些方法。

选择一种格式

编写配置文件出奇地灵活。我将配置选项保存在简单的逗号分隔文本文件中,也曾将选项保存在高度详细的 YAML 或 XML 中。关于配置文件最重要的事情是它们的一致性和可预测性。这使得您可以轻松编写代码,以便快速轻松地从配置文件中提取数据,以及在用户决定进行更改时保存和更新选项。

几种流行的配置文件格式。Java 拥有大多数常见配置格式的库,但在本文中,我将使用 XML 格式。对于某些项目,您可能会选择使用 XML,因为它天生能够提供关于其包含数据的许多元数据,而对于其他项目,您可能会选择避免使用它,因为它过于冗长。Java 使 XML 的使用相对容易,因为它默认包含强大的 XML 库。

XML 基础知识

XML 是一个很大的主题。仅我拥有的一本关于 XML 的书就超过 700 页。幸运的是,使用 XML 不需要深入了解其所有众多功能。与 HTML 一样,XML 是一种分层标记语言,具有开始和结束标签,这些标签可能包含零个或多个数据。这是一个 XML 示例片段

<xml>
  <node>
    <element>Penguin</element>
  </node>
</xml>

在这个相当不言自明的例子中,以下是 XML 解析器使用的术语

  • 文档: <xml> 标签打开一个文档</xml> 标签关闭它。
  • 节点: <node> 标签是一个节点
  • 元素: 从第一个 < 到最后一个 ><element>Penguin</element> 是一个元素
  • 内容:<element> 元素中,字符串 Penguin内容

信不信由你,这就是您需要了解的关于 XML 的全部知识,才能编写和解析它。

创建一个示例配置文件

一个最小化的配置文件示例是您学习如何解析 XML 所需的全部内容。想象一个配置文件跟踪 GUI 窗口的一些显示属性

<xml>
  <window>
    <theme>Dark</theme>
    <fullscreen>0</fullscreen>
    <icons>Tango</icons>
</window>
</xml>

创建一个名为 ~/.config/DemoXMLParser 的目录

$ mkdir ~/.config/DemoXMLParser

在 Linux 上,根据 Freedesktop 规范,~/.config 目录是默认的配置文件位置。如果您使用的操作系统不遵循 Freedesktop 标准,您仍然可以使用此位置,但您可能必须自己创建所有目录。

复制并将示例配置 XML 粘贴到一个文件中,并将其另存为 ~/.config/DemoXMLParser/myconfig.xml

使用 Java 解析 XML

如果您是 Java 新手,请首先阅读我的给 Java 新开发人员的 7 个技巧文章。一旦您对 Java 相对熟悉,请打开您最喜欢的集成开发环境 (IDE) 并创建一个新项目。我称我的项目为 myConfigParser

在最初不担心导入和错误捕获的情况下,您可以使用在 javaxjava.io 库中找到的标准 Java 扩展来实例化解析器。如果您使用 IDE,系统将提示您导入相应的库;否则,您可以在本文稍后的完整代码版本中找到库的完整列表。

Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
File configFile = new File(configPath.toString(), "myconfig.xml");

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

DocumentBuilder builder = null;
builder = factory.newDocumentBuilder();

Document doc = null;
doc = builder.parse(configFile);
doc.getDocumentElement().normalize();

此示例代码使用 java.nio.Paths 库来定位用户的 home 目录,并将默认配置位置添加到路径中。然后,它使用 java.io.File 库将要解析的配置文件定义为 File 对象。

接下来,它使用 javax.xml.parsers.DocumentBuilderjavax.xml.parsers.DocumentBuilderFactory 库来创建一个内部文档构建器,以便 Java 程序可以摄取和解析 XML 数据。

最后,Java 构建一个名为 doc 的文档,并将 configFile 文件加载到其中。使用 org.w3c.dom 库,它规范化了摄取的 XML 数据。

基本上就是这样。从技术上讲,您已经完成了数据解析。但是,如果您无法访问已解析的数据,那么它对您来说用处不大,因此请编写一些查询以从您的配置中提取重要值。

使用 Java 访问 XML 值

从您摄取的 XML 文档中获取数据,只需引用一个特定的节点,然后“遍历”它包含的元素即可。通常使用一系列循环来迭代节点中的元素,但我将在这里尽量减少循环的使用,只是为了保持代码易于阅读

NodeList nodes = doc.getElementsByTagName("window");

for (int i = 0; i < nodes.getLength(); i++) {
  Node mynode = nodes.item(i); 
  System.out.println("Property = " + mynode.getNodeName());
         
  if (mynode.getNodeType() == Node.ELEMENT_NODE) {
    Element myelement = (Element) mynode;
               
    System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
    System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
    System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
  }
}

此示例代码使用 org.w3c.dom.NodeList; 库创建一个名为 nodesNodeList 对象。此对象包含任何名称与字符串 window 匹配的子节点,这是本文中创建的示例配置文件中唯一的节点。

接下来,它创建一个 for 循环来迭代 nodes 列表,按出现顺序获取每个节点并使用 if-then 循环对其进行处理。if-then 循环创建一个名为 myelement 的 Element 对象,其中包含当前节点中的所有元素。您可以使用 getChildNodesgetElementById 等方法查询元素,如项目文档中所述。

在此示例中,元素本质上是配置键。值作为元素的内容存储,您可以使用 .getTextContent 方法提取内容。

在您的 IDE 中或作为二进制文件运行代码

$ java ./DemoXMLParser.java
Property = window
Theme = Dark
Fullscreen = 0
Icon set = Tango

这是完整代码

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

	public static void main(String[] args) {
		Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
		File configFile = new File(configPath.toString(), "myconfig.xml");
		DocumentBuilderFactory factory =
		DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = null;
		
		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
	
		Document doc = null;
	
		try {
			doc = builder.parse(configFile);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
        doc.getDocumentElement().normalize();
	
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i); 
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());
           } // close if
        } // close for
    } // close method
} //close class

使用 Java 更新 XML

用户有时会更改首选项。org.w3c.dom 库可以更新 XML 元素的内容;您只需以与读取 XML 元素相同的方式选择它即可。与使用 .getTextContent 方法不同,您使用 .setTextContent 方法

updatePref = myelement.getElementsByTagName("fullscreen").item(0);
updatePref.setTextContent("1");

System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());  

这会更改应用程序内存中的 XML 文档,但不会将数据写回驱动器。使用 javaxw3c 库的组合,您可以将摄取的 XML 放回配置文件中

TransformerFactory transformerFactory = TransformerFactory.newInstance();

Transformer xtransform;
xtransform = transformerFactory.newTransformer();

DOMSource mydom = new DOMSource(doc);
StreamResult streamResult = new StreamResult(configFile);

xtransform.transform(mydom, streamResult);

这将以转换后的数据静默覆盖之前的配置文件。

这是完整代码,包含更新程序

package myConfigParser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ConfigParser {

	public static void main(String[] args) {
		Path configPath = Paths.get(System.getProperty("user.home"), ".config", "DemoXMLParser");
		File configFile = new File(configPath.toString(), "myconfig.xml");
		DocumentBuilderFactory factory =
		DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = null;
		
		try {
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	
		Document doc = null;
	
		try {
			doc = builder.parse(configFile);
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        doc.getDocumentElement().normalize();
        Node updatePref = null;
//        NodeList nodes = doc.getChildNodes();
        NodeList nodes = doc.getElementsByTagName("window");
        for (int i = 0; i < nodes.getLength(); i++) {
           Node mynode = nodes.item(i); 
           System.out.println("Property = " + mynode.getNodeName());
           
           if (mynode.getNodeType() == Node.ELEMENT_NODE) {
               Element myelement = (Element) mynode;

               System.out.println("Theme = " + myelement.getElementsByTagName("theme").item(0).getTextContent());
               System.out.println("Fullscreen = " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());
               System.out.println("Icon set = " + myelement.getElementsByTagName("icons").item(0).getTextContent());

               updatePref = myelement.getElementsByTagName("fullscreen").item(0);
               updatePref.setTextContent("2"); 
               System.out.println("Updated fullscreen to " + myelement.getElementsByTagName("fullscreen").item(0).getTextContent());           
           } // close if
           
        }// close for

        // write DOM back to the file
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
     	Transformer xtransform;

	DOMSource mydom = new DOMSource(doc);
     	StreamResult streamResult = new StreamResult(configFile);

	try {
		xtransform = transformerFactory.newTransformer();
		xtransform.transform(mydom, streamResult);
	} catch (TransformerException e) {
		e.printStackTrace();
	}
     			
    } // close method
} //close class

保持配置无忧

配置可能是一个看似简单的例行程序。当您的应用程序只有几个可配置的功能时,您可能会从简单的纯文本配置格式开始,但是随着您引入更多选项,读取或写入不正确的数据可能会导致应用程序出现意外行为。使您的配置过程免受故障影响的一种方法是使用像 XML 这样的严格格式,并依靠您的编程语言的内置功能来处理复杂性。

我喜欢为此使用 Java 和 XML,正是因为这个原因。当我尝试读取错误的配置值时,Java 会告诉我,通常是因为我的代码声称要读取的节点在我期望的 XML 路径中不存在。XML 的高度结构化格式有助于我保持代码的可靠性,这对用户和开发人员都有好处。

接下来要读什么
标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算机行业工作,并且经常同时进行。

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.