The Properties class outputs the loaded content in sequence

Keywords: Java

Recently, when I was writing a factory, I encountered the problem of loading a configuration file without loading the data in the properties file in the order in which it was loaded.

I. Problem Code

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class PropertiesTest {
    public static void main(String[] args) {
        InputStream ips = null;
        try {
            ips = Properties.class.getResourceAsStream("/test.properities");
            Properties props = new Properties();
            props.load(ips);
            for(String name:props.stringPropertyNames())
                System.out.println(props.getProperty(name) + " "+name);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(ips != null){
                    ips.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

configuration file

cat=1
dog=2
bird=3
mouse=4
pig=5

Output results

II. Cause Analysis

public class Properties extends Hashtable<Object,Object>

Above is the definition of the Properties class, which you can see inherits the Hashtable class.

public synchronized void load(InputStream inStream) throws IOException {
        load0(new LineReader(inStream));
    }

The load method calls the load0 method

private void load0 (LineReader lr) throws IOException {
        char[] convtBuf = new char[1024];
        int limit;
        int keyLen;
        int valueStart;
        char c;
        boolean hasSep;
        boolean precedingBackslash;

        while ((limit = lr.readLine()) >= 0) {
            c = 0;
            keyLen = 0;
            valueStart = limit;
            hasSep = false;

            //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
            precedingBackslash = false;
            while (keyLen < limit) {
                c = lr.lineBuf[keyLen];
                //need check if escaped.
                if ((c == '=' ||  c == ':') && !precedingBackslash) {
                    valueStart = keyLen + 1;
                    hasSep = true;
                    break;
                } 
                else if ((c == ' ' || c == '\t' ||  c == '\f') && !precedingBackslash)                  {
                    valueStart = keyLen + 1;
                    break;
                }
                if (c == '\\') {
                    precedingBackslash = !precedingBackslash;
                } else {
                    precedingBackslash = false;
                }
                keyLen++;
            }
            while (valueStart < limit) {
                c = lr.lineBuf[valueStart];
                if (c != ' ' && c != '\t' &&  c != '\f') {
                    if (!hasSep && (c == '=' ||  c == ':')) {
                        hasSep = true;
                    } else {
                        break;
                    }
                }
                valueStart++;
            }
            String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
            String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart,                  convtBuf);
            put(key, value);
        }
    }

The load0 method can see that the put() method of the parent Hashtable is called after the key and value values are finally fetched, and the data is stored in Hashtable. The specific Hashtable source code is no longer posted here. In short, the put() method of Hashtable receives the value and stores it in hash value instead of in read order.

Next is the source code for reading. If you are interested, you can take a look at it.

public Set<String> stringPropertyNames() {
        Hashtable<String, String> h = new Hashtable<>();
        enumerateStringProperties(h);
        return h.keySet();
    }

enumerateStringProperties method:

private synchronized void enumerateStringProperties(Hashtable<String, String> h) {
        if (defaults != null) {
            defaults.enumerateStringProperties(h);
        }
        for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
            Object k = e.nextElement();
            Object v = get(k);
            if (k instanceof String && v instanceof String) {
                h.put((String) k, (String) v);
            }
        }
    }

Hashtable's keySet() method

public Set<K> keySet() {
        if (keySet == null)
            keySet = Collections.synchronizedSet(new KeySet(), this);
        return keySet;
    }

3. Solutions

Write a tool class that inherits the Properties class to implement the following functions:

  1. Create a collection framework that stores key values in properties files in read order
  2. Ability to return to the collection created in Step 1

Here I use LinkedList to store key values sequentially (or other set types), and then rewrite the put method. First, I store the values in my built LinkedList, and then call the method of the parent class. The collection that returns the Key value is a newly written orderStringPropertyNames() method that returns LinkedList

The implementation code is as follows:

import java.util.LinkedList;
import java.util.Properties;

public class LinkedProperities extends Properties {

    private LinkedList<String> linkedList = new LinkedList<String>();



    @Override
    public synchronized Object put(Object key, Object value) {
        linkedList.add((String) key);
        return super.put(key, value);
    }


    public LinkedList<String> orderStringPropertyNames() {
        return linkedList;
    }
}

IV. RESULTS TEST

Posted by billiondevil on Mon, 19 Aug 2019 21:46:32 -0700