java 数据集合类

Posted simple_孙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 数据集合类相关的知识,希望对你有一定的参考价值。

map相关类和其他集合类(List,Set等)的接口并不一致。map类继承自Map接口,而其他集合类继承自Collection接口,Collection接口继承自Iterable接口。凡是被Iterable接口修饰的类都是可以得到对应Iterator接口实现迭代遍历的。

遍历map

在java8之前,map类(比如hashmap)并不能实现自己的遍历,只能通过转化为Set<Entry>,Set<key>,Set<value>进行遍历。java新增了foreach的迭代方法,可以不用再转为set对象进行迭代了

        HashMap<String, String> hashMap = new HashMap<>();
        //方法一
        hashMap.forEach((key, value) -> {});
        //方法二
        for (Entry<String, String> entry : hashMap.entrySet()) {
            
        }
        //方法三,遍历key,再通过key得到对应键值
        for(String key : hashMap.keySet()) {
            
        }
        //方法四,直接遍历value
        for(String value : hashMap.values()) {
            
        }

遍历时修改集合

Collection接口的集合类在遍历的时候并不能进行删除和添加操作,否则就会报ConcurrentModificationException的异常,例如进行下面的遍历操作都会报错

        ArrayList<String> list = new ArrayList<>();
        list.add("ele1");
        list.add("ele2");
        list.add("ele3");
        list.add("ele4");
        list.add("ele5");
        list.add("ele6");
        //遍历一报错
        list.forEach(ele -> list.remove(ele));
        //遍历二报错
        for (String entry : list) {
            list.remove(entry);
        }
        //遍历三报错
        Iterator<String> ite = list.iterator();
        while (ite.hasNext()) {
            String value = ite.next();
            list.remove(value);
        }
        //遍历四不会报错,调用迭代器的删除方法
        Iterator<String> iter = list.iterator();
        while (iter.hasNext()) {
            String value = iter.next();
            iter.remove();
        }

因此要修改还必须依赖迭代器的删除方法。

关于产生ConcurrentModificationException报错的原因,请查看下面的文章分析:

https://www.cnblogs.com/zhuyeshen/p/10956822.html

https://blog.csdn.net/tttzzztttzzz/article/details/87556048

HashMap实现

初始化

hashmap时lazy-load方式,执行构造函数时只会设置初始容量和负载系数。

负载系统默认是0.75,这是查找和插入操作的最佳平衡点,如果过高,则容易发生hash冲突,造成同一hash值对应的链表长度过长,影响查找效率;如果过低,则会造成hash过于稀疏,浪费空间。

插入操作

hashmap是在插入第一个元素时确定容量和分配空间的,初始容量并不是真实的容量,容量都是略大于初始容量的二的幂次方。这是为了减少hash冲突,这里就要说一下hashmap的hash算法。先得到key值对应的hash值,然后将hash值左移16位,然后再与容量-1进行位的异或操作.

将hash值左移16位是因为hash的低16位更容易发生hash冲突,所以要利用高16位,而容量-1对应的每个二进制位都是1。这样在计算hash值时,能够最大限度的减少hash冲突。

数据结构

主体是数组类型,元素类型是key-value的Node类型链表。hash冲突的元素都会被放置到同一个链表中,但是如果链表长度过长就会影响查找效率,因此在Java1.8中,如果链表长度大于8,就自动将链表转化为红黑树。转化为红黑树后,插入操作会有影响,涉及到树的平衡操作。

resize操作

hashmap内部有一个门限值,其值为容量乘以负载系数。当hashmap的长度超过门限值时就会执行resize。resize的长度是将当前数组的长度扩大两倍,然后每个元素重新计算hash值确定新的位置,但因为是位操作,所以新的索引位置是由原来key的有效hash值左移一位决定的。如果该位值是0,则在新数组中的索引位置不变;如果是1,则新的索引位置是原索引加上原数组长度。

Java1.8之前,多线程下对线程进行扩容的时候会造成链表的循环引用问题,造成死循环,这是因为在链表中添加新元素的时候,总是把它加在链表头部。在1.8中就改成了链表尾部,既避免了循环应用,也因为要检查链表的长度,当长度超过8时,会将链表转化为一个红黑树。但是hashmap在1.8下的多线程下还是有很多别的问题,避免使用。

ConcurrentHashMap实现

Java1.7

put加锁
通过分段加锁segment,一个hashmap里有若干个segment,每个segment里有若干个桶,桶里存放K-V形式的链表,put数据时通过key哈希得到该元素要添加到的segment,然后对segment进行加锁,然后在哈希,计算得到给元素要添加到的桶,然后遍历桶中的链表,替换或新增节点到桶中
size
分段计算两次,两次结果相同则返回,否则对所以段加锁重新计算

 

Java1.8

put

CAS 加锁
1.8中不依赖与segment加锁,segment数量与桶数量一致;
首先判断容器是否为空,为空则进行初始化利用volatile的sizeCtl作为互斥手段,如果发现竞争性的初始化,就暂停在那里,等待条件恢复,否则利用CAS设置排他标志(U.compareAndSwapInt(this, SIZECTL, sc, -1));否则重试
对key hash计算得到该key存放的桶位置,判断该桶是否为空,为空则利用CAS设置新节点
否则使用synchronize加锁,遍历桶中数据,替换或新增加点到桶中
最后判断是否需要转为红黑树,转换之前判断是否需要扩容

size
利用LongAdd累加计算

上面的集合对象都不支持在直接遍历元素时,进行添加和删除操作,但是ConcurrentHashMap可以在遍历时进行更新操作。但并不能保证实时更新,本次遍历添加的元素可能不会在本次遍历出来,只能在下次才能遍历出来。详细说明如下:

https://www.cnblogs.com/williamjie/p/9099861.html

https://www.jianshu.com/p/d10256f0ebea

https://www.cnblogs.com/zhuawang/p/4779649.html

https://www.cnblogs.com/heqiyoujing/p/10928423.html

https://blog.csdn.net/killalllsd/article/details/83607945

以上是关于java 数据集合类的主要内容,如果未能解决你的问题,请参考以下文章

在Android中,如何将数据从类传递到相应的布局/片段文件?

solr分布式索引实战分片配置读取:工具类configUtil.java,读取配置代码片段,配置实例

java 代码片段

java代码在片段活动中不起作用

Java集合类的使用场景

代码片段 - Golang 实现集合操作