美团 Java 最新面试题(集合)-(1)

面试题

Posted by 朱坤乾 on November 1, 2019

1:ArrayList 和 Vector 的区别。

这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList、Vector和LinkedList。List用于存放多个元素,能够维护元素的次序,并且允许元素的重复。

二者都有一个初始容量大小,采用线性连续存储空间;当Vector或ArrayList中的元素超过它的初始大小时,Vector会将它的容量翻倍,而ArrayList只增加50%的大小,这样ArrayList就有利于节约内存空间。

Vector的方法都是同步的,是线程安全的,而ArrayList的方法不是,多线程中使用不安全.再多线程中某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。

ArrayList采用动态对象数组实现,默认构造方法创建了一个空数组.Vector采用动态数组对象实现,默认构造方法创建了一个大小为10的对象数组

2:说说 ArrayList,Vector, LinkedList 的存储性能和特性。

ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素, 但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据效率较低,

Vector由于使用了synchronized(同步)方法(线程安全),通常性能上较ArrayList差。但是由于ArrayList和LinkedListed都是非线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用

LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快。

LinkedList也是线程不安全的,LinkedList提供了一些方法,使得LinkedList可以被当作堆栈和队列来使用

3:快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?

一:快速失败(fail—fast)

在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。

原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。

二:安全失败(fail—safe)

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

  原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。

  缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

  场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

4:hashmap 的数据结构。

JDK&之前的HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

jdk8后采用数组+链表+红黑树的数据结构。我们通过put和get存储和获取对象。当我们给put()方法传递键和值时,先对键做一个hashCode()的计算来得到它在bucket数组中的位置来存储Entry对象。当 获取对象时,通过get获取到bucket的位置,再通过键对象的equals()方法找到正确的键值对,然后在返回值对象。

JDK8中,HashMap采用的是位桶+链表/红黑树的方式,当链表的存储的数据个数大于等于8的时候,不再采用链表存储,而采用了红黑树存储结构。

5:HashMap 的工作原理是什么?

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时, 它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对, 然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

6:Hashmap 什么时候进行扩容呢?

首先要了解HashMap的扩容过程,我们就得了解一些HashMap中的变量:

Node<K,V>:链表节点,包含了key、value、hash、next指针四个元素
table:Node<K,V>类型的数组,里面的元素是链表,用于存放HashMap元素的实体
size:记录了放入HashMap的元素个数
loadFactor:负载因子
threshold:阈值,决定了HashMap何时扩容,以及扩容后的大小,一般等于table大小乘以loadFactor

HashMap使用的是懒加载,构造完HashMap对象后,只要不进行put 方法插入元素之前,HashMap并不会去初始化或者扩容table

调用场景:

1.初始化数组table

2.当数组table的size达到阙值时即++size > load factor * capacity 时,也是在putVal函数中

实现过程:(细讲)

1.通过判断旧数组的容量是否大于0来判断数组是否初始化过

否:进行初始化

判断是否调用无参构造器,
    是:使用默认的大小和阙值
    否:使用构造函数中初始化的容量,当然这个容量是经过tableSizefor计算后的2的次幂数

是,进行扩容,扩容成两倍(小于最大值的情况下),之后在进行将元素重新进行与运算复制到新的散列表中

概括的讲:扩容需要重新分配一个新数组,新数组是老数组的2倍长,然后遍历整个老结构,把所有的元素挨个重新hash分配到新结构中去。

PS:可见底层数据结构用到了数组,到最后会因为容量问题都需要进行扩容操作

7:List、Map、Set 三个接口,存取元素时,各有什么特点?

List与Set都是单列元素的集合,它们有一个功共同的父接口Collection。

Set里面不允许有重复的元素,

存元素:add方法有一个boolean的返回值,当集合中没有某个元素,此时add方法可成功加入该元素时,则返回true;当集合含有与某个元素equals相等的元素时,此时add方法无法加入该元素,返回结果为false。

取元素:没法说取第几个,只能以Iterator接口取得所有的元素,再逐一遍历各个元素。

List表示有先后顺序的集合,

存元素:多次调用add(Object)方法时,每次加入的对象按先来后到的顺序排序,也可以插队,即调用add(int index,Object)方法,就可以指定当前对象在集合中的存放位置。

取元素:方法1:Iterator接口取得所有,逐一遍历各个元素

    方法2:调用get(index i)来明确说明取第几个。

Map是双列的集合,存放用put方法:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的key,这个重复的规则也是按equals比较相等。

取元素:用get(Object key)方法根据key获得相应的value。

    也可以获得所有的key的集合,还可以获得所有的value的集合,

    还可以获得key和value组合成的Map.Entry对象的集合。

List以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存key-value值,value可多值。

8:Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是 equals()? 它们有何区别?

Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。

equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个 分离的对象的内容和类型相配的话,返回真值。

9:两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对?

不对,如果两个对象x 和 y 满足 x.equals(y) == true,它们的哈希码(hashCode)应当相同。

Java 对于eqauls 方法和 hashCode 方法是这样规定的:

(1)如果两个对象相同(equals 方法返回 true),那么它们的hashCode 值一定要相同;

(2)如果两个对象的 hashCode 相同,它们并不一定相同。

10:heap 和 stack 有什么区别。

1:从存储方面来看堆内存主要存实例对象和jre classes 栈内存主要用于存储基本变量和对象引用

2:从存取速度来看的话,栈存储快,堆存储比较慢,因要要在运行时动态分配内存

3:从线程角度来看,每一个线程都有一个自己的java栈,所有线程共享一个java堆

4:从GC来看,栈区GC比较频繁,堆区不频繁

11:Java 集合类框架的基本接口有哪些?

总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合;

其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合;

而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用

HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;

12:HashSet 和 TreeSet 有什么区别?

HashSet HashSet有以下特点  不能保证元素的排列顺序,顺序有可能发生变化  不是同步的  集合元素可以是null,但只能放入一个null 当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据 hashCode值来决定该对象在HashSet中存储位置。 简单的说,HashSet集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值相 等 注意,如果要把一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写其hashCode()方法。其规则是如果两个对 象通过equals方法比较返回true时,其hashCode也应该相同。另外,对象中用作equals比较标准的属性,都应该用来计算 hashCode的值。

当向集合Set中增加对象时,首先集合计算要增加对象的hashCode码,根据该值来得到一个位置用来存放当前对象,如在该位置没有一个对象存在的话,那么集合Set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,在进行一次散列,将该对象放到散列后计算出的新地址里。如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。

TreeSet类 TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。 TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0 自然排序 自然排序使用要排序元素的CompareTo(Object obj)方法来比较元素之间大小关系,然后将元素按照升序排列。 Java提供了一个Comparable接口,该接口里定义了一个compareTo(Object obj)方法,该方法返回一个整数值,实现了该接口的对象就可以比较大小。 obj1.compareTo(obj2)方法如果返回0,则说明被比较的两个对象相等,如果返回一个正数,则表明obj1大于obj2,如果是 负数,则表明obj1小于obj2。 如果我们将两个对象的equals方法总是返回true,则这两个对象的compareTo方法返回应该返回0 定制排序 自然排序是根据集合元素的大小,以升序排列,如果要定制排序,应该使用Comparator接口,实现 int compare(T o1,T o2)方法。

TreeSet底层其实是一个二叉树机构,且每插入一个新元素(第一个除外)都会调用compareTo()方法去和上一个插入的元素作比较,并按二叉树的结构进行排列。

如果将compareTo()返回值写死为0,元素值每次比较,都认为是相同的元素,这时就不再向TreeSet中插入除第一个外的新元素。所以TreeSet中就只存在插入的第一个元素。
如果将compareTo()返回值写死为1,元素值每次比较,都认为新插入的元素比上一个元素大,于是二叉树存储时,会存在根的右侧,读取时就是正序排列的。
如果将compareTo()返回值写死为-1,元素值每次比较,都认为新插入的元素比上一个元素小,于是二叉树存储时,会存在根的左侧,读取时就是倒序序排列的。

根据自己需求,重写所有的方法

最重要:

1、TreeSet 是二差树实现的,Treeset中的数据是自动排好序的,不允许放入null值。

2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束。

3、HashSet要求放入的对象必须实现HashCode()方法,放入的对象,是以hashcode码作为标识的,而具有相同内容的 String对象,hashcode是一样,所以放入的内容不能重复。但是同一个类的对象可以放入不同的实例 。

13:HashSet 的底层实现是什么?

众所周知,hashset里面存储的元素都具有无序性,标识唯一性。但最近仔细研究了一下java里面的hashset,发现hashset里面大多数的内容都是在hashmap的基础上进行修改的。

(1)基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

(2)当我们试图把某个类的对象当成 HashMap的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的equals(Object obj)方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。

(3)HashSet的其他操作都是基于HashMap的。

面试题: HashSet与HashMap的区别

14:LinkedHashMap 的实现原理?

对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。

在LinkedHashMap中可以保持两种顺序,分别是插入顺序和访问顺序,这个是可以在LinkedHashMap的初始化方法中进行指定的。相对于访问顺序,按照插入顺序进行编排被使用到的场景更多一些,所以默认是按照插入顺序进行编排。

LinkedHashMap相对于HashMap,增加了双链表的结果(即节点中增加了前后指针),其他处理逻辑与HashMap一致,同样也没有锁保护,多线程使用存在风险。

HashMap是无序的,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了。LinkedHashMap是有序的,且默认为插入顺序。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。

HashMap是先遍历旧table,再遍历旧table中每个元素的单向链表,取得Entry以后,重新计算hash值,然后存放到新table的对应位置。LinkedHashMap是遍历的双向链表,取得每一个Entry,然后重新计算hash值,然后存放到新table的对应位置。

15:为什么集合类没有实现 Cloneable 和 Serializable 接口?

集合类接口Collection,List,Set,Map定义了自己集合类的抽象即可,如果接口的设计也要考虑是否可以克隆,串行化等一堆额外特性,那是不是还要额外考虑是否可以Closeable, 
接口就不是基于抽象,不是纯粹的接口了。 

对于具体的实现类, java.util.ArrayList,LinkedList,HashMap,HashSet, 有什么特性就实现什么接口,可以实现多个接口即可。实际这些类都实现了Cloneable和Serializable接口
,因为实际应用中集合类很常用,串行化和克隆也常用。

克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。

16:什么是迭代器 (Iterator)?

Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的 迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的 remove(Object Obj)删除,可以通过迭代器的remove()方法删除。

17:Iterator 和 ListIterator 的区别是什么?

 1. iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);

  2. ListIterator有add()方法,可以向List中添加对象,而Iterator不能

  3. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。

  4. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

  5. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。  

18:数组 (Array) 和列表 (ArrayList) 有什么区别?什么时候应该使用 Array 而不是 ArrayList?

1、存储内容比较:

Array 数组可以包含基本类型和对象类型,

ArrayList 却只能包含对象类型。

Array 数组在存放的时候一定是同种类型的元素。ArrayList 就不一定了 。

2、空间大小比较:

Array 数组的空间大小是固定的,所以需要事前确定合适的空间大小。

ArrayList 的空间是动态增长的,而且,每次添加新的元素的时候都会检查内部数组的空间是否足够。

3.方法上的比较:

ArrayList 方法上比 Array 更多样化,比如添加全部 addAll()、删除全部 removeAll()、返回迭代器 iterator() 等。

19:Java 集合类框架的最佳实践有哪些?

.根据应用需要正确选择要使用的集合类型对性能非常重要,比如:假如知道元素的大小是固定的,那么选用Array类型而不是ArrayList类型更为合适。

  2.有些集合类型允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以指定初始容量来避免重新计算hash值或者扩容等。

  3.为了类型安全、可读性和健壮性等原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。

  4.使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。

  5.编程的时候接口优于实现

  6.底层的集合实际上是空的情况下,返回为长度是0的集合或数组而不是null。

20:Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals()?它们有何区别?

Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。

总结如下:

==:

  基本类型:比较的是值是否相同

  引用类型:比较的是地址值是否相同

equals():

  引用类型:默认情况下,比较的是地址值,可进行重写,比较的是对象的成员变量值是否相同

21:Comparable 和 Comparator 接口是干什么的?列出它们的区别

Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明输入对象小于,等于,大于已经存在的对象。

Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。

equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候, 这个方法才返回true。

22:Collection 和 Collections 的区别。

1、Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。 Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

2、Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

区别:Collections 是一个包装类,Collection 表示一组对象,这些对象也称为 collection 的元素。 一些 collection 允许有重复的元素,而另一些则不允许,一些 collection 是有序的,而另一些则是无序的。