面试知识点二:Java集合
18.java 容器都有哪些?
19.Collection 和 Collections 有什么区别?
20.List、Set、Map 之间的区别是什么?
21.HashMap 和 Hashtable 有什么区别?
22.如何决定使用 HashMap 还是 TreeMap?
23.说一下 HashMap 的实现原理?
24.说一下 HashSet 的实现原理?
25.ArrayList 和 LinkedList 的区别是什么?
26.如何实现数组和 List 之间的转换?
27.ArrayList 和 Vector 的区别是什么?
28.Array 和 ArrayList 有何区别?
29.在 Queue 中 poll()和 remove()有什么区别?
30.哪些集合类是线程安全的?
31.迭代器 Iterator 是什么?
32.Iterator 怎么使用?有什么特点?
33.Iterator 和 ListIterator 有什么区别?
34.怎么确保一个集合不能被修改?
18.java 容器都有哪些?
简化图(其中粗线部分是重点的容器)
完整容器分类图
19.Collection 和 Collections 有什么区别?
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
Java中Collection和Collections的区别
20.List、Set、Map 之间的区别是什么?
List 是可重复集合,Set 是不可重复集合,这两个接口都实现了 Collection 父接口。
Map 未继承 Collection,而是独立的接口,Map 是一种把键对象和值对象进行映射的集合,它的每一个元素都包含了一对键对象和值对象,Map 中存储的数据是没有顺序的, 其 key 是不能重复的,它的值是可以有重复的。
面试题之List与set集合,Map集合的区别以及它们的实现类有哪些?
21.HashMap 和 Hashtable 有什么区别?
1、继承的父类不同
Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
2、线程安全性不同
HashMap是线程不安全,Hashtable是线程安全的。
3、是否提供contains方法
HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。Hashtable则保留了 contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
4、key和value是否允许null值
其中key和value都是对象,并且不能包含重复key,但可以包含重复的value。通过上面的ContainsKey方法和ContainsValue的源码我们可以很明显的看出:Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
5、hash值不同
哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值。Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值。Hashtable在求hash值对应的位置索引时,用取模运算,(int index = (hash & 0x7FFFFFFF) % tab.length;)而HashMap在求位置索引时,则用与运算。
6、内部实现使用的数组初始化和扩容方式不同
HashTable中数组默认大小是11,扩容的方式是 old*2+1。HashMap中数组默认大小是16,扩容的方式是为原来的2倍。
22.如何决定使用 HashMap 还是 TreeMap?
HashMap基于散列桶(数组+链表+红黑树)实现;TreeMap基于红黑树实现。
HashMap不支持排序;TreeMap默认是按照Key值升序排序的,可指定排序的比较器,主要用于存入元素时对元素进行自动排序。
HashMap大多数情况下有更好的性能,尤其是读数据。在没有排序要求的情况下,使用HashMap。
23.说一下 HashMap 的实现原理
1、利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
2、存储时,如果出现hash值相同的key,此时有两种情况。(1)如果key相同,则覆盖原始值;(2)如果key不同(出现冲突),则将当前的key-value放入链表中
3、获取时,直接找到hash值对应的下标,在进一步判断key是否相同,从而找到对应值。
4、理解了以上过程就不难明白HashMap是如何解决hash冲突的问题,核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
24.说一下 HashSet 的实现原理?
HashSet实际上是一个HashMap实例,都是一个存放链表的数组。它不保证存储元素的迭代顺序;此类允许使用null元素。HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个固定对象private static final Object PRESENT = new Object();
HashSet中add方法调用的是底层HashMap中的put()方法,而如果是在HashMap中调用put,首先会判断key是否存在,如果key存在则修改value值,如果key不存在这插入这个key-value。而在set中,因为value值没有用,也就不存在修改value值的说法,因此往HashSet中添加元素,首先判断元素(也就是key)是否存在,如果不存在这插入,如果存在着不插入,这样HashSet中就不存在重复值。
所以判断key是否存在就要重写元素的类的equals()和hashCode()方法,当向Set中添加对象时,首先调用此对象所在类的hashCode()方法,计算次对象的哈希值,此哈希值决定了此对象在Set中存放的位置;若此位置没有被存储对象则直接存储,若已有对象则通过对象所在类的equals()比较两个对象是否相同,相同则不能被添加。
25.ArrayList 和 LinkedList 的区别是什么?
1、ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 (LinkedList是双向链表,有next也有previous)
2、顺序插入速度ArrayList会比较快,因为ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置塞一个数据就好了;LinkedList则不同,每次顺序插入的时候LinkedList将new一个对象出来,如果对象比较大,那么new的时间势必会长一点,再加上一些引用赋值的操作,所以顺序插入LinkedList必然慢于ArrayList
3、对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
4、对于插入和删除操作,LinedList比较占优势,只需要改变前后Entry的引用地址。而ArrayList涉及到数据元素的批量复制。
26.如何实现数组和 List 之间的转换?
数组转为List
String[] s = {"a","b","c"};
List<String> list = Arrays.asList(s);
List转为数组
List<String> list=new ArrayList<String>(); String[] strings = new String[list.size()];
27.ArrayList 和 Vector 的区别是什么?
Vector,它是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样,区别在于:
1、Vector是线程安全的,ArrayList是线程非安全的
2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2。ArrayList正常情况下会扩容1.5倍。
ArrayList、Vector、HashMap、HashSet的默认初始容量、加载因子、扩容增量
28.Array 和 ArrayList 有何区别?
数组在内存中是连续存储的,所以它的索引速度是非常的快,但是数组也存在一些不足的地方。比如在数组的两个数据间插入数据也是很麻烦的,还有我们在声明数组的时候,必须同时指明数组的长度,数组的长度过长,会造成内存浪费,数组和长度过短,会造成数据溢出的错误。这样如果在声明数组时我们并不清楚数组的长度,就变的很麻烦了。
ArrayList的大小是按照其中存储的数据来动态扩充的。所以,我们在声明ArrayList对象时并不需要指定它的长度。ArrayList继承了List接口,所以它可以很方便的进行数据的添加,插入和移除。
29.在 Queue 中 poll()和 remove()有什么区别?
remove() 和 poll() 方法都是从队列中删除第一个元素。如果队列元素为空,调用remove() 的行为与 Collection 接口的版本相似会抛出异常,但是新的 poll() 方法在用空集合调用时只是返回 null。因此新的方法更适合容易出现异常条件的情况。
java Queue中 add/offer,element/peek,remove/poll区别
30.哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出
hashtable:就比hashmap多了个线程安全
31.迭代器 Iterator 是什么?
Iterator模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
在Java中Iterator为一个接口,它只提供了迭代了基本规则,在JDK中是这样定义的:对 collection 进行迭代的迭代器。迭代器取代了 Java Collections Framework 中的 Enumeration。迭代器与枚举有两点不同:
1、迭代器允许调用者利用定义良好的语义在迭代期间从迭代器所指向的 collection 移除元素。
2、方法名称得到了改进。
其接口定义如下:
boolean hasNext(); E next(); default void remove()
其中:
next():返回迭代器刚越过的元素的引用,返回值是E泛型,需要强制转换成自己需要的类型
hasNext():判断容器内是否还有可供访问的元素
remove():删除迭代器刚越过的元素
对于我们而言,我们只一般只需使用next()、hasNext()两个方法即可完成迭代。如下:
for(Iterator it = c.iterator(); it.hasNext(); ) { Object o = it.next(); //do something }
前面阐述了Iterator有一个很大的优点,就是我们不必知道集合的内部结构、状态由Iterator来维持,通过统一的方法hasNext()、next()来判断、获取下一个元素,至于具体的内部实现我们就不用关心了。
java中为什么要使用Iterator,或者Iterator的用途是什么
32.Iterator 怎么使用?有什么特点?
使用参考31。
Java中使用Iterator来遍历集合元素,Iterator遍历集合元素有以下几个特点:
Iterator遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出ConcurrentModificationEception的异常。
Iterator遍历集合元素的过程中可以通过remove方法来移除集合中的元素。
Iterator必须依附某个Collection对象而存在,Iterator本身不具有装载数据对象的功能。
Iterator.remove方法删除的是上一次Iterator.next()方法返回的对象。
强调以下next()方法,该方法通过游标指向的形式返回Iterator下一个元素。
33.Iterator 和 ListIterator 有什么区别?
我们在使用List,Set的时候,为了实现对其数据的遍历,我们经常使用到了Iterator(迭代器)。使用迭代器,你不需要干涉其遍历的过程,只需要每次取出一个你想要的数据进行处理就可以了。
但是在使用的时候也是有不同的。List和Set都有iterator()来取得其迭代器。对List来说,你也可以通过listIterator()取得其迭代器,两种迭代器在有些时候是不能通用的,Iterator和ListIterator主要区别在以下方面:
1. ListIterator有add()方法,可以向List中添加对象,而Iterator不能
2. ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
3. ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
4. 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
34.怎么确保一个集合不能被修改?
我们很容易想到用final关键字进行修饰,我们都知道
final关键字可以修饰类,方法,成员变量,final修饰的类不能被继承,final修饰的方法不能被重写,final修饰的成员变量必须初始化值,如果这个成员变量是基本数据类型,表示这个变量的值是不可改变的,如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变的。
那么,我们怎么确保一个集合不能被修改?首先我们要清楚,集合(map,set,list…)都是引用类型,所以我们如果用final修饰的话,集合里面的内容还是可以修改的。
我们用final关键字定义了一个map集合,这时候我们往集合里面传值,第一个键值对1,1;我们再修改后,可以把键为1的值改为100,说明我们是可以修改map集合的值的。
那我们应该怎么做才能确保集合不被修改呢?
我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报 java.lang.UnsupportedOperationException错。
同理:Collections包也提供了对list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)