文章目录
1.List,Map,Set存取的特点
- List
- 可以有重复元素
- 以特定索引保存
- Map和Set
- 可以一对一和多对一
- 有哈希存储和排序树两个版本
2.ArrayList、Vector、LinkedList的存储性能和特性
- ArrayList 和Vector
- 都是使用数组方式存储数据
- 都允许直接按序号索引元素
- Vector是线程安全的容器,添加了synchronized修饰,但性能上较ArrayList差
- LinkedList使用双向链表实现存储
- 可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高
- 插入数据时只需要记录本项的前后项即可,所以插入速度较快
3.ArrayList和LinkedList的区别
都实现了List接口,LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
- ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
- LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
4.HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
4.1继承的父类不同
Hashtable继承自Dictionary类。
HashMap继承自AbstractMap类。
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {}
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {}
4.2线程安全性不同
- HashMap是线程不安全的
- HashTable是线程安全的
public synchronized void putAll(Map<? extends K, ? extends V> t) {
for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
put(e.getKey(), e.getValue());
}
/**
* Clears this hashtable so that it contains no keys.
*/
public synchronized void clear() {
Entry<?,?> tab[] = table;
modCount++;
for (int index = tab.length; --index >= 0; )
tab[index] = null;
count = 0;
}
4.3key和value是否允许null值
- Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出
NullPointerException
异常,这是JDK的规范规定的。 - HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
4.4内部实现使用的数组初始化和扩容方式不同
-
HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
-
Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
- 将哈希表的大小固定为了2的幂,因为是取模得到索引值,故这样取模时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
- 扩容resize()的时候,原来哈希表中,有接近一半的节点的下标是不变的,而另外的一半的下标为 原来的length + 原来的下标。具体要看hash值对应扩容后的某一位是0还是1.
4.5迭代器不同
- HashMap 中的 Iterator 迭代器是 fail-fast 的
- Hashtable 的 Enumerator 不是 fail-fast 的。
快速失败(fail—fast)是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception
。
5.快速失败(fail-fast)和安全失败(fail-safe)的区别
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。
快速失败(fail-fast )是java集合中的一种机制,在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加,删除,修改),则会抛出
ConcurrentModification Exception。
- java.util包下面的所有的集合类都是快速失败的
- java.util.concurrent包下面的所有的类都是安全失败的。
- 快速失败的迭代器会抛出
ConcurrentModificationException
异常,而安全失败的迭代器永远不会抛出这样的异常。
找一下HashMap的源码,有一个int 类型的变量modCount。
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
-
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。
-
集合在被遍历期间如果内容发生变化,就会改变modCount的值。
-
每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
Tip:这里异常的抛出条件是检测到 modCount!=expectedmodCount
这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。
因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
6.Iterator和ListIterator的区别
- Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
- Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
- ListIterator继承了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
6.1ListIterator 继承自 Iterator 接口,在 Iterator 的基础上增加了 6 个方法:
- void hasPrevious()
- 判断游标前面是否有元素;
- Object previous()
- 返回游标前面的元素,同时游标前移一位。游标前没有元素就报 java.util.NoSuchElementException 的错,所以使用前最好判断一下;
- int nextIndex()
- 返回游标后边元素的索引位置,初始为 0 ;遍历 N 个元素结束时为 N;
- int previousIndex()
- 返回游标前面元素的位置,初始时为 -1,同时报
java.util.NoSuchElementException
错;
- 返回游标前面元素的位置,初始时为 -1,同时报
- void add(E)
- 在游标 前面 插入一个元素
注意,是前面
- 在游标 前面 插入一个元素
- void set(E)
- 更新迭代器最后一次操作的元素为 E,也就是更新最后一次调用 next() 或者 previous() 返回的元素。
- 注意,当没有迭代,也就是没有调用 next() 或者 previous() 直接调用 set 时会报
java.lang.IllegalStateException
错;
- void remove()
- 删除迭代器最后一次操作的元素,注意事项和 set 一样。
6.2ListIterator 有两种获取方式
- List.listIterator()
- List.listIterator(int location)
第二种可以指定 游标的所在位置。
7.什么是迭代器
- Iterator提供了统一遍历操作集合元素的统一接口, Collection接口也是继承Iterable接口.
每个集合都通过实现Iterable接口中iterator()方法返回Iterator接口的实例,然后对集合的元素进行迭代操作.
有一点需要注意的是:在迭代元素的时候不能通过集合的方法删除元素, 否则会抛出ConcurrentModificationException
异常. 但是可以通过Iterator接口中的remove()方法进行删除.
8.类和接口的关系
- 类和类:类继承类,单继承,多层继承
- 类和接口:类实现接口,可实现多个接口,接口名之间用逗号隔开
- 接口和接口:接口继承接口,多继承,为了弥补java中类的单继承特点