1. 程式人生 > >【記坑】Iterator遍歷時,多次呼叫next(),二次遍歷需要從Collection重新獲取迭代器

【記坑】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()。