Ответ 1
Если вы можете изменить имена свойств, вы можете префикс их с помощью числового или другого отсортированного префикса, а затем отсортировать набор ключей свойств.
У меня есть файл свойств, где важен порядок значений. Я хочу иметь возможность перебирать файл свойств и выводить значения в соответствии с порядком исходного файла.
Тем не менее, поскольку файл свойств поддерживается, поправьте меня, если я ошибаюсь, Map, которая не поддерживает порядок вставки, итератор возвращает значения в неправильном порядке.
Вот код, который я использую
Enumeration names = propfile.propertyNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
//do stuff
}
Есть ли способ вернуть свойства в порядке, если бы я не написал свой собственный анализатор файлов?
Если вы можете изменить имена свойств, вы можете префикс их с помощью числового или другого отсортированного префикса, а затем отсортировать набор ключей свойств.
Расширить java.util.Properties
, переопределить как put()
, так и keys()
:
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.HashMap;
public class LinkedProperties extends Properties {
private final HashSet<Object> keys = new LinkedHashSet<Object>();
public LinkedProperties() {
}
public Iterable<Object> orderedKeys() {
return Collections.list(keys());
}
public Enumeration<Object> keys() {
return Collections.<Object>enumeration(keys);
}
public Object put(Object key, Object value) {
keys.add(key);
return super.put(key, value);
}
}
Nope - карты по своей сути "неупорядочены".
Вы могли бы создать свой собственный подкласс Properties
, который перегружает setProperty
и, возможно, put
, но он, вероятно, будет очень специфичным для реализации... Properties
- яркий пример плохой инкапсуляции. Когда я в последний раз писал расширенную версию (около 10 лет назад!), Она оказалась отвратительной и определенно чувствительной к деталям реализации Properties
.
Решение Dominique Laurent выше отлично работает для меня. Я также добавил следующее переопределение метода:
public Set<String> stringPropertyNames() {
Set<String> set = new LinkedHashSet<String>();
for (Object key : this.keys) {
set.add((String)key);
}
return set;
}
Вероятно, не самый эффективный, но он выполняется только один раз в моем жизненном цикле сервлета.
Спасибо Доминик!
Рабочий пример:
Map<String,String> properties = getOrderedProperties(new FileInputStream(new File("./a.properties")));
properties.entrySet().forEach(System.out::println);
Код для него
public Map<String, String> getOrderedProperties(InputStream in) throws IOException{
Map<String, String> mp = new LinkedHashMap<>();
(new Properties(){
public synchronized Object put(Object key, Object value) {
return mp.put((String) key, (String) value);
}
}).load(in);
return mp;
}
Apache Commons Configuration может сделать трюк для вас. Я сам не тестировал это, но я проверил их источники и выглядел так, как ключи свойств поддерживаются LinkedList в классе AbstractFileConfiguration:
public Iterator getKeys()
{
reload();
List keyList = new LinkedList();
enterNoReload();
try
{
for (Iterator it = super.getKeys(); it.hasNext();)
{
keyList.add(it.next());
}
return keyList.iterator();
}
finally
{
exitNoReload();
}
}
В интересах полноты...
public class LinkedProperties extends Properties {
private final LinkedHashSet<Object> keys = new LinkedHashSet<Object>();
@Override
public Enumeration<?> propertyNames() {
return Collections.enumeration(keys);
}
@Override
public synchronized Enumeration<Object> elements() {
return Collections.enumeration(keys);
}
public Enumeration<Object> keys() {
return Collections.enumeration(keys);
}
public Object put(Object key, Object value) {
keys.add(key);
return super.put(key, value);
}
@Override
public synchronized Object remove(Object key) {
keys.remove(key);
return super.remove(key);
}
@Override
public synchronized void clear() {
keys.clear();
super.clear();
}
}
Я не думаю, что возвращающие методы методы должны быть переопределены, поскольку набор по определению не поддерживает порядок вставки
Map<String, String> mapFile = new LinkedHashMap<String, String>();
ResourceBundle bundle = ResourceBundle.getBundle(fileName);
TreeSet<String> keySet = new TreeSet<String>(bundle.keySet());
for(String key : keySet){
System.out.println(key+" "+bundle.getString(key));
mapFile.put(key, bundle.getString(key));
}
Это сохраняется в файле свойств
Вы должны переопределить также keySet(), если вы хотите экспортировать Свойства как XML:
public Set<Object> keySet() {
return keys;
}
См. https://github.com/etiennestuder/java-ordered-properties для полной реализации, которая позволяет читать/записывать файлы свойств в определенном порядке.
OrderedProperties properties = new OrderedProperties();
properties.load(new FileInputStream(new File("~/some.properties")));
Я добавлю еще один известный YAEOOJP (еще один пример упорядоченных свойств Java), потому что кажется, что никто не может заботиться о свойствах по умолчанию, которые вы можете подавать в свои свойства.
@see http://docs.oracle.com/javase/tutorial/essential/environment/properties.html
Что мой класс: наверняка не 10 16%, совместимый с любой возможной ситуацией, но это нормально для моих ограниченных тупых целей прямо сейчас. Любые дальнейшие комментарии для коррекции оценены так, чтобы получить Большее благо.
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Remember javadocs >:o
*/
public class LinkedProperties extends Properties {
protected LinkedProperties linkedDefaults;
protected Set<Object> linkedKeys = new LinkedHashSet<>();
public LinkedProperties() { super(); }
public LinkedProperties(LinkedProperties defaultProps) {
super(defaultProps); // super.defaults = defaultProps;
this.linkedDefaults = defaultProps;
}
@Override
public synchronized Enumeration<?> propertyNames() {
return keys();
}
@Override
public Enumeration<Object> keys() {
Set<Object> allKeys = new LinkedHashSet<>();
if (null != defaults) {
allKeys.addAll(linkedDefaults.linkedKeys);
}
allKeys.addAll(this.linkedKeys);
return Collections.enumeration(allKeys);
}
@Override
public synchronized Object put(Object key, Object value) {
linkedKeys.add(key);
return super.put(key, value);
}
@Override
public synchronized Object remove(Object key) {
linkedKeys.remove(key);
return super.remove(key);
}
@Override
public synchronized void putAll(Map<?, ?> values) {
for (Object key : values.keySet()) {
linkedKeys.add(key);
}
super.putAll(values);
}
@Override
public synchronized void clear() {
super.clear();
linkedKeys.clear();
}
private static final long serialVersionUID = 0xC00L;
}
На мой взгляд, Properties
во многом связан с Hashtable
. Я предлагаю прочитать это для LinkedHashMap
. Для этого вам нужно будет переопределить только один метод, Object put(Object key, Object value)
, игнорируя Properties
как контейнер ключ/значение:
public class InOrderPropertiesLoader<T extends Map<String, String>> {
private final T map;
private final Properties properties = new Properties() {
public Object put(Object key, Object value) {
map.put((String) key, (String) value);
return null;
}
};
public InOrderPropertiesLoader(T map) {
this.map = map;
}
public synchronized T load(InputStream inStream) throws IOException {
properties.load(inStream);
return map;
}
}
Использование:
LinkedHashMap<String, String> props = new LinkedHashMap<>();
try (InputStream inputStream = new FileInputStream(file)) {
new InOrderPropertiesLoader<>(props).load(inputStream);
}
В некоторых ответах предполагается, что свойства, считанные из файла, помещаются в экземпляр Properties
(по вызовам put
), чтобы они отображались в файле. Хотя это вообще, как он себя ведет, я не вижу никакой гарантии такого заказа.
IMHO: лучше читать файл строки за строкой (чтобы гарантировать, что заказ гарантирован), чем использовать класс Properties так же, как синтаксический анализатор одиночного свойства
и, наконец, сохраните его в некоторой упорядоченной коллекции, например LinkedHashMap
.
Это может быть достигнуто следующим образом:
private LinkedHashMap<String, String> readPropertiesInOrderFrom(InputStream propertiesFileInputStream)
throws IOException {
if (propertiesFileInputStream == null) {
return new LinkedHashMap(0);
}
LinkedHashMap<String, String> orderedProperties = new LinkedHashMap<String, String>();
final Properties properties = new Properties(); // use only as a parser
final BufferedReader reader = new BufferedReader(new InputStreamReader(propertiesFileInputStream));
String rawLine = reader.readLine();
while (rawLine != null) {
final ByteArrayInputStream lineStream = new ByteArrayInputStream(rawLine.getBytes("ISO-8859-1"));
properties.load(lineStream); // load only one line, so there is no problem with mixing the order in which "put" method is called
final Enumeration<?> propertyNames = properties.<String>propertyNames();
if (propertyNames.hasMoreElements()) { // need to check because there can be empty or not parsable line for example
final String parsedKey = (String) propertyNames.nextElement();
final String parsedValue = properties.getProperty(parsedKey);
orderedProperties.put(parsedKey, parsedValue);
properties.clear(); // make sure next iteration of while loop does not access current property
}
rawLine = reader.readLine();
}
return orderedProperties;
}
Обратите внимание, что метод, опубликованный выше, принимает InputStream
, который должен быть закрыт впоследствии (конечно, нет необходимости переписывать его, чтобы взять только файл в качестве аргумента).
Для тех, кто недавно читал эту тему: просто используйте класс PropertiesConfiguration из org.apache.commons: commons-configuration2. Я проверил, что он сохраняет порядок свойств (потому что он использует LinkedHashMap внутри). Выполнение:
'
PropertiesConfiguration properties = new PropertiesConfiguration();
properties.read(new FileReader("/some/path));
properties.write(new FileWriter("/some/other/path"));
'
удаляет только конечные пробелы и ненужные экранированные символы.
Альтернативой является просто написать собственный файл свойств с помощью LinkedHashMap, вот что я использую:
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
public class OrderedProperties {
private static Map<String, String> properties = new LinkedHashMap<String, String>();
private static OrderedProperties instance = null;
private OrderedProperties() {
}
//The propertyFileName is read from the classpath and should be of format : key=value
public static synchronized OrderedProperties getInstance(String propertyFileName) {
if (instance == null) {
instance = new OrderedProperties();
readPropertiesFile(propertyFileName);
}
return instance;
}
private static void readPropertiesFile(String propertyFileName){
LineIterator lineIterator = null;
try {
//read file from classpath
URL url = instance.getClass().getResource(propertyFileName);
lineIterator = FileUtils.lineIterator(new File(url.getFile()), "UTF-8");
while (lineIterator.hasNext()) {
String line = lineIterator.nextLine();
//Continue to parse if there are blank lines (prevents IndesOutOfBoundsException)
if (!line.trim().isEmpty()) {
List<String> keyValuesPairs = Arrays.asList(line.split("="));
properties.put(keyValuesPairs.get(0) , keyValuesPairs.get(1));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
lineIterator.close();
}
}
public Map<String, String> getProperties() {
return OrderedProperties.properties;
}
public String getProperty(String key) {
return OrderedProperties.properties.get(key);
}
}
Для использования:
OrderedProperties o = OrderedProperties.getInstance("/project.properties");
System.out.println(o.getProperty("test"));
Файл свойств образца (в данном случае project.properties):
test=test2