【記坑】Iterator遍歷時,多次呼叫next(),二次遍歷需要從Collection重新獲取迭代器
【記坑】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()。