Java集合基础

1 篇文章 0 订阅
1 篇文章 0 订阅

集合

HashMap

     HashMap是基于哈希表(散列表),实现Map接口的双列集合,数据结构采用数组+列表,key唯一,value可以重复,允许存储null键null值,元素无序。

  • 哈希表
         哈希表是基于数组实现的,对于任意的键值k ,经过散列函数的映射可以将其映射到哈希表中的一个地址,在jdk1.8中,散列函数采用的是键值的hashcode右移16位并与自身做异或得到的值,在散列表中的位置是散列值对散列表最大长度取模,为了加快取模的速度,采用了与运算,所以必须规定散列表的长度为2的整数幂

  • 哈希冲突
         如果两个不同的元素,它们经过散列函数得到了相同的地址,这种情况就是哈希冲突,也叫哈希碰撞,哈希函数的设计至关重要,好的哈希函数会尽可能的保证计算简单和地址分布均匀,但是再好的哈希函数也不能保证永远不会出现哈希冲突,所以需要解决冲突的方案,在HashMap中采用的是链地址法,当链接长度小于等于8时使用链表,当大于8时使用的是红黑树;其中当数组容量大于8时才会使用红黑树,当容量小于64,且一个桶中的元素个数大于等8时,将数组容量翻倍。
         链表和红黑树是为了解决哈希冲突,如果根据哈希操作得到的数组位置上不含有链表,那么哈希表的查找,添加操作很快,仅需要一次寻址即可,但是如果定位到的位置含有链表或者红黑树,那么对于添加和查找操作,它的时间复杂度退化为O(n)/O(logN)。所以,在HashMap中链表和红黑树要出现的越少越好,这要求key的散列结果要够分散。

  • HashMap的数据结构
         HashMap的底层就是一个数组,数组的元素都是一个Entry(在HashMap中的实现是Node),每个Entry都代表了HashMap的一个元素,Entry包含了key,value和next(链接的下一个元素,如果没有冲突下一个元素就是null), 在新建一个HashMap的时候会初始化一个数组,HashMap的构造函数有两个参数,一个是数组的初始大小(必须为2的整数幂),默认为16。

  • HashMap存储
         调用HashMap的put方法,如果哈希表中已经存在key值,那么会覆盖原来的key对应的value,如果哈希表中没有key值,那么会新增一个key,value对应的Entry。具体的操作是根据key计算出数组中对应的下标,如果对应下表中没有Entry,那么就在这个位置新增一个记录;如果当前位置有记录,那么判断该记录的key值与传入的key值是否一致,如果一致就修改当前位置的value;否则按照节点的类型(链表或者红黑树)采用不同的方法验证下一个节点的key是否与传入的key相同,如果相同,则修改,如果到最后都没有相同的key,则添加一个记录。如果是修改操作,那么返回修改之前的value;如果是增加操作,返回null,并根据增加后的容量来决定是否需要进行哈希表的扩容。

  • HashMap的扩容
         当HashMap中的元素个数越来越多,数组也变得越来越拥挤,出现碰撞的概率也变大,所以需要及时将数组扩容。当HashMap中的元素个数超过数组大小*装载因子时,就会对数组进行扩容,默认的装载因子为0.75,每次扩容将数组大小扩大一倍,然后计算元素在数组中的位置。

Hashtable

     Hashtable是一个线程安全的遗留类,很多功能跟HashMap类似,不同的是它继承Dictionary类,并且是线程安全的,任一时间只有一个线程可以写Hashtable,并发性不好。所以,可以在不需要线程安全的时候替换为HashMap,需要线程安全的时候可以用ConcurrHashMap。

  • Hashtable与HashMap的相同点
         Hashtable和HashMap相同都是基于哈希表实现的,同样每个元素都是一个key-value对,内部也是通过单链表来解决冲突问题,容量不足时,同样会自动增长。
         Hashtable同样实现了Serializable接口,它支持序列化,实现Cloneable接口,能被克隆,实现了Map接口。

  • Hashtable与HashMap的不同点

    1. 继承父类不同:Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。
    2. 线程安全不同:Hashtable中的方法是Synchronize的,而HashMap中的方法在缺省的情况下是非Synchronize的,在多线程的环境下,可以直接使用Hashtable
    3. 是否提供contains方法:HashMap把Hashtable中的contains方法去掉了,改成了containsValue和containsKey,因为contains方法容易引起误解。
    4. key和value是否允许null值:Hashtable中,key和value都不允许null值,而HashMap则都允许。
    5. 遍历方式不同:都使用Iterator,但Hashtable还是用了Enumeration的方式。
    6. hash不同:Hashtable使用了对象的hashCode,而HashMap重新计算了hash值。
    7. 内部实现使用的数组初始化和扩容方式不同:Hashtable默认容量为11,而HashMap为16,Hashtable扩容是,变为原来的两倍加1,HashMap变为两倍。

LinkedHashMap

    LinkedHashMap是HashMap类的一个子类,它拥有HashMap的一切特性,HashMap中的元素是无序存储的,而LinkedHashMap使用双向链表来保证它的迭代顺序。可以说LinkedHashMap=HashMap+LinkedList。除了维护一个Entry数组,还定义了双向链表的头尾节点来保证迭代顺序,在Entry类中还新增了每个节点的前后节点(before,after)。
    LinkedHashMap支持两种顺序:插入顺序和访问顺序
    1.插入顺序:先添加的在前面,后添加的在后面,修改操作不印象顺序。
    2.访问顺序:访问指的是get/put,对一个键执行get/put操作后,其对应的键值对会移到链表的末尾,所以最末尾的时最近访问的,最开始的是最久没有被访问的,这就是访问顺序。

TreeMap

    LinkedHashMap存储的元素是有序的,可以保持元素的插入顺序,但不能对元素进行自动排序,在一些场景下,如果数据在存储的过程中,能够对数据进行排序,将会极大提高编程效率。TreeMap是Map接口一个重要的实现类,TreeMap可以实现存储元素的自动排序。在TreeMap中,键值对之间按键有序存储,它的实现基础是红黑树。

  • TreeMap的存储结构
         红黑树的基础是平衡二叉树,它首先是一棵二叉树,即每一个节点最多只有两个子节点;其次它是一棵排序二叉树,即节点大于等于所有左子节点,小于等于所有右子节点;最后它还是一棵平衡二叉树,左右子树的高度差的绝对值小于等于1。
         红黑树不仅要满足平衡二叉树的所有特点,他还要满足以下五个特点
         1. 所有节点分别是黑色和红色。
         2. 根节点为黑色。
         3. 所有叶子节点为黑色。
         4. 红色节点的子节点都为黑色。
         5. 任一节点到所有叶子节点路径上的黑色节点数目相同。
         红黑树在新增一个节点后通过变色和旋转来调整自身使仍然满足红黑树的特点。
  • TreeMap的排序方式
         如果构造TreeMap时不传入比较器(Comparator)则按照key的自然顺序来进行排序,此时如果key不是基本类型,则要求传入的类实现了Comparable接口,否则会抛出异常。
  • TreeMap的插入
         put方法是TreeMap的核心,调用put方法后,首先判断红黑树是否建立(root是否为null),如果没有建立则先创建红黑树,并赋值给root;接着遍历红黑树各节点,与传入key值作比较,如果遍历过程中找到与key值相同键,则更新key所在Entry的value;如果没有找到,则将遍历到的最后的节点作为待插入节点的父节点,在上述父节点下插入子节点,调整红黑树的结构使其满足红黑树的特点。

ConcurrentHashMap

     ConcurrentHashMap是Java中的一个线程安全且高效的HashMap实现。它与HashMap一样是基于数组加链表/红黑树实现的,它通过CAS+synchronized来保证并发安全性(抛弃了使用segment)。ConcurrentHashMap不允许插入null键和null值。

  • ConcurrentHashMap的插入
         1. 首先判断Node数组是否初始化,没有则进行初始化.
         2. 通过hash定位数组的索引坐标,是否有Node节点,如果没有,直接使用CAS进行添加,失败则进入下次循环。
         3. 检查到内部正在扩容,就帮助它一起扩容。
         4. 如果有Node节点,就用synchronized锁住头元素。如果是链表节点,则执行链表添加操作,如果是红黑树,则执行红黑树的插入操作。

ArrayList

    ArrayList是集合的一种实现,它基于数组实现了List接口,可以动态增长和缩减数组长度。 ArrayList封装了一个动态再分配的Object数组。

  • 线程安全性
        ArrayList添加操作分两步执行,首先在Object[size]的位置上存放需要添加的元素,第二部将size值增加1,所以ArrayList是线程不安全的,如果要在多线程的环境下使用ArrayList,通常有两种解决办法:第一:使用Synchronized关键词;第二可以用Collections类中的静态方法synchronizedList();对ArrayList进行调用即可。
  • 扩容机制
        当向集合中添加元素是先要确保添加元素成功的最小集合容量minCapacity为size+1,然后判断数组是否为空,如果是,minCapacity就是minCapacity与集合默认容量中的较大值,然后通过minCapacity与当前数组的最大长度比较来决定是否需要对数组进行扩容;如果需要扩容,首先将数组长度扩大1.5倍,然后对扩容后的容量与minCapacity进行比较:1.新容量小于minCapacity,则新容量设为minCapacity;2. 新容量大于minCapacity,则指定新容量。最后将原数组拷贝到新数组中。
  • 总结
    优点
        1. 数组实现,随机访问,查找操作非常快
        2. 顺序添加一个元素非常快
        3. 根据下标遍历元素,效率高
        4. 可以自动扩容
    缺点
        1. 插入和删除元素效率不高
        2. 查找元素需要遍历整个数组,效率不高
        3. 线程不安全

LinkedList

     LinkedList也是实现了List接口的一个类,与ArrayList不同,它使用的数据结构是链表。链表的特点是元素分配的空间不必须连续、插入和删除元素时非常快,但是访问速度较慢。

    LinkedList是一个双向链表,当数据量很大或者操作很频繁的情况下,添加和删除元素时比ArrayList具有很好的性能,但在元素的随机访问和修改上不如ArrayList。LinkedList类每个节点都用内部类Node表示,LinkedList通过first和last引用分别指向链表的第一个和最后一个元素,当链表为空时,first和last都为null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值