重写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…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
深度学习水论文:mamba+图像增强
🧀当前视觉领域对高效长序列建模需求激增,对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模,以及动态计算优势,在图像质量提升和细节恢复方面有难以替代的作用。 🧀因此短时间内,就有不…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...
