zoukankan      html  css  js  c++  java
  • List-LinkedList、set集合基础增强底层源码分析

    List-LinkedList

    作者 : Stanley 罗昊

    转载请注明出处和署名,谢谢!

    继上一章继续讲解,上章内容

     List-ArreyLlist集合基础增强底层源码分析:https://www.cnblogs.com/StanleyBlogs/p/10396253.html

    List-LinkedList

    首先,LinkedList底层是一个链表结构,并且是双向链表;

     增删快 、查询慢 

    并分为 单向链表跟双向链表

    单向链表

    单向链表,每个元素都称之为一个节点,每个节点都由两部分组成分别是,数据 、指向下一个节点的指针;

    单向链表每一个节点在内存中存储上、空间位置上、都是无无序的;

    链表查询效率较低

    单向链表中的每个元素在空间的存储位置上没有规律,也没有顺序,那么在查找某个元素的时候,必须从头节点挨着往后找,直到找到为止;

    链表增 删 效率高

    因为链表每个元素在存储的空间是没有顺序的,,删除或者添加某个元素,只需要让指针重新指向即可,不需要将其他元素位移。所以随机增删效率较高

    双向链表

    双向链表的查找方式是交替查找,就是左表查找一个,右边查找一个,最终左边跟右边谁先返回,那么谁就先找到;

    双向链表就是双向开工,最后离谁近我返回谁,说白就是谁查的次数最少,我返回谁;

    LinkedList底层讲解

    那么,链表结构在底层保存的是什么结构呢?

    Node(节点),它底层保存的是Node,一个节点一个节点的;

    我们点进源码后我们开始进行分析:

        transient int size = 0;
    

      我们点进源码后,看到的第一个属性就是 size =0;

    这句话的意思就是,LinkedList初始值是0,也就是,你没有给它任何数据的时候,它的长度为0;

    再往下看:

       transient Node<E> first;//代表第一个节点 
    
        /**
         * Pointer to last node.
         * Invariant: (first == null && last == null) ||
         *            (last.next == null && last.item != null)
         */
    

      我们看到了Node,Node这个对象,就代表链表中的每一个元素;

    我们点进去看一下,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;
            }
        }
    

      我们点进去后,第一行有一个E,这个E是干什么的呢?

    这个E就代表,本节点的信息,比如说你这里插入的数据的类型都是String类型,那么这个节点的类型就是String,另外一个节点可能是int类型,double类型,那么节点类型也就不同,所以这个E是个泛型;

    再往下走,有一个next跟一个prev;

    next代表下一个,prev代表前一个;

    为什么会有这两个呢,是因为便于双向查找的时候能够找到对方;

        transient Node<E> last;//代表最后一个节点
    
        /**
         * Constructs an empty list.
         */

    我们看到在底层源码中,还有一个代表最后一个节点的方法,我们发现,两个方法分别声明,为什么不写在一起呢?

    因为,最后一个节点的信息,跟中间的信息保存的不一样!

    第一个节点只需要保存下一个节点,而最后一个节点只需要保存上一个节点;

    总结:

    我需要知道的是LinkedList是一个链表结构,链表结构的特点是查询慢 增删快;

    还有链表结构的每一个元素都是一个Node(节点),而Node的底层,就是一个双向链表;

    每个Node都会存储三个信息,prev item next;

    Set集合

    set集合特点:

    无序、不重复;

    这里的无序是指,没有添加顺序;

    它有两个实现类,分别是 HashSet、TreeSet

    首先我们先关注HashSet;

    HashSet

    创建一个HashSet集合:

    Set<泛型> 对象名 = new HashSet<String>();

    添加元素:

    对象名.add("");
    

    下面我们就做一些例子来更好的讲一下HashSet集合;

    1.创建一个HashSet赋值,并用增强for循环打印,添加相同元素观察状态;

    Set<String> set01 = new HashSet<String>();
    
    set.add("hh");
    
    set.add("aa");
    
    set.add("cc");
    
    set.add("hh");
    
    for(String str : set01){
    
    syso(str);
    
    }

    执行结果我们会发现,我们明明添加进去了两个,为什么却值打印出来一个hh?

    这就是HashSet的特性,值不能重复;

    那么它是如何做到的呢,它是如何保证元素不重复的?

    我们现在可以假设一下HashSet的底层是什么,我们假设它底层是一个数组,那么,数组是如何做到元素不重复的呢?

    是不是要从头开始遍历,直到遍历结束后发现这个元素没有出现过,那么就表明这个元素确实为唯一未重复的;

    但是,如果数组的长度非常长,这个时候,你这样的方法,还能行得通吗?当然不行了,因为性能太低了!

    它的底层确实是数组,但是,缺不是这样的遍历方式,而是hash算法;

    所有元素存储的时候,存的是hashcode的元素值,那个这个值是可以当成它索引;

    hash算法

    *任何一种hashcode的算法都无法达到绝对的完美*

    *必然会存在hashcode值的冲突*

    假如我现在要添加一个元素,加进来之后首先会计算这个值的hashcode值,如果这个hashcode值 = 50,那我就把你这个元素存到下标为50的数组的对应位置上;

    这个时候又假设又存进来一个“bb”,首先第一步还是需要先计算它的hashcode值,假设bb的hashcode值 = 25,那么,它就会被分配到下标为25的数组里;

    这个时候我又存进来了一个元素“cc”,当你插入cc元素的时候,首先还是需要先算一个你这个元素的hashcode值,假设这个cc还等与25,那么这个cc是不是也要去找相对应的下标为25的位置了,但是发现,这个25这个位置已经被bb占用了,这个时候就会触发底层的equals方法进行内容比较,如果内容相同,则不让你插入,如果不同,那么就会以列表的方式进行插入,就是挂在“bb”的下面;

    在java1.5的时候,以上这个结构被称之为,hashset桶表+链表;

    在java1.8点时候,以上转增结构被称之为,桶表+链表+二叉树;

    为什么要加二叉树?

    假设有许多hashcode的值 = 25,那么你是不是就需要一直的往下挂呀;

    大家都知道链表是有缺陷的,就是查找慢!那你查的时候,是不是就是从第一个开始遍历,去寻找啊,假设这个元素刚好在链表的最末端,那么你需要查多久啊;

    所以到1.8之后,为了避免这种情况出现,所以它对这个链表做了一个优化,链表深度超过8的时候,就会转化成二叉树

    hashset底层代码分析

    进去之后,首先第一句话:

        private transient HashMap<E,Object> map;
    
        // Dummy value to associate with an Object in the backing Map
    

      看到这个map,就说明set跟map是有一定关系的,你说白了set的底层实现,事实上就是map的底层实现;

    看一下set的底层构造方法:

        public HashSet() {
            map = new HashMap<>();
        }
    
        /**
         * Constructs a new set containing the elements in the specified
         * collection.  The <tt>HashMap</tt> is created with default load factor
         * (0.75) and an initial capacity sufficient to contain the elements in
         * the specified collection.
         *
         * @param c the collection whose elements are to be placed into this set
         * @throws NullPointerException if the specified collection is null
         */
    

      是HashMap;

    所以点进去后你会发现,最后还是到了HashMap的底层里面去了;

    首先看第一行:

       /**
         * The default initial capacity - MUST be a power of two.
         */
        static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    

      默认长度是 16;

    再往下走:

        /**
         * The maximum capacity, used if a higher value is implicitly specified
         * by either of the constructors with arguments.
         * MUST be a power of two <= 1<<30.
         */
        static final int MAXIMUM_CAPACITY = 1 << 30;
    

      这个是最大容量,int的最大值/2;10亿左右

    再往下:

        /**
         * The load factor used when none specified in constructor.
         */
        static final float DEFAULT_LOAD_FACTOR = 0.75f;

    这个是默认加载因子;

    也就是16元素,到第16*0.75(12);

    也就是到达12个元素即将到达第13个元素的时候,它就开始扩容,并以二倍的速度开始扩的;

    再往下看:

     /**
         * The bin count threshold for using a tree rather than list for a
         * bin.  Bins are converted to trees when adding an element to a
         * bin with at least this many nodes. The value must be greater
         * than 2 and should be at least 8 to mesh with assumptions in
         * tree removal about conversion back to plain bins upon
         * shrinkage.
         */
        static final int TREEIFY_THRESHOLD = 8;

    这个就是树结构控制;

    每个链表达到8之后,就开始自动转化为二叉树结构;

    什么是时候会触发链表啊?

    hash算法相同的时候会把值相同的放到同一个链表上;

  • 相关阅读:
    mysql索引
    springboot mybatis 后台框架平台 shiro 权限 集成代码生成器
    java 企业网站源码模版 有前后台 springmvc SSM 生成静态化
    java springMVC SSM 操作日志 4级别联动 文件管理 头像编辑 shiro redis
    activiti工作流的web流程设计器整合视频教程 SSM和独立部署
    .Net Core中的ObjectPool
    文件操作、流相关类梳理
    .Net Core中的配置文件源码解析
    .Net Core中依赖注入服务使用总结
    消息中间件RabbitMQ(一)
  • 原文地址:https://www.cnblogs.com/StanleyBlogs/p/10669608.html
Copyright © 2011-2022 走看看