java 集合类基础问题汇总

时间:2023-02-15 00:16:41

 1、Java集合类框架的基本接口有哪些?

参考答案

集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。
Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:
Collection:代表一组对象,每一个对象都是它的子元素。
Set:不包含重复元素的Collection。
List:有顺序的collection,并且可以包含重复元素。
Map:可以把键(key)映射到值(value)的对象,键不能重复。
 

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

参考答案

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

3、什么是迭代器(Iterator)?

参考答案

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

4、Iterator和ListIterator的区别是什么?

参考答案

下面列出了他们的区别:
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
 

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

参考答案

Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
 

6、Java中的HashMap的工作原理是什么?

参考答案

Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
 

7、hashCode()和equals()方法的重要性体现在什么地方?

参考答案

Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。
 
 

8、HashMap和Hashtable有什么区别?

参考答案

HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
HashMap允许键和值是null,而Hashtable不允许键或者值是null。
Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
一般认为Hashtable是一个遗留的类。

适用场景分析:

HashMap和HashTable:HashMap去掉了HashTable的contains方法,但是加上了containsValue()和containsKey()方法。HashTable同步的,而HashMap是非同步的,效率上比HashTable要高。HashMap允许空键值,而HashTable不允许。

HashMap:适用于Map中插入、删除和定位元素。

Treemap:适用于按自然顺序或自定义顺序遍历键(key)。

 

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

参考答案

下面列出了Array和ArrayList的不同点:
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
 

10、ArrayList和LinkedList有什么区别?

参考答案

ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
也可以参考ArrayList vs. LinkedList。

适用场景分析:

当需要对数据进行对此访问的情况下选用ArrayList,当需要对数据进行多次增加删除修改时采用LinkedList。

 

11、Comparable和Comparator接口是干什么的?列出它们的区别。

参考答案

Java提供了只包含一个compareTo()方法的Comparable接口。这个方法可以个给两个对象排序。具体来说,它返回负数,0,正数来表明已经存在的对象小于,等于,大于输入对象。
Java提供了包含compare()和equals()两个方法的Comparator接口。compare()方法用来给两个输入参数排序,返回负数,0,正数表明第一个参数是小于,等于,大于第二个参数。equals()方法需要一个对象作为参数,它用来决定输入参数是否和comparator相等。只有当输入参数也是一个comparator并且输入参数和当前comparator的排序结果是相同的时候,这个方法才返回true。
 

12、什么是Java优先级队列(Priority Queue)?

参考答案

PriorityQueue是一个基于优先级堆的*队列,它的元素是按照自然顺序(natural order)排序的。在创建的时候,我们可以给它提供一个负责给元素排序的比较器。PriorityQueue不允许null值,因为他们没有自然顺序,或者说他们没有任何的相关联的比较器。最后,PriorityQueue不是线程安全的,入队和出队的时间复杂度是O(log(n))。
 

13、你了解大O符号(big-O notation)么?你能给出不同数据结构的例子么?

参考答案

大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是一个渐进上界 。
大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号可以对大量数据的性能给出一个很好的说明。
 

14、如何权衡是使用无序的数组还是有序的数组?

参考答案

有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。
 

15、Java集合类框架的最佳实践有哪些?

参考答案

根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的数量是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。
有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。
为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。
使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。
编程的时候接口优于实现。
底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。
 

16、Enumeration接口和Iterator接口的区别有哪些?

参考答案

Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。

 

17、HashSet和TreeSet有什么区别?

参考答案

1.TreeSet 是二叉树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不允许放入null值。

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

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

HashSet是由一个hash表来实现的,因此,它的元素是无序的。add(),remove(),contains()方法的时间复杂度是O(1)。
另一方面,TreeSet是由一个树形的结构来实现的,它里面的元素是有序的。因此,add(),remove(),contains()方法的时间复杂度是O(logn)。
 

适用场景分析:

HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。为快速查找而设计的Set,我们通常都应该使用HashSet,在我们需要排序的功能时,我们才使用TreeSet。

 

18、集合框架,list,map,set都有哪些具体的实现类,区别都是什么?

参考答案 

1.List,Set都是继承自Collection接口,Map则不是;

2.List特点:元素有放入顺序,元素可重复;

Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法;

另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值)。

 

3.Set和List对比:

Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。

List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。

 

4.Map适合储存键值对的数据。

5.线程安全集合类与非线程安全集合类

 

LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;

HashMap是非线程安全的,HashTable是线程安全的;

StringBuilder是非线程安全的,StringBuffer是线程安全的。

 

19、ArrayList与Vector的区别和适用场景

 参考答案 

ArrayList有三个构造方法:

public ArrayList(int initialCapacity)//构造一个具有指定初始容量的空列表。    
public ArrayList()//构造一个初始容量为10的空列表。
public ArrayList(Collection<? extends E> c)//构造一个包含指定 collection 的元素的列表  

Vector有四个构造方法:

public Vector() // 使用指定的初始容量和等于零的容量增量构造一个空向量。    
public Vector(int initialCapacity) // 构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。
public Vector(Collection<? extends E> c)// 构造一个包含指定 collection 中的元素的向量
public Vector(int initialCapacity,int capacityIncrement)//使用指定的初始容量和容量增量构造一个空的向量

ArrayList和Vector都是用数组实现的,主要有这么三个区别:

1).Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不确定的结果。而ArrayList不是,这个可以从源码中看出,Vector类中的方法很多有synchronized进行修饰,这样就导致了Vector在效率上无法与ArrayList相比;

2).两个都是采用的线性连续空间存储元素,但是当空间不足的时候,两个类的增加方式是不同。

3).Vector可以设置增长因子,而ArrayList不可以。

4).Vector是一种老的动态数组,是线程同步的,效率很低,一般不赞成使用。

 

适用场景:

1.Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。

2.如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。



class A {}
class B extends A {}
class C extends A {}
class D extends B {}
Which four statements are true ?
  • The type List<A>is assignable to List.
  • The type List<B>is assignable to List<A>.
  • The type List<Object>is assignable to List<?>.
  • The type List<D>is assignable to List<?extends B>.
  • The type List<?extends A>is assignable to List<A>.
  • The type List<Object>is assignable to any List reference.
  • The type List<?extends B>is assignable to List<?extends A>.
查看正确选项
正确答案:A C D G
 
1. 只看尖括号里边的!!明确点和范围两个概念
2. 如果尖括号里的是一个类,那么尖括号里的就是一个点,比如List<A>,List<B>,List<Object>
3. 如果尖括号里面带有问号,那么代表一个范围,<? extends A> 代表小于等于A的范围,<? super A>代表大于等于A的范围,<?>代表全部范围
4. 尖括号里的所有点之间互相赋值都是错,除非是俩相同的点
5. 尖括号小范围赋值给大范围,对,大范围赋值给小范围,错。如果某点包含在某个范围里,那么可以赋值,否则,不能赋值
6. List<?>和List 是相等的,都代表最大范围
----------------------------------------------------------------------------------
7.补充:List既是点也是范围,当表示范围时,表示最大范围
 
下面贴代码:(本来用的Eclipse截图的,结果好像网站出来Bug,不能传图了o(╯□╰)o
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
         List<A> a;
         List list;
         list = a;    //A对,因为List就是List<?>,代表最大的范围,A只是其中的一个点,肯定被包含在内
         List<B> b;
         a = b;       //B错,点之间不能相互赋值
         List<?> qm;
         List<Object> o;
         qm = o;      //C对,List<?>代表最大的范围,List<Object>只是一个点,肯定被包含在内
         List<D> d;
         List<?  extends B> downB;
         downB = d;   //D对,List<? extends B>代表小于等于B的范围,List<D>是一个点,在其中
         List<? extends A> downA;
         a = downA;   //E错,范围不能赋值给点
         a = o;       //F错,List<Object>只是一个点
         downA = downB;   //G对,小于等于A的范围包含小于等于B的范围,因为B本来就比A小,B时A的子类嘛
     }

 

大概的观点就是:java数组具有协变性,而java集合不是协变的;
什么意思呢?我举几个例子:
1. 假设有一个函数 fun(Animal animal),如果我们传入一个Dog d 对象进去,编译器是不会报错的,这是多态的概念;
2. 假设有一个函数 fun(Animal[] animals),如果我们传如一个Dog[] dogs数组进去,编译器也不会报错,这就是数组的协变性;
3. 假设有一个函数 fun(List<Animal>  animal), 如果我们传如一个List <Dog>  dog 集合进去,编译器就会报错了,这就是集合泛型的不变性;
那么该怎么办呢?我们可以将泛型改成这样 fun (List <? extends Animal> ),这样之后,当我们再 传入一个List <Dog>  dog 集合进去,编译器就就不会报错了。也就是说可以传入包含Animal的子类的List了。

 

 

在Java中,关于HashMap类的描述,以下错误的是()?

  • HashMap能够保证其中元素的顺序
  • HashMap允许将null用作值
  • HashMap允许将null用作键
  • HashMap使用键/值得形式保存数据
参考答案:A
HashMap的底层是由数组加链表实现的,对于每一个key值,都需要计算哈希值,然后通过哈希值来确定顺序,并不是按照加入顺序来存放的,因此可以认为是无序的,但不管是有序还是无序,它都一个自己的顺序。故A错。
最开始有Hashtable,Hashtable是不允许key和value的值为空的,但后来开发者认为有时候也会有key值为空的情况,因为可以允许null为空,通过查看HashMap的源代码就知道:if(key = null) {putForNullKey(value);};
Map底层都是用key/value键值对的形式存放的
 
 
在jdk1.5的环境下,有如下4条语句:
Integer i01 =  59 ;
int i02 =  59 ;
Integer i03 =Integer.valueOf( 59 );
Integer i04 =  new Integer( 59 )。
 
以下输出结果为false的是:

正确答案: C   

System.out.println(i01== i02);
System.out.println(i01== i03);
System.out.println(i03== i04);
System.out.println(i02== i04);

Integer i01=59 的时候,会调用 Integer 的 valueOf 方法,

1
2
3
4
5
   public static Integer valueOf( int i) {
      assert IntegerCache.high>=  127 ;
      if (i >= IntegerCache.low&& i <= IntegerCache.high)
      return IntegerCache.cache[i+ (-IntegerCache.low)];
      return new Integer(i); }

这个方法就是返回一个 Integer 对象,只是在返回之前,看作了一个判断,判断当前 i 的值是否在 [-128,127] 区别,且 IntegerCache 中是否存在此对象,如果存在,则直接返回引用,否则,创建一个新的对象。

在这里的话,因为程序初次运行,没有 59 ,所以,直接创建了一个新的对象。

int i02=59 ,这是一个基本类型,存储在栈中。

Integer i03 =Integer.valueOf(59); 因为 IntegerCache 中已经存在此对象,所以,直接返回引用。 

Integer i04 = new Integer(59) ;直接创建一个新的对象。

System. out .println(i01== i02); i01 是 Integer 对象, i02 是 int ,这里比较的不是地址,而是值。 Integer 会自动拆箱成 int ,然后进行值的比较。所以,为真。

System. out .println(i01== i03); 因为 i03 返回的是 i01 的引用,所以,为真。

System. out .println(i03==i04); 因为 i04 是重新创建的对象,所以 i03,i04 是指向不同的对象,因此比较结果为假。

System. out .println(i02== i04); 因为 i02 是基本类型,所以此时 i04 会自动拆箱,进行值比较,所以,结果为真。