数组:存储区间连续,占用内存严重,空间复杂很大,时间复杂为O(1)。
- 优点:是随机读取效率很高,原因数组是连续(随机访问性强,查找速度快)。
- 缺点:插入和删除数据效率低,因插入数据,这个位置后面的数据在内存中要往后移的,且大小固定不易动态扩展。
链表:区间离散,占用内存宽松,空间复杂度小,时间复杂度O(N)。
1.单向链表由节点组成,每个节点都包含下一个节点的指针。
- 优点:插入和删除速度快(不需要像线性结构那样移动剩下的数据),内存利用率高,没有大小固定,扩展灵活。
- 缺点:不能随机查找,需要通过循环或者递归的方法访问到任意数据,平均的访问效率低于线性表(查询效率低)。单向链表只能从头到尾遍历。只能找到后继,无法找到前驱,也就是只能前进。
2.双向链表是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。
特点:
- 有两个指针,一个指向前一个节点,一个指向后一个节点
- 可以找到前驱和后继,可进可退
- 增加删除节点复杂,需要多分配一个指针存储空间
红黑树是一种特定类型的二叉树,也是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树,由于每一棵红黑树都是一颗二叉排序树,因此,在对红黑树进行查找时,可以采用运用于普通二叉排序树上的查找算法。
特点:
- 每个节点只能是红色或者黑色。
- 根节点必须是黑色。
- 红色的节点,它的叶节点只能是黑色。
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树
HashMap(哈希表):
是一个键值对的集合,每个节点用Node<K,V>表示
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node是一个内部类,这里的key为键,value为值,next指向下一个元素,可以看出HashMap中的元素不是一个单纯的键值对,还包含下一个元素的引用。
在JDK1.7时HashMap的底层是由数组+链表实现的,到了JDK1.8后改成了数组+链表+红黑树实现
在HashMap底层使用数组+(链表或红黑树)的结构完美的解决了数组和链表的问题,使得查询和插入,删除的效率都很高。
PUT操作:
1.计算关于key的hashcode值
2.如果散列表为空时,调用resize()初始化散列表
3.如果没有发生碰撞,直接添加元素到散列表中去
4.如果发生了碰撞(hashCode值相同),进行三种判断
1:若key地址相同或者equals后内容相同,则替换旧值
2:如果是红黑树结构,就调用树的插入方法
3:链表结构,循环遍历直到链表中某个节点为空,尾插法进行插入,插入之后判断链表个数是否到达变成红黑树的阙值8;也可以遍历到有节点与插入元素的哈希值和内容相同,进行覆盖。
5.如果桶满了大于阀值,则resize进行扩容
GET操作
- 对key的hashCode进行hashing
- 与运算计算下标获取bucket位置,如果在桶的首位上就可以找到就直接返回,否则在树中找或者链表中遍历找
- 如果有hash冲突,则利用equals方法去遍历链表查找节点
RESIZE操作
扩容的时候,HashMap是把长度扩为原来2倍,所以,元素的位置要么是在原位置,要么是在原位置再移动2次幂的位置。
1. HashMap的特性?
- 实现快速存储键值对,允许为null,key值不可重复,若key值重复则覆盖。
- 线程不安全。
- 底层是Hash表,不保证有顺序
2. HashMap底层原理?
- jdk7时采用数组+链表,jdk8后采用数组+链表+红黑树的数据结构。
3. HashMap put原理?
- 当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。
4. HashMap get原理?
- 当获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后再返回值对象。
5. HashMap扩容机制?
- 扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。
6. HashMap默认初始化长度为16,并且每次自动扩展或者是手动初始化容量时,为什么必须是2的次幂?
- 为了数据的均匀分布,减少哈希碰撞。因为确定数组位置是用的位运算,若数据不是2的次幂则会增加哈希碰撞的次数和浪费数组空间。
- 输入数据若不是2的幂,HashMap通过一通位移运算和或运算得到的肯定是2的幂次数,并且是离那个数最近的数字
7. HashMap大小超过了负载因子(load factor)定义的容量,怎么办?
- 超过阙值会进行扩容操作,概括的讲就是扩容后的数组大小是原数组的2倍,将原来的元素重新hashing放入到新的散列表中去。