Как считать данные из файла в LinkedHashMap?

Имеется:

Map<String, Card> cardsMap = new LinkedHashMap<>();

Где класс Card выглядит таким образом:

public class Card {
    private String definition;
    private int mistakes;

    public Card(String definition) {
        this.definition = definition;
    }

    public String getDefinition() {
        return definition;
    }

    public void setDefinition(String definition) {
        this.definition = definition;
    }

    public int getMistakes() {
        return mistakes;
    }

    public void setMistakes(int mistakes) {
        this.mistakes = mistakes;
    }
}

Записываю в файл данные следующим способом:

public static void saveGame() throws IOException {
    System.out.println("File name:");
    String fileName = reader.readLine();

    int count = 0;

    File exportCards = new File(fileName);
    FileOutputStream fos = new FileOutputStream(exportCards);
    PrintWriter pw = new PrintWriter(fos);

    for (Map.Entry<String, Card> element : Cards.getCardsMap().entrySet()) {
        pw.println(element.getKey() + "=" + element.getValue().getDefinition() + "=" + element.getValue().getMistakes());
        count++;
    }

    pw.flush();
    pw.close();
    fos.close();

    System.out.println(count + " cards have been saved.");
}

Получаю на выходе *.txt с данными (к примеру):

black=черный=0
white=белый=1
green=зеленый=1
red=красный=2

Как мне теперь правильно считать такого формата данные и запихнуть в LinkedHashMap, при этом обновив данные в таблице, а не полностью их переписывая. merge - да, stringTokenizer.... возникли сложности как записать значения definition и mistakes

Или использовать другой способ записи, какой? Как правильно сериализировать такой LinkedHashMap?


Ответы (2 шт):

Автор решения: Aziz Umarov

Попробуйте код наподобие этому.

try  
{  
    File file=new File("Demo.txt");    //creates a new file instance  
    FileReader fr=new FileReader(file);   //reads the file  
    BufferedReader br=new BufferedReader(fr);  //creates a buffering character input stream  
    String line;  
    Map<String, Card> cardsMap = new LinkedHashMap<>();
    while((line=br.readLine())!=null)  
    {  
      String [] lineChunks  = line.split("=");
      Card element = new Card(lineChunks[1]);
      element.setMistakes(parseInt(lineChunks[2]));
      cardsMap.put(lineChunks[0], element);
    }  
    fr.close();    //closes the stream and release the resources          
}  
catch(IOException e)  
{  
e.printStackTrace();  
}  
→ Ссылка
Автор решения: Дмитрий

Показывать, как использовать стандартные классы производные Reader я не вижу смысла. во-первых, таких примером уже валом, только ленивый не найдет. во-вторых, вряд ли это будет еще кому-то интересно, в т.ч. по причине номер 1. посему хочу показать более интересное решение. для начала делаем то, что приводит в дикий восторог любого ООП-ешника : создаем интерфейс. Именно в интерфейсе мы реализуем методы, отвечающие за чтение-запись, унаследуемся от Serializable. Наша задача сохранять и читать в/из текстового файла объект типа Map, в виде ключа использующий произвольную строку, а в виде значения объект-наследник нашего интерфейса(обеспечим это с помощью дженерика). Разделитель мы принимаем в параметрах методов, осуществляющих чтение-запись, как и имя файла. Мы не знаем, кто унаследует наш интерфейс, поэтому для метода сохранения должны обеспечить возможность преобразования объектов класса-наследника в строку с разделителем, который мы получаем в аргументах. Для этого напишем один метод без реализации - public String toLine(String delimiter), который и будет вызывать наш метод сохранения, получая от конкретного объекта его строковое представление с указанным в аргументах разделителем. Такой вызов (вызов абстрактного метода из реализованного метода ) называется шаблонный метод и является паттерном проектирования. С чтением все сложнее, потому что нам нужно создать коллекцию объектов, которые являются наследниками нашего интерфейса. Причем классов-наследников может быть много , что не позволяет нам написать new ... В этому случае используем лямбду, которая и будемт отвечать за преобразование массива строк в конкретный объект - наследник. Выглядит это так:

import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public interface Storable extends Serializable{

    public static <T extends Storable> void save(String fileName, Map<String, T> map, String delimiter) throws IOException {
        Files.write(Paths.get(fileName), map.entrySet().stream()
                        .map(entry->String.join(delimiter, entry.getKey(), entry.getValue().toLine(delimiter)))
                        .collect(Collectors.joining("\r\n")).getBytes());
    }

    public static <T extends Storable> Map<String, T> load(String fileName, 
            String delimiter, Function <String[], T> function) throws IOException {        
        try (Stream<String> stream = Files.lines(Paths.get(fileName), Charset.defaultCharset())) {            
            return stream.map(line -> line.split(delimiter))
                    .collect(Collectors.toMap(k -> k[0], 
                            v -> function.apply(Arrays.copyOfRange(v, 1, v.length)),  
                            (e1, e2) -> e2, LinkedHashMap::new));
        }
    }

    public String toLine(String delimiter);

}

А теперь унаследуем наш класс Card от созданного интерфейса и переопределим абстрактный метод.

public class Card implements Storable{

    private static final long serialVersionUID = -7729372580030662112L;

    private String definition;
    private int mistakes;

    public Card(String definition, int mistakes) {
        this.definition = definition;
        this.mistakes = mistakes;
    }

    public Card(String [] values) {
        this.definition = values[0];
        this.mistakes = Integer.valueOf(values[1]);
    }

    @Override
    public String toLine(String delimiter){
        return String.join(delimiter, definition, String.valueOf(mistakes));
    }

    public static void main(String[] args) throws IOException {

        Map<String, Card> map = new LinkedHashMap<>();
        map.put("black", new Card("черный", 0));
        map.put("white", new Card("белый", 1));
        map.put("green", new Card("зеленый", 1));
        map.put("red", new Card("красный", 2));

        String fileName = "file.txt";
        String delimiter = "=";

        Storable.save(fileName, map, delimiter);
        Map<String, Card> loadMap = Storable.load(fileName, delimiter, values->new Card(values));

        System.out.println(loadMap);

    }

}

В итоге, наследуясь от созданного нами интерфейса, мы можем выполнять аналогичные операции (чтения/записи) , переиспользуя код интерфейса , тем самым упростим разработку, избежим дублирование кода.

→ Ссылка