java读写properties配置文件不改变属性的顺序和注释

先贴代码

  1 import java.io.BufferedWriter;
  2 import java.io.File;
  3 import java.io.FileInputStream;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.io.InputStreamReader;
  7 import java.io.OutputStream;
  8 import java.io.OutputStreamWriter;
  9 import java.io.Reader;
 10 import java.io.Writer;
 11 import java.util.Iterator;
 12 import java.util.LinkedHashMap;
 13 import java.util.Map;
 14 import java.util.Properties;
 15 import java.util.Set;
 16 
 17 /**
 18  * 扩展properties工具类
 19  * 
 20  * @author tangming
 21  * @date 2017-11-10
 22  */
 23 public class SafeProperties {
 24 
 25     /**
 26      * 内部属性表
 27      */
 28     private final Properties props;
 29 
 30     /**
 31      * 保存key与comment的映射, 同时利用这个映射来保证key的顺序。
 32      */
 33     private final LinkedHashMap<String, String> keyCommentMap = new LinkedHashMap<String, String>();
 34 
 35     private static final String BLANK = "";
 36 
 37     public SafeProperties() {
 38         super();
 39         props = new Properties();
 40     }
 41 
 42     public SafeProperties(Properties defaults) {
 43         super();
 44         props = new Properties(defaults);
 45     }
 46 
 47     /**
 48      * 设置一个属性,如果key已经存在,那么将其对应value值覆盖。
 49      * 
 50      * @param key
 51      * @param value
 52      * @return
 53      */
 54     public String setProperty(String key, String value) {
 55         return setProperty(key, value, BLANK);
 56     }
 57 
 58     /**
 59      * 设置一个属性,如果key已经存在,那么将其对应value值覆盖。
 60      * 
 61      * @param key 键
 62      * @param value 与键对应的值
 63      * @param comment 对键值对的说明
 64      * @return
 65      */
 66     public synchronized String setProperty(String key, String value, String comment) {
 67         Object oldValue = props.setProperty(key, value);
 68         if (BLANK.equals(comment)) {
 69             if (!keyCommentMap.containsKey(key)) {
 70                 keyCommentMap.put(key, comment);
 71             }
 72         } else {
 73             keyCommentMap.put(key, comment);
 74         }
 75         return (String) oldValue;
 76     }
 77 
 78     /**
 79      * 根据key获取属性表中相应的value。
 80      * 
 81      * @param key
 82      * @return
 83      */
 84     public String getProperty(String key) {
 85         return props.getProperty(key);
 86     }
 87 
 88     /**
 89      * 根据key获取属性表中相应的value。 如果没找到相应的value,返回defaultValue。
 90      * 
 91      * @param key
 92      * @param defaultValue
 93      * @return
 94      */
 95     public String getProperty(String key, String defaultValue) {
 96         return props.getProperty(key, defaultValue);
 97     }
 98 
 99     /**
100      * 从一个字符流中读取属性到属性表中
101      * 
102      * @param reader
103      * @throws IOException
104      */
105     public synchronized void load(Reader reader) throws IOException {
106         load0(new LineReader(reader));
107     }
108 
109     /**
110      * 从一个字节流中读取属性到属性表中
111      * 
112      * @param inStream
113      * @throws IOException
114      */
115     public synchronized void load(InputStream inStream) throws IOException {
116         load0(new LineReader(inStream));
117     }
118 
119     /**
120      * 从一个字节流中读取属性到属性表中
121      * 
122      * @param inStream
123      * @param charset
124      * @throws IOException
125      */
126     public synchronized void load(InputStream inStream, String charset) throws IOException {
127         InputStreamReader reader = new InputStreamReader(inStream, charset);
128         load0(new LineReader(reader));
129     }
130 
131     /**
132      * 从一个文件中读取属性到属性表中
133      * 
134      * @param file 属性文件
135      * @param charset 字符集
136      * @throws IOException
137      */
138     public synchronized void load(File file, String charset) throws IOException {
139         FileInputStream inputStream = new FileInputStream(file);
140         InputStreamReader reader = new InputStreamReader(inputStream, charset);
141         load0(new LineReader(reader));
142     }
143 
144     /**
145      * 从一个文件中读取属性到属性表中 默认字符集为utf-8
146      * 
147      * @param file 属性文件
148      * @throws IOException
149      */
150     public synchronized void load(File file) throws IOException {
151         FileInputStream inputStream = new FileInputStream(file);
152         InputStreamReader reader = new InputStreamReader(inputStream, "utf-8");
153         load0(new LineReader(reader));
154     }
155 
156     /**
157      * 将属性表中的属性写到字符流里面。
158      * 
159      * @param writer
160      * @throws IOException
161      */
162     public void store(Writer writer) throws IOException {
163         store0((writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer), false);
164     }
165 
166     /**
167      * 将属性表中的属性写到字节流里面。
168      * 
169      * @param out
170      * @throws IOException
171      */
172     public void store(OutputStream out) throws IOException {
173         store0(new BufferedWriter(new OutputStreamWriter(out, "utf-8")), true);
174     }
175 
176     /**
177      * 如果属性表中某个key对应的value值和参数value相同 那么返回true,否则返回false。
178      * 
179      * @param value
180      * @return
181      */
182     public boolean containsValue(String value) {
183         return props.containsValue(value);
184     }
185 
186     /**
187      * 如果属性表中存在参数key,返回true,否则返回false。
188      * 
189      * @param key
190      * @return
191      */
192     public boolean containsKey(String key) {
193         return props.containsKey(key);
194     }
195 
196     /**
197      * 获取属性表中键值对数量
198      * 
199      * @return
200      */
201     public int size() {
202         return props.size();
203     }
204 
205     /**
206      * 检查属性表是否为空
207      * 
208      * @return
209      */
210     public boolean isEmpty() {
211         return props.isEmpty();
212     }
213 
214     /**
215      * 清空属性表
216      */
217     public synchronized void clear() {
218         props.clear();
219         keyCommentMap.clear();
220     }
221 
222     /**
223      * 获取属性表中所有key的集合。
224      * 
225      * @return
226      */
227     public Set<String> propertyNames() {
228         return props.stringPropertyNames();
229     }
230 
231     public synchronized String toString() {
232         StringBuffer buffer = new StringBuffer();
233         Iterator<Map.Entry<String, String>> kvIter = keyCommentMap.entrySet().iterator();
234         buffer.append("[");
235         while (kvIter.hasNext()) {
236             buffer.append("{");
237             Map.Entry<String, String> entry = kvIter.next();
238             String key = entry.getKey();
239             String val = getProperty(key);
240             String comment = entry.getValue();
241             buffer.append("key=" + key + ",value=" + val + ",comment=" + comment);
242             buffer.append("}");
243         }
244         buffer.append("]");
245         return buffer.toString();
246     }
247 
248     public boolean equals(Object o) {
249         // 不考虑注释说明是否相同
250         return props.equals(o);
251     }
252 
253     public int hashCode() {
254         return props.hashCode();
255     }
256 
257     private void load0(LineReader lr) throws IOException {
258         char[] convtBuf = new char[1024];
259         int limit;
260         int keyLen;
261         int valueStart;
262         char c;
263         boolean hasSep;
264         boolean precedingBackslash;
265         StringBuffer buffer = new StringBuffer();
266 
267         while ((limit = lr.readLine()) >= 0) {
268             c = 0;
269             keyLen = 0;
270             valueStart = limit;
271             hasSep = false;
272             // 获取注释
273             c = lr.lineBuf[keyLen];
274             if (c == '#' || c == '!') {
275                 String comment = loadConvert(lr.lineBuf, 1, limit - 1, convtBuf);
276                 if (buffer.length() > 0) {
277                     buffer.append("
");
278                 }
279                 buffer.append(comment);
280                 continue;
281             }
282             precedingBackslash = false;
283             while (keyLen < limit) {
284                 c = lr.lineBuf[keyLen];
285                 // need check if escaped.
286                 if ((c == '=' || c == ':') && !precedingBackslash) {
287                     valueStart = keyLen + 1;
288                     hasSep = true;
289                     break;
290                 } else if ((c == ' ' || c == '	' || c == 'f') && !precedingBackslash) {
291                     valueStart = keyLen + 1;
292                     break;
293                 }
294                 if (c == '\') {
295                     precedingBackslash = !precedingBackslash;
296                 } else {
297                     precedingBackslash = false;
298                 }
299                 keyLen++;
300             }
301             while (valueStart < limit) {
302                 c = lr.lineBuf[valueStart];
303                 if (c != ' ' && c != '	' && c != 'f') {
304                     if (!hasSep && (c == '=' || c == ':')) {
305                         hasSep = true;
306                     } else {
307                         break;
308                     }
309                 }
310                 valueStart++;
311             }
312             String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
313             String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
314             setProperty(key, value, buffer.toString());
315             // reset buffer
316             buffer = new StringBuffer();
317         }
318     }
319 
320     /*
321      * 基于java.util.Properties.LineReader进行改造
322      * 
323      * Read in a "logical line" from an InputStream/Reader, skip all comment and blank lines and filter out those leading whitespace characters ( , and ) from the beginning of a "natural line". Method
324      * returns the char length of the "logical line" and stores the line in "lineBuf".
325      */
326     class LineReader {
327         public LineReader(InputStream inStream) {
328             this.inStream = inStream;
329             inByteBuf = new byte[8192];
330         }
331 
332         public LineReader(Reader reader) {
333             this.reader = reader;
334             inCharBuf = new char[8192];
335         }
336 
337         byte[] inByteBuf;
338         char[] inCharBuf;
339         char[] lineBuf = new char[1024];
340         int inLimit = 0;
341         int inOff = 0;
342         InputStream inStream;
343         Reader reader;
344 
345         int readLine() throws IOException {
346             int len = 0;
347             char c = 0;
348 
349             boolean skipWhiteSpace = true;
350             boolean isNewLine = true;
351             boolean appendedLineBegin = false;
352             boolean precedingBackslash = false;
353             boolean skipLF = false;
354 
355             while (true) {
356                 if (inOff >= inLimit) {
357                     inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf);
358                     inOff = 0;
359                     if (inLimit <= 0) {
360                         if (len == 0) {
361                             return -1;
362                         }
363                         return len;
364                     }
365                 }
366                 if (inStream != null) {
367                     // The line below is equivalent to calling a
368                     // ISO8859-1 decoder.
369                     c = (char) (0xff & inByteBuf[inOff++]);
370                 } else {
371                     c = inCharBuf[inOff++];
372                 }
373                 if (skipLF) {
374                     skipLF = false;
375                     if (c == '
') {
376                         continue;
377                     }
378                 }
379                 if (skipWhiteSpace) {
380                     if (c == ' ' || c == '	' || c == 'f') {
381                         continue;
382                     }
383                     if (!appendedLineBegin && (c == '
' || c == '
')) {
384                         continue;
385                     }
386                     skipWhiteSpace = false;
387                     appendedLineBegin = false;
388                 }
389                 if (isNewLine) {
390                     isNewLine = false;
391                 }
392 
393                 if (c != '
' && c != '
') {
394                     lineBuf[len++] = c;
395                     if (len == lineBuf.length) {
396                         int newLength = lineBuf.length * 2;
397                         if (newLength < 0) {
398                             newLength = Integer.MAX_VALUE;
399                         }
400                         char[] buf = new char[newLength];
401                         System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
402                         lineBuf = buf;
403                     }
404                     // flip the preceding backslash flag
405                     if (c == '\') {
406                         precedingBackslash = !precedingBackslash;
407                     } else {
408                         precedingBackslash = false;
409                     }
410                 } else {
411                     // reached EOL
412                     if (len == 0) {
413                         isNewLine = true;
414                         skipWhiteSpace = true;
415                         len = 0;
416                         continue;
417                     }
418                     if (inOff >= inLimit) {
419                         inLimit = (inStream == null) ? reader.read(inCharBuf) : inStream.read(inByteBuf);
420                         inOff = 0;
421                         if (inLimit <= 0) {
422                             return len;
423                         }
424                     }
425                     if (precedingBackslash) {
426                         len -= 1;
427                         // skip the leading whitespace characters in following line
428                         skipWhiteSpace = true;
429                         appendedLineBegin = true;
430                         precedingBackslash = false;
431                         if (c == '
') {
432                             skipLF = true;
433                         }
434                     } else {
435                         return len;
436                     }
437                 }
438             }
439         }
440     }
441 
442     /*
443      * Converts encoded &#92;uxxxx to unicode chars and changes special saved chars to their original forms
444      */
445     private String loadConvert(char[] in, int off, int len, char[] convtBuf) {
446         if (convtBuf.length < len) {
447             int newLen = len * 2;
448             if (newLen < 0) {
449                 newLen = Integer.MAX_VALUE;
450             }
451             convtBuf = new char[newLen];
452         }
453         char aChar;
454         char[] out = convtBuf;
455         int outLen = 0;
456         int end = off + len;
457 
458         while (off < end) {
459             aChar = in[off++];
460             if (aChar == '\') {
461                 aChar = in[off++];
462                 if (aChar == 'u') {
463                     // Read the xxxx
464                     int value = 0;
465                     for (int i = 0; i < 4; i++) {
466                         aChar = in[off++];
467                         switch (aChar) {
468                         case '0':
469                         case '1':
470                         case '2':
471                         case '3':
472                         case '4':
473                         case '5':
474                         case '6':
475                         case '7':
476                         case '8':
477                         case '9':
478                             value = (value << 4) + aChar - '0';
479                             break;
480                         case 'a':
481                         case 'b':
482                         case 'c':
483                         case 'd':
484                         case 'e':
485                         case 'f':
486                             value = (value << 4) + 10 + aChar - 'a';
487                             break;
488                         case 'A':
489                         case 'B':
490                         case 'C':
491                         case 'D':
492                         case 'E':
493                         case 'F':
494                             value = (value << 4) + 10 + aChar - 'A';
495                             break;
496                         default:
497                             throw new IllegalArgumentException("Malformed \uxxxx encoding.");
498                         }
499                     }
500                     out[outLen++] = (char) value;
501                 } else {
502                     if (aChar == 't')
503                         aChar = '	';
504                     else if (aChar == 'r')
505                         aChar = '
';
506                     else if (aChar == 'n')
507                         aChar = '
';
508                     else if (aChar == 'f')
509                         aChar = 'f';
510                     out[outLen++] = aChar;
511                 }
512             } else {
513                 out[outLen++] = (char) aChar;
514             }
515         }
516         return new String(out, 0, outLen);
517     }
518 
519     private void store0(BufferedWriter bw, boolean escUnicode) throws IOException {
520         synchronized (this) {
521             Iterator<Map.Entry<String, String>> kvIter = keyCommentMap.entrySet().iterator();
522             while (kvIter.hasNext()) {
523                 Map.Entry<String, String> entry = kvIter.next();
524                 String key = entry.getKey();
525                 String val = getProperty(key);
526                 String comment = entry.getValue();
527                 key = saveConvert(key, true, escUnicode);
528                 /*
529                  * No need to escape embedded and trailing spaces for value, hence pass false to flag.
530                  */
531                 val = saveConvert(val, false, escUnicode);
532                 if (!comment.equals(BLANK))
533                     writeComments(bw, comment);
534                 bw.write(key + "=" + val);
535                 bw.newLine();
536             }
537         }
538         bw.flush();
539     }
540 
541     private static void writeComments(BufferedWriter bw, String comments) throws IOException {
542         bw.write("#");
543         int len = comments.length();
544         int current = 0;
545         int last = 0;
546         while (current < len) {
547             char c = comments.charAt(current);
548             if (c > 'u00ff' || c == '
' || c == '
') {
549                 if (last != current)
550                     bw.write(comments.substring(last, current));
551                 if (c > 'u00ff') {
552                     bw.write(c);
553                 } else {
554                     bw.newLine();
555                     if (c == '
' && current != len - 1 && comments.charAt(current + 1) == '
') {
556                         current++;
557                     }
558                     if (current == len - 1
559                             || (comments.charAt(current + 1) != '#' && comments.charAt(current + 1) != '!'))
560                         bw.write("#");
561                 }
562                 last = current + 1;
563             }
564             current++;
565         }
566         if (last != current)
567             bw.write(comments.substring(last, current));
568         bw.newLine();
569     }
570 
571     /*
572      * Converts unicodes to encoded &#92;uxxxx and escapes special characters with a preceding slash
573      */
574     private String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode) {
575         int len = theString.length();
576         int bufLen = len * 2;
577         if (bufLen < 0) {
578             bufLen = Integer.MAX_VALUE;
579         }
580         StringBuffer outBuffer = new StringBuffer(bufLen);
581 
582         for (int x = 0; x < len; x++) {
583             char aChar = theString.charAt(x);
584             // Handle common case first, selecting largest block that
585             // avoids the specials below
586             if ((aChar > 61) && (aChar < 127)) {
587                 if (aChar == '\') {
588                     outBuffer.append('\');
589                     outBuffer.append('\');
590                     continue;
591                 }
592                 outBuffer.append(aChar);
593                 continue;
594             }
595             switch (aChar) {
596             case ' ':
597                 if (x == 0 || escapeSpace)
598                     outBuffer.append('\');
599                 outBuffer.append(' ');
600                 break;
601             case '	':
602                 outBuffer.append('\');
603                 outBuffer.append('t');
604                 break;
605             case '
':
606                 outBuffer.append('\');
607                 outBuffer.append('n');
608                 break;
609             case '
':
610                 outBuffer.append('\');
611                 outBuffer.append('r');
612                 break;
613             case 'f':
614                 outBuffer.append('\');
615                 outBuffer.append('f');
616                 break;
617             case '=': // Fall through
618             case ':': // Fall through
619             case '#': // Fall through
620             case '!':
621                 outBuffer.append('\');
622                 outBuffer.append(aChar);
623                 break;
624             default:
625                 if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) {
626                     outBuffer.append('\');
627                     outBuffer.append('u');
628                     outBuffer.append(toHex((aChar >> 12) & 0xF));
629                     outBuffer.append(toHex((aChar >> 8) & 0xF));
630                     outBuffer.append(toHex((aChar >> 4) & 0xF));
631                     outBuffer.append(toHex(aChar & 0xF));
632                 } else {
633                     outBuffer.append(aChar);
634                 }
635             }
636         }
637         return outBuffer.toString();
638     }
639 
640     /**
641      * Convert a nibble to a hex character
642      * 
643      * @param nibble the nibble to convert.
644      */
645     private static char toHex(int nibble) {
646         return hexDigit[(nibble & 0xF)];
647     }
648 
649     /** A table of hex digits */
650     private static final char[] hexDigit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
651             'F' };
652 
653 }
原文地址:https://www.cnblogs.com/wangzhisdu/p/7815549.html