Rewrite the Properties class to realize the orderly reading and writing of properties files, append data, and solve Chinese garbled characters

foreword

        *.properties file is a type of configuration file supported by Java, and Java provides the properties class to read the information in the properties file. In the file, the configuration information that will be reused in the project is stored in the form of key-value pairs "key=value", and the information is read through the "Properties" class to achieve "write once, call multiple places; configuration needs to be modified file, only modify one place" effect .

        This article introduces [reading and writing properties files], [adding data], [realizing orderly reading and writing], and [solving Chinese garbled characters] .


text

1. Introduction to the Properties class

        Properties has a special role, which is specially used to load the xxx.properties configuration file. Properties inherit from Hashtable and represent a persistent set of properties that can be stored in or loaded from a stream. In the property list, each key and its corresponding value is a string.

  • Common methods: 
method name meaning
public String getProperty(String key) Searches for a property in this property list with the specified key

public Object setProperty(String key,String value)

Add properties to Properties, overwrite if there is one, add new if not
public void load(InputStream in) Read a property list (key and element pair) from the input stream
public void load(Reader reader) Reads a property list (key and element pair) from an input character stream in a simple line-oriented format
public void store(OutputStream out, String comments) writes a list of properties (key and element pairs) from this Properties table to the output stream
public void store(Writer writer, String comments) Writes a list of properties (key and element pairs) from this Properties table to the output character


2. Read and write properties files

        Since Properties inherits from Hashtable, when new properties are written to the .properties file, the order in which the results are displayed may not be the order in which we originally set the properties. Pay attention to this problem, we will solve it later.

  • Properties inherit from Hashtable

  • With the help of IO stream, use the Properties class to manipulate .properties files
    // .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;
    }
  • Running the above code creates several problems:
  1. The order is unordered, or sorted by hash, not the order of set;
  2. Every time write, the data in the properties file will be overwritten, and only the latest data will be displayed;
  3. If the value contains Chinese, it will be garbled;

        Below, we will use this to solve these problems.


3. Solve the problems of appending, orderly and garbled characters

2.1 Realize orderly reading and writing

        Customize a PropertiesUtil class, which inherits from Properties. PropertiesUtil provides a method that returns a List composed of keys according to the order in which they are stored, getKeyList(), which can solve the problem of orderly writing. As for reading, the same is true.

  • How to ensure that the getKeyList() method returns a collection of ordered keys?

        By querying the source code of the Properties method, it is found that when the Properties.load() method loads content from the .properties file, it reads in the order of the files, and the bottom layer of the Properties.setProperty() method calls the parent class HashTable.put() method to store.

        HashTable itself is unordered, so the solution is to let PropertiesUtil hold a private set that can store keys in an orderly manner, and then rewrite the put() method of the parent class, and use super.put() in the method body as usual The attribute is stored, and the key is added to the collection of stored keys at the same time.

        Check the source code again, and found that: Properties store the content of the current object in the specified output stream and there are two methods, save() and store(), but their underlying logic is the same, all by calling Properties.keys( ) method to obtain an Enumeration , then traverse the Enumeration, and write the corresponding key and value to the output stream in turn. To ensure that the writing is in order, it is necessary to ensure that the element keys extracted from the Enumeration returned by the traversal keys() method are in order.

        Therefore, the final solution is to rewrite the keys() method to ensure that the keys obtained by traversing the Enumeration are in order.

  • The complete code of the PropertiesUtil class is as follows:
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 Realize data append

       In the above [reading and writing properties files], the method of writing is briefly introduced, because new Properties() will be added every time, so all the source data will be overwritten every time. After knowing the reason, we naturally found a solution: load the source file before writing, and then append it in an orderly manner.

  • Source code: first load the input stream, then set new attributes, and finally store the output stream
    /**
     * 模拟向 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 running results:

  •  Properties running results:

2.3 Solve Chinese garbled characters

        The above test uses Chinese. If the parameter contains [Chinese], there will be garbled characters. The solution: when inputting and outputting, set [charsetName = "utf-8"].

  • Code display:
    /**
     * 模拟向 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;
    }
  • The result display:


4. Introduction to PropertiesConfiguration

        PropertiesConfiguration is a class that Apache helps us to read properties files in the order of files, and it can do what the Properties class can do. Not only that, he also has many convenient and practical additional functions.

  • Sample Code: Functional Demonstration
    /**
     * 模拟向 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();
        }
    }

Guess you like

Origin blog.csdn.net/weixin_44259720/article/details/130615132