共计 2644 个字符,预计需要花费 7 分钟才能阅读完成。
遍历中删除
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
异样
即并发批改异样
简略看看异样是怎么产生的:
首先,从抛出异样的地位动手,发现起因是因为: modCount
的值不等于 expectedModCount
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 与期待值相等
然而在循环中进行了删除操作后,modeCount 进行了自增。
最初因为 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());
起因也一样:在 modCount 与期待值不一样,产生了并发批改异样
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 次。如下图。所以可能会导致漏判断。(有空在进行钻研)
如果咱们把 LinkList 换成 一些功能性比拟好的队列,就不会有这种状况。比方 带有 ReentrantLock 锁的 LinkedBlockingQueue。
Queue<Integer> queue = new LinkedBlockingQueue<>();
就能失常循环 3 次,并失常删除。
迭代器遍历
那么应该应用哪些遍历呢?
比拟举荐应用 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 个变量就相等了,不会触发异样。
同时益处还有一个,无论是什么构造的数据,个别都能够用迭代器拜访。
从 JDK1.8 开始,能够应用 removeIf()办法来代替 Iterator 的 remove()办法实现一边遍历一边删除,IDEA 中也会提醒:
其原理也是应用迭代器的 remove。
mysql5.6 版本增加 unique 失败
我的项目应用 jpa 主动生成数据库,增加 unique 字段的时候,发现报错:
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;
后果就胜利了。
起因:
数据库查看:
原来是因为指定字段长度过长。未指定长度的字符串,默认为 255 varchar,utf8mb4 字符集每个 varchar 为 4bytes,即为总长 255×4=1020bytes,大于了 767bytes。
因而,unique 字段的最大长度为 767/4 = 191 varchar。(注:utf8mb4 字符集)
MySQL 5.6 版(及之前的版本)中的 767 字节是 InnoDB 表的规定前缀限度。在 MySQL 5.7 版(及更高版本)中,此限度已减少到 3072 字节。
能够抉择降级版本,或者设置长度