互联网架构-Java8集合框架源码分析-043:HashMap1.7扩容死循环问题与二叉搜索树结构

1 HashMapJdk1.7面试题总结

HashMap默认的初始容量? 16

HashMap默认情况每次容量扩容多少? 2倍

HashMap如何解决hashCode冲突问题? 单向链表,新值指向旧值。

Index冲突会产生哪些问题? 会导致值被覆盖,使用链表解决。

HashMap底层如何减少index冲突问题? return h&(length-1);位移运算取模分摊

HashMap线程是否安全,与HashTable区别? 不安全;线程安全、null、继承HashMap

是否可以存放key为空的对象? 支持,存放在数组第0个位置(第一个链表)

HashMap底层实现扩容的时候存在哪些问题? jdk7并发情况扩容可能出现死循环问题

HashMap是否可以存放键值对为自定义对象? 可以,底层使用泛型

HashMap加载因子为什么是0.75而不是其他? 加载因子越大,hash冲突概率越大,空间利用率高但是链表长度更长导致查询效率更低;加载因子越小,hash冲突概率越小,频繁扩容导致空间利用率不是很高。0.75是兼顾“冲突机会”与“空间利用率”相对来说最合适的值。

HashMap中index冲突与hash冲突有什么区别? Index冲突是因为底层做二进制运算时产生相同的index,对象不同但是二级制运算得出index相同;hash冲突,对象不同但是hashCode相同。

同一个链表中存放哪些内容? index冲突或者hash值冲突

HashMap链表长度过长会产生哪些问题? 导致整个查询效率降低,时间复杂度o(n)

Jdk7HashMap存在哪些问题? HashMap线程不安全;链表如果过长会导致查询效率降低;底层扩容的时候可能会存在死循环问题

Jdk7HashMap计算hash值与jdk8区别? Jdk7中HashMap计算得出的hash值非常均摊,能够减少hash冲突问题,因为链表查询效率低;Jdk8中计算hash非常简单,存在hash冲突的概率相比jdk7更大,但是不影响,因为java8使用红黑树解决查询效率问题。

描述Jdk7中HashMap查询? Jdk7中HashMap根据key查询分两种情况:如果该key没有发生hash冲突,根据key计算index,直接从数组中查询;如果该key存在hash冲突问题,会形成一个链表,遍历链表查询,查询效率低。

2 HashMapJdk1.7扩容死循环问题及原理分析

HashMap在并发情况下,扩容时候会存在哪些问题?
Jdk7使用链表头插赋值法,在多线程的情况下扩容可能会导致出现死循环的问题,jdk8已经解决。
在这里插入图片描述
原理分析:在多线程情况下同时对HashMap进行扩容,因为每次数组在扩容的时候,新的数组长度发生了变化,需要重新计算index值,需要将原来table中的数据移动到新table中。在HashMap1.7版本598行“e.next = newTable[i];”代码,直接改变原来的next关系,导致出现脏读的数据。
误区:扩容的时候不是重新计算hash值而是重新计算index值。

3 HashMap1.8为什么需要使用红黑树

课程内容:
1.为什么Java8HashMap需要引入红黑树
2.时间复杂度与空间复杂度之间区别
3.折半查找、二叉树、平衡二叉树区别
4.写一个史上最牛逼的二叉搜索树

为什么Java8HashMap需要引入红黑树

  1. Jdk7中HashMap通过链表+数组实现,当产生hashCode冲突的时候会使用同一个链表存放数据,如果链表的长度过长会导致查询效率非常慢,时间复杂度为o(n);在Java8中HashMap使用数组+链表+红黑树,在链表长度大于8的时候,将后面的数据以红黑树实现存放,从而加快检索速度;
  2. 修复多线程情况下扩容可能出现的死循环bug;

4 数据结构中时间查询复杂度对比

时间复杂度与空间复杂度基本概念
O(1):最低的时空复杂度,也就是耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标(不考虑冲突的话)。
O(n):比如时间复杂度为O(n),就代表数据量增大几倍,耗时也增大几倍。比如常见的遍历算法,要找到一个数组里面最大的一个数,你要把n个变量都扫描一遍,操作次数为n,那么算法复杂度是O(n)。
O(log n):当数据增大n倍时,耗时增大log n倍(这里的log是以2为底的,比如,当数据增大256倍时,耗时只增大8倍,是比线性还要低的时间复杂度)。二分查找就是O(log n)的算法,每找一次排除一半的可能,256个数据中查找只要找8次就可以找到目标。

HashMap根据key查询,数据量的增多会导致根据key查询效率降低吗?
HashMap根据key获取hash值,计算index,分两种场景:

  1. 如果当前的key没有发生index冲突问题,不管怎么增加HashMap中的数据,都是可以一次性查询,时间复杂度o(1);
  2. 如果当前的key发生了index冲突的问题,形成一个链表,需要重头开始遍历查询,时间复杂度为o(n)。

5 常用集合时间复杂度原理对比

在Java集合中ArrayList、LinkeList、HashMap时间复杂度分别为多少?
ArrayList的底层是采用数组实现,根据下标查询时间复杂度为o(1)、根据元素值查询时间复杂度o(n);
LinkeList底层采用链表实现,根据下标查询时间复杂度为o(n)、根据元素值查询时间复杂度为o(n);
Jdk7HashMap底层数组+链表实现,根据key查询时间复杂度为o(1)或者o(n);
总结:直接通过数组下标查找元素的情况下o(1),不断遍历o(n)。

6 折半查找算法底层实现原理分析

思考:比如在链表中有1-100个节点,那么如何根据下标查询到某个节点?
举例查询链表中第63个节点
For(int i=0; i<63; i++){
//查询63次 效率非常低
}

二分查找
第一次查询 中间值50 63>50,从51-100之间查询
第二次查询 中间值75 63<75,从51-75之间查询
第三次查询 中间值63 命中
时间复杂度o(log n),查询效率非常高
存在问题:必须保证数据有序性

7 二叉搜索树基本实现原理与思想

二叉搜索树基本特征

  1. 节点的左子树只包含小于当前根节点的数。
  2. 节点的右子树只包含大于当前根节点的数。
  3. 所有左子树和右子树自身必须也是二叉搜索树。

查找效率:20亿个数,2^32=21亿+,查询21次。
时间复杂度:2^x=n ==> x=log2^n=log n
二叉搜索树存在哪些问题:不平衡。使用第一次添加的节点作为根节点,如果后面添加节点值都大于根节点值的情况下,会形成链表。查找时间复杂度其实就是树的深度,也就是变为O(n)。
在这里插入图片描述
使用平衡二叉树(通过旋转的形式找平衡点)
在这里插入图片描述
8 完全手写二叉搜索树(添加、查询)功能

public class MayiktBinarySearchTree {
    // 二叉搜索树特征:添加第一个节点作为平衡值,也就是最底层根节点

    /**
     * 元素节点值
     */
    int data;
    /**
     * 左子树
     */
    MayiktBinarySearchTree left;
    /**
     * 右子树
     */
    MayiktBinarySearchTree right;

    public MayiktBinarySearchTree(int data) {
        this.data = data;
    }

    // 左小右大
    public void insert(MayiktBinarySearchTree root, int data) {
        if (data > root.data) {
            // 如果当前根节点右边为空,说明第一次添加右子树
            if (root.right == null) {
                root.right = new MayiktBinarySearchTree(data);
            } else {
                // 如果右边有值,使用递归
                insert(root.right, data);
            }
        } else {
            // 如果当前添加的节点小于根节点,存放到左边
            if (root.left == null) {
                root.left = new MayiktBinarySearchTree(data);
            } else {
                insert(root.left, data);
            }
        }
    }

    public void inSearch(MayiktBinarySearchTree root) {
        if (root != null) {
            // 递归找到最小的数
            inSearch(root.left);
            System.out.println(root.data);
            inSearch(root.right);
        }
    }

    public static void main(String[] args) {
        int data[] = {3, 6, 5, 2, 1, 4};
        // 3为data中的平衡值
        MayiktBinarySearchTree root = new MayiktBinarySearchTree(data[0]);
        for (int i = 1; i < data.length; i++) {
            root.insert(root, data[i]);
        }
        root.inSearch(root);
    }
}

运行结果:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值