代码改变世界

java学习:Iterator数据加工厂

2015-09-13 19:09  14174  阅读(488)  评论(1编辑  收藏  举报

前不久听一大神讲课,期间提到Iterator,别看它方法简单,可是组装起来就像“数据加工厂”一样!

下面详细一记
首先略介绍一下Iterator的三个主要方法:

next()获得序列中的下一个元素。

hasNext()检查序列中是否还有元素。

remove()将迭代器新返回的元素删除。

数据加工厂主要用到的是next()和hasNext()这两个方法,拿下面这个问题来说

<问题>

linux下有很多对文本进行操作的命令,比如cat filename 可以将文件的所有内容输出到控制台上。

grep keyword filename可以将文件内容中包含keyword的行内容输出到控制台上。

wc -l filename可以统计filename文件中的行数。

|是代表管道的意思,管道左边命令的结果作为管道右边命令的输入,比如cat filename | grep exception | wc -l,可以用来统计一个文件中exception出现的行数。

请实现一个功能,可以解析一个linux命令(只包含上面三个命令和上面提到的参数以及和管道一起完成的组合,其它情况不考虑),比如下面这几个例子:

cat xx.txt

cat xx.txt | grep xml

wc -l xx.txt

cat xx.txt | grep xml | wc --l

</问题>

上面这个问题是要对一个数据源进行几种不同的处理,

第一种思路大致如下:

首先把输入的命令分割成单个的命令,然后根据命令的功能编写处理数据的功能模块,遍历命令列表,对数据处理,将结果记录,作为下一个命令的传入参数,直到所有命令执行完毕。

优点:灵活性不错,各种命令可以很方便地组合成不同的功能,扩展性也很好,需要增加新的命令的时候只需要编写新的功能模块就行了,执行的主流程几乎不用修改。

缺点:需要留存中间结果,也就是说当数据源过大的时候就无能为力了,另外,如果处理数据的中途出现意外,那么即使你已经完成绝大部分工作,到最后还是什么结果也不会得到。

第二种思路:流水线工厂,使用Iterator的特点来实现:(hasNext()判断是否有下一条数据,next()取出下一条数据,下面提到的每个Iterator都将反复用到这两个方法)

首先,将数据源(上面的问题里是文件)构造成Iterator;1

然后对每个命令编写各自的迭代器构造函数,各自实现hasNext和next,传入参数为Iterator,返回为Iterator;2

接着,根据输入的命令来拼接每个Iterator,得到最终的结果result(同样是Iterator);3

最后,输出result。4

上面的操作步骤中,123都没有对数据源进行任何操作,只是构造出了一套处理逻辑,最后的第4步看似是输出结果,其实在这一步里面数据才被加工。

优点:包含第一种思路的所有优点,而且第一种思路的缺点也得到解决,数据源任意大(因为内存里面永远只有当前正在处理的一行),而且即使中途出现问题,出现问题之前的正常数据也会得到正常的处理并且输出。

缺点:需要一直占用数据源的read句柄,其实占用的时间虽然比上一种方法长,也长不了多少

第一种方法占用的时间为:数据长度*(一次io时间 + 做一次处理的时间)

第二种方法占用的时间为:数据长度*(一次io时间 + 做多次处理的时间)

一般来说io时间是远大于处理时间的,因此占用时间其实是长不了多少的。

下面看第二种方法的代码:

主流程,根据命令构造一套处理逻辑

//result用于记录最后一个迭代器
Iterator<String> result = null;
for (String command : commands) {
List<String> args = splitter.omitEmptyStrings().splitToList(command);
if (result == null) {
//第一次执行的时候,result为空,需要对源文件进行处理得到源文件的迭代器
result = new FileIterator(new File(args.get(args.size() - 1)));
}
//根据命令名对相应的迭代器进行初始化
result = commandMap.getObject(args.get(0)).init(args, result);
}
return result;

//构造文件迭代器FileIterator

private BufferedReader reader;
private String line = null;

public FileIterator(File file) throws FileNotFoundException {
reader = Files.newReader(file, Charsets.UTF_8);
}

@Override public boolean hasNext() {
try {
line = (line == null) ? reader.readLine() : line;
} catch (IOException e) {
LOGGER.error("readLine() error", e);
}
if (line == null) {
IOHandler.closeIgnoreException(reader);
}
return line != null;
}

@Override public String next() {
try {
String temp = (line == null) ? reader.readLine() : line;
line = null;
if(temp == null){
IOHandler.closeIgnoreException(reader);
}
return temp;
} catch (IOException e) {
LOGGER.error("readLine() error", e);
}
return null;
}

//Grep命令的迭代器(其他的差不多,就不贴了)

private String filter;
private String line = null;

private void getLine() {
String temp;
while (line == null) {
if (!parent.hasNext()) {
break;
}
temp = parent.next();
if (temp.contains(filter)) {
line = temp;
break;
}
}
}


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

@Override public String next() {
getLine();
String result = line;
line = null;
return result;
}

@Override public Iterator<String> init(List<String> args, Iterator<String> parent) {
this.parent = parent;
filter = args.get(1);
return this;
}

 

注:上面的代码都只是一部分,只是方便参看。

 

 

/*****************************2015-09-21*****************************/

重看这篇,当时主要是为了实践和体会老师介绍的一种方法,现在回头看,要做这个功能的话,没有必要用Iterator,功能方面是没问题,但是编码方面逻辑比较分散,后来维护的人比较难读。不如就用一个Queue,读到命令行时初始化queue,存入的类继承同一个接口,接口里设定处理方法和初始化方法,各命令类实现,初始化完成后得到一个处理逻辑的队列,读每一行遍历处理就好了。

当然之前的也是Iterator接口的一种使用方式,在合适的地方用合适的接口吧。