ArrayList 和 HaahMap简介


容器中
容器框架中的接口和实现接口的类的继承方法(该图片来自于Java程序设计基础第五版)

1. ArrayList 和 LinkedList的异同

  • 是否线程安全:两者都是不同步的,即 不保证线程安全。

  • 底层数据结构:ArrayList的底层是Object数组,LinkList的底层是双向链表结构

  • 插入和删除是否受元素位置的影响:

    • ArrayList 采用数组存储,插入、删除元素受元素位置影响。
    • LinkedList 采用链表存储,插入。删除不受位置影响。
  • 快速随机访问:ArrayList支持,快速随机访问就是通过元素序列号快速获取元素对象(对应于get(int index)方法)。LinkedList 不支持快速随机访问。

  • 内存空间占用:ArrayList 的空间浪费主要体现在 list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在他的每一个元素都需要消耗比ArrayList更多的空间(因为要存放 直接前驱 和 直接后继 以及数据)

  • 扩容机制:ArrayList的默认初始容量大小为10;每次扩容为当前容量的1.5倍。

        /**
         * ArrayList扩容的核心方法。
         */
        private void grow(int minCapacity) {
           //elementData为保存ArrayList数据的数组
           ///elementData.length求数组长度elementData.size是求数组中的元素个数
            // oldCapacity为旧容量,newCapacity为新容量
            int oldCapacity = elementData.length;
            //将oldCapacity 右移一位,其效果相当于oldCapacity /2,
            //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //再检查新容量是否超出了ArrayList所定义的最大容量,
            //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,
            //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

2. HashMap

  • 底层数据结构:JDK1.8之前 底层是 数组和链表 结合在一起,也就是 链表散列
    • HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash值,然后通过 (n - 1) & hash判断当前元素存放的位置(n:数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法去解决冲突。
    • 扰动函数:HashMap 的 hash方法,使用 hash 方法就是为了防止一些实现比较差的hashCode()方法。换句话说,使用扰动函数之后可以减少碰撞。
    • 拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可(链地址法)。
  • 1.8之后,在解决哈希冲突时有了较大的变化,当链表长度大于阈(yù)值(默认为8)时,将链表转化为红黑树,以减少搜索时间。
  • 初始容量:初始容量:16,(长度始终保持为2的n次方),如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。(3->4、7->8、9->16)。
  • 扩容机制:每次扩容为原来的2倍。
HashMap 和 Hashtable 的区别
  • 线程是否安全:HashMap 非线程安全,HashTable是线程安全的,HashTable 内部的方法都经过了 synchronized 修饰。(但要保证线程安全时,也是使用的 ConcurrentHashMap)
  • 效率:因为线程安全的问题,HashMap的效率要比HashTable的效率要高一点。另外,HashTable基本是一个遗留类,尽量不要再代码中使用它
  • 对空键和空值的支持:HashMap中,null可以作为键,但这样的键只有一个,可以有一个或多个键所对应的值为null。但是在HashTable中put 进的键值只要有一个 null,直接抛出 NullPointerException。
HashMap的长度为什么是2的幂次方
  • 为了能让 HashMap 存取高效,尽量减少碰撞,也就是要尽量吧数据分配均匀。Hash值的范围是 -231(-2147483648)到231-1(2147483647),前后加起来是一个大约40亿的映射空间。只要哈希函数映射的比较均匀松散。但问题是一个40亿长度的数组,内存是放不下去的。 所以这个散列值是不能直接拿来使用的。用之前还有先对数组的长度取模运算,得到的余数就是存放的位置(对应数组的下标),数组下标计算方法是(n - 1) & hash,(n代表数组长度)。

    这个算法应该如何设计呢?

    取余啊 % 。但是重点来了 取余(%)操作中,如果除数是2的幂次 则等价于 与其除数减一的与(&)操作,也就是说 hash % n == hash & (n - 1),前提是n 必须是2的n次方。并且采用二进制位操作 与& ,相对的能够提高运算效率,这就是为什么 HashMap的长度是2的幂次方。

HashMap多线程操作导致死循环问题
  • 在多线程下,进行 put 操作会导致 HashMap 死循环。原因在于 HashMap 的扩容 resize()方法。由于扩容是新建一个数组,复制原数据到数组。由于数组下标挂有链表,所以需要复制链表,但是多线程操作有可能导致环形链表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值