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 讲解
集合:collection(最基本的集合接口)
集合可以分为单值和双值
单值
list:arrayList,LinkedList,vector,stack
set:HashSet,TreeSet
(set集合就是map集合的一部分: key)
双值的
map:Hashmap, treemap, hashtable
List集合: 有序(有元素的插入顺序),可重复的集合
底层实现: 数组
如何实现的:
创建数组的时候 长度是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)
底层实现: 线性链表
(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关键字的作用:
简单地说,就是让某些被修饰的成员属性变量不被序列化,
这一看好像很好理解,就是不被序列化
vector:线程同步数组 基本抛弃使用。
vector 和arrayList的区别
vector:线程安全的((方法用了synchronized修饰)) 但是性能低
arrayList 线程不安全的 性能高
ArrayList:底层数据结构是数组,查询快,增删慢
LinkedList:底层数据结构是链表,查询慢,增删快
stack:(相当于一个桶)
栈结构
先进后出的
set集合:无序(没有插入顺序的),不可重复的
set集合就是map集合的一部分(key部分)
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 每次扩容 扩大2倍 32
hashset如何去重的:
hash值不同的两个元素 一定不是同一个元素
先判断hash值,后比较equals方法(地址)
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比较器)
以kv键值对的形式对元素进行存储
底层结构:数组(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.2.2 TreeSet详细讲解 大致相同,不再讲解
hashTable 和hashMap比较:
hashtable:线程安全(就是加锁方法用了synchronized修饰),性能低,整个数组的线程加锁,所以出现了concurrenthashMap
不允许有null的键和值
hashmap 线程非安全的 性能高
允许有null的键和值
concurrenthashMap: 既可以做到线程安全 又可以保证性能
采用:分段线程锁+读写锁
从数据的操作,锁的分类:
读锁:共享锁 所有的线程可以共用这把锁
写锁:排他锁 同一时间只能允许一个线程操作
会对hashMAP底层的数组进行分段加锁, 通过具体的操作,决定加的是读锁还是写锁