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

1、数据结构

  • 数据结构的研究对象

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

    2. 数据结构的研究对象
      研究对象1:数据间逻辑关系

    集合结构
    线性结构:一对一关系
    树形结构:一对多关系
    图形结构:多对多关系

    研究对象2:数据的存储结构

    结构1:顺序结构
    结构2:链式结构
    结构3:索引结构
    结构4:散列结构

    开发中更关注存储结构:

    线性表:数组、单向链表、双向链表、栈、队列等
    树:二叉树、B+树
    图:无序图、有序图
    散列表:HashMap、HashSet

    研究对象3:

    • 分配资源,建立结构,释放资源
    • 插入和删除
    • 获取和遍历
    • 修改和排序
  • 常见的数据存储结构

    1. 常见存储结构之:数组

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

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

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

    }

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

    }

    1. 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。

    1. 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的底层实现原理?

相关推荐
王老师青少年编程1 分钟前
gesp(二级)(12)洛谷:B3955:[GESP202403 二级] 小杨的日字矩阵
c++·算法·矩阵·gesp·csp·信奥赛
什么想法都无14 分钟前
stream
java·java stream
m0_7482336414 分钟前
WebService简介
java
Murphy202314 分钟前
.net4.0 调用API(form-data)上传文件及传参
开发语言·c#·api·httpwebrequest·form-data·uploadfile·multipart/form-
love静思冥想15 分钟前
Stream `Collectors.toList()` 和 `Stream.toList()` 的区别(Java)
java·stream
我曾经是个程序员25 分钟前
C#Directory类文件夹基本操作大全
服务器·开发语言·c#
白云~️27 分钟前
uniappX 移动端单行/多行文字隐藏显示省略号
开发语言·前端·javascript
编码浪子32 分钟前
构建一个rust生产应用读书笔记7-确认邮件2
开发语言·后端·rust
Ch.yang34 分钟前
【Spring】 Bean 注入 HttpServletRequest 能保证线程安全的原理
java·spring·代理模式
web1508509664135 分钟前
基于Mysql、JavaScript、PHP、ajax开发的MBTI性格测试网站(前端+后端)
java