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算法相同的时候会把值相同的放到同一个链表上;

List-LinkedList、set集合基础增强底层源码分析的更多相关文章

  1. List-ArrayList集合基础增强底层源码分析

    List集合基础增强底层源码分析 作者:Stanley 罗昊 [转载请注明出处和署名,谢谢!] 集合分为三个系列,分别为:List.set.map List系列 特点:元素有序可重复 有序指的是元素的 ...

  2. LInkedList总结及部分底层源码分析

    LInkedList总结及部分底层源码分析 1. LinkedList的实现与继承关系 继承:AbstractSequentialList 抽象类 实现:List 接口 实现:Deque 接口 实现: ...

  3. Java集合系列[4]----LinkedHashMap源码分析

    这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...

  4. java集合系列之ArrayList源码分析

    java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...

  5. Vector总结及部分底层源码分析

    Vector总结及部分底层源码分析 1. Vector继承的抽象类和实现的接口 Vector类实现的接口 List接口:里面定义了List集合的基本接口,Vector进行了实现 RandomAcces ...

  6. JAVA ArrayList集合底层源码分析

    目录 ArrayList集合 一.ArrayList的注意事项 二. ArrayList 的底层操作机制源码分析(重点,难点.) 1.JDK8.0 2.JDK11.0 ArrayList集合 一.Ar ...

  7. Spring基础系列-AOP源码分析

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...

  8. Java集合:HashSet的源码分析

    Java集合---HashSet的源码分析   一.  HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该 ...

  9. JUC之线程池基础与简单源码分析

    线程池 定义和方法 线程池的工作时控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等待其他线程执行完成,再从队列中取出任 ...

随机推荐

  1. DevOps之三 Git的安装与配置

    Centos7 安装Git 一.卸载Centos7 自带的git # git --version git version 1.8.3.1# whereis gitgit: /usr/bin/git / ...

  2. 卸载重装Mysql

    卸载重装前请备份数据库 卸载 sudo apt autoremove --purge mysql-server-core-5.7 清理残留 sudo rm -r /var/lib/mysql* sud ...

  3. Spring+Redis的部署与Redis缓存使用示例

    由于项目的业务需要,这两天折腾了一下Spring-redis配置,有了前面用Spring托管hibernate的经验,这次可以说是顺风顺水,大概说一下流程. ubuntu 安装 redis sudo ...

  4. 腾讯云Unubtu 16.04 (gunicorn+supervisor+ngnix+mongodb)部署Flask应用

    1.申请腾讯云服务 我申请了免费使用的云服务器 ,选择安装的Linux版本是ubuntu16.04.1 LTSx86_64.我个人PC安装使用的也是这个版本,比较熟悉些. 详细参考帮助文档. 2.登录 ...

  5. Coding theano under remote ubuntu server from local Mac (在本地mac机器上,写、跑、调试、看-远程ubuntu上的theano代码)

    本人是奇葩,最近鼓捣了一套在mac上coding远程ubuntu上的theano代码的东东,记之以期造福后人. Overview: 下图是我的编程环境和网络环境 我期望能在本地mac机器上对远程的ub ...

  6. ActiveMQ的消息持久化机制

    为了避免意外宕机以后丢失信息,需要做到重启后可以恢复消息队列,消息系统一般都会采用持久化机制. ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,无论使用哪种持久化方式 ...

  7. selenium中的xpath定位

    一.简介 XPath 是一门在 XML 文档中查找信息的语言.XPath 用于在 XML 文档中通过元素和属性进行导航. 二.定位 2.1 利用自己的本身属性定位 //标签[文本属性和值] 下面百度的 ...

  8. Go调用C代码,Cgo札记

    http://www.myexception.cn/program/679738.html Go调用C代码,Cgo笔记 参考: 官方文档: http://golang.org/cmd/cgo/ htt ...

  9. AJAX初步学习

    AJAX(Asynchronous JavaScript and XML)即异步的JavaScript与XML技术,指的是一套综合了多项技术的浏览器端网页开发技术.其实就是为了解决传统页面同步刷新,消 ...

  10. Oracle解锁scott账号

    在安装Oracle的最后一步,有一个口令管理的操作,当时忘了给scott账号解锁了(Oracle为程序测试提供的一个普通账户,口令管理中可以对数据库用户设置密码,默认是锁定的).现在想给scott这个 ...