遍历中删除
List或Queue等数据结构中,如何一边遍历一遍删除?
1. 常犯错误
ArrayList
可能没遇到坑过的人会用增强for循环这么写:
public static void main(String[] args) {
1 List 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 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 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 queue = new LinkedBlockingQueue<>();
就能正常循环3次, 并正常删除。
迭代器遍历
那么应该使用哪些遍历呢?
比较推荐使用Iterator迭代器进行遍历:
使用它的 iterator.remove()方法不会产生并发修改错误。
1 List list = new ArrayList<>();
2 list.add(1);
3 list.add(2);
4 list.add(3);
5 Iterator 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,即为总长255x4=1020bytes,大于了767bytes。
因此,unique 字段的最大长度为767/4 = 191 varchar。(注:utf8mb4字符集)
MySQL 5.6 版(及之前的版本)中的 767 字节是 InnoDB 表的规定前缀限制。在 MySQL 5.7 版(及更高版本)中,此限制已增加到3072 字节。
可以选择升级版本,或者设置长度