我所理解的JDK集合类(一):基本介绍

在日常开发中,集合类是经常使用的工具,是JDK根据基础的java代码通过java语言来进行的一些封装。Java Collection由两套并行的接口组成,一套是Collection接口,一套是Map接口。Collection接口的子接口及实现类有List、Set、Stack、Queue等。

  List接口的特点是有序可重复。实现类通常使用的是ArrayList和LinkedList。

  Set接口的特点是不可重复。实现类通常使用的是HashSet、LinkedHashSet和TreeSet。

  Stack类的特点是后进先出,内部通过继承Vector来实现。

  Queue接口的特点是先进先出,是中间件开发常用的类。

  Map接口的特点是存储键值对,键不可重复。实现类通常使用的是HashMap、LinkedHashMap和TreeMap。

  ArrayList底层由数组实现,数组的默认初始大小size为10,扩容系数为0.5。在构造函数中可以指定size。ArrayList特点是可以根据数组索引快速定位到元素。 扩容的源码:

int newCapacity = oldCapacity + (oldCapacity >> 1);  //  >>位移运算符,快速计算 /2的大小。
...  // 省略其他
elementData = Arrays.copyOf(elementData, newCapacity); // Arrays.copyOf()底层是native的数组复制,快速。

  LinkedList底层由双向链表实现,特点是增加元素时可以快速添加。删除元素时,定位元素比较慢,定位到之后可以快速删除。

  ArrayList和LinkedList是线程不安全的,推荐在方法内使用。Vector是线程安全版的ArrayList。

  Set系列的实现是通过组合对应的Map实现类来实现的,Set存储的元素作为Map的key,value = new Object();

  HashMap底层由hash表+链表+数组实现的。JDK1.8之后HashMap的底层增加了红黑树结构。数组的默认初始大小size为16,扩容系数factor为2。在构造函数中可以指定size和factor。HashMap的特点是查询快速,是典型的以空间换时间的做法。缺点是扩容时很耗时。

  LinkedHashMap是通过继承HashMap,重写entry内部类中增加双向链表来实现的。通过双向链表来表示元素的存取顺序。

  TreeMap的底层是通过红黑树来实现的。

  HashMap、LinkedHashMap和TreeMap是线程不安全的,推荐在方法内使用。HashTable是线程安全版的HashMap,但是不支持null作为key。

接下来简单介绍一下HashMap的原理
  put(k,v)时,先根据hashCode和equels判断是否已经存在相同的key,如果存在则返回旧的value并保存新的value。存储key时,先根据k.hashCode()得到hash值,再通过HahsMap自己实现的hash()等到更加均匀散列的hash值(JDK1.8去掉了此步骤),通过hash值对数组的长度求余来确定该key应该存在的位置,其实就是获取hash值对应的数组索引。如果当前位置有值(说明发生了hash碰撞),则通过链表来增加该元素。最后再判断是否需要进行扩容resize()。

  get(k)时,根据k.hashCode()计算得到数组索引,再遍历链表判断hashCode和equels确定已存储的key来返回对应的value,如果没有则返回null。

  根据hash值求余计算数组的索引,源码中采用的是位移运算 hash & (table.length -1),这也就是初始数组的大小会是2的n次方以及扩容系数是2的原因。而在构造中保证数组大小是不小于传参的最小的2的n次方实现,也是通过位移运算(>>)来实现的。通过位移运算来保证运算速度,设计精巧。

HashMap的扩容resize()。
  每一次put(k,v)元素之后,都需要进行判断是否进行扩容。当当前存储的元素的个数 size > table.length * factor 的时候,就需要进行扩容。扩容很耗时,需要建立新的数组,然后遍历就数组的每一个元素并重新计算对应的数组索引并复制存储。所以使用HashMap的优雅方式是在构造参数中指定数组的大小(提前预估),即使是默认的16。这是优化HashMap的一个方法。HashMap在并发扩容的时候可能会发生死循环

  JDK1.8的HashMap中引入了红黑树结构,在put(k,v)的时候,当链表的节点数≥8,则自动转化为红黑树结构存储,目的是减少在极端情况下hash冲突导致都存储在同一个链表上时查询过慢的情况。因为链表查找的时间复杂度为O(n),而红黑树则是O(logn)。

  HashMap允许存储key为null的情况。内部实现是通过将null作为特殊的key判断,存储在数组的第一个元素中。

ConcurrentModificationException
  List、Set在进行foreach遍历的时候,是不允许通过list.remove(e)等操作来增删元素的,否则回报ConcurrentModificationException。foreach是通过iterator的.hasNext()和.next()来实现的。List有个modCount的属性来记录当前list被操作的次数,在add(e)、remove(e)、clear()等操作的时候都会触发modCount++。Iterator内部有expectedModCount属性,在构造Iterator的时候会expectedModCount = modCount。当使用iterator.remove()的时候,会调用list.remove(e)以及强制expectedModCount = modCount。每次iterator.next()的时候都会判断expectedModCount和modCount是否相等。如果不相等则表示被不正确的修改过,然后 throw new ConcurrentModificationException() 。如果要在遍历期间修改元素,请使用iterator.remove()或者listIterator.add(e)。

转载于:https://my.oschina.net/u/3466682/blog/1575664

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值