Java面试题——集合(主要包含Collection部分,Map部分单写)

一、集合

 1什么是集合?

  集合是存储数据的容器,这里的数据指的是对象,可以存储不同的对象,并且长度可变。

2、集合与数组的区别?

  • 数组的长度是固定的,集合的长度是可变的
  • 数组可以存储基本数据类型,也可以存储引用数据类型,集合只能存储引用数据类型
  • 数组存储的元素必须是同一个数据类型,集合存储的对象可以是不同类型。  

3、哪些集合是线程安全的?

  SHE+v

  • Stack:堆栈类,先进后出
  • HashTable:就比HashMap多了个线程安全
  • enumeration:枚举,相当于迭代器
  • Vetor就比ArrayList多了个同步机制(线程安全),但因为效率低,已经很少使用了    

二、Colletion接口

1、 Iterator和ListIterator有什么区别? 

  • Iterator可以遍历List和Set的集合,而ListIterator只能遍历List集合
  • Iterator只支持单向遍历,而ListIterator支持双向遍历(前/后)
  • ListIterator实现Iterator接口,又添加了一些额外功能,如添加一个元素,替换一个元素。

2、遍历List的方式

 遍历方法:

  1. for循环遍历:基于计数器,集合外部维护着一个计数器,然后依次读取每个元素,直到读到最后一个元素即可
  2. Iterator遍历:Iterator是面向对象的一种设计模式,目的是屏蔽不同集合的特点,统一遍历集合接口,Collection中采用的迭代器模式
  3. foreach:内部还是使用了迭代器的方式实现,使用时不需要显示的声明迭代器和计数器,优点是代码简洁,不易出错,缺点是,在遍历的时候不能操作集合,如增删改,否则会报并发异常

 最佳方法:Java Collection接口中提供了RandomAccess 接口,用于标记集合是否支持RandomAccess接口

  1. 如果一个集合实现了该接口,则它支持快速随机访问,按位置读取元素的平均时间复杂度是O(1),如ArrayList
  2. 如果集合没有实现该接口,表示不支持RandomAccess接口,如LinkedList

    综上:推荐的做法是,如果集合支持RandomAccess可以使用for遍历,否则使用Iterator/foreach遍历。

3、说下ArrayList的优缺点

 增删慢,查找快

 优点:

  • ArrayList底层采用数组实现,是一种随机访问模式,并且实现了RandomAccess接口,因此查找的速度非常快
  • ArrayList顺序添加元素非常方便  

 缺点:

  • 删除元素的时候需要做元素的复制操作,如果元素很多的话,很消耗性能
  • 插入元素的时候,同样遇到上面的问题,移动元素,让出插入位置

4、数组与List之间的转换

  • 数组转List:使用Arrays.asList方法
  • List转数组:使用toArray方法
 List<Integer> list = new ArrayList<Integer>();
 list.add(11);
 list.add(22);
 list.add(33);

 Object[] objects = list.toArray();  //list->[]

 String []strs = new String[]{
   "aa","bb"};
 List<String> strings = Arrays.asList(strs);   //[]->list

 

5、ArrayList与LinkedList之间的区别

  • 数据结构:ArrayList底层采用的是数组实现,LinkedList底层采用的是双向链表实现
  • 数据访问:ArrayList要优于LinkedList,因为ArrayList底层采用数组实现,LinkedList采用线性的链式存储结构,需要移动指针来访问。
  • 增删效率:LinkedList要优于ArrayList,ArrayList增删要复制元素,影响其它数组元素的下标,而LinkedList只需要移动指针。
  • 线程安全:ArrayList和LinkedList都是线程不同步的,都不能保证线程安全。
  • 内存空间:ArrayList 内存空间会耗费在列表后面的预留空间;LinkedList 内存空间会耗费在每个数据要多存储一个前驱和后继。

  综上:如果对集合元素增删比较频繁的话推荐使用LinkedList,查找比较频繁的话推荐使用ArrayList。  

6、多线程场景下使用ArrayList

  由于ArrayList非线程安全,多线程的场景下使用ArrayList,可能会出现问题,可以使用Collections的synchronizedList方法将其转成一个线程安全的List再使用

  List<Integer> list = new ArrayList<Integer>();
  list.add(11);
  list.add(22);
  list.add(33);

  List<Integer> integers = Collections.synchronizedList(list);
  integers.add(44);

7、List与Set的区别

 List和Set都继承与Collection接口

 特点

  List:一个有序(存入和取出的顺序一致)的容器,可以插入Null值,可以有重复元素,元素都有索引,常用的实现类有ArrayList,LinkedList,Vector.

  Set:一个无序(存入和取出的顺序可能不一致)的容器,不允许重复元素,只允许存入一个NULL值,必须保证元素的的唯一性,Set常见的实现类有HashSet,LinkedHashSet,TreeSet.

 遍历

  List因为是有序的,所以支持使用for,也就是通过下标进行遍历,迭代器遍历,而Set只支持迭代器遍历,因为他是无序的,无法通过下标来获取值。

8、说一下HashSet的实现原理,

  HashSet是基于HashMap实现的HashSet的值存放在HashMap的 key 上,相关的HashSet的操作,基本上都是直接调用底层的HashMap的相关方法来实现的,并且HashSet不可重复,是无序的

9、说一下HashSet如何检查重复,如何保证数据不可重复?

  说在前面:HashSet是Set的实现,不是Map接口下的实现。。。。
  HashSet的add方法底层使用HashMap的put方法来插入数据,判断数据是否存在的依据,不仅要比较hashcode值还要equals方法。
  HashSet部分源码:

private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;

public HashSet() {
   
    map = new HashMap<>();
}

public boolean add(E e) {
   
    // 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
    return map.put(e, PRESENT)==null;
}

 

  HashMap的key是唯一的,由源码可知HashSet的key就是使用HashMap的key,如果HashSet中遇到相同的key时,对于新的value就会覆盖旧的value,返回旧的value,所以是不会重复的。
  HashMap比较key是否相等是先比较hashcode,再equals

  <mark>回顾hashcode和equals。</mark>
  Java对hashcode()和equals()有以下规定:

  1. 如果两个对象相等,则它们的hashcode()一定相等
  2. 如果两个对象相等,则它们的equals也返回true
  3. 如果两个对象的hashcode相等,但是它们不一定相等。

  综上我们再重写equals时,规定也必须重写hashcode
  hashcode()的默认行为是对堆上的对象产生独特值,如果没有重写hashcode(),无论如何两个对象都不会相等(即使两个对象指向相同的数据)
  == 与 equals的区别

    • == 是判断两个变量或实例是否指向相同的内存地址,equals是判断两个变量或实例指向的内存地址对应的值是否相等
    • ==是对内存地址进行比较,equals是对字符串进行比较
    • ==是比较引用是否相同,equals是值是否相同

10、 HashSet和HashMap的区别?

  

HashMapHashSet
实现Map接口 实现Set接口
存储键值对 存储对象
调用put方法向map中添加元素 调用add方法向Set中添加元素
HashMap使用key来计算hashcode值 HashSet使用成员对象来计算hashcode值,如果相等则在调用equals判断,如果对象不同的话,返回false
HashMap相比HashSet快,因为它是使用唯一的key来获取值 HashSet比HashMap稍慢点

11、comparable和comparator的区别

  1. comparable在java.lang.Comparable它有一个comparaTo方法用于排序;comparator在java.util包中,它有一个compare(Object obj1,Object obj2)用于排序
  2. 如果实现类没有实现comparable接口或者实现了comparable接口但是不满足于comparable接口提供的compareTo方法,我们可以自定义比较方法,只需要实现Comparator接口重写compare(Object obj1,Object obj2)方法。
  3. comparable需要比较对象来实现接口,使用对象调用方法来比较对象,需要改变对象内部结构(重写compareTo),耦合度高。comparator相当于一个通用的比较工具接口,需要定制一个比较类去实现它,重写里面的compare方法,方法参数即比较的对象,对象不用做任何的改变,解耦。

12、Collection和Collections的区别

  Collectionjava.utils.Collectioni的集合接口,也是集合的顶级接口,它提供了对集合对象的基本操作和通用方法,Collection类在Java类库中有很多体现,直接实现类有List,Set。
  Collections:是集合类的一个工具类,提供了一系列的静态方法,用于对集合中的元素进行排序,搜索等操作

 

三、Map

1、Hashtable和ConcurrentHashMap有什么区别? 

  锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。 

  ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。

  ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。

2、Vector、Collections.synchronizedList 和CopyOnWriteArrayList有什么区别? 

  ArrayList是线程不安全的,但是有几个与它结构相似的容器是线程安全的: Vector、Collections.synchronizedList 和CopyOnWriteArrayList

  下面分别说一下这几个线程安全的容器的区别:

  Vector:它几乎在每个方法声明处都加了synchronized关键字来使容器安全

  Collections.synchronizedList:也是几乎都是每个方法都加上synchronized关键字的,只不过它不是加在方法的声明处,而是方法的内部

  CopyOnWriteArrayList

原文入口:https://blog.nowcoder.net/n/62973d62e5214fc4aebc1df8cdd571dd#11__5

 

posted @ 2021-07-08 10:50  潜跃  阅读(212)  评论(0编辑  收藏  举报