【记坑】Iterator遍历时,多次调用next(),二次遍历需要从Collection重新获取迭代器
2018年02月10日 11:02:46
阅读数:681
业务需求,从一份excel表中取到X轴(项目)和Y轴(平台)的数据,和数据库中的数据进行比较,如果匹配不上,则把所有匹配不上的信息返回前端,当时采取的是
-
List<ProjectVo> shareProjects = projectMapper.selectAllShareProject();
-
List<ProjectVo> sharePlats = projectMapper.selectAllSharePlat();
-
for (int i = 0; i < header.getLastCellNum(); i++) {
-
Cell cell = header.getCell(i);
-
String projectName = cell.getStringCellValue();
-
for (ProjectVo shareProject : shareProjects) {
-
List<String> nameList = ExcelHelper.buildProjectUsedNames(shareProject);
-
String pureProjectName = ExcelHelper.trimSpaceAndSpecialSymbol(projectName);
-
if (nameList.contains(pureProjectName)) {
-
columnMap.put(columnId, shareProject.getId());
-
break;
-
}
-
}
-
}
这样只能判断excel中的现有数据是否和数据库匹配,无法判断数据库的数据是否全部存在excel表中,并且我觉得每次遍历一个完整的Collection让我觉得浪费性能。于是改成了如下
-
List<ProjectVo> shareProjects = projectMapper.selectAllShareProject();
-
Iterator<ProjectVo> shareProjectIterator = shareProjects.iterator();
-
List<ProjectVo> sharePlats = projectMapper.selectAllSharePlat();
-
Iterator<ProjectVo> sharePlatIterator = sharePlats.iterator();
-
String projectName = cell.getStringCellValue();
-
String pureProjectName = ExcelHelper.trimSpaceAndSpecialSymbol(projectName);
-
while (shareProjectIterator.hasNext()) {
-
ProjectVo item = shareProjectIterator.next();
-
List<String> nameList = ExcelHelper.buildProjectUsedNames(item);
-
if (nameList.contains(pureProjectName)) {
-
columnMap.put(columnId, item.getId());
-
shareProjectIterator.remove();
-
break;
-
}
-
}
-
String platName = cell.getStringCellValue();
-
String purePlatName = ExcelHelper.trimSpaceAndSpecialSymbol(platName);
-
while (sharePlatIterator.hasNext()) {
-
List<String> nameList = ExcelHelper.buildProjectUsedNames(sharePlatIterator.next());
-
if (nameList.contains(purePlatName)) {
-
rowMap.put(rowId, sharePlatIterator.next().getId());
-
sharePlatIterator.remove();
-
break;
-
}
-
}
这时候问题出现,当excel中的第一个cell遍历过后,后序所有的cell全部被判断为异常了,通断debug发现只有第一个cell能进入while(Iterator.hasNext())条件中,查看ArrayList源码
-
public Iterator<E> iterator() {
-
return new Itr();
-
}
-
private class Itr implements Iterator<E> {
-
/**
-
* Index of element to be returned by subsequent call to next.
-
*/
-
int cursor = 0;
-
/**
-
* Index of element returned by most recent call to next or
-
* previous. Reset to -1 if this element is deleted by a call
-
* to remove.
-
*/
-
int lastRet = -1;
-
/**
-
* The modCount value that the iterator believes that the backing
-
* List should have. If this expectation is violated, the iterator
-
* has detected concurrent modification.
-
*/
-
int expectedModCount = modCount;
-
public boolean hasNext() {
-
return cursor != size();
-
}
-
public E next() {
-
checkForComodification();
-
try {
-
int i = cursor;
-
E next = get(i);
-
lastRet = i;
-
cursor = i + 1;
-
return next;
-
} catch (IndexOutOfBoundsException e) {
-
checkForComodification();
-
throw new NoSuchElementException();
-
}
-
}
-
public void remove() {
-
if (lastRet < 0)
-
throw new IllegalStateException();
-
checkForComodification();
-
try {
-
AbstractList.this.remove(lastRet);
-
if (lastRet < cursor)
-
cursor--;
-
lastRet = -1;
-
expectedModCount = modCount;
-
} catch (IndexOutOfBoundsException e) {
-
throw new ConcurrentModificationException();
-
}
-
}
-
final void checkForComodification() {
-
if (modCount != expectedModCount)
-
throw new ConcurrentModificationException();
-
}
-
}
通过源码发现主要就是
[java] view plain copy
- <code class="language-java">int cursor; //当前索引
- int lastRet = -1; //前一位索引
- int expectedModCount = modCount; //Iterator 修改次数 = collection修改次数
- hasNext() //返回 cursor != size()
- next() //获取cursor指向的对象,并lastRet=cursor & cursor++
- remove() //移除lastRet指向的对象,并cursor-- & lastRet=-1
- checkForComodification() //判断集合的修改次数是否合法</code>
当Collection调用iterator方法后,根据当前Collection对象,返回一个新的Iterator,
hasNext():
返回 cursor!=size()结果;
checkForComodification():
判断 modCount!=expectedModCount ,为true则抛出ConcurrentModificationException();
next():
调用Collection.get(cursor),返回cursor值指向的索引的元素,并lastRet=cursor,cursor++(这里就是第二次遍历无法进行的原因,
当Iterator遍历完成,cursor == Collection.size(),调用hasNext()时返回false),
当调用出现
IndexOutOfBoundsException()
时,异常会被捕捉,并调用checkForComodification()方法,如果修改次数合法,则抛出
NoSuchElementException()
这里注意我的代码
-
List<String> nameList = ExcelHelper.buildProjectUsedNames(sharePlatIterator.next());
-
if (nameList.contains(purePlatName)) {
-
rowMap.put(rowId, sharePlatIterator.next().getId());
-
sharePlatIterator.remove();
-
break;
-
}
我这里调用了两次Iterator.next()方法,这会导致Iterator索引移动两次,数据不是预期的结果;
remove() :
首先判断lastRet<0,如果为true,则抛出异常,例 : 当你没有使用next()就直接remove()。
然后调用checkForComodification()方法,判断修改是否合法,接着调用ArrayList.remove(lastRet)(这里就是remote之前必须调用next()的原因,因为没有调用next(),lastRet很大概率=-1),接着判断lastRet<cursor(我觉得这有点多余,因为lastRet一直比cursor少),接着
-
cursor--; //下次使用next()时,就还是当前这个索引值,刚好和next()方法获取完cursor值的下标元素后lastRet=cursor,cursor++相对应
-
lastRet = -1;
-
expectedModCount = modCount;
回到最初的坑,结论:
当调用next()时,返回当前索引(cursor)指向的元素,然后当前索引值(cursor)会+1,如果你只是想在一次遍历中取到的元素都是同一个,Object ob = Iterator.next(),使用ob来进行你的业务逻辑,当所有元素遍历完,cursor == Collection.size(),此时再使用while(Iterator.hasNext())做循环条件时,返回的是false,无法进行下次遍历,如果需要多次使用Iterator进行遍历,当一次遍历完成,需要重新初始化Collection的iterator()。