红黑树

集合框架系列 Map(十):HashMap

柔情痞子 提交于 2020-03-18 18:17:13
目录 1 概述 2 原理 3 源码分析  3.1 构造方法   3.1.1 构造方法分析   3.1.2 初始容量、负载因子、阈值  3.2 查找  3.3 遍历  3.4 插入   3.4.1 插入逻辑分析   3.4.2 扩容机制   3.4.3 链表树化、红黑树链化与拆分  3.5 删除  3.6 其他细节  3.7 总结 1. 概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap 。HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现。HashMap 允许 null 键和 null 值,在计算哈键的哈希值时,null 键哈希值为 0。HashMap 并不保证键值对的顺序,这意味着在进行某些操作后,键值对的顺序可能会发生变化。另外,需要注意的是,HashMap 是非线程安全类,在多线程环境下可能会存在问题。 在本篇文章中,我将会对 HashMap 中常用方法、重要属性及相关方法进行分析。需要说明的是,HashMap 源码中可分析的点很多,本文很难一一覆盖,请见谅。 2. 原理 上一节说到 HashMap 底层是基于散列算法实现,散列算法分为散列再探测和拉链式。HashMap 则使用了拉链式的散列算法,并在 JDK 1.8 中引入了红黑树优化过长的链表。数据结构示意图如下: 对于拉链式的散列算法,其数据结构是由数组和链表(或树形结构)组成

Kernel数据结构移植(list和rbtree)

浪子不回头ぞ 提交于 2020-03-18 07:15:58
主要移植了内核中的 list,rbtree。使得这2个数据结构在用户态程序中也能使用。 同时用 cpputest 对移植后的代码进行了测试。(测试代码其实也是使用这2个数据结构的方法) 内核代码的如下文件:(内核版本 v3.2 debian 7.5源码) include/linux/list.h (删除了 hlist 相关内容) include/linux/rbtree.h lib/rbtree.c 对上面的代码进行了一些简化,只留了常用的函数。同时删除了其中和内核相关的部分。 主要内容: list 介绍 (循环双向链表) rbtree 介绍 1. list 介绍 (循环双向链表) 1.1 简介 Linux中的链表用法与一般数据结构书中介绍的用法有些不一样。 Linux内核中,为了保证链表的通用性,将链表的节点结构单独抽取了出来,也就是将链表的结构和链表的数据分开定义。 一般数据结构的书中介绍到的链表都是将链表的数据和链表的结构一起定义的。 注: 具体介绍可以我之前的博客参见: http://www.cnblogs.com/wang_yb/archive/2013/04/16/3023892.html 中的 1.2节 里面很重要的一点就是:链表结构和数据分开后,是如何通过链表节点结构来获取数据的? 带有safe的函数或者宏都是可以用于多线程的 1.2 修改部分

红黑树(一)之 原理和算法详细介绍

☆樱花仙子☆ 提交于 2020-03-18 07:14:22
概要 前面分别介绍了红黑树的理论知识 以及 通过C语言实现了红黑树。本章继续会红黑树进行介绍,下面将Linux 内核中的红黑树单独移植出来进行测试验证。若读者对红黑树的理论知识不熟悉,建立先学习 红黑树的理论知识 ,再来学习本章。 转载请注明出处: http://www.cnblogs.com/skywang12345/p/3624202.html 更多内容: 数据结构与算法系列 目录 (01) 红黑树(一)之 原理和算法详细介绍 (02) 红黑树(二)之 C语言的实现 (03) 红黑树(三)之 Linux内核中红黑树的经典实现 (04) 红黑树(四)之 C++的实现 (05) 红黑树(五)之 Java的实现 (06) 红黑树(六)之 参考资料 Linux内核中红黑树(完整源码) 红黑树的实现文件(rbtree.h) 1 /* 2 Red Black Trees 3 (C) 1999 Andrea Arcangeli <andrea@suse.de> 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software

HashMap底层源码剖析

十年热恋 提交于 2020-03-17 19:17:23
HashMap底层源码剖析 数组+单向链表+红黑树 数组:    数组每一项都是一个链表,其实就是数组和链表的结合体 单向链表:    当法神hash碰撞时,首先会找到数组对应位置,然后1.8采用尾插入法(1.7采用头插入法),形成一个单项链表结构 红黑树:   当数组中每项的链表长度大于8时,会转换为红黑树 什么是hash碰撞?解决方案 hash碰撞:    不同的key可能会产生相同的hash值; 方案:    链表发,再哈希法; hashMap中采用链表发,在ConcurrentHashMap中采用哈希法; 为什么采用红黑树,比如二叉查找树,并且为什么临界值为8    二叉查找树在特殊情况下也会变为线性结构,和原来链表有共同的问题,节点太深,查找性能慢 使用红黑树主要用于提升查询速度,红黑树是平衡二叉树的一种,插入新的数据都会通过左旋,右旋,变色等操作来保持平衡,解决节点的深度问题   当数据较少时,采用链表要比红黑树效率高,因为平衡二叉树保持平衡需要耗费资源,那么前期数据较少时采用链表,当数据到达一定的界限后,再采用 红黑树,可以加快数据查询速度,官方测试8为性能最优 put()底层源码剖析 public V put(K key, V value) { return putVal(hash(key), key, value, false, true);     } /**

Java集合框架——Map

江枫思渺然 提交于 2020-03-17 08:09:30
Map hashMap:存放键值对的容器 ConcurrentHashMap(1.7) 1.8中ConcurrentHashMap SynchronizedMap&HashTable hashMap:存放键值对的容器 1、数据结构: 1.7:数组+链表 1.8:数组+链表+红黑树 (1.7Entry类;1.8Node类。本质一样) 内部包含了一个 Entry 类型的数组 table。Entry 存储着键值对,又存放了下一个Entry。所以Entry其实是一个链表。即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值和散列桶取模运算结果相同的 Entry。 1.8较1.7引入了红黑树对hashMap数据结构进行优化; 引入原因:提高性能 当链表过长导致索引效率慢,利用红黑树快速增删改查优点,将时间复杂度由O(n)–>O(log(n)) 应用场景:链表长度 >8,转化为红黑树 2、主要使用API: 1.7与1.8基本相同 V get ( Object key ) ; // 获得指定键的值 V put ( K key , V value ) ; // 添加键值对 void putAll ( Map < ? extends K , ? extends V > m ) ; // 将指定Map中的键值对 复制到 此Map中 V

关于HashMap的一些知识点整理

只愿长相守 提交于 2020-03-17 01:11:17
本文来自网上各个知识点的整理 HashMap采用Entry数组(Java8叫Node)来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题。 但链表的查询复杂度大,一但链表过长,查询的效率下降。 在Jdk1.8中HashMap的实现方式做了一些改变,数据结构的存储由数组+链表的方式,变化为数组+链表+红黑树的存储方式,当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。但每次插入新的数据,都得维护红黑树的结构,复杂度为O(logn)。 链表:插入复杂度O(1),查找复杂度O(n) 红黑树:插入复杂度O(logn),查找复杂度O(logn) 但既然红黑树这么棒,那为什么HashMap为什么不直接就用红黑树呢? 因为树节点所占空间是普通节点的两倍,所以只有当节点足够多的时候,才会使用树节点。也就是说,节点少的时候,尽管时间复杂度上,红黑树比链表好一点,但是红黑树所占空间比较大,综合考虑,认为只能在节点太多的时候,红黑树占空间大这一劣势不太明显的时候,才会舍弃链表,使用红黑树。 那为什么链表长度为8时才会选择使用红黑树呢? 为了配合使用分布良好的HashCode,树节点很少使用。并且在理想状态下,受随机分布的HashCode影响

红黑树原理

眉间皱痕 提交于 2020-03-16 20:18:29
1 R-B Tree简介 R-B Tree一种特殊的二叉查找树。红黑树的每个节点上都有存储位表示节点的颜色,可以是红(Red)或黑(Black)。 红黑树的特性: (1)每个节点或者是黑色,或者是红色。 (2)根节点是黑色。 (3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!] (4)如果一个节点是红色的,则它的子节点必须是黑色的。 (5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。 红黑树示意图如下: 2 R-B Tree旋转操作 红黑树的基本操作是 添加 、 删除 。在对红黑树进行添加或删除之后,都会用到旋转方法。为什么呢?道理很简单,添加或删除红黑树中的节点之后,红黑树就发生了变化,可能不满足红黑树的5条性质,也就不再是一颗红黑树了,而是一颗普通的树。而通过旋转,可以使这颗树重新成为红黑树。简单点说,旋转的目的是让树保持红黑树的特性。 旋转包括两种: 左旋 和 右旋 。下面分别对它们进行介绍。 2.1 左旋 对x进行左旋,意味着"将x变成一个左节点"。 左旋的伪代码《算法导论》:参考上面的示意图和下面的伪代码,理解“红黑树T的节点x进行左旋”是如何进行的。 LEFT-ROTATE(T, x) y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作 right[x] ← left[y]

Map-HashMap

[亡魂溺海] 提交于 2020-03-15 18:03:04
一、HashMap数据结构   JDK 1.7 采用数组 + 链表实现。   JDK 1.8 采用数组 + 链表 + 红黑树实现。链表采用内部类Node节点实现。红黑树采用内部类TreeNode节点实现。 二、重要参数  // 1. 容量(capacity): 必须是2的幂 & <最大容量(2的30次方),默认是16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;  // 最大容量 = 2的30次方(若传入的容量过大,将被最大值替换) static final int MAXIMUM_CAPACITY = 1 << 30; // 2. 加载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度 // 加载因子过大则容器内可添加更多元素,空间效率高,但是容易导致哈希冲突。反之反之 final float loadFactor; // 实际加载因子 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认加载因子 = 0.75 h // 3. 扩容阈值 = 容量 x 加载因子,哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表 int threshold; // 4. 其他 transient Node<K,V>[] table; //

【原创】(五)Linux进程调度-CFS调度器

安稳与你 提交于 2020-03-15 02:10:15
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本:4.14 ARM64处理器,Contex-A53,双核 使用工具:Source Insight 3.5, Visio 1. 概述 Completely Fair Scheduler ,完全公平调度器,用于Linux系统中普通进程的调度。 CFS 采用了红黑树算法来管理所有的调度实体 sched_entity ,算法效率为 O(log(n)) 。 CFS 跟踪调度实体 sched_entity 的虚拟运行时间 vruntime ,平等对待运行队列中的调度实体 sched_entity ,将执行时间少的调度实体 sched_entity 排列到红黑树的左边。 调度实体 sched_entity 通过 enqueue_entity() 和 dequeue_entity() 来进行红黑树的出队入队。 老规矩,先上张图片来直观了解一下原理: 每个 sched_latency 周期内,根据各个任务的权重值,可以计算出运行时间 runtime ; 运行时间 runtime 可以转换成虚拟运行时间 vruntime ; 根据虚拟运行时间的大小,插入到CFS红黑树中,虚拟运行时间少的调度实体放置到左边;

数据结构与算法之美——散列表

核能气质少年 提交于 2020-03-14 05:06:11
1.1 散列思想 ​ 将数据以散列函数的方式(键值对)存储 1.2 散列函数 ​ 形如hash(key)的键值对函数叫散列函数,hash(key)是值,key是键。 1.3 散列函数设计的基本要求 散列函数计算值应该是非负整数 如果散列函数的键相等,则函数一定相等 如果键不相等,则值也一定不相等 ​ 但是在真实的情况下第三个条件很难满足,这种不满足的情况叫它 散列冲突 。 1.4 散列冲突的解决办法 ​ 散列冲突有两类解决办法:开放寻址法、链表法。 开放寻址法 ​ 开放寻址法的思想是如果出现了散列冲突,就向后探测空闲位置,将其插入。列举下比较简单的方法,比如: 线性探测法 。 删除操作不能直接将其值赋为null,否则会使寻址法失效,如果一定用的话,可以用个表示来标记要删除的元素,寻址时发现此标识就向后寻找。 ​ 其实,线性探测法有很多的问题,比如:当空闲的位置越来越少的时候,寻找时间越来越久,性能会下降很多,最坏的情况下会达到O(n)。除了线性探测,还有 二次探测 、 双重散列 等方法可以解决。 ​ 不过,不管哪一种方法,空闲位置不多时,性能都会下降很多。一般情况,都会使空闲位置与散列表的长度处于一个健康的比值,这个比值叫 装载因子 。 链表法 ​ 链表法是相比寻址法较好的解决散列冲突的解决办法。他会根据键值的不同来划分不同的‘桶’,每一个桶都会对应一条链表