Java面试题-Java集合

这篇博客主要探讨了Java面试中常见的集合问题,包括HashMap与Hashtable的区别、HashMap的底层实现、ConcurrentHashMap与Hashtable的异同、TreeMap的特点、ArrayList与LinkedList的差异、HashSet与TreeSet的不同、Iterator与ListIterator的使用区别、LinkedHashMap与HashMap的细节以及数组和链表的数据结构及时间复杂度分析。
摘要由CSDN通过智能技术生成

Java面试题

集合

1.Java中常见的集合有哪些。

1.Map接口和Collection接口是所有集合框架的父接口
2.Collection接口的子接口包括:Set接口和List接口
3.List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
4.Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
5.Map接口的实现类主要有:HashMap、TreeMap、Hashtable、LinkedHashMap、
  ConcurrentHashMap以及Properties等

2.HashMap和Hashtable的区别有哪些

1.HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
2.HashMap允许null作为Key;Hashtable不允许null作为Key,Hashtable的value也不可以为null
拓展1:HashMap为什么不是线程安全的集合。
     答案:HashMap集合在多线程环境下扩容会出现死循环的现象。
拓展2:HashTable一定是线程安全的吗。
      答案:Hashtable线程安全是由于其内部实现在put和remove等方法上使用synchronized进行了同步,
      所以对单个方法的使用是线程安全的。但是对多个方法进行复合操作时,线程安全性无法保证。
      比如一个线程在进行get操作,一个线程在进行remove操作,往往会导致下标越界等异常。

3.HashMap底层实现结构是什么。

HashMap底层实现数据结构为数组+链表的形式,JDK8及其以后的版本中使用了数组+链表+红黑树实现,
解决了链表太长导致的查询速度变慢的问题。
拓展1:HashMap的初始容量,加载因子,扩容增量是多少。
      答案:HashMap的初始容量16,加载因子为0.75,扩容增量是原容量的1倍。如果HashMap的容量为16,
      一次扩容后容量为32。HashMap扩容是指元素个数(包括数组和链表+红黑树中)超过了16*0.75=12
      之后开始扩容。
拓展2:HashMap的长度为什么是2的幂次方。
	  答案:1.我们将一个键值对插入HashMap中,通过将Key的hash值与length-1进行&运算,
	       实现了当前Key的定位,2的幂次方可以减少冲突(碰撞)的次数,提高HashMap查询效率
           2.如果length为2的幂次方,则length-1 转化为二进制必定是11111……的形式,
           在与h的二进制与操作效率会非常的快,而且空间不浪费
           3.如果length不是2的幂次方,比如length为15,则length-114,对应的二进制为1110,
           在与h与操作,最后一位都为0,而0001001101011001101101111101这几个
           位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以
           使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
           这样就会造成空间的浪费。
拓展3:HasMap的存储和获取原理是什么。
      答案:当调用put()方法传递键和值来存储时,先对键调用hashCode()方法,
      返回的hashCode用于找到bucket位置来储存Entry对象,也就是找到了该元素应该被存储的桶中(数组)。
      当两个键的hashCode值相同时,bucket位置发生了冲突,也就是发生了Hash冲突,这个时候,
      会在每一个bucket后边接上一个链表(JDK8及以后的版本中还会加上红黑树)来解决,
      将新存储的键值对放在表头(也就是bucket中)。
           当调用get方法获取存储的值时,首先根据键的hashCode找到对应的bucket,
      然后根据equals方法来在链表和红黑树中找到对应的值。
拓展4:解决Hash冲突的方法有哪些。
      答案:1.拉链法 (HashMap使用的方法)
           2.开放地址法
           3.再哈希法
           4.建立公共溢出区
拓展5:哪些类适合作为HashMap的键。
      答案:String和Interger这样的包装类很适合做为HashMap的键,因为他们是final类型的类,
           而且重写了equals和hashCode方法,避免了键值对改写,有效提高HashMap性能。
           为了计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashCode的话,
           那么就不能从HashMap中找到你想要的对象。

4.ConcurrentHashMap和Hashtable的区别是什么。

1.底层数据结构
  1.Concurrent
    JDK1.7的ConcurrentHashMap底层采用分段的数组+链表实现,JDK1.8采用的数据结构跟HashMap1.8的结构一样,
    都是数组+链表/红黑树。
  2.Hashtable
    Hashtable和JDK1.8之前的HashMap的底层数据结构类似都是采用数组+链表的形式,数组是HashMap的主体,
    链表则是主要是为了解决哈希冲突而存在的。
    
2.实现线程安全的方式
  1.Concurrent
    在JDK1.7的时候,ConcurrentHashMap(分段锁)对真个桶数组进行了分割分段(segment),每一把锁只锁容器其中一部分数据,
    多线程访问容器里不同数据段的是数据,就不会存在锁竞争,从而提高了并发访问率。到了JDK1.8的时候已经摒弃了Segment的概念,
    而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用synchronized和CAS来操作。
    (JDK1.6以后对synchronized锁做了很多优化)真个看起来就像是优化过且线程安全的HashMap,
    虽然在JDK1.8中还能看到Sebment的数据结构,但是已经简化了属性,知识为了兼容旧版本;
   2.Hashtable
     Hashtable(同一把锁)使用synchronized来保证线程安全,效率非常低下。当一个线程访问同步方法时,
     其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put添加元素,另一个线程不能使用put添加元素,
     也不能使用get,竞争会越来越激烈效率越低。
拓展1:jdk1.8为什么会放弃ConcurrentHashMap的分段锁。
      答案:通过 JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:
           1.加入多个分段锁浪费内存空间。
           2.生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
           3.为了提高 GC 的效率
拓展2:ConcurrentHashMap为什么不直接是红黑树,还要用链表。
      答案:之所以还要用链表不直接用红黑树的原因是,链表所占的内存要小于红黑树,所以采用链表。
拓展3:ConcurrentHashMap为什么是到第8个元素才转换为红黑树,第5个,第6个元素不可以吗。
      答案:之所以到第8个才转换成红黑树,是因为在一般情况下链表是不会到达8个的,到达8个元素的概率是千万分之几,
           概率非常小,但是还要保证在极端情况下的查询效率,所以才在第8个元素将链表转换成红黑树。

5.TreeMap有哪些特性。

TreeMap底层使用红黑树实现,TreeMap中存储的键值对按照键来排序。
1.如果Key存入的是字符串等类型,那么会按照字典默认顺序排序
2.如果传入的是自定义引用类型,比如说User,那么该对象必须实现Comparable接口,并且覆盖其compareTo方法;
  或者在创建TreeMap的时候,我们必须指定使用的比较器。如下代码所示:
  // 方式一:定义该类的时候,就指定比较规则
class User implements Comparable{
    @Override
    public int compareTo(Object o) {
        // 在这里边定义其比较规则
        return 0;
    }
}
public static void main(String[] args) {
    // 方式二:创建TreeMap的时候,可以指定比较规则
    new TreeMap<User, Integer>(new Comparator<User>() {
        @Override
        public int compare(User o1, User o2) {
            // 在这里边定义其比较规则
            return 0;
        }
    });
}

6.ArrayList和LinkedList有哪些区别。

1.ArrayList底层使用了动态数组实现,实质上是一个动态数组
2.LinkedList底层使用了双向链表实现,可当作堆栈、队列、双端队列使用
3.ArrayList在随机存取方面效率高于LinkedList
4.LinkedList在节点的增删方面效率高于ArrayList
5.ArrayList必须预留一定的空间,当空间不足的时候,会进行扩容操作
6.LinkedList的开销是必须存储节点的信息以及节点的指针信息
拓展:在多线程的环境下,可以使用什么集合代替ArrayList集合。
     答案:使用CopyOnWriteArrayList来代替。该集合的原理就是写时复制。在有写操作的时候,会复制一个集合的副本进行操作
          不会影响原来集合的读操作,但是缺点是不能实时的让其他线程读到数据。

7.HashSet和TreeSet有哪些区别。

1.HashSet底层使用了Hash表实现。
  保证元素唯一性的原理:判断元素的hashCode值是否相同。如果相同,还会继续判断元素的equals方法,是否为true
2.TreeSet底层使用了红黑树来实现。
  保证元素唯一性是通过Comparable或者Comparator接口实现

8.Iterator和ListIterator的区别是什么。

1.Iterator可以遍历list和set集合;ListIterator只能用来遍历list集合
2.Iterator前者只能前向遍历集合;ListIterator可以前向和后向遍历集合
3.ListIterator其实就是实现了前者,并且增加了一些新的功能。

9.LinkedHashMap和HashMap的区别是什么。

LinkedHashMap集合继承于HashMap集合,它采用双向链表的形式维持着插入的顺序使HashMap变成有序集合。

10.数组和链表数据结构描述,各自的时间复杂度。

1.数组 
  是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。
  但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。
  同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和
  删除元素,就应该用数组。其时间复杂度是O(1)2.链表 
  中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:
  一个是存储 数据元素 的 数据域,另一个是存储下一个结点地址的 指针。 
 如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。
 但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。
 如果应用需要经常插入和删除元素你就需要用链表。其时间复杂度是O(n)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值