集合中list,set,map底层实现,扩容机制以及优缺点讲解

目录

1.集合分类
2.具体讲解
  2.1 List集合
    2.1.1 arrayList详细讲解
    2.1.2 LinkedList详细讲解
    2.1.3 vector详细讲解
    2.1.4 stack详细讲解
  2.2 Set集合
    2.2.1 HashSet详细讲解:
    2.2.2 TreeSet详细讲解:
  2.3 Map集合
    2.3.1 HashMap详细讲解
    2.3.2 TreeMap详细讲解
    2.3.3 HashTable 讲解
    2.3.4 ConCurrentHashMap 讲解

1.集合分类:

集合:collection(最基本的集合接口)

集合可以分为单值双值

单值			
list:arrayList,LinkedList,vector,stack
set:HashSet,TreeSet
	(set集合就是map集合的一部分: key)

双值的
map:Hashmap, treemap, hashtable

2.具体讲解

2.1 List集合

List集合: 有序(有元素的插入顺序),可重复的集合

2.1.1 arrayList详细讲解:

底层实现: 数组
如何实现的:

创建数组的时候  长度是0
第一次添加元素的时候   初始化数组的长度10

数组的扩容
数组的元素个数超过10个的时候 开始 扩容的工作

grow():
//1.数组扩容的时候 每次1.5倍进行扩容的
//2.进行数组的复制的工作
int newCapacity = oldCapacity + (oldCapacity >> 1);
                = 1.5*oldCapacity

数组的上限
arrayList数组的上限:Integer_max(约21亿)-8
15亿------15*1.5=====22.亿
数组的上限 integer_maxvalue 21亿
arraylist的size的最大上限是integer_maxvalue

性能瓶颈10亿左右

ArrayList的构造方法:

//构造方法中有参构造   无参构造
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
       this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
       this.elementData = EMPTY_ELEMENTDATA;
    } else {
       throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
   }
}
有参构造的使用场景:
  如果使用无参构造,初始化数组的长度0  
  第一次添加元素的时候开始  长度10   
  以后的每次扩容   1.5倍进行扩容
         1000
         10   15    22   33    50   75   112   。。。。
  数组扩容----数组的拷贝的过程  消耗资源的过程

  使用有参构造  参数:代表的是我们数组初始化的长度
  参数500---750----1000+
  当arraylist中元素个数很多的时候 ,最好使用有参构造,减少底层数组的扩容的次数,提升性能
  方法有:
    add(E e)
    remove(E e)

数组特点:
查询快 增删慢
因为数组是有索引的,通过下标直接访问
时间复杂度o(1)

2.1.2 LinkedList详细讲解:

底层实现: 线性链表
1.5之前采用单向链表, 1.5之后采用双向链表

链表:
线性链表:
       单向链表:
          只有一个方向的链表,上一个元素知道下一个元素,但是下一个元素不知道上一个元素的,访问的时候只能从一端开始
       双向链表:
          两个方向的链表,每一个元素,都知道自己的上一个元素和下一个元素是谁,可以从两个方向访问
环形链表:
       首尾相连的链表

链表结构中的每一个元素 就叫做node 对象 Node
Node的结构:

private static class Node<E> {
     E item;
     Node<E> next;
     Node<E> prev;

     Node(Node<E> prev, E element, Node<E> next) {
         this.item = element;
         this.next = next;
         this.prev = prev;
     }
}

addFirst(E e): 添加元素的时候 实际上是对Node的操作

扩容机制
由于它的底层是用双向链表实现的,所以它对元素的增加、删除效率要比ArrayList好;
它是一个双向链表,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。

优点
增删快 查询慢

查看底层代码可能会遇到transient关键字:
	Java中transient关键字的作用:
		简单地说,就是让某些被修饰的成员属性变量不被序列化,
		这一看好像很好理解,就是不被序列化
2.1.3 vector详细讲解:

vector:线程同步数组 基本抛弃使用。

vector 和arrayList的区别          
    vector:线程安全的((方法用了synchronized修饰))  但是性能低
    arrayList  线程不安全的   性能高
	    ArrayList:底层数据结构是数组,查询快,增删慢
        LinkedList:底层数据结构是链表,查询慢,增删快
2.1.4 stack详细讲解:
stack:(相当于一个桶)
        栈结构
        先进后出的

2.2 Set集合

set集合:无序(没有插入顺序的),不可重复的
set集合就是map集合的一部分(key部分)

2.2.1 HashSet详细讲解:

HashSet:HashMap中k-v中的k部分(可先查看底部map讲解
底层结构数组(初始容量16)+链表(单向的)+树(红黑树)
元素存储的时候 先对元素(key)取hash值

hash算法:就是生成一个唯一的散列的数
	不存在一个绝对完美的hash算法可以做到 任意两个甚至多个key的hash值不同的
	任何一个hash算法都会存在hash冲突的问题
hash冲突:
	不同的key得到的hash值相同
Node[] tab;  数组  数组的初始化容量  16
Node p;    记录每一个元素的
int n, i;  记录数组的下标

数组扩容的节点:
数组进行扩容的阀值 ,数组的容量达到整个数组的0.75就会进行数组的扩容的操作 :每次扩容 扩大2倍 :
16*0.75=12

static final float DEFAULT_LOAD_FACTOR = 0.75f;
newCap = oldCap << 1  每次扩容  扩大232

hashset如何去重的:
hash值不同的两个元素 一定不是同一个元素
先判断hash值,后比较equals方法(地址)

2.2.2 TreeSet详细讲解:

TreeSet : TreeMap中k-v中的k部分(可先查看底部map讲解
特点有序(元素值是排序的) 不可重复的
底层结构红黑树
排序方式自然排序

	如果是数值:值的大小  默认升序的
	如果是字符串:字典顺序进行排序的
		例如:
		字符串数组:12   11   1   2   3   4   34   23   26
		排序结果:1,11,12,2,23,26,3,34,4

要求:我们的set集合中存放的元素是有排序能力的(如果是自定义对象,需要实现compare比较器)

2.3 Map集合

以kv键值对的形式对元素进行存储

2.3.1 HashMap详细讲解:

底层结构:数组(16)+链表(单向链表)+树(红黑树)
默认的初始化容量(底层数组)1<<4 – 16
链表结构转换为树结构最大阀值:8(1.8之后的)
树结构转换为链表结构阀值:6
默认的最大容量:1<<30 2的30次方
加载因子:0.75 (存储到数组0.75后就会2倍扩容)

数组扩容的节点
数组进行扩容的阀值 数组的容量达到整个数组的0.75就会进行数组的扩容的操作(每次扩容 ,扩大2倍) 16*0.75=12

static final float DEFAULT_LOAD_FACTOR = 0.75f;
newCap = oldCap << 1 // 每次扩容  扩大2倍   32

链表和树的转换:
因为是一个单向链表(8)所以查询的时候性能低,在1.8之后做了一个优化,当链表的深度超过8的时候就将链表转换为树结构

       链表结构转换为树结构的最大阀值:8(1.8之后的)
       树结构转换为链表结构的阀值:6

put添加的实现
元素存储的时候 先对元素(key)取hash值。
对元素取hash值就会造成hash冲突 (hash碰撞)

hash算法:就是生成一个唯一的散列的数
           不存在一个绝对完美的hash算法可以做到 任意两个甚至多个key的hash值不同的
           任何一个hash算法都会存在hash冲突的问题
hash冲突:不同的key得到的hash值相同
       Node<K,V>[] tab;  数组  数组的初始化容量  16
       Node<K,V> p;    记录每一个元素的
       int n, i;  记录数组的下标

数组扩容:
数组进行扩容的阀值 数组的容量达到整个数组的0.75就会进行数组的扩容的操作(每次扩容 ,扩大2倍) 16*0.75=12

static final float DEFAULT_LOAD_FACTOR = 0.75f;
newCap = oldCap << 1 // 每次扩容  扩大2倍   32

为什么要扩容2倍数?

原来数组下标中链表中存储了元素,原来模余16,现在如果两倍 就是模余32 
可以把原来链表中的元素分担到新扩容的数组下标下

为什么出现了树转变为链表的阀值:

因为数组扩容,所以出现了树转链表结构的阀值:因为深度减少了

hashset如何去重的?

hash值不同的两个元素  一定不是同一个元素
先判断hash值 ,再用equals方法(地址)判断:
	如果equals相同,值不进行存储,因为是set
2.3.2 TreeMap详细讲解:

2.2.2 TreeSet详细讲解 大致相同,不再讲解

2.3.3 HashTable 讲解:

hashTable 和hashMap比较:

hashtable:线程安全(就是加锁方法用了synchronized修饰),性能低,整个数组的线程加锁,所以出现了concurrenthashMap
		  	不允许有null的键和值
hashmap   线程非安全的   性能高
			允许有null的键和值
2.3.4 ConCurrentHashMap 讲解:

concurrenthashMap: 既可以做到线程安全 又可以保证性能
采用:分段线程锁+读写锁

从数据的操作,锁的分类:
		读锁:共享锁   所有的线程可以共用这把锁
		写锁:排他锁  同一时间只能允许一个线程操作

会对hashMAP底层的数组进行分段加锁, 通过具体的操作,决定加的是读锁还是写锁

你可能感兴趣的:(Java学习)