【记坑】Iterator遍历时,多次调用next(),二次遍历需要从Collection重新获取迭代器

【记坑】Iterator遍历时,多次调用next(),二次遍历需要从Collection重新获取迭代器

2018年02月10日 11:02:46

阅读数:681

业务需求,从一份excel表中取到X轴(项目)和Y轴(平台)的数据,和数据库中的数据进行比较,如果匹配不上,则把所有匹配不上的信息返回前端,当时采取的是

 
  1. List<ProjectVo> shareProjects = projectMapper.selectAllShareProject();

  2. List<ProjectVo> sharePlats = projectMapper.selectAllSharePlat();

 
  1. for (int i = 0; i < header.getLastCellNum(); i++) {

  2. Cell cell = header.getCell(i);

  3. String projectName = cell.getStringCellValue();

  4. for (ProjectVo shareProject : shareProjects) {

  5. List<String> nameList = ExcelHelper.buildProjectUsedNames(shareProject);

  6. String pureProjectName = ExcelHelper.trimSpaceAndSpecialSymbol(projectName);

  7. if (nameList.contains(pureProjectName)) {

  8. columnMap.put(columnId, shareProject.getId());

  9. break;

  10. }

  11. }

  12. }

这样只能判断excel中的现有数据是否和数据库匹配,无法判断数据库的数据是否全部存在excel表中,并且我觉得每次遍历一个完整的Collection让我觉得浪费性能。于是改成了如下

 
  1. List<ProjectVo> shareProjects = projectMapper.selectAllShareProject();

  2. Iterator<ProjectVo> shareProjectIterator = shareProjects.iterator();

  3. List<ProjectVo> sharePlats = projectMapper.selectAllSharePlat();

  4. Iterator<ProjectVo> sharePlatIterator = sharePlats.iterator();

 
  1. String projectName = cell.getStringCellValue();

  2. String pureProjectName = ExcelHelper.trimSpaceAndSpecialSymbol(projectName);

  3. while (shareProjectIterator.hasNext()) {

  4.      ProjectVo item = shareProjectIterator.next();

  5.      List<String> nameList = ExcelHelper.buildProjectUsedNames(item);

  6.      if (nameList.contains(pureProjectName)) {

  7.          columnMap.put(columnId, item.getId());

  8.          shareProjectIterator.remove();

  9.          break;

  10.      }

  11.  }

 
  1. String platName = cell.getStringCellValue();

  2. String purePlatName = ExcelHelper.trimSpaceAndSpecialSymbol(platName);

  3. while (sharePlatIterator.hasNext()) {

  4. List<String> nameList = ExcelHelper.buildProjectUsedNames(sharePlatIterator.next());

  5. if (nameList.contains(purePlatName)) {

  6. rowMap.put(rowId, sharePlatIterator.next().getId());

  7. sharePlatIterator.remove();

  8. break;

  9. }

  10. }

这时候问题出现,当excel中的第一个cell遍历过后,后序所有的cell全部被判断为异常了,通断debug发现只有第一个cell能进入while(Iterator.hasNext())条件中,查看ArrayList源码

 
  1. public Iterator<E> iterator() {

  2. return new Itr();

  3. }

 
  1. private class Itr implements Iterator<E> {

  2. /**

  3. * Index of element to be returned by subsequent call to next.

  4. */

  5. int cursor = 0;

  6.  
  7. /**

  8. * Index of element returned by most recent call to next or

  9. * previous. Reset to -1 if this element is deleted by a call

  10. * to remove.

  11. */

  12. int lastRet = -1;

  13.  
  14. /**

  15. * The modCount value that the iterator believes that the backing

  16. * List should have. If this expectation is violated, the iterator

  17. * has detected concurrent modification.

  18. */

  19. int expectedModCount = modCount;

  20.  
  21. public boolean hasNext() {

  22. return cursor != size();

  23. }

  24.  
  25. public E next() {

  26. checkForComodification();

  27. try {

  28. int i = cursor;

  29. E next = get(i);

  30. lastRet = i;

  31. cursor = i + 1;

  32. return next;

  33. } catch (IndexOutOfBoundsException e) {

  34. checkForComodification();

  35. throw new NoSuchElementException();

  36. }

  37. }

  38.  
  39. public void remove() {

  40. if (lastRet < 0)

  41. throw new IllegalStateException();

  42. checkForComodification();

  43.  
  44. try {

  45. AbstractList.this.remove(lastRet);

  46. if (lastRet < cursor)

  47. cursor--;

  48. lastRet = -1;

  49. expectedModCount = modCount;

  50. } catch (IndexOutOfBoundsException e) {

  51. throw new ConcurrentModificationException();

  52. }

  53. }

  54.  
  55. final void checkForComodification() {

  56. if (modCount != expectedModCount)

  57. throw new ConcurrentModificationException();

  58. }

  59. }

通过源码发现主要就是

 

[java] view plain copy

  1. <code class="language-java">int cursor;     //当前索引            
  2. int lastRet = -1;   //前一位索引   
  3. int expectedModCount = modCount; //Iterator 修改次数 = collection修改次数   
  4. hasNext() //返回 cursor != size()  
  5. next() //获取cursor指向的对象,并lastRet=cursor & cursor++  
  6. remove() //移除lastRet指向的对象,并cursor-- & lastRet=-1  
  7. 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()

这里注意我的代码

 
  1. List<String> nameList = ExcelHelper.buildProjectUsedNames(sharePlatIterator.next());

  2. if (nameList.contains(purePlatName)) {

  3. rowMap.put(rowId, sharePlatIterator.next().getId());

  4. sharePlatIterator.remove();

  5. break;

  6. }

我这里调用了两次Iterator.next()方法,这会导致Iterator索引移动两次,数据不是预期的结果;

remove() :

首先判断lastRet<0,如果为true,则抛出异常,例 : 当你没有使用next()就直接remove()。

然后调用checkForComodification()方法,判断修改是否合法,接着调用ArrayList.remove(lastRet)(这里就是remote之前必须调用next()的原因,因为没有调用next(),lastRet很大概率=-1),接着判断lastRet<cursor(我觉得这有点多余,因为lastRet一直比cursor少),接着

 
  1. cursor--; //下次使用next()时,就还是当前这个索引值,刚好和next()方法获取完cursor值的下标元素后lastRet=cursor,cursor++相对应

  2. lastRet = -1;

  3. expectedModCount = modCount;

回到最初的坑,结论:

当调用next()时,返回当前索引(cursor)指向的元素,然后当前索引值(cursor)会+1,如果你只是想在一次遍历中取到的元素都是同一个,Object ob = Iterator.next(),使用ob来进行你的业务逻辑,当所有元素遍历完,cursor == Collection.size(),此时再使用while(Iterator.hasNext())做循环条件时,返回的是false,无法进行下次遍历,如果需要多次使用Iterator进行遍历,当一次遍历完成,需要重新初始化Collection的iterator()。

猜你喜欢

转载自blog.csdn.net/qq_36371953/article/details/81462766