集合

前言

时间复杂度
时间复杂度是用来来评估代码的执行耗时的,大O表示法:不具体表示代码的真正执行时间,而是表示代码执行时间随数据规模增长的变化趋势。
当n很大时,低阶、常量、系数并不能影响其增长趋势,因此可以忽略

List

ArrayList

list底层是数组实现,数组是一种连续内存空间存储相同数据类型数据的线性结构。

在根据数组索引获取元素的时候,会用索引和寻址公式来计算内存所对应的元素数据,寻址公式是**:数组的首地址+索引乘以存储数据的类型大小**
a[i] = baseAddress + i * dataTypeSize

1、随机查询(根据索引查询)
数组元素的访问是通过索引来访问的,计算机通过数组的首地址和寻址公式能够很快速的找到想要访问的元素

随机(通过下标)查询的时间复杂度是O(1)

2、未知索引查询
查找元素(未知下标)的时间复杂度是O(n)
查找元素(未知下标但排序)通过二分查找的时间复杂度是O(logn)

插入和删除的时间复杂度
插入和删除时,为了保证数组的内存连续性,需要移动数组元素,最好的情况为O(1),最坏的情况为O(n),平均时间复杂度为O(n)

ArrayList的实现原理
ArrayList底层是基于动态数组实现的
1、ArrayList的初始容量为0,当第一次添加数据时才会初始化容量为10
2、ArrayList在进行扩容时会扩容到原来容量的1.5倍,每次扩容时需要拷贝数组
3、ArrayList在添加数据时

  • 确保数组已使用长度(size)加1之后足够存下下一个数据
  • 计算数组的容量,如果当前数组已使用长度+1后的大于当前的数组长度,则调用grow方法扩容(原来的1.5倍)
  • 确保新增的数据有地方存储之后,则将新元素添加到位于size的位置上。
  • 返回添加成功布尔值。

ArrayList源码分析

面试题
**List list = new ArrayList(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);}}

答案:,没有扩容
如何实现数组和List之间的转换
数组转List,调用Array.asList(arr)方法
List转数组,调用list.toArray(new DataType(list.size())) 返回该对象数组
调用list.toArray() 返回Object数组

用Arrays.asList转List后,如果修改了数组内容,list受影响吗
asList方法

@SafeVarargs@SuppressWarnings("varargs")public static <T> List<T> asList(T... a) {return new ArrayList<>(a);}/** * @serial include */private static class ArrayList<E> extends AbstractList<E>implements RandomAccess, java.io.Serializable{private static final long serialVersionUID = -2764017481108945198L;private final E[] a;ArrayList(E[] array) {a = Objects.requireNonNull(array);}.... }

答案:受影响
分析:Arrays.asList转换list之后,如果修改了数组的内容,list会受影响,因为它的底层使用的Arrays类中的一个内部类ArrayList来构造的集合,在这个集合的构造器中,把我们传入的这个集合进行了包装而已,最终指向的都是同一个内存地址

List用toArray转数组后,如果修改了List内容,数组受影响吗
toArray方法

 public <T> T[] toArray(T[] a) {if (a.length < size)// Make a new array of a's runtime type, but my contents:return (T[]) Arrays.copyOf(elementData, size, a.getClass());System.arraycopy(elementData, 0, a, 0, size);if (a.length > size)a[size] = null;return a;}

答案:不受影响
分析:list用了toArray转数组后,如果修改了list内容,数组不会影响,当调用了toArray以后,在底层是它是进行了数组的拷贝,跟原来的元素就没啥关系了,所以即使list修改了以后,数组也不受影响

LinkedList

单向链表
单向链表∶每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。记录下个结点地址的指针叫作后继指针next

  • 链表的每个元素称之为结点
  • 物理存储单元是不连续的

双向链表

  • 每个结点不止有一个后继指针next 指向后面的结点
  • 有一个前驱指针prev指向前面的结点

高频面试题:ArrayList和LinkedList的区别
总结如下:

  • ArrayList底层使用了动态数组实现,实质上是一个动态数组
  • LinkedList底层使用了双向链表实现,可当作堆栈、队列、双端队列使用
  • ArrayList在随机查询方面效率高于LinkedList
  • LinkedList在节点的增删方面效率高于ArrayList
  • ArrayList必须预留一定的空间,当空间不足的时候,会进行扩容操作
  • LinkedList的开销是必须存储节点的信息以及节点的指针信息

Map

前言

二叉树

二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点。
二叉树每个节点的左子树和右子树也分别满足二叉树的定义。

常见的二叉树:

  • 满二叉树
  • 完全二叉树
  • 二叉搜索树
  • 红黑树

二叉搜索树

二叉搜索树(Binary Search Tree,BST)又名二叉查找树,有序二叉树或者排序二叉树,是二叉树中比较常用的一种类型二叉查找树要求,在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值

红黑树
红黑树(Red Black Tree):也是一种自平衡的二叉搜索树(BST),之前叫做平衡二叉B树(Symmetric Binary B-Tree)

红黑树性质:

  • 性质1:节点要么是红色,要么是黑色
  • 性质2:根节点是黑色
  • 性质3:叶子节点都是黑色的空节点
  • 性质4:红黑树中红色节点的子节点都是黑色
  • 性质5:从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

红黑树原理

散列表

散列(Hash)表

HashMap实现原理

HashMap底层使用hash表数据结构,即数组、链表(jdk1.7及之前)或红黑树(jdk1.8)

1、当往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
2、存储时,若出现hash值相同的key,此时有两种情况:

  • 若key相同,覆盖旧值

  • 若key不同,则将当前的k-v值放入链表或红黑树中
    3、当调用get方法时,直接找到hash值对应的下标,再判断key是否相同,从而找到对应的value。

    面试题:HashMap在jdk1.7和jdk1.8中有什么区别

    在JDK1.6,JDK1.7中,HashMap采用位桶(数组)+链表实现,即使用链表处理冲突,同一hash值的键值对会被放在同一个位桶里,当桶中元素较多时,通过key值查找的效率较低。
    而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(默认为8)且数组长度超过64时,将链表转换为红黑树,这样大大减少了查找时间。当扩容resize()方法时,红黑树拆分成的树节点数小于或等于临界值6时,红黑树将退化成链表。

HashMap的put方法执行流程

HashMap的put方法执行步骤
1、判断键值对数组table是否为空或为null,否则执行resize()进行扩容(初始化)
2、根据键值key计算hash值得到数组索引
3、判断table[i]==null,条件成立,直接新建节点添加
4、如果table[i]==null ,不成立

  • 4.1、判断table[i]的首个元素是否和key一样,如果相同直接覆盖value
  • 4.2、判断table[i]是否为treeNode,即table[i]是否是红黑树,如果是红黑树,则直接在树中插入键值对
  • 4.3、遍历tablei],链表的尾部插入数据,然后判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,遍历过程中若发现key已经存在直接覆盖value

5、插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold(数组长度*0.75),如果超过,进行扩容。

更多解析
HashMap底层实现原理概述
HashMap实现原理
HashMap实现原理分析