Java集合面试题

文章结构

ArrayList、LinkedList、Vector
HashMap、HashSet、ConcurrentHashMap

Q: 你常用的集合是哪些
A: ArrayList、LinkedList、HashMap
Q: ArrayList和LinkedList的区别
【A】

  1. 首先实现方式不同:ArrayList:底层是动态数组实现,LinkedList:是通过链表实现
  2. 存储结构不同:ArrayList:是连续的存储空间,LinkedList: 存储是非连续性空间
  3. 使用性能不同:ArrayList: 查询快,增删慢; LinkedList: 查询慢,增删快
    注:ArrayList默认初始容量为10,超出容量后扩容为原来的1.5倍;

Q: ArrayList、LinkedList线程是否安全
【A】: ArrayList线程不安全,创建线程安全的ArrayList类可以使用Collection.synchronizedList进行包装或者使用并发包下的CopyOnWriterArrayList类。
LinkedList线程安全
Q: Vector如何实现线程安全?
【A】vector与ArrayList类似,底层使用数组实现。但是Vector是线程安全的,因为Vector在实现重写方法时使用synchronized关键字,由于使用synchronized使得其性能不如ArrayList高。


Q:你讲讲hashMap
【实现原理】
采用数组+链表方法实现的一种存储结构,提高增删查的效率。
hashMap基于哈希表的Map接口的非同步实现,存储结构为键值对的形式。
HashMap数据不同步、线程不安全,允许key/value为null,不保证数据存储顺序一致, 通过hashCode()方法和equals方法保证键的唯一性。

【存储过程】
1. 对存储键值对的key进行hash计算(即哈希函数)求得hashCode
2. 得到存储桶元素的下标(数组下标),将key/value存储到Entry类
3. 再对一个key/value进行存储,当该hashCode值与上一个hashCode相等,则发生哈希冲突(哈希碰撞)。

【存取机制】

  • put() : hashMap对key为null会进行特殊处理,将其存储在table[0]的位置。

    1. 通过hash()对key 计算得到hash值,将hash对table.length取模值得到index,在table[index]位置存储该key-value。当该位置已经存储其他元素,那么会在该位置形成一个链表,将新元素存储在table[index], 原来元素通过Entry类的next指针连接,以链表的形式解决hash冲突。
    2. 当元素数量达到临界值时,则进行扩容,table数组长度变为table.length*2。默认初始长度为16。
    3. jdk 1.7的扩容是插入之前之前判断,而jdk 1.8是插入之后再判断是否需要扩容;在1.8中扩容时保持了原来链表中的顺序,避免出现死循环。
  • get() : 同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素。get的过程是先计算hash然后通过hash与table.length取摸计算index值,然后遍历table[index]上的链表,直到找到key,然后返回。

【如何解决哈希冲突】

  • 解决方法:有三种方法:开放定地址法,拉链法,再hash法。最常用的是拉链法。
  • 拉链法:思路是将哈希值相同的元素构成一个同义词(桶)的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词(桶)链表中进行。
  • 【问题引入】采用拉链法,会存在hash链过长问题。

【哈希冲突hashMap进行相应的设计】

  • 在不同JDK实现:JDK 1.7 完全采用单链表来存储同义词;
  • JDK 1.8则采用了一种混合模式,对于(链表长度大于8的)解决复杂度高的长链用了红黑树的结构,会转换为红黑树存储。
  • 【问题引入】但是解决hash冲突,但hashMap还会有并发和同步的问题。

【解决线程不安全】

  • 线程不安全的几种情况:
    1. 多线程执行put()方法时,会导致元素丢失
    2. 在resize()时,会出现循环链导致死循环
    3. 在使用迭代器的过程中,其他线程修改map时,会导致出现异常。
    4. 【问题引入】 hashTable线程安全,ConCurrentHashMap线程安全

【hashTable如何实现线程安全】

  1. 从hashTable源码中看到这些对容器中数据进行操作的方法都被 synchronized关键字修饰,这种jdk自带的内置锁可以使得被synchronized关键字修饰的方法体和代码块一次只能被一个线程执行,也就保证了线程安全的问题。
  2. 【问题引入】随着容器数据量越来越大,当一个线程修改容器数据时会将容器数据数据完全锁住,其他线程将无法访问容器其他数据,导致容器数据的操作效率随着数据量的增大而降低。

【ConcurrentHashMap】

  1. 原理
  • ConcurrentHashMap(juc包下类)线程安全也是通过synchronized关键字控制线程代码同步的实现。
  • ConcurrentHashMap不允许key/value为null,否则会报NullPonitException异常。
  • ConcurrentHashMap与HashTable在线程同步的粒度不同,ConcurrentHashMap采用分段锁机制使得线程同步更加细分化,而不是像HashTable那样将所有数据都锁起来。
  • 【问题引入 – JDK的底层实现】分段锁底层实现:数组+链表+红黑树
  1. 实现过程
    ConcurrentHashMap对HashMap中的全部桶进行分段。在获取、添加元素时,通过key的Hash值得到段的位置,然后对该段进行加锁,不影响其他段位的数据操作。
  • JDK1.7实现
    Jdk1.7采用分段锁机制,对全部桶进行分段,比如8个桶为一组。
    ConcurrentHashMap在jdk1.7及以前的效率会提高8倍,当然数据量越大,提高的效率将越多。
  • JDK1.8实现
    依旧采用分段锁机制,但是比1.7加锁的粒度更加细分,1.8是将每个桶划分为一组,以每个数组索引为锁来进行实现。
    比如HashMap数组中长度有128,那么就会存在128个锁将每个索引锁住。这样相比于jdk1.7之前在效率上有了很大的改进。

【总结】

  • HashTable和ConcurrentHashMap都是线程安全的容器。
  • HashTable: 线程安全,效率和容器的大小成正比。容器数据量越大,效率越慢
  • ConcurrentHashMap: 线程安全,效率相对于不如HashMap,但是和HashTable相比,效率得到很大的提升。
  • 综合考虑,如果使用线程安全容器,推荐使用ConcurrentHashMap

【TreeMap和TreeSet】

  • TreeMap:
  1. 基于红黑树实现的,该集合根基key的自然顺序排序。
  2. 红黑树
    红黑树相比普通的 avl (平衡树)树,在检索的时候效率其实差不多,都是通过平衡来二分查找。但对于插入、删除等操作效率提高很多,红黑树不像 avl 树一样追求绝对的平衡,他允许局部很少的不完全平衡,这样对于效率影响不大,但省去了很多没有必要的调平衡操作,avl 树调平衡有时候代价较大,所以效率不如红黑树。
    红黑树能够以O(log2(N))的时间复杂度进行搜索、插入、删除操作。此外,任何不平衡都会在3次旋转之内解决,这一点是AVL所不具备的。
  3. 红黑树的插入:
    将红黑树当作一颗二叉排序树,将节点插入,并将节点着为红色;然后通过旋转和重新着色等方法来修正该树,使之重新成为一颗红黑树。
  4. 红黑树的删除:
    将红黑树当作一颗二叉排序树,将该节点从二叉排序树中删除;然后,通过"旋转和重新着色"等一系列操作来修正该树,使之重新成为一棵红黑树。
    删除节点分三种情况:
    (1)节点是叶节点,那么直接将该节点删除就OK了。
    (2)节点只有一个孩子节点,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
    (3)节点有两个儿子,先找出它的后继节点,然后把“它的后继节点的内容”复制给“该节点的内容”,之后删除“它的后继节点”。
  • TreeSet:
    TreeSet实现与TreeMap相似

  • 区别:
    相同点:

    1. TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步。
    2. 运行速度都要比Hash集合慢,基于红黑树实现,所以他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。
    3. TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的。

    不同点:

    1. 最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口
    2. TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)。
    3. TreeSet中不能有重复对象,而TreeMap中可以存在重复对象。

【参考链接】
HashTable和ConcurrentHashMap是如何实现线程安全的?
不同JDK实现hashMap
HashMap基本原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值