如何使用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
是
- 更改行为、修复错误而不破坏其他东西、扩展功能等都很容易
我有一个包含两百万行的大文件。我希望遍历文件的每一行,并将其处理成键值对并将其存储到哈希图中,以便稍后进行比较。但是,为了 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
是 - 更改行为、修复错误而不破坏其他东西、扩展功能等都很容易