重写Properties类,实现对properties文件的有序读写,数据追加,解决中文乱码

前言
*.properties文件,是 Java 支持的一种配置文件类型,并且 Java 提供了 properties 类来读取 properties 文件中的信息。文件中以键值对 "键=值"的形式,存储工程中会多次重复使用的配置信息,通过“Properties”类来读取这些信息,以实现“一次编写,多处调用;需要修改配置文件时,只修改一处即可”的效果。
本文介绍【读写 properties 文件】,【追加数据】,【实现有序读写】,【解决中文乱码】问题。
正文
一、Properties 类简介
Properties有一个特殊的作用,专门用来加载xxx.properties配置文件。Properties继承于Hashtable,表示了一个持久的属性集,可保存在流中或从流中加载。属性列表中,每个键及其对应值都是一个字符串。
- 常用方法:
| 方法名 | 含义 |
|---|---|
| public String getProperty(String key) | 用指定的键在此属性列表中搜索属性 |
| public Object setProperty(String key,String value) | 向Properties中增加属性,有则覆盖,没有则新增 |
| public void load(InputStream in) | 从输入流中读取属性列表(键和元素对) |
| public void load(Reader reader) | 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对) |
| public void store(OutputStream out, String comments) | 将此 Properties 表中的属性列表(键和元素对)写入输出流 |
| public void store(Writer writer, String comments) | 将此 Properties 表中的属性列表(键和元素对)写入输出字符 |
二、读写 properties 文件
由于Properties继承于Hashtable,所以,当新增属性写入到 .properties 文件的时候,结果显示的顺序可能并不是我们当初set属性的顺序。这个问题要注意,我们再后面会解决。
- Properties继承于Hashtable

- 借助IO流,利用Properties类操作.properties文件
// .properties文件的相对路径private static final String FILE_ADDRESS = "src/main/resources/application.properties";@GetMapping("/test")public String test() {log.info("test 接口调用:【开始】 --------------------");this.writeIntoText(new Properties());Properties prop = this.readFromText();log.info("jdbc.username = " + properties.getProperty("jdbc.username"));log.info("test 接口调用:【结束】 --------------------");return "success";}/*** 模拟向 properties 文件写入数据*/private void writeIntoText(Properties properties){OutputStream output = null;try {output = new FileOutputStream(FILE_ADDRESS);properties.setProperty("jdbc.driver", "com.mysql.jdbc.Driver");properties.setProperty("jdbc.url", "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8");properties.setProperty("jdbc.username", "root");properties.setProperty("jdbc.password", "123456");properties.store(output, "tjm modify");} catch (IOException io) {io.printStackTrace();} finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 读取 properties 文件,返回 Properties 对象*/private Properties readFromText(){Properties properties = new Properties();InputStream input = null;try {input = new FileInputStream(FILE_ADDRESS);properties.load(input);} catch (IOException io) {io.printStackTrace();} finally {if (input != null) {try {input.close();} catch (IOException e) {e.printStackTrace();}}}return properties;}
- 运行上述代码会产生几个问题:
- 顺序是无序的,或者说是hash排序的,并不是set的顺序;
- 每次write,properties文件的数据都会被覆盖掉,只展示最新数据;
- 如果value里面包含中文,那一定会是乱码;
下面,我们就以此解决这几个问题。
三、解决追加、有序、乱码问题
2.1 实现有序读写
自定义一个PropertiesUtil类,该类继承自Properties。PropertiesUtil提供一个返回由key按照存入顺序组成的List的方法,getKeyList(),这样就可以解决有序写的问题。至于读嘛,也是一样的道理。
- 如何保证getKeyList()方法返回的就是有序的key组成的集合?
通过查询Properties方法的源码,发现:Properties.load()方法从.properties文件加载内容时,是按照文件顺序进行读取,而Properties.setProperty()方法底层调用的是父类HashTable.put()方法进行储存。
HashTable本身就是无序的,所以,解决办法就是让PropertiesUtil持有一个私有的可以有序存储key的集合,然后重写父类的put()方法,在方法体中照常通过super.put()进行属性的存储,同时将key添加到存储key的集合中。
再查源码,发现: Properties将当前对象的内容存放到指定的输出流的方法又2个,save()和store(),但是它们的底层逻辑都是一样的,都是通过调用Properties.keys()方法获取一个Enumeration,然后对该Enumeration进行遍历,依次将对应的key和value写入到输出流中。要保证写入是有序的,就要保证遍历keys()方法返回的Enumeration取出的元素key是有序的。
所以,得到最终的解决方法是:重写keys()方法,保证遍历Enumeration得到的key是有序的。

- PropertiesUtil类的完整代码如下:
import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;/*** @Description: Java Punk* @Created: by JavaPunk on 2023/5/10 16:33* @Version: 1.0.0*/
public class PropertiesUtil extends Properties {private static final long serialVersionUID = 1L;private List<Object> keyList = new ArrayList<Object>();/*** 默认构造方法*/public PropertiesUtil() {}/*** 从指定路径加载信息到Properties* @param path*/public PropertiesUtil(String path) {try {InputStream is = new FileInputStream(path);this.load(is);} catch (FileNotFoundException e) {e.printStackTrace();throw new RuntimeException("指定文件不存在!");} catch (IOException e) {e.printStackTrace();}}/*** 重写put方法,按照property的存入顺序保存key到keyList,遇到重复的后者将覆盖前者。*/@Overridepublic synchronized Object put(Object key, Object value) {this.removeKeyIfExists(key);keyList.add(key);return super.put(key, value);}/*** 重写remove方法,删除属性时清除keyList中对应的key。*/@Overridepublic synchronized Object remove(Object key) {this.removeKeyIfExists(key);return super.remove(key);}/*** keyList中存在指定的key时则将其删除*/private void removeKeyIfExists(Object key) {keyList.remove(key);}/*** 获取Properties中key的有序集合* @return*/public List<Object> getKeyList() {return keyList;}/*** 保存Properties到指定文件,默认使用UTF-8编码* @param path 指定文件路径*/public void store(String path) {this.store(path, "UTF-8");}/*** 保存Properties到指定文件,并指定对应存放编码* @param path 指定路径* @param charset 文件编码*/public void store(String path, String charset) {if (path != null && !"".equals(path)) {try {OutputStream os = new FileOutputStream(path);BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, charset));this.store(bw, null);bw.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}} else {throw new RuntimeException("存储路径不能为空!");}}/*** 重写keys方法,返回根据keyList适配的Enumeration,且保持HashTable keys()方法的原有语义,* 每次都调用返回一个新的Enumeration对象,且和之前的不产生冲突*/@Overridepublic synchronized Enumeration<Object> keys() {return new EnumerationAdapter<Object>(keyList);}/*** List到Enumeration的适配器*/private class EnumerationAdapter<T> implements Enumeration<T> {private int index = 0;private final List<T> list;private final boolean isEmpty;public EnumerationAdapter(List<T> list) {this.list = list;this.isEmpty = list.isEmpty();}public boolean hasMoreElements() {// isEmpty的引入是为了更贴近HashTable原有的语义,在HashTable中添加元素前调用其keys()方法获得一个Enumeration的引用,// 之后往HashTable中添加数据后,调用之前获取到的Enumeration的hasMoreElements()将返回false,但如果此时重新获取一个// Enumeration的引用,则新Enumeration的hasMoreElements()将返回true,而且之后对HashTable数据的增、删、改都是可以在// nextElement中获取到的。return !isEmpty && index < list.size();}public T nextElement() {if (this.hasMoreElements()) {return list.get(index++);}return null;}}
}
2.2 实现数据追加
在上面【读写 properties 文件】中简单介绍了写入的方法,因为每次都会 new Properties(),所以每次都会把源数据全部覆盖掉。清楚了原因,自然也就找到了解决办法:写入之前先将源文件加载出来,再进行有序的追加。
- 源码:先加载输入流,随后set新属性,最后store输出流
/*** 模拟向 properties 文件追加写入数据*/private void addToText(){// 将 PropertiesUtil 换成重写前的 Properties 类,最后写入的顺序是hash排序的// Properties properties = new Properties();PropertiesUtil properties = new PropertiesUtil();InputStream input = null;OutputStream output = null;try {// 先用输入流加载.properties文件input = new BufferedInputStream(new FileInputStream(FILE_ADDRESS));properties.load(new InputStreamReader(input));// 输出流(FileOutputStream)对象,必须在Properties类加载(load)完以后创建(new)output = new FileOutputStream(FILE_ADDRESS);properties.setProperty("jdbc2.username", "PropertiesUtil Orderly test");properties.store(output, "tjm modify");} catch (IOException io) {io.printStackTrace();} finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}if (input != null) {try {input.close();} catch (IOException e) {e.printStackTrace();}}}}
- PropertiesUtil运行结果:

- Properties运行结果:

2.3 解决中文乱码
上面测试使用的都是中文,如果参数中包含【中文】,就会出现乱码问题,解决办法:输入输出时,设置【charsetName = "utf-8"】。
- 代码展示:
/*** 模拟向 properties 文件追加写入数据*/private void addToText(){// 将 PropertiesUtil 换成重写前的 Properties 类,最后写入的顺序是hash排序的
// Properties properties = new Properties();PropertiesUtil properties = new PropertiesUtil();InputStream input = null;OutputStream output = null;try {// 先用输入流加载.properties文件input = new BufferedInputStream(new FileInputStream(FILE_ADDRESS));properties.load(new InputStreamReader(input, "utf-8"));// 输出流(FileOutputStream)对象,必须在Properties类加载(load)完以后创建(new)output = new FileOutputStream(FILE_ADDRESS);properties.setProperty("jdbc3.username", "PropertiesUtil 有序测试");properties.store(new OutputStreamWriter(output, "utf-8"), "tjm modify");} catch (IOException io) {io.printStackTrace();} finally {if (output != null) {try {output.close();} catch (IOException e) {e.printStackTrace();}}if (input != null) {try {input.close();} catch (IOException e) {e.printStackTrace();}}}}/*** 读取 properties 文件,返回 Properties 对象*/private Properties readFromText(){PropertiesUtil properties = new PropertiesUtil();InputStream input = null;try {input = new FileInputStream(FILE_ADDRESS);properties.load(new InputStreamReader(input, "utf-8"));log.info("举例说明:jdbc3.username = " + properties.getProperty("jdbc3.username"));} catch (IOException io) {io.printStackTrace();} finally {if (input != null) {try {input.close();} catch (IOException e) {e.printStackTrace();}}}return properties;}
- 结果展示:

四、PropertiesConfiguration介绍
PropertiesConfiguration 是 Apache 帮我们实现按照文件的顺序读取properties文件的类,Properties类能做的它都能做。不仅如此,他还有许多方便实用的附加功能。
- 示例代码:功能展示
/*** 模拟向 properties 文件写入数据*/private void setParam(){try {PropertiesConfiguration propsConfig = new PropertiesConfiguration(FILE_ADDRESS);propsConfig.setEncoding("utf-8");// 修改属性之后自动保存,省去了propsConfig.save()过程propsConfig.setAutoSave(true);// setProperty:遇到同名key会替换value,没有则新增propsConfig.setProperty("set.name", "123456");// addProperty:只会新增,即使遇到遇到同名key也不会替换propsConfig.addProperty("add.name", "456789");}catch (Exception ex) {ex.printStackTrace();}}/*** 模拟向 properties 文件读取数据*/private void getParam(){try {PropertiesConfiguration propsConfig = new PropertiesConfiguration(FILE_ADDRESS);propsConfig.setEncoding("utf-8");String username = propsConfig.getString("jdbc.username");log.info("举例说明:jdbc.username = " + username);}catch (Exception ex) {ex.printStackTrace();}}
相关文章:
重写Properties类,实现对properties文件的有序读写,数据追加,解决中文乱码
前言 *.properties文件,是 Java 支持的一种配置文件类型,并且 Java 提供了 properties 类来读取 properties 文件中的信息。文件中以键值对 "键值"的形式,存储工程中会多次重复使用的配置信息,通过“Properties”类来读…...
态势感知与信质、信量
未来的新智能是人机环境系统智能,而人机融合的态势感知是其关键,简单地说,态势感知(situation awareness)就是智能体在“一定时间和空间环境中的元素的感知,对它们的含义的理解,并对他们稍后状态…...
20230508----重返学习-call()与bind()重写-JS中数据类型检测汇总-装箱与拆箱-类的多种继承方案
day-065-sixty-five-20230508-call()与bind()重写-JS中数据类型检测汇总-装箱与拆箱-类的多种继承方案 call()与bind()重写 call()重写 call()的作用例子 let obj {name: 珠峰培训 } const fn function fn(x, y, ev) {console.log(this, x, y, ev)return x y } let res f…...
Node.js对ES6 及更高版本的支持
目录 1、简介 2、默认情况下什么特性随着 Node.js 一起发布? 3、有哪些特性在开发中? 4、移除这个标记(--harmony)吗 5、Node.js 对应 V8 引擎 1、简介 Node.js 是针对 V8 引擎构建的。通过与此引擎的最新版本保持同步&…...
【华为OD机试2023】工作安排 100% C++ Java Python
【华为OD机试2023】工作安排 100% C++ Java Python 前言 如果您在准备华为的面试,期间有想了解的可以私信我,我会尽可能帮您解答,也可以给您一些建议! 本文解法非最优解(即非性能最优),不能保证通过率。 Tips1:机试为ACM 模式 你的代码需要处理输入输出,input/cin接收…...
面试题Spring - 关于Spring的25个经典问题和答案
文章目录 1 什么是Spring框架?Spring框架有哪些主要模块?2 使用Spring框架能带来哪些好处?3 什么是控制反转(IOC)?什么是依赖注入?4 请解释下Spring框架中的IoC?5 BeanFactory和ApplicationContext有什么区…...
C++学习day--10 条件判断、分支
1、if语句 if 语句的三种形态 形态1:如果。。。那么。。。 #include <iostream> using namespace std; int main( void ) { int salary; cout << " 你月薪多少 ?" ; cin >> salary; if (salary < 20000) { cout <&…...
和月薪5W的聊过后,才发现自己一直在打杂···
前几天和一个朋友聊面试,他说上个月同时拿到了腾讯和阿里的offer,最后选择了阿里。 我了解了下他的面试过程,就一点,不管是阿里还是腾讯的面试,这个级别的程序员,都会考察项目管理能力,并且权重…...
SSM框架学习-AOP通知类型
在AOP中,通知(Advice)是对切点进行操作的方法,用于实现切面定义的具体逻辑。Spring框架支持五种类型的通知: 1. 前置通知(Before advice) 在连接点执行前,执行通知 Before("**…...
微信小程序原生开发功能合集十四:登录健权及注册功能实现
本章实现微信自动登录及注册修改功能,包括匿名账号生成、权限自动检测、注册界面及注册流程的实现。 另外还提供小程序开发基础知识讲解课程,包括小程序开发基础知识、组件封装、常用接口组件使用及常用功能实现等内容,具体如下: 1. CSDN课程: https://edu.csdn…...
【Java零基础入门篇】第 ⑤ 期 - 抽象类和接口(二)
博主:命运之光 专栏:Java零基础入门 学习目标 1.了解什么是抽象类,什么是接口; 2.掌握抽象类和接口的定义方法; 3.理解接口和抽象类的使用场景; 4.掌握多态的含义和用法; 5.掌握内部类的定义方法…...
Halcon 集合运算(差集difference、交集intersection、并集union2、打散connection与 合集 union1)
文章目录 1 差集difference2. 交集intersection3. 并集union24 打散connection与 合集 union1 (二者互为反义词)4.1 打散connection与4.2 合集 union1 (注意与交集的区别)5 示例原图1 差集difference difference (Operator) Name difference — Calculate the difference …...
Allegro约束规则设计
首先是物理规则。 然后是间距规则。 如果有些特殊要求,还需要设计电气规则。 原则上,把规则设计好,然后把规则赋值给网络。 物理规则。PCS。 对于名字为DEFAULT的PCS,这是最基础的整板默认规则。 没有特殊要求的网络,…...
PyQt5桌面应用开发(11):摸鱼也要讲基本法之桌面精灵
本文目录 PyQt5桌面应用系列鼠标不要钱,手腕还不要钱吗?PyQt5源程序python文件资源定义界面定义文件 技术要素资源文件StyleSheetsQMainWindow设置窗体几何 结论 PyQt5桌面应用系列 PyQt5桌面应用开发(1):需求分析 PyQ…...
Talk预告 | 大连理工大学IIAU Lab在读博士生严彬:走向通用实例感知
本期为TechBeat人工智能社区第495期线上Talk! 北京时间5月10日(周三)20:00,大连理工大学IIAU Lab在读博士生—严彬的Talk将准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “走向通用实例感知”,届时将介绍和探讨通用实…...
2023-05-04 LeetCode每日一题(摘水果)
2023-05-04每日一题 一、题目编号 2106. 摘水果二、题目链接 点击跳转到题目位置 三、题目描述 在一个无限的 x 坐标轴上,有许多水果分布在其中某些位置。给你一个二维整数数组 fruits ,其中 fruits[i] [positioni, amounti] 表示共有 amounti 个水…...
[工具]Pytorch-lightning的使用
Pytorch-lightning的使用 Pytorch-lightning介绍Pytorch-lightning与Pytorch的区别Pytorch-lightning框架的优势Pytorch-lightning框架 重要资源 Pytorch-lightning介绍 这里介绍Pytorch_lighting框架. Pytorch-lightning与Pytorch的区别 Pytorch-lightning可以简单的看作是…...
互联网摸鱼日报(2023-05-09)
互联网摸鱼日报(2023-05-09) InfoQ 热门话题 面向数字化提质提效的低代码架构设计 | 低代码技术内幕 提升字节规模化效能的平台化思路 | 极客有约 从微服务转为单体架构、成本降低 90%,亚马逊内部案例引发轰动!CTO&…...
MySQL常见的存储引擎
InnoDB:InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在MySQL 5.5之后,InnoDB是默认的MySQL存储引擎。 特点:1、DML操作遵循ACID模型,支持事务; 2、行级锁,提高并发访问性能; 3、支持外键FOREIGN KEY约…...
迅为i.MX6ULL开发板生成 KEY 文件,并安装
使用“ssh-keygen” 生成个四个 key 文件“ssh_host_rsa_key” “ssh_host_dsa_key” “ssh_host_ecdsa_key” 和“ssh_host_ed25519_key” 。 1 在虚拟机 Ubuntu 控制台, “ /home/ssh/openssh-4.6p1” 目录下, 使用命 令“ssh-keygen -t rsa -f ssh…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
【Linux手册】探秘系统世界:从用户交互到硬件底层的全链路工作之旅
目录 前言 操作系统与驱动程序 是什么,为什么 怎么做 system call 用户操作接口 总结 前言 日常生活中,我们在使用电子设备时,我们所输入执行的每一条指令最终大多都会作用到硬件上,比如下载一款软件最终会下载到硬盘上&am…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
医疗AI模型可解释性编程研究:基于SHAP、LIME与Anchor
1 医疗树模型与可解释人工智能基础 医疗领域的人工智能应用正迅速从理论研究转向临床实践,在这一过程中,模型可解释性已成为确保AI系统被医疗专业人员接受和信任的关键因素。基于树模型的集成算法(如RandomForest、XGBoost、LightGBM)因其卓越的预测性能和相对良好的解释性…...
Python爬虫实战:研究Restkit库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...
