java常用数据结构

220 阅读5分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

list

  • 常用方法 add ,remove , contains
  • 允许重复元素

arrayList

  • 采用数组实现
  • 数组集合,自动扩容0.5倍 ,插入慢,索引快
  • 线程不安全

Vector

  • 数组集合,底层采用数组实现
  • 自动扩容1倍
  • 线程安全

LinkedList

链表集合,无容量限制,插入快,索引慢

map

  • k - v 表,
  • k不能重复
  • 常用方法 get , put ,containsKey

ArrayMap

  • 采用双数组实现
  • 非线程安全
  • 节约内存 image.png

hashMap

  • HashMap 底层是数组+链表(jdk1.8是数组+链表/红黑树),HashMap可能也是应用最多的数据结构了
  • 非线程安全
  • 由于hash的存在,输出与输入顺序不一致,也常被称为无序表。 image.png

hashMap什么时候链表会进化为红黑树

  • 链表长度超过8,并且容量大于64,会转化为红黑树
  • 链表长度小于6,退化为链表。

hashMap为什么大小总是2的n方

hashmap的初始值是16,即2的4次方,之后的每次扩容都是两倍扩容,为什么呢?两条与运算比取模高效;

  • 如果往hashmap中存放数据,我们首先得保证它能够尽量均匀分布,为了保证能够均匀分布,我们可能会想到用取模的方式去实现,如果用传统的‘%’方式来实现效率不高,当大小(length)总为2的n次方时,h&(length-1)运算等价于对length取模,也就是h%lenth,但是&比%具有更高的效率,同时也减少了hash碰撞。
  • 和h&(length-1)相关,当容量大小(n)为2的n次方时,n-1 的二进制的后几位全是1,在h为随机数的情况下,与(n-1)进行与操作时,会分布的更均匀,想一想,如果n-1的二进制数是1110,当尾数为0时,与出来的值尾数永远为0,那么0001,1001,1101等尾数为1的位置就永远不可能被entry占用,就造成了空间浪费。

为什么用与运算取代取余运算

取余运算是通过多次循环除法运算实现的,效率远低于与运算。 假设是2381除以7。2381,二进制为1001 0100 11017,

  • 二进制为111用2381的最高位1001减去111,得10。相当于2381-7*256 = 589
  • 589 = 刚才的答案10+之前没用过的右边01001101 = 1001001101
  • 再次重复刚才的过程589的最高位1001减去111,得10。相当于589-7*64 = 141141 = 刚才的答案10+之前没用过的右边001101 = 10001101。。。。。
  • 如此循环,最后,当被除数剩1时即为余数

如何保证空间大小是2的幂次

image.png

image.png

  • 之所以在开始移位前先将容量-1,是为了避免给定容量已经是8,16这样2的幂时,不减一直接移位会导致得到的结果比预期大。比如预期16得到应该是16,直接移位的话会得到32。
  • 由于hashMap的最大大小只能是1<<<32位,所以,不需要继续右移32位了。

LinkedHashMap

  • 双向链表实现
  • 输入与输出顺序一致,有序
  • 非线程安全

hashTable

线程安全,过缺点:因为采用synchronized保证同步,每次都会锁住整个map,所以高并发线程在争夺同一把锁的时候性能急剧下降

TreeMap

  • 是一个红黑树版本的map,实现了NavigableMap<K,V>并且NavigableMap又继承了SortedMap<K,V>类,SortedMap接口有一个Comparator<? super K> comparator();比较器,所以TreeMap是支持比较排序的
  • 内部的key是有序的

ConcurrentHashMap

底层也是HashMap,同时保证了线程安全,与HashTable不同的ConcurrentHashMap采用分段锁思想,抛弃了使用synchronized修饰操作方法的同步方式。

都知道HashTable效率低下的原因是因为整个容器只有一把锁,多线程争抢同一把锁导致。 ConcurrentHashMap分段锁指得是将数据分成一个个的Segment<K,V>,每个Segment又继承ReentrantLock,这样一个map容器就会有多个Lock,每个Lock锁不同的数据段,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

1.7与1.8的区别:

  • 因为底层是HashMap,1.8之后也变成了数组+链表/红黑树。
  • 1.8之后放弃了分段锁,采用了synchronized+CAS来保证并发。

为何放弃分段锁:

  • 我认为主要是1.8对synchronized进行了优化(偏向锁、轻量级锁、自旋锁、自适宜自旋)
  • 加入多个分段锁浪费内存空间。
  • 生产环境中,map在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待

set集合

  • 元素不能重复

HashSet

  • HashSet 基于HashMap实现,利用Map的key不能重复来实现Set的元素唯一性

LinkedHashSet

  • 采用双向链表和hashMap实现

stack栈

  • 采用Vector实现
  • 线程安全
  • 后进先出

Queue

  • 队列
  • 先进先出
  • add 超出容量会跑出异常,offer不会。

Deque

double end queue , 双向操作嘟列

ArrayDeque

  • 数组实现的双向操作队列
  • 非线程安全

LinkedBlockingDeque

  • 线程安全队列
  • ReentrainLock实现等待
  • 链表实现的阻塞双头队列

ArrayBlockingQueue

  • 线程安全
  • ReentrainLock实现等待
  • 数组实现的阻塞双头队列