看了ArrayList源碼後,發現batchRemove還是較爲複雜的,所以在這裏寫篇文章記錄一下,以免以後再看時又頭疼,哈哈哈。
先以自己的理解,弄一個簡單版的,由淺入深,幹掉他,如果讀者發現有什麼不正確的或有什麼問題請及時指出哦。
先使用兩個list模擬數組,從list1中移除list2存在的元素。
@Test
public void testBatchRemove() { // 遍歷list1,從list1中移除list2存在的元素
// 初始化數據
List<Integer> list1 = new ArrayList<>(10);
for (int i = 0; i < 5; i++) {
list1.add(i);
}
// 初始化數據
List<Integer> list2 = new ArrayList<>(10);
list2.add(2);
// 修改list數據只通過set方法,r和w作爲數組的索引
int r = 0, w = 0; // r:讀, w:寫
/**
* 從下面例子中可以看出
* 在第一輪和第二輪循環中,r和w是同步增加的
* 讀會不停的往後讀(即 r),但是寫只會寫入不被刪除的數據
* example
* list1 = [0, 1, 2, 3, 4]
* list2 = [2]
* 第一輪循環結束 r = 1, w = 1 list1 = [0, 1, 2, 3, 4]
* 第二輪循環結束 r = 2, w = 2 list1 = [0, 1, 2, 3, 4]
* 第三輪循環結束 r = 3, w = 2 list1 = [0, 1, 2, 3, 4] 注:第三輪將不會進入if,導致w的更新比r慢一步
* 第四輪循環結束 r = 4, w = 3 list1 = [0, 1, 3, 3, 4] 注:元素 [2] 已被刪除
* 第五輪循環結束 r = 5, w = 4 list1 = [1, 3, 4, 5, 5] 注:後面的元素將會向前推進一步
*/
for(;r < list1.size();r++) {
// 如果不包含,說明該位置的元素不能修改,則修改讀(r)和寫(w)的索引
// 如果包含,說明該位置的元素需要修改,則只修改讀(r)的索引
if (!list2.contains(list1.get(r))) { // 搞清楚這個if就行啦!打斷點看會舒服很多
list1.set(w++, list1.get(r));
}
// (r + 1)爲了模擬for循環結束一輪的場景,因爲打印的時候r不會++,
System.out.println(list1 + " w: " + w + " r: " + (r + 1));
}
// 只需要截取掉從0~w的元素即可
// 那怎麼知道修改了多少次呢? 在截取之前使用 list.size() - w 即可知道修改了多少次元素 5 - 4 = 1
List<Integer> integers = list1.subList(0, w);
System.out.println(integers);
}
那問題來了,剛剛是移除list2中存在的元素,那要是想保留list2中存在的元素,怎麼辦?那難道還要再寫一個方法嗎?當然不是啦,只需要變一下就行啦~
增加變量
complement 是否補充數組中的數據,false則刪除,true則保留
來了來了,新版本
/**
* 遍歷list1,從list1中移除或只保留list2存在的元素
*/
@Test
public void testComplement() {
// 是否補充數組中的數據,false則刪除,true則保留
boolean complement = false;
// 初始化數據
List<Integer> list1 = new ArrayList<>(10);
for (int i = 0; i < 5; i++) {
list1.add(i);
}
// 初始化數據
List<Integer> list2 = new ArrayList<>(10);
list2.add(2);
// 修改list數據只通過set方法,r和w作爲數組的索引
int r = 0, w = 0; // r:讀, w:寫
for(;r < list1.size();r++) {
if (list2.contains(list1.get(r)) == complement) { // 搞清楚這個if就行啦!打斷點看會舒服很多
list1.set(w++, list1.get(r));
}
// (r + 1)爲了模擬for循環結束一輪的場景,因爲打印的時候r不會++,
System.out.println(list1 + " w: " + w + " r: " + (r + 1));
}
List<Integer> integers = list1.subList(0, w);
System.out.println(integers);
}
繼續。恩,是的,問題他又接着來了,現在有兩個數組
list1 = [1, 2, 3, 4, 5, 6, 7]
list2 = [5, 6, 7]
像這樣的,list1要刪除list2中存在的元素,那list1中開頭的第1~4個元素都不需要修改。現在這小數據不需要修改,那在生產環境中誰知道呢,哈哈,所以開頭的優化下撒。
最終版本來了,這是你****的全新版本
/**
* 遍歷list1,從list1中移除或只保留list2存在的元素
* 優化開頭的操作
* list1.set(w++, list1.get(r));
*/
@Test
public void testOptimize() {
// 是否補充數組中的數據,false則刪除,true則保留
boolean complement = false;
// 初始化數據
List<Integer> list1 = new ArrayList<>(10);
for (int i = 0; i < 5; i++) {
list1.add(i);
}
// 初始化數據
List<Integer> list2 = new ArrayList<>(10);
list2.add(3);
// 修改list數據只通過set方法,r和w作爲數組的索引
int r = 0, w = 0; // r:讀, w:寫
/**
* 該for循環用於省略數組開頭的賦值操作 :
* list1.set(w++, list1.get(r));
*/
for (;r < list1.size(); r++) {
// 如果沒有要刪除的操作,則直接返回即可,這樣就完全省略了賦值操作
// list1.set(w++, list1.get(r));
if (r == list1.size()) {
return;
}
// 當需要刪除是,則跳出
if (list2.contains(list1.get(r)) != complement) {
break;
}
// r 和 w同步增加
w++;
}
// 經過上個for循環的優化,該for循環的第一個操作必定的刪除操作
// 由於r當前位置已經確定要被刪除,所以r = r + 1,讀取下一個
for(r = r + 1;r < list1.size();r++) {
if (list2.contains(list1.get(r)) == complement) { // 搞清楚這個if就行啦!打斷點看會舒服很多
list1.set(w++, list1.get(r));
}
// (r + 1)爲了模擬for循環結束一輪的場景,因爲打印的時候r不會++,
System.out.println(list1 + " w: " + w + " r: " + (r + 1));
}
List<Integer> integers = list1.subList(0, w);
System.out.println(integers);
}
其實到這也差不多了,現原形!以下是JDK13的源碼。
boolean batchRemove(Collection<?> c, boolean complement,
final int from, final int end) {
// 校驗集合是否爲空
Objects.requireNonNull(c);
final Object[] es = elementData;
// 優化
int r;
// Optimize for initial run of survivors
for (r = from;; r++) {
if (r == end)
return false;
if (c.contains(es[r]) != complement)
break;
}
// 和優化版本的代碼同理 r = r + 1
int w = r++;
try {
for (Object e; r < end; r++)
if (c.contains(e = es[r]) == complement)
es[w++] = e;
} catch (Throwable ex) {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
System.arraycopy(es, r, es, w, end - r);
w += end - r;
throw ex;
} finally {
// 計算修改次數,應該是用來觸發快速失敗的
modCount += end - w;
// 收尾收尾
shiftTailOverGap(es, w, end);
}
return true;
}
之前看了JDK8的代碼,發現有點不一樣,無非是少了優化階段,然後收尾階段也沒有封裝,就這樣撒。大家有什麼問題的歡迎下面留言哦~