记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败

遍历中删除

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 异常
即并发修改异常

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第1张图片

简单看看异常是怎么产生的:

首先,从抛出异常的位置入手,发现原因是因为: modCount 的值不等于 expectedModCount

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第2张图片

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 与期待值相等

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第3张图片

但是在循环中进行了删除操作后,modeCount 进行了自增。
记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第4张图片

最后由于 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());

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第5张图片

原因也一样: 在 modCount 与期待值不一样, 发生了并发修改异常

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第6张图片

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次。如下图。 所以可能会导致漏判断。(有空在进行研究)

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第7张图片

如果我们把 LinkList 换成 一些功能性比较好的队列,就不会有这种情况。 比如 带有ReentrantLock 锁的LinkedBlockingQueue。

 Queue queue = new LinkedBlockingQueue<>();

就能正常循环3次, 并正常删除。
记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第8张图片

迭代器遍历

那么应该使用哪些遍历呢?

比较推荐使用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个变量就相等了,不会触发异常。

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第9张图片

同时好处还有一个,无论是什么结构的数据,一般都可以用迭代器访问。

从JDK1.8开始,可以使用removeIf()方法来代替 Iterator的remove()方法实现一边遍历一边删除,IDEA中也会提示:
其原理也是使用迭代器的remove。
记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第10张图片

mysql5.6版本添加unique失败

项目使用jpa自动生成数据库, 添加unique字段的时候,发现报错:

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第11张图片

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;

结果就成功了。

原因:

数据库查看:

记录java 在遍历中删除元素 以及 mysql5.6版本添加unique失败_第12张图片

原来是因为指定字段长度过长。未指定长度的字符串,默认为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

你可能感兴趣的:(springboot)