java集合使用+底层实现原理

java集合

在这里插入图片描述
在这里插入图片描述

1.List

(1)ArrayList:底层数据结构是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
(2)LinkedList 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
(3)Vector:底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素

ArrayList:

底层为动态数组,非线性安全,查快,增删慢
private transient Object[] elementData;

1.ArrayList有三个构造方法:

  • public ArrayList() //默认构造一个初始容量为10的空列表。

  • public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。

  • public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection
    的元素的列表
    2.存储
    ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法
    3.读取:get()
    4.删除:
    5.调整数组容量:ensureCapacity(int min)
    public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
    Object oldData[] = elementData;
    int newCapacity = (oldCapacity * 3)/2 + 1;
    if (newCapacity < minCapacity)
    newCapacity = minCapacity;
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
    }
    数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍,如果还是不够,则根据实际长度来扩容
    6.采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
    我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

    这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

Java代码 收藏代码
HashIterator() {
expectedModCount = modCount;
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}

在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:

注意到modCount声明为volatile,保证线程之间修改的可见性。
7. remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC
8.add、remove操作对于ArrayList其运行时间是O(N),因为在它当中在前端进行添加或移除构造新数组是 System.arraycopyO(N)操作;get方法的调用为O(1)操作。要是使用一个增强的for循环,对于任意List的运行时间都是O(N),因为迭代器将有效地从一项到下一项推进。

2.LinkedList实现原理要点概括

public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable

  1. LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  2. LinkedList 实现 List 接口,能对它进行队列操作。
  3. LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  4. LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  5. LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
  6. LinkedList 是非同步的,并允许包括null在内的所有元素。
    //链表长度
    transient int size = 0;
    //头部节点
    transient Node first;
    //尾部节点
    transient Node last;
/\*\* \* 静态内部类,存储数据的节点 \*/
private static class Node\<E\> {
    //自身结点
    E item;
    //下一个节点
    Node<E> next;
    //上一个节点
    Node<E> prev;
}

1.构造:
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll©;
}
第一个构造方法不接受参数,用于表示一个空的链表
第二个构造方法接收一个Collection参数c,调用第一个构造方法构造一个空的链表,之后通过addAll将c中的元素全部添加到链表中。
3.Vector
Vector 是矢量队列,也是基于动态数组实现,容量可以自动扩容。跟 ArrayList 实现原理一样,但是 Vector 是线程安全,使用 Synchronized 实现线程安全,性能非常差,已被淘汰,使用 CopyOnWriteArrayList 替代 Vector。
4.Stack
stack是栈,先进后出原则,Stack 继承 Vector,也是通过数组实现,线程安全。因为效率比较低,不推荐使用,可以使用 LinkedList(线程不安全)或者 ConcurrentLinkedDeque(线程安全)来实现先进先出的效果。
实现原理:

Stack 执行 push() 时,将数据推进栈,即把数据追加到数组的末尾。
Stack 执行 peek 时,取出栈顶数据,不删除此数据,即获取数组首个元素。
Stack 执行 pop 时,取出栈顶数据,在栈顶删除数据,即删除数组首个元素。
Stack 继承于 Vector,所以 Vector 拥有的属性和功能,Stack 都拥有,比如 add()、set()
5. CopyOnWriteArrayList
CopyOnWriteArrayList 是线程安全的 ArrayList,写操作(add、set、remove 等等)时,把原数组拷贝一份出来,然后在新数组进行写操作,操作完后,再将原数组引用指向到新数组。CopyOnWriteArrayList 可以替代 Collections.synchronizedList(List list)。

**数据结构:**动态数组

特征:

线程安全;
读多写少,比如缓存;
不能保证实时一致性,只能保证最终一致性。
缺点:

写操作,需要拷贝数组,比较消耗内存,如果原数组容量大的情况下,可能触发频繁的 Young GC 或者 Full GC;
不能用于实时读的场景,因为读取到数据可能是旧的,可以保证最终一致性。
实现原理:

CopyOnWriteArrayList 写操作加了锁,不然多线程进行写操作时会复制多个副本;读操作没有加锁,所以可以实现并发读,但是可能读到旧的数据,比如正在执行读操作时,同时有多个写操作在进行,遇到这种场景时,就会都到旧数据
6.CopyOnWriteArraySet
CopyOnWriteArraySet 是线程安全的无序并且不能重复的集合,可以认为是线程安全的 HashSet,底层是通过 CopyOnWriteArrayList 机制实现。

**数据结构:**动态数组(CopyOnWriteArrayList),并不是散列表。

特征:

线程安全
读多写少,比如缓存
不能存储重复元素

2.7 ArrayList 和 Vector 区别
Vector 线程安全,ArrayList 线程不安全;
ArrayList 在扩容时默认是扩展 1.5 倍,Vector 是默认扩展 1 倍;
ArrayList 支持序列化,Vector 不支持;
Vector 提供 indexOf(obj, start) 接口,ArrayList 没有;
Vector 构造函数可以指定扩容增加系数,ArrayList 不可以。

Hashtable实现原理要点概括
参考文献:http://blog.csdn.net/zheng0518/article/details/42199477
和 HashMap 一样,Hashtable 也是一个哈希散列表,Hashtable 继承于 Dictionary,使用重入锁 Synchronized 实现线程安全,key 和 value 都不允许为 Null。HashTable 已被高性能的 ConcurrentHashMap 代替。

原文链接:https://blog.csdn.net/weixin_41818794/article/details/104394587
Hashtable是基于哈希表的Map接口的同步实现,不允许使用null值和null键
底层使用数组实现,数组中每一项是个单链表,即数组和链表的结合体
Hashtable在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
synchronized是针对整张Hash表的,即每次锁住整张表让线程独占
ConcurrentHashMap实现原理要点概括
参考文献:http://blog.csdn.net/zheng0518/article/details/42199477

ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。
它使用了多个锁来控制对hash表的不同段进行的修改,每个段其实就是一个小的hashtable,它们有自己的锁。只要多个并发发生在不同的段上,它们就可以并发进行。
ConcurrentHashMap在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)
ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。
HashSet实现原理要点概括
参考文献:http://zhangshixi.iteye.com/blog/673143l

HashSet由哈希表(实际上是一个HashMap实例)支持,不保证set的迭代顺序,并允许使用null元素。
基于HashMap实现,API也是对HashMap的行为进行了封装,可参考HashMap
LinkedHashMap实现原理要点概括
参考文献:http://zhangshixi.iteye.com/blog/673789l

LinkedHashMap继承于HashMap,底层使用哈希表和双向链表来保存所有元素,并且它是非同步,允许使用null值和null键。
基本操作与父类HashMap相似,通过重写HashMap相关方法,重新定义了数组中保存的元素Entry,来实现自己的链接列表特性。该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而构成了双向链接列表。
LinkedHashSet实现原理要点概括
参考文献:http://zhangshixi.iteye.com/blog/673319l

对于LinkedHashSet而言,它继承与HashSet、又基于LinkedHashMap来实现的。LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承与HashSet,其所有的方法操作上又与HashSet相同。

HashMap实现原理要点概括
参考文献:http://zhangshixi.iteye.com/blog/672697
参考文献:http://blog.csdn.net/lizhongkaide/article/details/50595719

HashMap是基于哈希表的Map接口的非同步实现,允许使用null值和null键,但不保证映射的顺序。
底层使用数组实现,数组中每一项是个单向链表,即数组和链表的结合体;当链表长度大于一定阈值时,链表转换为红黑树,这样减少链表查询时间。
HashMap在底层将key-value当成一个整体进行处理,这个整体就是一个Node对象。HashMap底层采用一个Node[]数组来保存所有的key-value对,当需要存储一个Node对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Node时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Node。
HashMap进行数组扩容需要重新计算扩容后每个元素在数组中的位置,很耗性能
采用了Fail-Fast机制,通过一个modCount值记录修改次数,对HashMap内容的修改都将增加这个值。迭代器初始化过程中会将这个值赋给迭代器的expectedModCount,在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map,马上抛出异常

HashMap、Hashtable、ConccurentHashMap 三者的区别(Java 8):

HashMap 线程不安全,没有锁机制,数组 + 链表 + 红黑树
Hashtable 线程安全,锁住整个对象,数组 + 链表
ConccurentHashMap 线程安全,CAS+Synchronized,数组 + 链表 + 红黑树
HashMap 的 key 和 value 都可为 null,其他两个都不可以。

构造hash表时,如果不指明初始大小,默认大小为16(即Node数组大小16),如果Node[]数组中的元素达到(填充比*Node.length)重新调整HashMap大小 变为原来2倍大小,扩容很耗时

HashMap中的put()和get()的实现原理
1、map.put(k,v)实现原理
(1)、首先将k,v封装到Node对象当中(节点)。
(2)、然后它的底层会调用K的hashCode()方法得出hash值。
(3)、通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。

2、map.get(k)实现原理
(1)、先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
(2)、通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。重点理解如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。

为什么放在hashMap集合key部分的元素需要重写equals方法?
因为equals方法默认比较的是两个对象的内存地址

HashMap在jdk1.8之后引入了红黑树的概念,表示若桶中链表元素超过8时,会自动转化成红黑树;若桶中元素小于等于6时,树结构还原成链表形式。

原因:

红黑树的平均查找长度是log(n),长度为8,查找长度为log(8)=3,链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,这才有转换成树的必要;链表长度如果是小于等于6,6/2=3,虽然速度也很快的,但是转化为树结构和生成树的时间并不会太短。

还有选择6和8的原因是:

中间有个差值7可以防止链表和树之间频繁的转换。假设一下,如果设计成链表个数超过8则链表转换成树结构,链表个数小于8则树结构转换成链表,如果一个HashMap不停的插入、删除元素,链表个数在8左右徘徊,就会频繁的发生树转链表、链表转树,效率会很低。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java集合框架是Java中用于存储和操作数据的重要工具。它提供了一组接口和类,用于处理不同类型的数据结构,如列表、集合、映射等。下面是Java集合框架的底层原理: 1. 数据结构Java集合框架中的数据结构可以分为两类:基于数组和基于链表。基于数组的数据结构如ArrayList,它使用动态数组来实现。基于链表数据结构如LinkedList,它使用双向链表实现。 2. 接口和实现类:Java集合框架提供了一组接口,如List、Set、Map等,定义了不同类型的集合操作。每个接口都有一些对应的实现类,如ArrayList、HashSet、HashMap等。 3. 迭代器:Java集合框架提供了迭代器接口(Iterator),用于遍历集合中的元素。迭代器隐藏了底层集合的具体实现细节,使得我们可以以统一的方式访问集合中的元素。 4. 泛型:Java集合框架使用泛型来支持不同类型的元素存储和操作。通过使用泛型,可以在编译时检查类型安全性,并提供更好的代码重用性和可读性。 5. 效率和性能:Java集合框架在设计上考虑了效率和性能。例如,ArrayList在随机访问元素时具有较好的性能,但在插入和删除元素时较差。LinkedList在插入和删除元素时具有较好的性能,但在随机访问元素时较差。 总的来说,Java集合框架的底层原理是通过不同的数据结构和接口实现类来提供高效的数据存储和操作功能,同时使用泛型来支持类型安全性。这些特性使得Java集合框架成为开发Java应用程序中不可或缺的工具之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值