BAT面试总结(二)

32 篇文章 0 订阅
16 篇文章 0 订阅

11. ArrayList、LinkedList、Vector的区别
List的三个子类的特点

  • ArrayList:底层数据结构是数组,查询快,增删慢;线程不安全,效率高。
  • Vector:底层数据结构是数组,查询快,增删慢;线程安全,效率低。
  • LinkedList:底层数据结构是链表,查询慢,增删快;线程不安全,效率高。

Vector相对ArrayList查询慢(线程安全的)。
Vector相对LinkedList增删慢(数组结构)。

Vector和ArrayList的区别:
Vector是线程安全的,效率低;
ArrayList是线程不安全的,效率高。
共同点:底层数据结构都是数组实现的,查询快,增删慢。

ArrayList和LinkedList的区别:
ArrayList底层是数组结果,查询和修改快;
LinkedList底层是链表结构的,增和删比较快,查询和修改比较慢。
共同点:都是线程不安全的

List有三个子类使用:查询多用ArrayList;增删多用LinkedList;如果都多用ArrayList。

12. String、StringBuffer与StringBuilder的区别

  • String:适用于少量的字符串操作的情况。
  • StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况。
  • StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
  • StringBuilder:是线程不安全的,而StringBuffer是线程安全的。
    这三个类之间的区别主要是在两个方面,即运行速度和线程安全这两方面。
    首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:StringBuilder > StringBuffer > String。
    String最慢的原因:String为字符串常量,而StringBuilder和StringBuffer均为字符串变量,即String对象一旦创建之后该对象是不可更改的,但后两者的对象是变量,是可以更改的。
    再来说线程安全。在线程安全上,StringBuilder是线程不安全的,而StringBuffer是线程安全的。
    如果一个StringBuffer对象在字符串缓冲区被多个线程使用时,StringBuffer中很多方法可以带有synchronized关键字,所以可以保证线程是安全的,但StringBuilder的方法则没有该关键字,所以不能保证线程安全,有可能会出现一些错误的操作。所以如果要进行的操作是多线程的,那么就要使用StringBuffer,但是在单线程的情况下,还是建议使用速度比较快的StringBuilder。

13. Map、Set、List、Queue、Stack的特点与用法

  • Map:
    Map是键值对,键Key是唯一不能重复的,一个键对应一个值,值可以重复。
    TreeMap可以保证顺序。
    HashMap不保证顺序,即为无序的。
    Map中可以将Key和Value单独抽取出来,其中KeySet()方法可以将所有的keys抽取正一个Set。而Values()方法可以将map中所有的values抽取成一个集合。
  • Set:
    不包含重复元素的集合,set中最多包含一个null元素。
    只能用Lterator实现单项遍历,Set中没有同步方法。
  • List:
    有序的可重复集合。
    可以在任意位置增加删除元素。
    用Iterator实现单向遍历,也可用ListIterator实现双向遍历。
  • Queue:
    Queue遵从先进先出原则。
    使用时尽量避免add()和remove()方法,而是使用offer()来添加元素,使用poll()来移除元素,它的优点是可以通过返回值来判断是否成功。
    LinkedList实现了Queue接口。
    Queue通常不允许插入null元素。
  • Stack:
    Stack遵从后进先出原则。
    Stack继承自Vector。
    它通过五个操作对类Vector进行扩展,允许将向量视为堆栈,它提供了通常的push和pop操作,以及取堆栈顶点的peek()方法、测试堆栈是否为空的empty方法等。
  • 用法:
    如果涉及堆栈,队列等操作,建议使用List。
    对于快速插入和删除元素的,建议使用LinkedList。
    如果需要快速随机访问元素的,建议使用ArrayList。
    更为精炼的总结:Collection 是对象集合, Collection 有两个子接口 List 和 Set。
    List 可以通过下标 (1,2…) 来取得值,值可以重复。Set 只能通过游标来取值,并且值是不能重复的。
    ArrayList , Vector , LinkedList 是 List 的实现类
    ArrayList 是线程不安全的, Vector 是线程安全的,这两个类底层都是由数组实现的。
    LinkedList 是线程不安全的,底层是由链表实现的。
    Map 是键值对集合。
    HashTable 和 HashMap 是 Map 的实现类。
    HashTable 是线程安全的,不能存储 null 值。
    HashMap 不是线程安全的,可以存储 null 值。
    Stack类:继承自Vector,实现一个后进先出的栈。提供了几个基本方法,push、pop、peak、empty、search等。
    Queue接口:提供了几个基本方法,offer、poll、peek等。已知实现类有LinkedList、PriorityQueue等。

14. HashMap和HashTable的区别
Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现,它们都是集合中将数据无序存放的。
1)hashMap去掉了HashTable?的contains方法,但是加上了containsValue()和containsKey()方法
HashTable Synchronize同步的,线程安全,HashMap不允许空键值为空,效率低。
HashMap 非Synchronize线程同步的,线程不安全,HashMap允许空键值为空,效率高。
Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现,它们都是集合中将数据无序存放的。
Hashtable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。
查看Hashtable的源代码就可以发现,除构造函数外,Hashtable的所有 public 方法声明中都有 synchronized 关键字,而HashMap的源代码中则连 synchronized 的影子都没有,当然,注释除外。
2)Hashtable不允许 null 值(key 和 value 都不可以),HashMap允许 null 值(key和value都可以)。
3)两者的遍历方式大同小异,Hashtable仅仅比HashMap多一个elements方法。4、HashTable使用Enumeration,HashMap使用Iterator

15. JDK7与JDK8中HashMap的实现

  • JDK7中的HashMap
    HashMap底层维护一个数组,数组中的每一项都是一个Entry。
    transient Entry<K,V>[] table;我们向 HashMap 中所放置的对象实际上是存储在该数组当中。
    而Map中的key,value则以Entry的形式存放在数组中。
static class Entry<K,V> implements Map.Entry<K,V> { 
       final K key;        
       V value;        
       Entry<K,V> next;        
       int hash;

总结一下map.put后的过程:
当向 HashMap 中 put 一对键值时,它会根据 key的 hashCode 值计算出一个位置, 该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组当中;如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(为了判断是否是否值相同,map不允许<key,value>键值对重复), 如果此链上有对象的话,再去使用 equals方法进行比较,如果对此链上的每个对象的 equals 方法比较都为 false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。

  • JDK8中的HashMapJ
    DK8中采用的是位桶+链表/红黑树(有关红黑树请查看红黑树)的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
    JDK8中,当同一个hash值的节点数不小于8时,将不再以单链表的形式存储了,会被调整成一颗红黑树。这就是JDK7与JDK8中HashMap实现的最大区别。
    接下来,我们来看下JDK8中HashMap的源码实现。
    JDK中Entry的名字变成了Node,原因是和红黑树的实现TreeNode相关联。
transient Node<K,V>[] table;

当冲突节点数不小于8-1时,转换成红黑树。

static final int TREEIFY_THRESHOLD = 8;

16. HashMap和ConcurrentHashMap的区别,HashMap的底层源码
为了线程安全从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放到哪个HashTable中。
Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。
ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。

  • JDK6,7中的ConcurrentHashmap主要使用Segment来实现减小锁粒度,把HashMap分割成若干个Segment,在put的时候需要锁住Segment,get时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。
  • jdk7中ConcurrentHashmap中,当长度过长碰撞会很频繁,链表的增改删查操作都会消耗很长的时间,影响性能。
  • jdk8 中完全重写了concurrentHashmap,代码量从原来的1000多行变成了 6000多 行,实现上也和原来的分段式存储有很大的区别。
  • JDK8中采用的是位桶+链表/红黑树(有关红黑树请查看红黑树)的方式,也是非线程安全的。当某个位桶的链表的长度达到某个阀值的时候,这个链表就将转换成红黑树。
  • JDK8中,当同一个hash值的节点数不小于8时,将不再以单链表的形式存储了,会被调整成一颗红黑树。这就是JDK7与JDK8中HashMap实现的最大区别。
  • 主要设计上的变化有以下几点
    1)jdk8不采用segment而采用node,锁住node来实现减小锁粒度。
    2)设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
    3)使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
    4)sizeCtl的不同值来代表不同含义,起到了控制的作用。

17. ConcurrentHashMap能完全替代HashTable吗?

  • hashTable虽然性能上不如ConcurrentHashMap,但并不能完全被取代,两者的迭代器的一致性不同的,hash table的迭代器是强一致性的,而concurrenthashmap是弱一致的。
  • ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也将这个判断留给用户自己决定是否使用ConcurrentHashMap。
    ConcurrentHashMap与HashTable都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
    那么既然ConcurrentHashMap那么优秀,为什么还要有Hashtable的存在呢?ConcurrentHashMap能完全替代HashTable吗?
    HashTable虽然性能上不如ConcurrentHashMap,但并不能完全被取代,两者的迭代器的一致性不同的,HashTable的迭代器是强一致性的,而ConcurrentHashMap是弱一致的。ConcurrentHashMap的get,clear,iterator 都是弱一致性的。 Doug Lea 也将这个判断留给用户自己决定是否使用ConcurrentHashMap。
    那么什么是强一致性和弱一致性呢?
    get方法是弱一致的,是什么含义?可能你期望往ConcurrentHashMap底层数据结构中加入一个元素后,立马能对get可见,但ConcurrentHashMap并不能如你所愿。换句话说,put操作将一个元素加入到底层数据结构后,get可能在某段时间内还看不到这个元素,若不考虑内存模型,单从代码逻辑上来看,却是应该可以看得到的。
    下面将结合代码和java内存模型相关内容来分析下put/get方法。put方法我们只需关注Segment#put,get方法只需关注Segment#get,在继续之前,先要说明一下Segment里有两个volatile变量:count和table;HashEntry里有一个volatile变量:value。
    ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁,这就与Hashtable和同步的HashMap一样了。

18. 为什么HashMap是线程不安全的?
HashMap 在并发执行 put 操作时会引起死循环,导致 CPU 利用率接近100%。因为多线程会导致 HashMap 的 Node 链表形成环形数据结构,一旦形成环形数据结构,Node 的 next 节点永远不为空,就会在获取 Node 时产生死循环。

19. 如何线程安全的使用HashMap?
了解了 HashMap 为什么线程不安全,那现在看看如何线程安全的使用 HashMap。这个无非就是以下三种方式:
Hashtable
ConcurrentHashMap
Synchronized Map

20. 多并发情况下HashMap是否还会产生死循环?
ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。既然会产生死循环,为什么并发情况下,还是用ConcurrentHashMap。jdk 好像有,但是Jdk8 已经修复了这个问题。
码巢架构
获取更多资料,请扫码进群或关注我们的公众号
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值