数据结构

1. 红黑树(R-B Tree)

参考

  • 非严格均衡的二叉搜索树。
  • TreeMapTreeSet都是基于红黑树实现的,而Jdk8中HashMap当链表长度大于8时也会转化为红黑树。
  • 红黑树的特点:
    • 节点分为红色或者黑色;
    • 根节点必为黑色;
    • 叶子节点都为黑色,且为null;
    • 连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点;
    • 从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;
    • 新加入到红黑树的节点为红色节点。

2. HashMap、ConcurrentHashmap、HashTable

参考1
参考2
参考3

2.1 HashMap

  • 存储键值对的数据结构。

  • 使用散列(hashsing)实现,在O(1)时间内查找、插入以及删除一个元素。

  • 底层用数组+链表/ 红黑树实现。

  • 不是线程安全的,在实现的时候没有做任何同步操作,同一时刻多个线程都可以对其进行读写,数据很可能会被破坏,所以并发会出问题。

  • 元素个数大于等于 容量*负载因子(默认0.75f) 时,需要进行扩容。新建一个大小为原来2倍的数组,并重新计算元素在新数组中的位置,将原来的条目重新装载到新数组中。【称为再散列

    • 再散列非常影响性能,如果已经预知hashmap中元素的个数,那么设置hashmap的初始容量能够有效的提高hashmap的性能。
  • jdk1.7

    • 数组加链表,数组的每个元素是一个链表。
      Entry的定义
      当冲突严重时,链表越来越长,查询的效率越来越低,由于查询需要遍历链表,时间复杂度为O(N)。
  • jdk1.8

    • 数组加链表/红黑树。
      • 由于红黑树是一种平衡二叉搜索树,可以在O(logN)的时间内实现查找(优于链表的O(N))。
      • 设定用于判断是否需要将链表转换为红黑树的阈值(8)。如果当前链表的长度大于预设的阈值,就要转换为红黑树。

2.2. ConcurrentHashMap

线程安全的,采用锁分离技术,将锁的粒度降低,利用多个锁来控制多个小的table,提高并发能力,解决HashTable效率低下的问题。
jdk1.8与jdk1.7实现的比较

  1. JDK1.8的实现进一步降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)。
  2. JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了。
  3. JDK1.8使用红黑树来优化链表。基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替长度在一定阈值以上的链表。
  • jdk1.7

    • 由一个Segment数组和多个HashEntry组成。Segment数组的意义就是将一个大的table分割成多个小的table来分别进行加锁,也就是上面的提到的锁分离技术。而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样。

    • 理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程获取锁访问一个 Segment 时,不会影响到其他的 Segment。

    • Segment 是 ConcurrentHashMap 的一个内部类,继承了ReentrantLock。

    • HashEntry和Hashmap中的类似,只是value 和指向链表下一个元素的引用变量next 用volatile 修饰,保证了获取时的可见性。
      在这里插入图片描述

    • 需要两次定位。首先通过key的hashcode定位到具体的segment;然后再一次通过key的hashcode定位到当前segment的数组中具体的HashEntry。

    • get操作不需要加锁。因为HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

    • put操作仍然需要加锁处理。因为value 是用 volatile 关键词修饰的,并不能保证并发的原子性。

      • 首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put。
      • 尝试自旋获取锁。如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。
      • 通过key的 hashcode 将当前 Segment 中的数组定位到具体的HashEntry。
      • 遍历该位置的链表,判断HashEntry的Key是否与传入的 key相等,相等则用新值覆盖旧的 value。
      • 若链表为空或未找到与传入的key相同的key,则新建一个HashEntry,先判断是否需要扩容,再加到链表中。
      • 最后释放当前 Segment 的锁。
  • jdk1.8

    • 摒弃了Segment,直接用Node数组+链表/ 红黑树的数据结构来实现,使用Synchronized和CAS来实现并发控制。

    • 将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。其中的 val和next 都用了 volatile 修饰,保证了可见性。
      在这里插入图片描述

    • ConcurrentHashMap的初始化其实是一个空实现,并没有做任何事。这也是和其他的集合类有区别的地方,初始化操作并不是在构造函数实现的,而是在put操作中实现。

    • put方法

      • 判断是否需要初始化

      • 根据key的hashcode计算出数组中对应的下标,如果定位到的Node为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。

      • 判断是否需要扩容。

      • 如果以上判断都不满足,也就是存在hash冲突,就要进行加锁操作。利用 synchronized 锁写入数据。【锁定的对象为定位到的Node,即链表或红黑树的头节点】

        • 如果该Node是链表结构,则采用遍历链表的方式写入数据。【判断链表元素的key是否与要插入的key相同,若相同则用新值覆盖旧值;若没有找到key相同的元素,就新建一个Node插入链表尾部】
        • 如果是红黑树结构,就用红黑树的方式写入数据。
      • 判断链表长度是否大于等于设定的阈值,若需要即把链表转换为红黑树。

      • 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容。【helpTransfer()方法调用多个工作线程一起帮助进行扩容,这样的效率就会更高,而不是只有检查到要扩容的那个线程进行扩容操作,其他线程就要等待扩容操作完成才能工作】

    • get方法

      • 根据key的hashcode确定对应的数组的下标,如果是首节点是就直接返回。
      • 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,查找该节点,匹配就返回。
      • 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null。

2.3 HashTable

  • 实现原理与jdk1.7HashMap的实现原理是相同的。散列、数组+链表。
  • 是线程安全的。它的方法使用synchronized关键字修饰,锁住整个HashTable。这就意味着所有的线程都在竞争同一把锁,在多线程的环境下,无疑是效率低下的。

2.4 HashTable与HashMap比较

HashTableHashMap
继承关系继承Dictionary,实现Map接口继承AbstractMap,实现Map接口
线程安全性线程安全线程不安全
key和value都不允许null值null可以作为键,这样的键只有一个,key为null的键值对永远都放在以table[0]为头结点的链表中;可以有一个或多个键所对应的值为null
效率

3. 二叉堆

  • 是一棵完全二叉树;
    • 完全二叉树:每一层都是满的,或者最后一层不满且最后一层的叶子都是靠左放置的。
  • 每个节点大于等于他的任意一个孩子。

4. 二叉树

4.1 二叉树

是一种层次结构,要么是空集,要么是由一个称为根的元素和两棵不同的二叉树组成的,分别是左子树和右子树。允许两棵子树中的一棵或两棵为空。

4.2 二叉搜索树

对于树中的每一个节点,它的左子树中节点的值都小于该节点的值,右子树中节点的值都大于该节点的值。

4.3 二分查找的判定树(Decision Tree)或比较树(Comparison Tree)

二分查找过程可用二叉树来描述:把当前查找区间的中间位置上的结点作为根,左子表和右子表中的结点分别作为根的左子树和右子树。由此得到的二叉树,称为描述二分查找的判定树(Decision Tree)或比较树(Comparison Tree)
- 例题

4.4 哈夫曼树

参考
例题

4.5 线索二叉树

  • 在二叉树的结点上加上线索的二叉树称为线索二叉树,对二叉树以某种遍历方式(如先序、中序、后序或层次等)进行遍历,使其变为线索二叉树的过程称为对二叉树进行线索化。
  • 左线索指前驱,右线索指后继(前驱和后继按照具体的遍历顺序而定)
  • 例题

5. 图

  • 邻接矩阵:若图中有n个顶点,使用n*n的二维矩阵来表示边。若从顶点i到顶点j存在一条边,那么matrix[i][j]为1,否则为0。
  • 邻接表:顶点i的邻接顶点线性表包含了所有与i有边相连的顶点。顶点i的邻接边线性表包含了所有与i有边相连的边。
  • 邻接矩阵较适合存储边数多的图(稠密图),邻接表较适合存储边数少的图(稀疏图)。

6. B树、B+数

参考1
参考
参考2
参考3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值