Java基础复习笔记 第14章:数据结构与集合源码

1、数据结构

  • 数据结构的研究对象
1. 数据结构概念:
数据结构,就是一种程序设计优化的方法论,研究数据的`逻辑结构`和`物理结构`以及它们之间相互关系,
并对这种结构定义相应的`运算`,目的是加快程序的执行速度、减少内存占用的空间。

2. 数据结构的研究对象
研究对象1:数据间逻辑关系
> 集合结构
> 线性结构:一对一关系
> 树形结构:一对多关系
> 图形结构:多对多关系


研究对象2:数据的存储结构
> 结构1:顺序结构
> 结构2:链式结构
> 结构3:索引结构
> 结构4:散列结构

开发中更关注存储结构:
> 线性表:数组、单向链表、双向链表、栈、队列等
> 树:二叉树、B+树
> 图:无序图、有序图
> 散列表:HashMap、HashSet


研究对象3:
- 分配资源,建立结构,释放资源
- 插入和删除
- 获取和遍历
- 修改和排序
  • 常见的数据存储结构
3. 常见存储结构之:数组


4. 常见存储结构之:链表
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;


5. 常见存储结构之:二叉树
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;


6. 常见存储结构之:栈 (先进后出、后进先出、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;
    }

}



7. 常见存储结构之:队列(先进先出、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
1. ArrayList的特点:
主要实现类;线程不安全的,效率高;底层使用Object[]存储
对于频繁的查找、尾部添加,性能较高,时间复杂度O(1)


2. 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
1. Vector的特点:
古老的实现类;线程安全的,效率低;底层使用Object[]存储


2. Vector源码解析:(以jdk1.8.0_271为例)

Vector在jdk1.8中初始化时就创建了长度为10的Object[]数组。当添加元素到满的时候,默认扩容为原来的2倍。





三、LinkedList
1. LinkedList的特点:
使用双向链表存储数据;
对于频繁的删除、插入操作,性能较高,时间复杂度为O(1)


2. 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;
    }
}


3. LinkedList是否存在扩容问题?没有!


四、启示与开发建议
> Vector基本不用,效率低,使用ArrayList替换
> 对于频繁的删除、插入操作,使用LinkedList替换ArrayList
> 除此之外,我们首推ArrayList。
    > new ArrayList() / new ArrayList(int capacity) (推荐,避免出现不必要的多次扩容)
  • HashMap的底层实现
一、HashMap
1. HashMap中元素的特点
> HashMap中的所有的key彼此之间不相同,且无序。多个key构成一个Set。--->key所在的类要重写equals()、hashCode()
> HashMap中的所有的value彼此之间可以相同,且无序。多个value构成一个Collection。--> value所在的类要重写equals()
> HashMap中的一个key-value构成一个Entry。
> HashMap中的所有的entry彼此之间不相同,且无序。多个entry构成一个Set。


2. 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; //最小树化容量64

transient Node<K,V>[] table; //数组
transient int size;  //记录有效映射关系的对数,也是Entry对象的个数
int threshold; //阈值,当size达到阈值时,考虑扩容
final float loadFactor; //加载因子,影响扩容的频率


二、LinkedHashMap
1. LinkedHashMap 与 HashMap 的关系:继承关系。在HashMap的Node的基础上,增加了一对双向链表,记录
添加的先后顺序。



2. 底层结构:
重写了如下的方法:
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的底层实现原理?
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是数据结构算法复习笔记: 一、数据结构 1. 数组:数组是一种线性数据结构,可以用于存储同一类型的元素。 2. 栈:栈是一种先进后出(Last In First Out)的数据结构,只能在栈顶进行插入和删除操作。 3. 队列:队列是一种先进先出(First In First Out)的数据结构,只能在队尾进行插入操作,在队头进行删除操作。 4. 链表:链表是一种动态数据结构,可以随时插入或删除元素。单向链表只能从头部开始遍历,而双向链表可以从头部或者尾部开始遍历。 5. 树:树是一种非线性数据结构,它由节点和边组成。每个节点有一个父节点和若干个子节点。 6. 堆:堆是一种特殊的树,它分为大根堆和小根堆。大根堆中父节点的值大于等于子节点的值,小根堆中父节点的值小于等于子节点的值。 7. 图:图是由节点和边组成的非线性数据结构,节点之间的连线称为边。图分为有向图和无向图。 二、算法 1. 排序:排序是将一组数据按照某个特定的顺序进行排列的过程。常见的排序算法包括冒泡排序、插入排序、选择排序、快速排序、归并排序等。 2. 查找:查找是在一组数据中找到特定元素的过程。常见的查找算法包括线性查找、二分查找、哈希查找等。 3. 字符串匹配:字符串匹配是在一个文本串中查找一个模式串的过程。常见的字符串匹配算法包括暴力匹配、KMP算法、Boyer-Moore算法、Rabin-Karp算法等。 4. 贪心算法:贪心算法是一种在每一步选择中都采取当前状态下最优解的策略。贪心算法常用于求解最小生成树、最短路径等问题。 5. 动态规划:动态规划是一种通过划分问题为子问题并解决子问题来求解原问题的方法。动态规划常用于求解最长公共子序列、背包问题等。 以上是数据结构算法复习笔记,希望对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值