java集合

本文深入解析Java集合框架,包括Queue、List、Set、Map的基本形式及其实现类,探讨了Collections工具类的功能,对比了synchronizedMap与ConcurrentHashMap的区别,详细分析了Iterator与ListIterator的特性,以及Collection下各实现类的特点,特别对HashMap的数据结构、工作原理进行了详尽阐述。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

conllection是集合框架的顶级接口。下面有子接口以及实现类。

集合有四种基本形式:Queue(其实现类只有一个List下的LinkedList),List3,Set2,Map4。


conllections是工具类,是集合框架的工具类,对集合进行操作。里面有很多静态方法。

Collections.synchronizedMap()与ConcurrentHashMap主要区别是:Collections.synchronizedMap()和Hashtable一样,实现上在调用map所有方法时,都对整个map进行同步,而ConcurrentHashMap的实现却更加精细,它对map中的所有桶加了锁。所以,只要要有一个线程访问map,其他线程就无法进入map,而如果一个线程在访问ConcurrentHashMap某个桶时,其他线程,仍然可以对map执行某些操作。

为什么所有的集合类不直接实现Iterator,而是实现Iterable接口后调用iterator()方法后返回Iterator类的对象再进行遍历?

因为Iterator接口的核心方法next()或者hasNext() 是依赖于迭代器的当前迭代位置的。如果Collection直接实现Iterator接口,势必导致集合对象中包含当前迭代位置的数据(指针)。

       当集合在不同方法间被传递时,由于当前迭代位置不可预置,那么next()方法的结果会变成不可预知。

 《一》Iterator与ListIterator:

Iterator实现很简单,链式存储结构根据指针找下一个元素,顺序存储根据地址找下一个元素就可以。

Iterator可以遍历所有集合,ListIterator只能遍历List集合。

Iterator只能正向遍历,ListIterator正反向都可以遍历。

Iterator只能删除元素,ListIterator不仅可以删除,还可以添加和修改元素。

《二》Collection:

1) List的实现类:

LinkedList采用双向链表结构实现,ArrayList和Vector都采用数组实现(顺序存储,插入或删除元素时必须移动插入位置的前面或后面的所有元素)。存取和增减元素的效率自然明了。ArrayList和Vector虽然用数组实现(java中的数组用顺序存储结构实现),但可实现数组长度增加,原理是将原来数组复制到新的数组。前者是线程不安全的,没有上锁,效率高,后者是线程安全的,由于上了锁,效率低。

LinkedList实现了双端队列Deque(继承自队列Queue):

 首位可以增加删除元素,感觉没什么卵用。

Vector还有一个继承类Stack(栈):表示先进后出的栈,它在Vector的基础上增加了五个操作将向量扩展为栈:push和pop入栈出栈操作,读取栈顶元素的peek方法,测试栈是否为空的empty方法和确定某元素从栈顶起是第几个元素。

2)Set的实现类:

HashSet完全用hashmap实现,区别是一个是存键值对,一个存正常元素(最终调用hashmap的put方法,添加对象作为key)。所以hashset不能添加重复元素:调用hashmap的添加元素方法,hashmap添加相同key的元素时返回原始value(先覆盖value)不返回null导致hashset添加重复元素返回false。

hashset添加元素

public boolean add(E paramE)
{
return this.map.put(paramE, PRESENT) == null;
}

由此知当hashset添加重复元素会返回false(因为hashmap添加key相同的元素时更新value后返回原始value而不是null)

TreeSet完全用treemap实现,区别是一个是存键值对,一个存正常元素。所以treeset不能添加重复元素:调用treemap的添加元素方法,tree添加相同key的元素时返回新value(先覆盖value)不返回null导致treeset添加重复元素返回false。

测试HashSet发现HashSet只是间接引用了其中的元素,没有表面上看起来的添加(hashmap的key却不是这样,value是这样):

TestBean p1 = new TestBean();
TestBean p2 = new TestBean();
Set set = new HashSet();
set.add(p1);
set.add(p2);
System.out.println("添加前"+ JSON.toJSON(set));
p1.setAge(100);
System.out.println("添加后"+JSON.toJSON(set));

输出结果:

添加前[{"age":5},{"age":5}]
添加后[{"age":5},{"age":100}]

 3)Map的实现类:

 http://blog.youkuaiyun.com/xy2953396112/article/details/54891527

hashmap:同hashset。只不过哈希表的数据结构是针对key而已(value是可以相同的)。用数组加链表的数据结构实现。

java容器里hash map的源码也是设计的最走心的,很多优化都感觉很巧妙。比如通过key的hashcode计算下标位置的时候,它不是直接的hashcode取模运算,要先经过高位运算进行扰动,就是把hashcode右移然后做异或,这样就保证在数组比较小时候hashcode的高低位都能参与到运算中,防止一些散列不均匀的情况。Jdk1.8以前是进行四次扰动计算,可能从速度功效各方面考虑,jdk1.8变成扰动一次,低16位和高16位进行异或计算。

JDK7中HashMap采用的是位桶+链表的方式。而JDK8中采用的是位桶+链表/红黑树的方式,当某个位桶的链表的长度超过8的时候,这个链表就将转换成红黑树。因为引入了树,所以其他操作也更复杂了,比如put方法以前只要通过hash计算下标位置,判断该位置有没有元素,如果有就往下遍历,如果存在相同的key就替换value,如果不存在就添加。但是到了8以后,就要判断是链表还是树,如果是链表,插入后还要判断要不要转化成树。引入了红黑树,这一点在hash不均匀并且元素个数很多的情况时,对hashmap的性能提升非常大。

LinkedHashMap:HashMap的子类,用链表结构保证了插入元素的顺序。遍历时会根据插入顺序来展示。

treemap:同treeset。只不过哈希表的数据结构是针对key而已。

基于红黑树实现,主要用于存入元素的时候对元素进行自动排序,迭代输出的时候就按排序顺序输出。

weakHashMap:可以看到Entry继承扩展了WeakReference类,在其构造函数中,构造了key的弱引用。 每次get()、put()、size()都间接或者直接调用了expungeStaleEntries()方法,以清理持有弱引用的key的表象。 如果key在别的地方被引用过则不会被回收。如果weakHashMap中全部元素的key都被引用,weakHashMap退化成hashmap。

hashtable已过时

hashMap详述:

HashMap数据结构用数组和链表形式实现:
每个数组中存储的是链表的头元素,或者说数组中存的是最后插入的元素。每个元素是个entry,包含键和值。
hashmap的get(key)方法得到的是key对应的value,并不是key-value键值对。

添加元素:

调用put方法存储元素时,会先调用hashcode()方法返回键的hashcode,键的hashcode对数组的长度进行取余运算,得到的值便是桶的位置,比如两个元素的key的hashcode为12和28,数组长度为16,取余的结果都是12,将一起存储在数组下标为12的位置,存储entry实体(不是只存储值哦)。之后调用Map.entry可以获取。
上面hashcode为12和28的key,其对应实体往下标相同的数组中put时怎么put?任何一个元素进来存在数组(数组中存的是最后一个插入的元素),并且该数组的元素有entry属性,其中的next会指向前一个存在数组中的元素。

public V put(K key, V value) {
     // 若“key为null”,则将该键值对添加到table[0]中。
         if (key == null) 
            return putForNullKey(value);
     // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
         int hash = hash(key.hashCode());
     //搜索指定hash值在对应table中的索引
         int i = indexFor(hash, table.length);
     // 循环遍历Entry数组,若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
         for (Entry<K,V> e = table[i]; e != null; e = e.next) { 
             Object k;
              if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同则覆盖并返回旧值
                  V oldValue = e.value;
                 e.value = value;
                 e.recordAccess(this);
                 return oldValue;
              }
         }
     //修改次数+1
         modCount++;
     //将key-value添加到table[i]处
     addEntry(hash, key, value, i);
     return null;

}

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        //先定位到数组元素,再遍历该元素处的链表
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
}

对hashcode()方法进行再哈希的原因:

https://www.zhihu.com/question/20733617

本身hashcode是一个int型的值,取值范围有40多亿空间。只要哈希函数映射比较均匀松散,一般不会出现哈希碰撞。但是!由于想把各个对象均匀的分不到hashmap的桶中,因此需要对hashcode做取模运算,取模运算保留的是低位区的数字,比如hashmap桶的数量为16时,对hashcode取模保留的是低四位的数字。对于两个对象的hashcode,32位的hashcode一样的情况少,但它们的低四位一样的情况概率挺大的,因此必须对hashcode的低位增加随机性。可以直接对低位使用随机函数,因此采用高低位进行异或运算来增加低位的随机性,以便对象可以在不同的桶中随机分布。

4)队列:https://blog.youkuaiyun.com/javazejian/article/details/53375004

队列(Queue)是限定只能在表的一端进行插入和在另一端进行删除操作的线性表;先进先出

栈(Stack)是限定只能在表的一端进行插入和删除操作的线性表。先进后出

 

只有继承了Object的类才有hashcode,所以操作基本类型如int都是使用它们的包装类型。

二:

在java集合中判断两个对象是否相同的规则是:

1.判断两个对象的hashcode是否相同,不相同则两个对象不相同

2.如果两个对象的hashcode相同,再进一步用equals方法判断两个对象是否相同。

对于普通类:

若普通同类没有重写hashcode()和equals()方法,那么其对象在比较时是集成的Object的hashcode()方法,object类的hashcode()是一个本地方法,其方法的返回值是对象的引用地址,object类中定义的equals()方法也是对引用地址的比较,在set集合中,对象引用地址不一样则元素不重复。

对于String,Integer,Double等:

由于这些类已经重写了hashcode()方法和equals()方法,方法的返回值跟对象的内容有关而不是引用地址,在集合中比较他们的内容即可,内容相同则表示覆盖了已存在的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值