FlatFileItemReader经过测试我发现,FlatFileItemReader的性能随着文件列数的增长会变得很差。

究其根源,DefaultFieldSet中的indexOf(String name)实现是主要元凶。

简单来讲,通常我们会用以下的Code来把一个文件里的内容,读成一个个对象。

//File Header
private static final String[] names = "a,b,c".split(",");

DelimitedLineTokenizer t = new DelimitedLineTokenizer();
t.setDelimiter("|".charAt(0));
t.setNames(names);
DefaultLineMapper<A> lm = new DefaultLineMapper<A>();
lm.setLineTokenizer(t);
FieldSetMapper<A> fm = new FieldSetMapper<A>(){

@Override
public A mapFieldSet(FieldSet fieldSet)
throws BindException {

A a = new A();
  //fieldSet.readString 是造成性能问题的关键所在
a.setA(fieldSet.readString("a"));
a.setB(fieldSet.readString("b"));
a.setC(fieldSet.readString("c"));
return a;

}
};
lm.setFieldSetMapper(fm);

FlatFileItemReader<A> reader = new FlatFileItemReader<A>();
reader.setLineMapper(lm);
reader.setLinesToSkip(1);

reader.setResource(new FileSystemResource(file));

ExecutionContext ex = new ExecutionContext();
reader.open(ex);

A a = null;


while((a = reader.read())!=null){

}

众所周知,FlatFileReader主要有两部分构成,一部分是LineTokenizer,另外就是FieldSetMapper。

LineTokenizer主要负责返回一个FieldSet对象。以其默认实现DefaultFieldSet为例,该对象主要包含两个String数组,一个是该行内容构成的数组,一个是由程序传递给LineTokenizer的Head Names数组(ArrayList)。
而FieldSetMapper主要用于把FieldSet对象转换成你想要的POJO对象。

public interface LineTokenizer {

/**
* Yields the tokens resulting from the splitting of the supplied
* <code>line</code>.
*
*
@param line the line to be tokenized (can be <code>null</code>)
*
*
@return the resulting tokens
*/
FieldSet tokenize(String line);
}

 

public interface FieldSetMapper<T> {

/**
* Method used to map data obtained from a {
@link FieldSet} into an object.
*
*
@param fieldSet the {@link FieldSet} to map
*
@throws BindException if there is a problem with the binding
*/
T mapFieldSet(FieldSet fieldSet) throws BindException;
}

 

public class DefaultLineMapper<T> implements LineMapper<T>, InitializingBean {

private LineTokenizer tokenizer;

private FieldSetMapper<T> fieldSetMapper;

public T mapLine(String line, int lineNumber) throws Exception {
return fieldSetMapper.mapFieldSet(tokenizer.tokenize(line));
}

....
}

 

然而,如果用我最开始的那种Code来使用FlatFileItemReader将FieldSet转换POJO对象就会导致性能问题。

这里关键在与FieldSet的默认实现DefaultFieldSet的readXXX(String name)方法实现。

当调用这个方法时,它会做两件事,首先调用indexOf(String name)方法在包含Header的ArrayList中根据name找到index,然后再调用readXXX(int index)方法在内容数组上返回tokens[index].

这个方法是o(n)的。这样读完一行就是o(n2)的效率了。

要处理这样的性能问题,目前我可以想到两种方法,一种是实现自己的LineTokenizer和FieldSet实现。

第二种就是把Names和其对应的Index关系在调用的一开始就维护一个map。

在mapFieldSet(FieldSet fieldSet)方法中直接调用fieldSet.readXXX(int index)方法,这样读取每一行就变为o(n)的操作了。

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"