1. 数据结构
-
数据结构的研究对象:
-
① 数据间的逻辑关系(集合关系、一对一、一对多、多对多)
-
② 数据的存储结构(或物理结构)
-
角度一:顺序结构、链式结构、索引结构、哈希结构
-
角度二:线性表:一对一关系(一维数组、链表、栈、队列)、树(二叉树、B+树)、图(多对多)、哈希表(HashMap、HashSet)
-
-
③ 相关运算
-
分配资源,建立结构,释放资源;插入和删除;获取和遍历;修改和排序
-
-
-
树(了解)
-
相关数据结构的核心Node的设计(单向链表、双向链表、二叉树、栈、队列)(理解)
-
常见存储结构之:链表 链表中的基本单位是:节点(Node) 单向链表 class Node{ Object data; Node next; public Node(Object data){ this.data = data; } } 创建对象: Node node1 = new Node("AA"); Node node2 = new Node("BB"); node1.next = node2; 双向链表 class Node{ Node prev; Object data; Node next; public Node(Object data){ this.data = data; } public Node(Node prev,Object data,Node next){ this.prev = prev; this.data = data; this.next = next; } } 创建对象: Node node1 = new Node(null,"AA",null); Node node2 = new Node(node1,"BB",null); Node node3 = new Node(node2,"CC",null); node1.next = node2; node2.next = node3; 常见存储结构之:二叉树 class TreeNode{ TreeNode left; Object data; TreeNode right; public TreeNode(Object data){ this.data = data; } public TreeNode(TreeNode left,Object data,TreeNode right){ this.left = left; this.data = data; this.right = right; } } 创建对象: TreeNode node1 = new TreeNode(null,"AA",null); TreeNode leftNode = new TreeNode(null,"BB",null); TreeNode rightNode = new TreeNode(null,"CC",null); node1.left = leftNode; node1.right = rightNode; 或 class TreeNode{ TreeNode parent; TreeNode left; Object data; TreeNode right; public TreeNode(Object data){ this.data = data; } public TreeNode(TreeNode left,Object data,TreeNode right){ this.left = left; this.data = data; this.right = right; } public TreeNode(TreeNode parent,TreeNode left,Object data,TreeNode right){ this.parent = parent; this.left = left; this.data = data; this.right = right; } } 创建对象: TreeNode node1 = new TreeNode(null,null,"AA",null); TreeNode leftNode = new TreeNode(node1,null,"BB",null); TreeNode rightNode = new TreeNode(node1,null,"CC",null); node1.left = leftNode; node1.right = rightNode; 常见存储结构之:栈(stack、先进后出、first in last out、FILO、LIFO) > 属于抽象数据类型(ADT) > 可以使用数组或链表来构建 数组实现栈 class Stack{ Object[] values; int size;//记录存储的元素的个数 public Stack(int length){ values = new Object[length]; } //入栈 public void push(Object ele){ if(size >= values.length){ throw new RuntimeException("栈空间已满,入栈失败"); } values[size] = ele; size++; } //出栈 public Object pop(){ if(size <= 0){ throw new RuntimeException("栈空间已空,出栈失败"); } Object obj = values[size - 1]; values[size - 1] = null; size--; return obj; } } 常见存储结构之:队列(queue、先进先出、first in first out、FIFO) > 属于抽象数据类型(ADT) > 可以使用数组或链表来构建 数组实现队列 class Queue{ Object[] values; int size;//记录存储的元素的个数 public Queue(int length){ values = new Object[length]; } public void add(Object ele){ //添加 if(size >= values.length){ throw new RuntimeException("队列已满,添加失败"); } values[size] = ele; size++; } public Object get(){ //获取 if(size <= 0){ throw new RuntimeException("队列已空,获取失败"); } Object obj = values[0]; //数据前移 for(int i = 0;i < size - 1;i++){ values[i] = values[i + 1]; } //最后一个元素置空 vlaues[size - 1] = null; size--; return obj; } }
2. List接口下的实现类的源码剖析
-
层次1:
-
|-----子接口:List:存储有序的、可重复的数据 ("动态"数组)
|---- ArrayList:List的主要实现类;线程不安全的、效率高;底层使用Object[]数组存储
在添加数据、查找数据时,效率较高;在插入、删除数据时,效率较低
|---- LinkedList:底层使用双向链表的方式进行存储;在对集合中的数据进行频繁的删除、插入操作时,建议使用此 类在插入、删除数据时,效率较高;在添加数据、查找数据时,效率较低;
|---- Vector:List的古老实现类;线程安全的、效率低;底层使用Object[]数组存储 -
层次2:
-
//如下代码的执行:底层会初始化数组,即:Object[] elementData = new Object[]{}; ArrayList<String> list = new ArrayList<>(); list.add("AA"); //首次添加元素时,会初始化数组elementData = new Object[10];elementData[0] = "AA"; list.add("BB");//elementData[1] = "BB"; ... 当要添加第11个元素的时候,底层的elementData数组已满,则需要扩容。默认扩容为原来长度的1.5倍。并将原有数组 中的元素复制到新的数组中。
3.Map接口下的实现类的源码剖析
-
(掌握)HashMap的底层源码的剖析
-
HashMap中元素的特点
> HashMap中的所有的key彼此之间是不可重复的、无序的。所有的key就构成一个Set集合。--->key所在的类要重写hashCode()和equals()
> HashMap中的所有的value彼此之间是可重复的、无序的。所有的value就构成一个Collection集合。--->value所在的类要重写equals()
> HashMap中的一个key-value,就构成了一个entry。
> HashMap中的所有的entry彼此之间是不可重复的、无序的。所有的entry就构成了一个Set集合。 -
在jdk8中,当我们创建了HashMap实例以后,底层并没有初始化table数组。当首次添加(key,value)时,进行判断,如果发现table尚未初始化,则对数组进行初始化。
在jdk8中,HashMap底层定义了Node内部类,替换jdk7中的Entry内部类。意味着,我们创建的数组是Node[]
在jdk8中,如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中。如果此时角标i位置上有元素。在jdk7中是将新的(key,value)指向已有的旧的元素(头插法),而在jdk8中是旧的元素指向新的(key,value)元素(尾插法)。 "七上八下"
jdk7:数组+单向链表
jk8:数组+单向链表 + 红黑树
什么时候会使用单向链表变为红黑树:如果数组索引i位置上的元素的个数达到8,并且数组的长度达到64时,我们就将此索引i位置上的多个元素改为使用红黑树的结构进行存储。
什么时候会使用红黑树变为单向链表:当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树结构退化为单向链表。
属性/字段: -
(熟悉)LinkedHashMap的底层源码的剖析
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的初始容量 16 static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 1 << 30 static final float DEFAULT_LOAD_FACTOR = 0.75f; //默认加载因子 static final int TREEIFY_THRESHOLD = 8; //默认树化阈值8,当链表的长度达到这个值后,要考虑树化 static final int UNTREEIFY_THRESHOLD = 6;//默认反树化阈值6,当树中结点的个数达到此阈值后,要考虑变为链表 //当单个的链表的结点个数达到8,并且table的长度达到64,才会树化。 //当单个的链表的结点个数达到8,但是table的长度未达到64,会先扩容 static final int MIN_TREEIFY_CAPACITY = 64; //最小树化容量64 transient Node<K,V>[] table; //数组 transient int size; //记录有效映射关系的对数,也是Entry对象的个数 int threshold; //阈值,当size达到阈值时,考虑扩容 final float loadFactor; //加载因子,影响扩容的频率
-
LinkedHashMap在HashMap使用的数组+单向链表+红黑树的基础上,又增加了一对双向链表,记录添加的(key,value)的
先后顺序。便于我们遍历所有的key-value。
LinkedHashMap重写了HashMap的如下方法:Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; } 底层结构:LinkedHashMap内部定义了一个Entry static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; //增加的一对双向链表 Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
-
(了解)HashSet、LinkedHashSet的底层源码的剖析