java容器

List

1.ArrayList是动态数组,支持随机快速访问,由于其实现了RandomAccessable接口,其内部终究还是数组的实现。所以其快速访问的能力特别出众,但与之相反,插入,更改的性能则相当付出代价。

2.LinkedList是基于双向链表实现,支持顺序访问,也可以快速的进行增删改操作。还可以用作栈,队列,双向队列的实现方案。

3.vector:与ArrayList类似,不同的地方在于效率较低。但是支持多线程的共享,内部用sycnized关键字进行同步。

Set

TreeSet:根据红黑树实现,支持有序性操作,可以在一定范围内寻找元素,但是寻找的效率没有hashSet高,HashSet的时间复杂度为O(1),TreeSet的时间复杂度为O(logN);

HashSet:是基于hash表实现的,不允许重复,不支持有序操作,丢失插入顺序和索引。不支持根据索引查找元素,因此具有不稳定性。根据iterator查找HashSet结果是不确定的。

LinkedHashSet:双向链表实现的结构,支持跟hashSet一样的查询效率,也支持维护插入的顺序。

Map

HashMap:hash表生成的map

HashTable:线程安全的Map,不支持插入空键值,由于效率不高,现在一般改用ConcurrentHashMap代替参与有多线程的场景开发。因为ConcurrentHashMap引入了分段锁

LinkedHashMap:双向链表维护的Map 顺序为插入顺序或者最近最少使用(LRU)顺序。

TreeMap:红黑树实现的Map

容器中的设计模式

迭代器模式:Collection继承了Iterator接口,可以让容器里的元素利用迭代器进行遍历。

List<String> array=new ArrayList<>();
​
for(String i:a)
​
{
​
sout(i);
​
}

适配器模式:Arrays.asList();可以将数组适配成List对象 asList只能穿包装类,不可以传基本数据类型。

源码分析

ArrayList:ArrayList的默认长度为10,ElementDate Object[]数组

增加:

每次增加的时候会先确定当前容量+1是否满足增加的条件。扩容的操作过程如下:

(1) 如果加入的元素为空,直接return

(2)如果加入的元素不为空,要先确认容量是否够用。ensureCapicity方法,传参为当前容量加1.如果容量不够那么进行扩容grow操作,grow操作是当前长度1.5倍 具体是当前容量+当前容量右移运算两位。

(3)删除操作:删除,涉及到数组的移动和拷贝工作 比较耗时

(4) 修改操作:首先changeRange,检查边界是否合法,然后使得对应的位置变为更改过后的元素

(5) 查询操作:边界检查,直接返回index上的元素。

(6)ArrayList重写了序列化的方法,使得ArrayList序列化直接可以跳过空元素,只序列化list中有元素的部分。

快速失败机制:主要发生在迭代器中遍历元素,但与此同时发生了修改容器结构的操作。

Vector

vector相比较ArrayList是线程同步的,他的方法都是用Sycnized关键字修饰的。

扩容方面,会根据当前容量进行2倍扩增。

由于其效率并不高,而且不是所有操作都需要同步,所以需要一个可以定制化的同步容器。所以引出了CopyOnWriteArrayList。

CopyOnWriteArrayList

copyOnWriteArrayList是读写分离的ArrayList,其读操作在原数组上执行。写操作需要加锁,加锁的实现方式是ReentrantLock来上锁,这个锁需要显示的上锁,finally中显示的解锁。由于写操作和读操作操作的不是一个对象,所以实时性并不是很好。所以这种容器适用场景为:读多写少的场景,性能比较优良,典型的空间换时间的处理方式。由于读写分离,其实时性并不强。

LinkedList

 

增加:由于其实链表形式的容器,不存在扩容的形式,直接在链表尾部追加元素即可。

删除:将前一个节点的next指针指向next的next节点。

修改 :直接修改节点值即可,开销不大

查询:

查询是从头到尾进行遍历,时间开销比较大。

Map

HashMap

HashMap是K_V形式的数据结构,内部是数组加链表的实现方式,有一个Entry[]数组,作为桶。桶中放入一组链表,主要hash值相等或者散列桶取模运算结果相同的实体。

 

拉链法的工作原理

HashMap<String, String> map = new HashMap<>();
map.put("K1", "V1");
map.put("K2", "V2");
map.put("K3", "V3");
  • 新建一个 HashMap,默认大小为 16;

  • 插入 <K1,V1> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。

  • 插入 <K2,V2> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。

  • 插入 <K3,V3> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 <K2,V2> 前面。

应该注意到链表的插入是以头插法方式进行的,例如上面的 <K3,V3> 不是插在 <K2,V2> 后面,而是插入在链表头部

查找需要分成两步进行:

  • 计算键值对所在的桶;

  • 在链表上顺序查找,时间复杂度显然和链表的长度成正比。

HashMap:put操作,先计算存进来的key的hashCode值来定位要插入的桶的位置,这里需要好的hashcode算法因为好的hashcode算法可以让桶的分布更加均匀,避免hash碰撞,这样就会转化成链表,这样在下次进行查找时,就会增加时间复杂度。

jdk1.7 数组+链表 jdk1.8 数组+链表+红黑树

Map的初始长度为16 负载因子:0.75 链表长度达到8的时候会转化成红黑树。

参数含义
capacitytable 的容量大小,默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。
size键值对数量。
thresholdsize 的临界值,当 size 大于等于 threshold 就必须进行扩容操作。
loadFactor装载因子,table 能够使用的比例,threshold = (int)(capacity* loadFactor)。

扩容:hashMap扩容会扩容为原来容量的2的n次幂倍,使得桶散列的更加均匀,减少hash碰撞;

ConcurrentHashMap

ConcurrentHashMap之所以效率高,是因为采用了分段锁技术,相比较HashTable,用了segment分段锁技术锁住部门entry数组,使得多线程可以同时进入容器操作,然后hashTable每次只能进一个线程。所以相比较ConcurrentHashMap是HashTable的替代方案之一。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值