1. 数据结构
1.1 数据结构概念:(了解)
- 数据结构,就是一种程序设计优化的方法论,研究数据的
逻辑结构
和物理结构
以及他们之间的关系 - 并对这种结构定义相应的
运算
,目的是加快程序的执行速度、减少内存占用的空间
1.2 数据结构的研究对象
1.2.1 数据之间的逻辑关系
- 集合结构
- 线性结构:一对一关系
- 树形结构:一对多关系
- 图形结构:多对多关系
1.2.2 数据存储结构(物理结构)
-
顺序结构
-
链式结构
-
索引结构
-
散列结构
-
开发中,我们更习惯下面的方式理解结构
- 线性表(一对一关系):一维数组、单向链表、双向链表、栈、队列
- 树(一对多关系):各种树。如:二叉树,B+树
- 图(多对多关系)
- 哈希表:如HashMap、HashSet
1.2.3 相关的算法操作
- 分配资源,建立结构,释放结构
- 插入和删除
- 获取和遍历
- 修改和排序
1.3 常见存储结构之:数组
略。
1.4 常见存储结构之:链表
链表中的基本单位是:节点(Node)
1.4.1 单向链表
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;
1.4.2 双向链表
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;
mode2.next = node3;
1.5 常见存储结构之:二叉树
class TreeNode{
TreeNode left;
Object data;
TreeNode right;
public TreeNode(Object data){
this.data = data
}
public TreeNode(TreeNode left,Object date ,TreeNode right){
this.left = left;
this.data = date;
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(Tree left,Object data,TreeNode right){
this.left = left;
this.data = data;
this.right = right;
}
public TreeNode(TreeNode parent,Tree 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 = rightNodel
1.6 常见存储结构之:栈
- stack,先进后出,后进先出
- first in last out/FILO —> LIFO
- 属于抽象数据类型(ADT) Abstract Data Type
- 可以使用数组或链表来构建
数组实现栈
class Stack{
Object[] values;
int size; //记录储存的元素的个数
public Stack(int length){
values = new Object[length];
}
//入栈
public void push(Object ele){
if(size>=length){
throw new RuntimeException("栈空间已满,入栈失败");
}
value[size] = ele;
size++;
}
//出栈
public Object pop(){
if(size <=0 ){
throw new RuntimeException("栈空间已空,出栈失败");
}
Object obj = value[size - 1];
values[size - 1] = null;
size--;
return obj;
}
}
1.7 常见存储结构之:队列
-
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>=length){
throw new RuntimeException("队列已满,无法添加");
}
values[size] = ele;
size ++;
}
//出列
public Object get(){
if(size <= 0){
throw new RuntimeException("队列已空,无法获取");
}
Object obj = values[0];
//数据前移
for(i = 0;i<size-1;i++){
values[i] = values[i+1];
}
//最后一个元素置空
values[size - 1] = null;
size--;
return obj;
}
}
2. List接口下的实现类的源码剖析
2.1 ArrayList
2.1.1 ArrayList的特点:
- 实现了List接口,存储有序的、可重复的数据
- 底层使用了Object[] 数组存储
- 线程不安全的
2.1.2 ArrayList源码解析:
jdk7版本:(以jdk1.7.0_07为例)
如下代码的执行,底层会初始化数组,数组的长度为10。
Object[] elementData = new Object[10]
ArrayList<String> list = new ArrayList<>();
list.add("AA"); // elementData[0] = "AA";
list.add("BB"); // elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementDate数组已满,则需要扩容。默认为原来长度的1.5倍。
并将原有数组的元素赋值到新的数组中
jdk8版本:(以jdk1.8.0_271为例)
如下代码的执行,底层会初始化数组。
Object[] elementData = new Object[]{};
ArrayList<String> list = new ArrayList<>();
list.add("AA"); // elementData[0] = "AA"; 首次添加元素数,会初始化数组elementData = new Object[10];
list.add("BB"); // elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementDate数组已满,则需要扩容。默认为原来长度的1.5倍。
并将原有数组的元素赋值到新的数组中
小结:
jdk1.7.0_07版本中:ArrayList类似于饿汉式
jdk1.8.0_271版本中:ArrayList类似于懒汉式
2.2 Vector
2.2.1 Vector的特点:
- 实现了List接口,存储有序的、可重复的数据
- 底层使用了Object[] 数组存储
- 线程安全的
2.2.2 Vector源码解析:(以jdk1.8.0_271为例) 了解
Vector v = new Vector(); //底层初始化数组,长度为10 Object[] elementData = new Object[10]
v.add("AA"); //elementData[0] = "AA";
v.add("BB"); //elementData[1] = "BB";
...
当要添加第11个元素的时候,底层的elementDate数组已满,则需要扩容。默认为原来长度的2倍。
2.3 LinkedList
2.3.1 LinkedList的特点:
- 实现了List接口,存储有序的、可重复的数据
- 底层使用了双向链表存储
- 线程安全的
2.3.2 LinkedList在jdk8中的源码解析:
LinkedList<String> list = new LinkedList(); //底层没做啥
list.add("AA"); //将“AA”封装到一个Node对象1中,list对象的属性first,last都指向此Node对象1
list.add("BB"); //将“BB”封装到一个Node对象2中,对象1和对象2构成一个双向链表,同时last指向对象2
...
因为LinkedList使用的是双向链表,不需要考虑扩容问题
内部Node的声明
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
2.4 LinkedList是否存在扩容问题?
- 不存在
2.5 启示与开发建议
-
Vector基本不使用了
-
ArrayList底层使用数组结构,查找和添加(尾部添加)操作效率高,时间复杂度为O(1)
删除和插入操作效率低,时间复杂度为O(n) -
LinkedList底层使用双向链表,删除和插入操作效率高,时间复杂度为O(1)
查找和添加操作效率低,时间复杂度O(n),添加有可能是O(1)的 -
如果选择了ArrayList的前提下,new ArrayList();底层创建长度为10的数组
new ArrayList(int capacity):底层创建指定capacity的数组
- 如果开发中,大体确定数组的长度,推荐使用ArrayList(int capacity)这个构造器,避免底层多次扩充、复制数组
3. Map接口下的实现类的源码剖析 #java重点 #java掌握 #java熟练使用
3.1 HashMap
3.1.1 HashMap中元素的特点
- 所有的key彼此之间是不可重复的、无序的。所以key就构成了一个Set集合。—> key所在的类要求重写hashCode()和equals()方法
- 所有的value彼此之间是可重复、无序的。所以value就构成了一个Collection集合。 —> value所在的类要重写equals()方法
- 一个key-value就构成了一个entry(node)
- 所有的entry彼此之间是不可重复的、无序的。所以entry就构成了一个Set集合。
3.1.2 HashMap源码解析(极高极高的!!!)
jdk7中创建对象和添加数据过程(以JDK1.7.0_07为例说明):
//底层是数组+对象链表的结构
//创建对象的过程中,底层会初始化数组Entry[] table = new Entry[16]; 2的四次幂
HashMap<String,Integer> map = new HashMap<>();
...
//"AA"和78封装到一个Entry对象中,将此对象添加到table数组中
map.put("AA",78)''
...
添加/修改的过程: #java重点
-
将(key1,value1)添加到当前的map中
-
首先,需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2。
-
哈希值2再金果果某种算法(indexFor())之后,就确定了(key1,value1)其在数组table中的索引位置i
- 如果此索引位置i的数组上没有元素,则(key1,value1)添加成功 —>情况1
- 如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1和key2的哈希值2 —>哈希冲突
- 如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功。 —>情况2
- 如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals(),要调用key1所在类的equals(),将key2作为参数传递进去
- 调用equals(),返回false:则(key1,value1)添加成功 —>情况3
- 调用equals(),返回tru,则key1和key2是相同的。默认情况下value1替换原有value2
-
说明:
-
情况1,将(key1,value1)存放到数组的索引i的位置
-
情况2,情况3:(key1,value1)元素与现有的(key2,value2)构成单向链表结构,(key1,value1)指向(key2,value2)
-
随着不断的添加元素,在满足如下条件的情况下,则会进行扩容
(size >= threshold) && (null != table\[bucketIndex]) //bucketIndex=i,即超过12且出现链表了!!!
- 当元素的个数达到临界值(->数组的长度 * 加载因子(16*0.75=12))时,就考虑扩容。默认临界值为12
- 默认扩容为原来的2倍。
jdk8与jdk7的不同之处(以jdk1.8.0_271为例):
- 创建HashMap的实例后,底层并没有初始化table数组,当首次添加(key,value)时,进行判断,如果发现table尚未初始化,则对数组进行初始化
- HashMap,底层定义了Node内部类,替换了jdk7中的Entry内部类。意味着,我们创建的是Node[]
- 如果当前的(key,value)经过一系列的判断后,可以添加到当前的数组角标i中,若角标i中有元素,jdk7中是新的指向旧的(头插法),jdk8中是旧的指向新的(尾插法)”七上八下“
- 数组+单向链表 —> 数组+单向链表+红黑树。
- 如果数组索引i位置上的元素的个数达到8,并且数组的长度达到64时,我们就将此索引i位置上的多个元素改为使用红黑树的结构进行储存
- 进行put、get、remove操作的时间复杂度为O(logn),比单向链表的O(n)。性能更好
- 什么时候使用红黑树变为单向链表:当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树退化为单向链表
3.1.2 属性/字段
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; //加载因子,影响扩容的频率
3.2 LinkedHashMap
3.2.1 LinkedHashMap 与 HashMap 的关系:
- LinkedHashMap 是 HashMap的子类
- LinkedHashMap 在 HashMap使用的数组+单向链表+红黑树的基础上,又增加了一对双向链表,记录添加的(key,value)的先后顺序。便于我们遍历所有的node;
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;
}
3.2.2 底层结构:
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);
}
}
3.3 HashSet和LinkedHashSet的源码分析
- HashSet底层使用的是HashMap
- LinkedHashSet底层使用的是LinkedHashMap