如何使用java中的迭代器模式批量加载文件到hashmap

How to use iterator pattern in java to load file into hashmap in batches

我有一个包含两百万行的大文件。我希望遍历文件的每一行,并将其处理成键值对并将其存储到哈希图中,以便稍后进行比较。但是,为了 space 的复杂性,我不想拥有一个包含 200 万个键值对的 hashmap。相反,我想遍历文件的 N 行并将它们的键值对加载到 hashmap 中,进行比较,然后将下 N 行加载到 hashmap 中,依此类推。

用例示例:

File.txt:

1 Jack London
2 Mary Boston
3 Jay  Chicago
4 Mia  Amsterdam
5 Leah New York
6 Bob  Denver
.
.
.

假设 N=3 作为我的 hashmap 的大小,在第一次迭代时,我的 hashmap 将存储文件前三行的键值对,即

1 Jack London
2 Mary Boston
3 Jay  Chicago

在对这些键值对进行比较后,接下来的 3 行作为键值对加载到 hashmap 中:

4 Mia  Amsterdam
5 Leah New York
6 Bob  Denver

等等,直到文件中的所有行都被迭代。我如何使用 java 中的迭代器设计模式来实现它?

你可以这样做:

public class Temp {

  public static void main(String[] args) throws Exception {
    BufferedReader reader = new BufferedReader(new InputStreamReader(get file input stream here));
    int maxSize = 3;
    Map<String, String> map = new HashMap<>(maxSize);
    String line = reader.readLine();
    while (line != null) {
      String[] data = line.split("\s+");//some dummy parsing
      String key = data[1];
      String value = data[2];
      map.put(key, value);
      if (map.size() == maxSize) {
        //do whatever operations you need
        System.out.println(map);
        map.clear();
      }
      line = reader.readLine();
    }
    if (map.size() > 0) {
      //deal with leftovers
      System.out.println(map);
    }
  }
}

基本上就是读取解析直到map达到max size,然后对里面的内容进行操作清空。继续这样做,直到您阅读了整个文件。最后对剩余的内容进行操作,如果有的话。

编辑:您需要将读取包装在迭代器中,并保持一次读取的最大行数。

public class CountingFileLineIterator implements Iterator<Iterable<String>> {

  private final BufferedReader reader;
  private String line;
  private final int maxDataCount;

  public CountingFileLineIterator(InputStream inputStream, int maxDataCount) {
    this.reader = new BufferedReader(new InputStreamReader(inputStream));
    this.setLine();
    this.maxDataCount = maxDataCount;
  }

  private void setLine() {
    try {
      this.line = this.reader.readLine();
    } catch (IOException exc) {
      //handle however you need
      throw new RuntimeException("Error reading", exc);
    }
  }

  @Override
  public boolean hasNext() {
    return this.line != null;
  }

  @Override
  public Iterable<String> next() {
    List<String> next = new ArrayList<>(this.maxDataCount);
    for (int i = 0; i < this.maxDataCount; i++) {
      if (!this.hasNext()) {
        break;
      }
      next.add(this.line);
      this.setLine();
    }
    return next;
  }
}

这只是一个可能的解决方案,return是一个 Iterable(在这个确切的实现中是 List),最多包含要处理的最大行数一次。这是我为了将迭代器完成的处理保持在绝对最低限度而想出的。您可以(并且应该)有另一个 class,它实际上处理数据的处理(将其解析为 Map 等等)。问题是,即便如此,迭代器也承担了比它应有的更多责任——创建数据批次。

我的提议是让迭代器只在下一行 return,根本不进行任何处理——这正是它应该做的。

public class FileLineIterator implements Iterator<String> {

  private final BufferedReader reader;
  private String line;

  public FileLineIterator(InputStream inputStream) {
    this.reader = new BufferedReader(new InputStreamReader(inputStream));
    this.setLine();
  }

  private void setLine() {
    try {
      this.line = this.reader.readLine();
    } catch (IOException exc) {
      //handle however you need
      throw new RuntimeException("Error reading", exc);
    }
  }

  @Override
  public boolean hasNext() {
    return this.line != null;
  }

  @Override
  public String next() {
    String line = this.line;
    this.setLine();
    return line;
  }
}

然后创建一个抽象,它将准备要处理的数据:

public interface DataPreparer {

  boolean hasMoreData();
  DataHandler prepareData();
}

像这样,您可以实现批量(根据您的情况)、逐行或一次全部准备数据,无论您需要什么。批次的确切实现可能是:

public class BatchDataPreparer implements DataPreparer {

  private final Iterator<String> iterator;
  private final int batchSize;

  public BatchDataPreparer(Iterator<String> iterator, int batchSize) {
    this.iterator = iterator;
    this.batchSize = batchSize;
  }

  @Override
  public boolean hasMoreData() {
    return this.iterator.hasNext();
  }

  @Override
  public DataHandler prepareData() {
    Map<String, String> data = new LinkedHashMap<>(this.batchSize);
    while (this.iterator.hasNext()) {
      String line = this.iterator.next();
      //parse in whatever way you need
      String[] parsed = line.split("\s+");
      data.put(parsed[0] + " - " + parsed[1], parsed[2]);
      if (data.size() == this.batchSize) {
        break;
      }
    }
    return new SimpleDataHandler(data);
  }
}

数据解析应该单独完成(你也可以为此创建抽象),但对于这个例子我不会这样做。

上面的DataHandler界面:

public interface DataHandler {

  void handleData();
}

简单实现:

public class SimpleDataHandler implements DataHandler {

  private final Map<String, String> data;

  public SimpleDataHandler(Map<String, String> data) {
    this.data = data;
  }

  @Override
  public void handleData() {
    //handle however you need
    System.out.println(this.data);
    this.data.clear();
  }
}

并合二为一:

public class Temp {

  public static void main(String[] args) {
    int maxDataCount = 3;
    System.out.println("------------------------Concern Separation Result-------------------------------");
    Iterator<String> iterator = new FileLineIterator(InputStream);
    DataPreparer dataPreparer = new BatchDataPreparer(iterator, maxDataCount);
    while (dataPreparer.hasMoreData()) {
      DataHandler dataHandler = dataPreparer.prepareData();
      dataHandler.handleData();
    }
    System.out.println("------------------------Counting Iterator Result--------------------------------");
    System.out.println("------------------------Just to test it works-----------------------------------");
    Iterator<Iterable<String>> anotherIterator = new CountingFileLineIterator(InputStream, maxDataCount);
    while (anotherIterator.hasNext()) {
      Iterable<String> lines = anotherIterator.next();
      //some handling
      System.out.println(lines);
    }
  }
}
  • 您的主程序(或者您的程序的结构)不关心数据是如何准备的,DataPreparer 实现关心的是
  • preparer 和 main 都不关心数据是如何处理的,只有DataHandler
  • 更改行为、修复错误而不破坏其他东西、扩展功能等都很容易