[Java程序员面试笔记] 面试笔试部分 --Java容器

如果一个类是专门用来存储其他类的对象,则这个类成为容器类,也叫做集合类。Java中有一个容器类的类库,其用途是保存对象或者保存对象的引用。
Java中的容器类主要从两个不同的接口(Collection和Map)派生出来,这两个不同的接口定义了不同的对象存储方式。
1)Collection :定义独立元素的序列
2)Map:定义成对的键值对(Key- Value),一个Map不能包含重复的键(Key)
Java中容器类的基本架构如下图所示:

1 Collection和Iterator

知识点梳理

  • 1 Collection接口
    Collection接口是一类用于保存单一元素的数据结构的接口。Collection接口是容器类的根接口之一,它有3个子接口:List、Set和Queue。
    1)List代表元素有序的、可重复的集合
    2)Set代表元素无序的、不可重复的集合
    3)Queue代表队列集合,它是一种特殊的线性表,只允许在表的前端删除元素,在表的后端插入元素。

List在元素输出时保证了与元素添加时的顺序一致,并允许元素重复,这一点类似于一个线性表(数组或链表)。
Set表示一个无序的不可重复的集合,Set打破了元素添加时的顺序,不保证按照元素添加时的顺序输出,也不进行排序,同时在添加元素的过程中过滤掉了重复元素。
Queue提供了队列的实现方法,满足FIFO(即先进先出)原则。

  • Iterator 接口
    Iterator 接口直译为迭代器,它可以在不知道容器中元素类型的情况下遍历容器中的元素

面试题1 常识性问题

对于Collection和Collections类的区分:
(1)Collection是一类用于实现保存单一元素的数据结构的接口。Collection接口是容器类的根接口之一,是各类容器接口的父接口。它的包结构是java.util.Collection,它是接口而不是类。
(2)**Collections是一个集合或者容易操作的工具类。它是一个实现类,不是一个接口。**它是一个工具类,它的方法都是静态方法,用于操作各个容器类。它的包结构是java.util.Collections.

Java的类命名有这样一个特点,如果一个类名后带s,基本上都是辅助的工具类,
比如容器的辅助工具类Collections,数组的辅助工具类Arrays, 对象工具类Objects, 它们的方法都是静态方法。

面试题2 简述Collection和Collection的区别

  • (1)Collection接口
    **Collection接口是容器类的顶层接口之一,**它的子接口主要有List和Set,所有实现了List或Set接口的容器类,如ArrayList、 LinkedList、HashSet 和TreeSet等,同时也实现了Collection接口。Collection接口定义了这些容器类操作容器元素的基本方法, 比如向某个容器中添加元素、删除元素、遍历元素等。
  • (2)Collections类
    Collections是一个容器框架的工具类,该工具类中的方法都是静态方法,可通过类名直接调用。 通过这些方法可对容器中的元素进行排序、查找、求最大值等辅助操作,因此Collections是容器类中最重要的辅助工具类。

2 HashSet 和 TreeSet

知识点梳理

  • (1) HashSet
    HashSet是java.util包中的类,实现了Set接口,封装了HashMap, 元素是通过HashMap来保存的。
    关于HashSet有以下几点需要补充说明:
    1)HashSet中的元素可以是null,但只能有一个null(因为实现了Set接口,所以不允许有重复的值)。
    2)HashSet是非线程安全的。
    3)插入HashSet中的对象不保证与插入的顺序一致,元素的排列顺序可能改变
    4)向HashSet中添加新的对象时,HashSet类会进行重复对象元素判断;判断添加对象和容器内已有对象是否重复,如果重复则不添加,如果不重复则添加。

  • (2)TreeSet
    TreeSet是java.util包中的类,也实现了Set接口,因此TreeSet中同样不能有重复元素。TreeSet封装了TreeMap,所以是一个有序的容器,容器内的元素是排好序的。
    关于TreeSet,有以下几点需要补充说明:
    1)TreeSet中的元素是一个有序的集合(在插入元素时会进行排序),不允许放入null值
    2)TreeSet是非线程安全的
    3)向TreeSet中添加新的对象时,TreeSet会将添加对象和已有对象进行比较,存在重复对象则不进行添加,不存在重读对象的情况下,新插入对象和已有对象根据比较结果排序再进行存储。

面试题2 Set接口的实现类

Set接口的实现类有哪些?HashSet、TreeSet和LinkedHashSet的区别是什么?TreeSet如何保证有序序列?

  • (1) Set接口的主要实现类
    Set接口的主要实现类包括HashSet、TreeSet、LinkedHashSet、AbstractSet等。
    HashSet是java.util包中的类,它只能存储不重复的对象。HashSet类实现了Iterable、Set等接口,底层使用HashMap来保存数据,子类有LinkedHashSet等。
    TreeSet是AbstractSet的子类,是一个有序的集合,TreeSet是基于TreeMap来实现。
    LinkedHashSet类是HashMap的子类,它的元素也是唯一的,LinkedHashSet是基于HashMap和双向链表的实现类。
    AbstractSet类是一个抽象类,同时实现了Collection 和Set接口,HashSet和TreeSet都是它的子类。

  • (2) HashSet、TreeSet和LinkedHashSet之间的主要区别
    1)HashSet是基于哈希表HashMap来实现的,它包含的是不保证有序的不重复的元素。
    2)TreeSet是基于TreeMap来实现的,而TreeMap基于红黑树算法实现,红黑树是一种平衡的排序二叉树,它包含的是有序且不重复的元素。TreeSet支持两种排序方式:自然排序和定制排序。
    3)LinkedHashSet继承自HashSet,与HashSet相比,它底层是用双向链表实现,用链表记录数据,实现了按照插入的顺序有序,也就是说,遍历序和插入序是一致的。LinkedHashSet在迭代访问Set中全部元素时性能会比HashSet好,但插入元素时性能不如HashSet。

  • (3) TreeSet 保证有序的方式
    TreeSet底层数据结构是一种自平衡的二叉树,即红黑树,红黑树保证了元素的有序性,无论按照前序、中序、后序都可以有序的读取集合中的元素,也就是说,按照红黑树的结点进行存储和取出数据。

3 ArrayList、Vector和LinkedList

知识点梳理

List是用于存放多个元素的容器,它允许有重复的元素,并保证元素之间的先后顺序。List有3个主要的实现类:ArrayList、Vector和LinkedList。

  • (1) ArrayList
    ArrayList类又称为动态数组,该容器类实现了列表的相关操作。ArrayList的内部结构由数组实现,因此可对容器内元素实现快速随机访问。但因为在ArrayList中插入或删除一个元素需要移动其他元素,所以不适合在插入和删除频繁的场景下使用ArrayList。与此同时,ArrayList的容量可以随着元素的增加而自动增加,所以不用担心ArrayList容量不足的问题。另外ArrayList是非线程安全的。

  • (2) Vector
    Vector类又称为向量类,也实现了类似动态数组的功能,内部数据结构也由数组实现。与ArrayList不同的是,Vector是线程安全的,它的方法都是同步方法,所以访问效率低于ArrayList,另外Vector是非泛型集合,可以往其中随意插入不同类的对象,不需要考虑类型和预先选定向量的容量,可方便的进行查找等操作。当然也可以使用Vector的泛型取代非泛型类型(如Vectort)。

  • (3)LinkedList
    LinkedList也是List的一个重要实现类。它的内部数据结构由链表实现、并且是非线程安全的,适合数据的动态插入和删除,插入和删除元素时不需要对数据进行移动,所以插入、删除效率高,但随机访问速度较慢。

面试题1 ArrayList和LinkedList

  • (1)ArrayList 扩容规则
    ArrayList类又称为动态数组,内部数据结构由数组实现,数组的容量可以自动增长,当数组容量不足以存放新增元素时,需要进行数组的扩容,扩容的基本策略如下:
    每次向数组中添加元素时,要检查添加元素后的容量是否超过当前数组的长度,如果没有超过,则添加该元素,不做其他操作;如果超过,每次扩充原容量的1.5倍。

(2)ArrayList和LinkedList 的区别
第一:首先ArrayList和LinkedList都实现了List接口,同时LinkedList也实现Deque接口,这样能将LinkedList当作双端队列使用。
Deque接口的定义如下:

public interface Deque<E> extends Queue<E>

Deque 继承Queue接口,所以LinkedList也实现了Queue接口

第二:ArrayList是可改变大小的数组,底层由数组实现,能够自动扩容。LinkedList是双向链接队列,底层由链表来实现。所以说,ArryaList 是可改变大小的数组,而LinkedList是双向链接队列。

第三:ArrayList是基于数组结果实现的,数组具备高速随机访问的特点,所以ArrayList支持高校的随机元素访问。
LinkedList是基于链表结构实现的,如果想返回某个元素的位置,必须从前往后遍历链表,所以LinkedList不支持高效的随机访问。

第四:LinkedList是链式存储结构,所以插入和删除元素效率较高,因此不需要移动元素。
ArrayList是数组存储结构,所以插入和删除操作都会引起后续元素移动,而且在容量不足时还存在扩容问题。

面试题2 简述ArrayList和Vector 的区别

ArrayList和Vector 的共同点:ArrayList和Vector 都是List的主要实现类,内部数据结构都用数组来实现,都允许对元素快速随机访问。
除此之外, ArrayList和Vector 也存在很多的区别,总结起来有以下几点:

  • (1) 线程安全:
    Vector类是线程安全类,大部分方法都是痛不的,而ArrayList是非线程安全的。所以Vector有较大的系统开销,ArrayList在性能上优于Vector。

  • (2) 容量扩展机制:
    当需要扩容时,ArrayList和Vector 都可以进行自动容量扩展。ArrayList扩展后数组的大小为原数组长度的1.5倍,同时ArrayList可以用构造函数指定数组容量的大小。对于Vector,当它的扩容因子大于0时,新数组长度为原数组长度+扩容因袭,否则扩展后的数组大小为原数组的2倍。

  • (3)类成员属性
    ArrayList有2个属性,即存储数据的数组elementData、存储记录元素个数的size。Vector有3个属性,即存储数据的数组elementData、存储记录元素个数的size,同时还有扩展数组大小的扩展因子capacityIncrement。

4 HashMap 和 Hashtable

HashMap 和 Hashtable是Map接口的主要实现类。

  • HashMap 类
    (1) HashMap 的概念和特点
    HashMap 又称为哈希表,它是根据键key的hashCode值来存储数据的。它存储的是键值对(key- value)映射,具有快速定位的特点。HashMap 继承于AbstractMap, 实现了Map等接口。它的实现是不同步的,因此不是线程安全的。它的key、value都可以为null,而key最多只能出现一个null。同时HashMap中的映射不是有序的(存储不等于插入序)。
    (2)HashMap 的数据结构
    HashMap 的数据结构是由数组+链表+红黑树来实现的。HashMap 底层是一个数组Entry[] table,数组中的每个元素Entry都是一个单向链表的引用,从JDK1.8开始,当链表长度大于8时,链表会调整为红黑树结构。
    (3)HashMap 对象的两个重要属性和扩容
    HashMap 对象有两个重要的属性:初始容量和加载因子。
    初始容量是指HashMap 在创建时的容量,加载因子是HashMap 在其容量自动增加之前可以达到多满的一种尺度。
    HashMap 的初始容量默认值为16,默认加载因子是0.75,当HashMap 中的元素数目超出加载因子与当前容量的乘积时,则要对该HashMap 进行扩容操作,扩容后数组大小为当前的2倍。
    (3)HashMap 的冲突管理
    HashMap 采用“hash算法”来决定每个元素的存储位置,当添加新的元素时,系统会调用hashCode()方法得到一个hashCode值,再根据这个hashCode值决定这个元素在HashMap中的存储位置。当不同的对象的hashCode值相同时,就出现了冲突。
    HashMap 采用链地址法,即用单链表将所有冲突的元素链接起来,通过这种方法来进行冲突管理。当链表的元素个数大于8时,会自动转为红黑树结构,这样会提升查询性能,把顺序搜索链表记录的时间复杂度从O(n)提高到O(logn)。

  • Hashtable类
    Hashtable与HahMap类似,不同的是Hashtable是线程安全的,而且属于遗留类。
    需要注意的是,如果对同步性和遗留代码的兼容性没有特殊要求,建议使用HahMap类,这是因为Hashtable虽然由线程安全的特点,但是效率较低。

HashMap和 Hashtable是Map接口的两个重要实现类,HashMap是Hashtable的轻量级实现。
HashMap是非线程安全的,HashMap的键(key) 和 值(value)都支持null。
Hashtable是线程安全的, Hashtable键(key)和值(value)都不支持null。

面试题2 HashMap为什么要引入红黑树结构

这道题目是目前HashMap笔试面试中考察拼了最高的题目之一,几乎是逢考必有的题目。

HashMap 采用数组和链表相结合的数据结构,底层是一个数组,每个数组元素都是一个链表结构,链表的每个节点就是HashMap中每个元素(键值对)。当要向HashMap在添加一个键值对时,会先调用该键值对的key的hashCode()方法计算出hashCode值,从而得到该元素在数组中的下标。如果数组在该位置上已保存有元素(已存在一个链表),则说明发生了冲突(不同的key值对应了同一个hash值,所以映射的数组下标也相同),接下来就要按照HashMap冲突管理算法进行处理。

HashMap采用链地址法,即用单链表将所有冲突的元素链接起来,通过这种方法来进行冲突管理。但是这个链表并不会无限的增长,当立案标准元素个数大于8时,这个链表会自动转为红黑树结构。
之所以引入红黑树结构是因为在链表中查找每个元素的时间复杂度都是O(n),而在红黑树中查找元素的时间复杂度为O(logn),这样当HashMap中元素量较多并产生了大量Hash冲突时,红黑树的快速增删改查的特点能提高HashMap的性能。

红黑树(Red Black Tree) 是一种自平衡二叉查找树,红黑树用红色和黑色来标记节点,并且有以下三个特点:
1) 根和叶子结点都是黑色的
2)从每个叶子都根的所有路径上不能有两个连续的红色结点
3)从任一结点到它所能到达的叶子结点的所有简单路径都包含相同数目的黑色结点。
以上三个特征保证了红黑树比其他的二叉查找树有更好的结点查找稳定性、查找效率和增删结点的效率。

鉴于以上原因,引入了红黑树来解决HashMap的哈希冲突效率等问题。

问题:红黑树这么好?为什么在元素个数小于8个时还要用链表,而不直接使用红黑树?

回答:当元素数目较少时,链表的效率更高,而红黑树的实现和调整都更复杂,反而会影响整体性能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章