記錄java 在遍歷中刪除元素 以及 mysql5.6版本新增unique失敗

weiewiyi發表於2023-01-19

遍歷中刪除

List或Queue等資料結構中,如何一邊遍歷一遍刪除?

1. 常犯錯誤

ArrayList

可能沒遇到坑過的人會用增強for迴圈這麼寫:

public static void main(String[] args) {
1 List<Integer> list = new ArrayList<>();
2    list.add(1);
3    list.add(2);
4    list.add(3);
5
6    for (Integer value : list) {
7        if (value.equals(2)) {
8            list.remove(value);
9        }
10    }
11    System.out.println(list);
}

但是一執行,結果卻拋 java.util.ConcurrentModificationException 異常
即併發修改異常

image.png

簡單看看異常是怎麼產生的:

首先,從丟擲異常的位置入手,發現原因是因為: modCount 的值不等於 expectedModCount

image.png

modCount值是什麼呢?

The number of times this list has been structurally modified. Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.

從原始碼註釋中可以看出來,表示的是 修改該表結構的次數, 包括修改列表大小等。

所以原因很簡單了: 剛開始迴圈時 modCount 與期待值相等

image.png

但是在迴圈中進行了刪除操作後,modeCount 進行了自增。
image.png

最後由於 modCount 的值不等於 expectedModCount,丟擲異常。

HashMap:

HashMap中也同理, 如果我們簡單使用forEach,然後remove。同樣報
java.util.ConcurrentModificationException

1    HashMap<Integer,String> hashMap = new HashMap<>();
2    hashMap.put(1, "張三");
3    hashMap.put(2, "李四");
4    hashMap.put(3, "王五");
5    hashMap.forEach((key, value) -> {
6        logger.info("當前值: key" + key +  " value " + value) ;
7        if ((hashMap.get(key) ==  "李四")) {
8            hashMap.remove(key);
9        }
10    });
11    logger.info(hashMap.toString());

image.png

原因也一樣: 在 modCount 與期待值不一樣, 發生了併發修改異常

image.png

Queue

我們再來看看佇列

1    Queue<Integer> queue = new LinkedList<>();
2    queue.offer(1);
3    queue.offer(2);
4    queue.offer(3);
5
6    logger.info("刪除前" + queue.toString());
7    for(Integer value : queue) {
8        logger.info("當前值" + value);
9        if (value.equals(2)) {
11           queue.remove(value);
12      }
13    }
14    logger.info("刪除後" + queue.toString());

如果我們用 LinkList 結構作為佇列,可以成功刪除。

但是我們按理來說迴圈3次, 實際上只迴圈2次。如下圖。 所以可能會導致漏判斷。(有空在進行研究)

image.png

如果我們把 LinkList 換成 一些功能性比較好的佇列,就不會有這種情況。 比如 帶有ReentrantLock 鎖的LinkedBlockingQueue。

 Queue<Integer> queue = new LinkedBlockingQueue<>();

就能正常迴圈3次, 並正常刪除。
image.png

迭代器遍歷

那麼應該使用哪些遍歷呢?

比較推薦使用Iterator迭代器進行遍歷:
使用它的 iterator.remove()方法不會產生併發修改錯誤。

1 List<Integer> list = new ArrayList<>();
2        list.add(1);
3        list.add(2);
4        list.add(3);
5        Iterator<Integer> iterator = list.iterator();
6        while (iterator.hasNext()) {
7            Integer integer = iterator.next();
8            if (integer == 2) {
9                logger.info("刪除" + integer);
10                iterator.remove();
11            }
12        }
13        System.out.println(list);

原因可以從原始碼中找到: 每次刪除一個元素,都會將 modCount 的值重新賦值給expectedModCount,這樣2個變數就相等了,不會觸發異常。

image.png

同時好處還有一個,無論是什麼結構的資料,一般都可以用迭代器訪問。

從JDK1.8開始,可以使用removeIf()方法來代替 Iterator的remove()方法實現一邊遍歷一邊刪除,IDEA中也會提示:
其原理也是使用迭代器的remove。
image.png

mysql5.6版本新增unique失敗

專案使用jpa自動生成資料庫, 新增unique欄位的時候,發現報錯:

image.png

Specified key was too long; max key length is 767 bytes

反覆測試後,發現,Long物件的 unique 可以新增成功,String 物件的不行,然後我把 String 物件加上length限制

@Column(unique = true, nullable = false, length = 20)
private String acctName;

結果就成功了。

原因:

資料庫檢視:

image.png

原來是因為指定欄位長度過長。未指定長度的字串,預設為255 varchar,utf8mb4字符集每個 varchar 為4bytes,即為總長255x4=1020bytes,大於了767bytes。

因此,unique 欄位的最大長度為767/4 = 191 varchar。(注:utf8mb4字符集)

MySQL 5.6 版(及之前的版本)中的 767 位元組是 InnoDB 表的規定字首限制。在 MySQL 5.7 版(及更高版本)中,此限制已增加到3072 位元組。

可以選擇升級版本,或者設定長度
image.png

相關文章