重写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;
    }
  • 运行上述代码会产生几个问题:
  1. 顺序是无序的,或者说是hash排序的,并不是set的顺序;
  2. 每次write,properties文件的数据都会被覆盖掉,只展示最新数据;
  3. 如果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,遇到重复的后者将覆盖前者。
     */
    @Override
    public synchronized Object put(Object key, Object value) {
        this.removeKeyIfExists(key);
        keyList.add(key);
        return super.put(key, value);
    }
    /**
     * 重写remove方法,删除属性时清除keyList中对应的key。
     */
    @Override
    public 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对象,且和之前的不产生冲突
     */
    @Override
    public 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();
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/18975.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

4.Redis10大数据类型

Redis10大数据类型 Which 101.String&#xff08;字符串&#xff09;2.List&#xff08;列表&#xff09;3.hash &#xff08;哈希&#xff09;4.Set&#xff08;集合&#xff09;5.zset(sorted set&#xff1a;有序集合)6.Redis GEO &#xff08;地理空间&#xff09;7.HyperL…

Linux最常用的15个基本命令

目录 Linux基本命令 命令1&#xff1a;ls &#xff08;查看指定目录中有哪些内容&#xff09; ls / 相当于查看根目录中的内容&#xff0c;相当于查看我的电脑 ls -l&#xff08;小写l&#xff0c;或者使用ll&#xff09;详细查看目录下所有内容 ls /usr/lib&#xff08…

“AI代劳”,跨域赋能“智慧企业”

随着全球数字信息化的到来&#xff0c;各大新兴行业企业也逐渐意识到“智慧化”转型的重要性&#xff0c;但目前仍有不少企业在面临着人力成本高、运营管理效率低、营销获客效果差、数据分析能力薄弱等瓶颈&#xff0c;那么&#xff0c;处于这些瓶颈期的企业该如何实现“智慧化…

【Java零基础入门篇】第 ④ 期 - 继承(二)

博主&#xff1a;命运之光 专栏&#xff1a;JAVA入门 学习目标 1.掌握继承性的主要作用、实现、使用限制&#xff1b; 2.掌握this和super的含义及其用法&#xff1b; 3.掌握方法覆写的操作&#xff1b; 4.掌握final关键字的使用&#xff1b; 5.掌握类变量、实例变量和局部变量的…

数据库创建与管理

目录 一、创建数据库 1&#xff0e;准备创建数据库 2&#xff0e;创建数据库实例分析 方法一&#xff1a;使用对象资源或企业管理器创建数据库 方法二&#xff1a;使用Transact-SQL命令创建数据库 二、管理数据库 1&#xff0e;修改数据库 使用SQL命令修改数据库 2&…

如何将ChatGPT训练成某个领域的专家!附完整示例!

背景 最近听了 八叉的一个ChatGPT讲座&#xff0c;讲的是如何将ChatGPT训练成领域专家&#xff0c;这样我们就可以用它来解决该领域的各种问题。 整个讲座中最让我印象深刻的就是训练方法&#xff0c;它是通过让ChatGPT向我们提问&#xff0c;然后由我们给出答案的方式进行训…

自学Java怎么找工作?好程序员学员大厂面试经验分享!

简历要详细&#xff1a; 简历中的项目用到的技术点和个人负责的模块尽量写详细一些。其次&#xff0c;根据自己项目中用到的熟悉的技术点&#xff0c;在个人技能介绍模块去突出&#xff0c;面试官基本会根据你简历上写的去提问的&#xff0c;这样我们回答起来就会更加得心应手。…

Web3中文|乱花渐欲meme人眼,BRC-20总市值逼近10亿美元

现在的Web3加密市场&#xff0c;用“乱花渐欲meme人眼”来形容再合适不过了。 何为meme&#xff1f; “meme”这个词大概很多人都不知道如何正确发音&#xff0c;并且一看到它就会和狗狗币Dogecoin等联系在一起。那它究竟从何而来呢&#xff1f; Meme&#xff1a;[mi:m]&#x…

【C++初阶】C++模版(初阶)

文章目录 前言泛型编程函数模版函数模板概念函数模板格式函数模板的原理函数模板的实例化1.隐式实例化2.显式实例化 函数模版的匹配规则 类模版类模板的定义格式类模板的实例化 总结 前言 C的模版也是相较于C语言更有优势的地方&#xff0c;正是有了模版&#xff0c;才让C真正…

Python基础入门(3)—— 什么是函数递归、字典、集合、文件操作、异常处理?

文章目录 01 | &#x1f698;函数 && 递归&#x1f694;函数&#x1f694;递归 02 | &#x1f698;字典03 | &#x1f698;集合04 | &#x1f698;文件05 | &#x1f698;异常处理06 | &#x1f698;习题 Better late than never. 迟做总比不做好;晚来总比不来好。 01 …

2023年湖北省建设厅特种作业操作证报名条件是什么?

建筑施工特种作业人员是指在房屋建筑和市政工程施工活动中&#xff0c;从事可能对本人、他人及周围设备设施的安全造成重大危害作业的人员。建筑施工特种作业人员必须经建设主管部门考核合格&#xff0c;取得建筑施工特种作业人员操作资格证书&#xff08;以下简称“资格证书”…

常用数据处理方式

文章目录 缺失值处理删除法填充法基于统计学变量填充基于插值填充基于模型填充基于预测填充 不处理 异常值处理基于统计分析的方法基于聚类的方法基于树的方法基于预测的方法 数据重采样标准化min-max标准化&#xff08;归一化&#xff09;z-score标准化&#xff08;规范化&…

CSS var()的使用

最近在做流量对比的功能&#xff0c;有如下的效果图&#xff0c;当某个节点失败的时候&#xff0c;点击能够弹出对应的提示信息。 这个库使用的是jenkins-pipeline 的库&#xff0c; 但是由于它原本的提示框比较糟糕&#xff0c;所以我们想结合antd的tooltip进行展示&#xff0…

WhatsApp 营销:获得更多潜在客户和销售(一)

你需要了解客户的世界观才能进行有效的营销&#xff0c;你应该投入时间和精力来学习和实施你的业务WhatsApp营销 -因为你的客户出现在WhatsApp上&#xff0c;他们希望在那里联系&#xff0c;而不是在他们讨厌被打断的电子邮件或电话中。 SaleSmartly&#xff08;ss客服&#x…

商户查询的缓存——添加redis缓存

1.什么是缓存 缓存就是数据交换的缓冲区&#xff08;Cache&#xff09;,是存储数据的临时地方&#xff0c;一般读写性能较高 2.添加redis缓存 Autowired private StringRedisTemplate stringRedisTemplate; /*** 通过id查询商户信息* param id* return*/ Override public Resu…

首发 | 人工智能赋能的未来作战构想(上): 海上作战篇

源自&#xff1a;中国指挥与控制学会 ▲图1&#xff1a;报告封面和封底 一、人工智能赋能改变战争形态 ▲图2&#xff1a;以AI赋能万物互联 二、人工智能赋能的海上作战 ▲图3&#xff1a;海上作战要保持持续有效的火力输出 &#xff08;一&#xff09;海上防御作战构想 ▲图4&…

通过Modbus实现TTS语音全彩声光告警-博灵语音通知终端-网络语音报警灯

背景 目前PLC在工业领域应用广泛&#xff0c;在运行过程中可能会涉及到各种告警。 为了简单快速的实现语音声光告警&#xff0c;本文以大连英仕博科技出品的博灵语音通知终端为例&#xff0c;演示如何通过Modbus TCP协议实现声光告警推送。 播报效果演示 Modbus-博灵语音通知…

自学黑客(网络安全)看这篇就够了

写了这么多编程环境和软件安装的文章&#xff0c;还有很多小伙伴在后台私信说看不懂。我都有点头疼了&#xff0c;但是小伙伴们求学的心情我还是能理解&#xff0c;竟然这么多人给我反馈了&#xff0c;那我就再写一篇网络安全自学的教程吧&#xff01;大家耐心看完&#xff0c;…

音频信号处理库librosa

参考&#xff1a; 1. librosa官网 2. librosa语音信号处理 3. 语音信号处理库 ——Librosa 4. librosa音频处理教程 5. Python音频信号处理库函数librosa介绍 0 谱分析函数 1. librosa 读取信号 librosa.load(path, sr22050, monoTrue, offset0.0, durationNone)读取音频文件…

2023年5月DAMA-CDGA/CDGP数据治理认证开班啦,我要报名学习

6月18日DAMA-CDGA/CDGP数据治理认证考试开放报名中&#xff01; 考试开放地区&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特、杭州、南京、济南、成都、西安。其他地区凑人数中… DAMA-CDGA/CDGP数据治理认证班进行中&#xff0c;报名从速&#xff01; DAMA认证为数据…