1、数据结构
-
数据结构的研究对象
-
数据结构概念:
数据结构,就是一种程序设计优化的方法论,研究数据的逻辑结构
和物理结构
以及它们之间相互关系,
并对这种结构定义相应的运算
,目的是加快程序的执行速度、减少内存占用的空间。 -
数据结构的研究对象
研究对象1:数据间逻辑关系
集合结构
线性结构:一对一关系
树形结构:一对多关系
图形结构:多对多关系研究对象2:数据的存储结构
结构1:顺序结构
结构2:链式结构
结构3:索引结构
结构4:散列结构开发中更关注存储结构:
线性表:数组、单向链表、双向链表、栈、队列等
树:二叉树、B+树
图:无序图、有序图
散列表:HashMap、HashSet研究对象3:
- 分配资源,建立结构,释放资源
- 插入和删除
- 获取和遍历
- 修改和排序
-
-
常见的数据存储结构
-
常见存储结构之:数组
-
常见存储结构之:链表
4.1 单向链表
class Node{
Object data;
Node next;public Node(){}
public Node(Object data){
this.data = data;
}public Node(Object data,Node next){
this.data = data;
this.next = next;
}
}
举例:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
node1.next = node2; //尾插法node2.next = node1;//头插法
或
Node node2 = new Node("BB",node1);4.2 双向链表
class Node{
Object data;
Node prev;
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("AA");
Node node2 = new Node(node1,"BB",null);
Node node3 = new Node(node2,"CC",null);node1.next = node2;
node2.next = node3;-
常见存储结构之:二叉树
class Node{
Object data;
Node left;
Node right;public Node(Object data){
this.data = data;
}public Node(Node left,Object data,Node right){
this.left = left;
this.data = data;
this.right = right;
}
}
举例:
Node node1 = new Node("AA");
Node node2 = new Node("BB");
Node node3 = new Node("CC");
node1.left = node2;
node1.right = node3;或者:
class Node{
Object data;
Node left;
Node right;
Node parent;public Node(Object data){ this.data = data; } public Node(Node left,Object data,Node right){ this.left = left; this.data = data; this.right = right; } public Node(Node parent,Node left,Object data,Node right){ this.parent = parent; this.left = left; this.data = data; this.right = right; }
}
举例:
Node node1 = new Node("AA");
Node node2 = new Node(node1,null,"BB",null);
Node node3 = new Node(node1,null,"CC",null);
node1.left = node2;
node1.right = node3;- 常见存储结构之:栈 (先进后出、后进先出、FILO、LIFO)
(ADT:abstract data type,栈可以使用数组、链表生成)
class Stack{
Object[] values; int size;//记录添加的元素个数 public Stack(int capacity){ values = new Object[capacity]; } //入栈 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 returnValue = values[size - 1]; values[size - 1] = null; size--; return returnValue; }
}
- 常见存储结构之:队列(先进先出、FIFO)
(ADT:abstract data type,栈可以使用数组、链表生成)
class Queue{
Object[] values; int size;//记录添加的元素个数 public Queue(int capacity){ values = new Object[capacity]; } //添加元素 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 returnValue = values[0]; for(int i = 0;i < size - 1;i++){ values[i] = values[i + 1]; } values[size - 1] = null; size--; return returnValue; }
}
-
2、集合源码#
-
List的实现
一、ArrayList
-
ArrayList的特点:
主要实现类;线程不安全的,效率高;底层使用Object[]存储
对于频繁的查找、尾部添加,性能较高,时间复杂度O(1) -
ArrayList源码解析:
2.1 jdk7版本:(以jdk1.7.0_07为例)
ArrayList list = new ArrayList(); //底层创建了一个长度为10的Object[]:Object[] elementData = new Object[10];
list.add("AA");//elementData[0] = "AA";
...
当添加第11个元素时:由于容量不够,需要扩容,默认扩容为原来的1.5倍。
2.2 jdk8版本:(以jdk1.8.0_271为例)
ArrayList list = new ArrayList(); //底层并没有创建长度为10的Object[]数组,而是Object[] elementData = {};
list.add("AA");//当首次添加元素时,底层创建长度为10的数组,赋给elementData,同时elementData[0] = "AA";
...
当添加第11个元素时:由于容量不够,需要扩容,默认扩容为原来的1.5倍。类比:1.7类似于饿汉式;1.8类似于懒汉式。
二、Vector
-
Vector的特点:
古老的实现类;线程安全的,效率低;底层使用Object[]存储 -
Vector源码解析:(以jdk1.8.0_271为例)
Vector在jdk1.8中初始化时就创建了长度为10的Object[]数组。当添加元素到满的时候,默认扩容为原来的2倍。
三、LinkedList
-
LinkedList的特点:
使用双向链表存储数据;
对于频繁的删除、插入操作,性能较高,时间复杂度为O(1) -
LinkedList在jdk8中的源码解析:
LinkedList list = new LinkedList(); //底层没有做什么操作
list.add("AA");//内部创建一个Node对象a,LinkedList内部的属性first、last都指向对象a
list.add("BB");//内部创建一个Node对象b,对象a的next指向对象b,对象b的prev指向对象a,last指向对象b。
内部声明的Node如下:
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }
}
- LinkedList是否存在扩容问题?没有!
四、启示与开发建议
Vector基本不用,效率低,使用ArrayList替换
对于频繁的删除、插入操作,使用LinkedList替换ArrayList
除此之外,我们首推ArrayList。
new ArrayList() / new ArrayList(int capacity) (推荐,避免出现不必要的多次扩容) -
-
HashMap的底层实现
一、HashMap
- HashMap中元素的特点
HashMap中的所有的key彼此之间不相同,且无序。多个key构成一个Set。--->key所在的类要重写equals()、hashCode()
HashMap中的所有的value彼此之间可以相同,且无序。多个value构成一个Collection。--> value所在的类要重写equals()
HashMap中的一个key-value构成一个Entry。
HashMap中的所有的entry彼此之间不相同,且无序。多个entry构成一个Set。- HashMap源码解析
2.1 jdk7中创建对象和添加数据过程(以JDK1.7.0_07为例说明):数组+单向链表
HashMap map = new HashMap();//底层创建了长度为16的数组:Entry[] table = new Entry[16];
map.put("AA",67);//添加过程如下。
将(key1,value1)添加到map中:
1)通过key1所在的hashCode(),计算得到key1的哈希值1,此哈希值1经过某种算法(hash())以后得到哈希值2。
此哈希值2经过某种算法(indexFor())以后,得到key1-value1在数组table中的存储位置i。
2)判断table[i] 是否为空。
如果为空,key1-value1添加成功。 --->添加成功1
如果不为空,假设已有元素(key0,value0),则需要继续比较。 ----> 哈希冲突
3) 比较key1的哈希值2与key0的哈希值2是否相等。
如果两个哈希值2不相等。则认为key1-value1与key0-value0不相同。key1-value1添加成功。 --->添加成功2
如果两个哈希值2相同,则需要继续比较。
4) 调用key1所在类的equals(),将key0放入equals()的形参中。看返回值。
如果返回值为false,则key1和key0不同,则key1-value1添加成功。 --->添加成功3
如果返回值为true,则认为key1和key0相同,则value1替换value0。理解为修改成功。说明:
添加成功1:将key1-value1封装在entry的对象中,将此对象放入数组的位置
添加成功2、3:key1-value1封装在entry的对象1中,与key0-value0封装的entry对象0构成单向链表的结构。
jdk7中是entry对象1指向entry对象0,entry对象1放在数组里。...
不断的添加,添加到什么情况时会扩容呢?一旦达到临界值(且索引i的位置上恰好还有元素),就考虑扩容。默认扩容为原来的2倍。
(源码为:if ((size >= threshold) && (null != table[bucketIndex])) 条件下扩容)。2.2 jdk8与jdk7的不同之处(以jdk1.8.0_271为例):
1)HashMap map = new HashMap(); 底层并没有创建长度为16的数组。
2)调用put(k,v)添加元素。如果是首次添加,则底层默认创建长度为16的table[]
3)jdk8中HashMap内部使用Node[]替换Entry[]。
4)如果要添加的key1-value1在经过一系列判断后,确定能添加到索引i的位置。此时,采用尾插法。
即原有的此索引i位置上的链表的最后一个元素指向新要添加的key1-value1。 "七上八下"
5)如果索引i位置上的元素达到8了,且数组的长度达到64的情况下,索引i位置上的多个元素要改为使用红黑树存储。
目的:为了提升查找的效率。(链表情况下查找的复杂度:O(n),红黑树的查找的复杂度:O(logN))当索引i位置上的元素个数少于6个时,会将此索引i位置上的红黑树改为单向链表。
2.3 属性字段:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认的初始容量 16
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; //最小树化容量64transient Node<K,V>[] table; //数组
transient int size; //记录有效映射关系的对数,也是Entry对象的个数
int threshold; //阈值,当size达到阈值时,考虑扩容
final float loadFactor; //加载因子,影响扩容的频率二、LinkedHashMap
-
LinkedHashMap 与 HashMap 的关系:继承关系。在HashMap的Node的基础上,增加了一对双向链表,记录
添加的先后顺序。 -
底层结构:
重写了如下的方法:
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;
}
其中:
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的底层实现原理?