1. Java集合类框架的基本接口有哪些?
Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。
Java集合类里面最基本的接口有:
1 Collection:代表一组对象,每一个对象都是它的子元素。
2 Set:不包含重复元素的Collection。
3 List:有顺序的collection,并且可以包含重复元素。
4 Map:可以把键(key)映射到值(value)的对象,键不能重复。"
2. 为什么Java集合类没有实现Cloneable和Serializable接口?
1.Cloneable接口作用是将一个对象的属性值复制给另一个对象,而不是对象的一个引用。
2.Serializable接口作用(这个罗嗦一下)
#序列化的用途
1.有时候,如果想让一个对象持久的存储下来(存到磁盘),或者是进行远程的对象调用,那就要使用序列化实现这些作用。我们必须对所有支持持久化存储的类实现Serializable接口,读取的时候也要进行反序列化。
2.对于jvm来说,进行持久化的类必须有个标记,就是实现Serializable接口,关联serialVersionUID,这个变量就是在反序列话中确定用那个类加载这个对象。
3.值得主意的是,持久化的数据都是存在在java堆中,static类型的数据存在在方法区中,不能被持久化。如果不想让某个成员变量持久化,变量前面用transient关键字
4.当然序列化的那个serialVersionUID这个还可以进行自定义
#为什么集合类中不实现上面两个接口呢?
其实不难看出,Cloneable是复制对象的,序列化也是针对对象的操作,集合类只是管理对象的一个工具,就好比说list能够线性的管理对象,set集合能够对对象去重等,这些集合类都是针对与为管理对象而产生的。
其实,着两个接口都是针对真是的对象,而不是集合类这样的管理对象的对象。这个从语义上就是集合类的Cloneable接口和Serializable接口
应该又集合中具体的类型实现,而不是又集合类来实现序列化。
假设集合类实现了这两个接口,如果我要生成一个不需要序列化,不需要clone的集合,那么集合类就强行实现,这样有违集合的设计原则。
3. Java中的HashMap的工作原理是什么?
在 Java 中,HashMap 属于常用的基于哈希表实现的键值对存储结构,它采用了数组+链表/红黑树的方式进行实现。下面将从以下几个方面介绍 HashMap 的实现原理:哈希函数、数组+链表的实现、扩容机制。
一、哈希函数
HashMap 的核心思想是哈希映射,即将任意长度的输入(即键)通过哈希函数变换成固定长度的输出(即该键在数组中的索引位置),并将该键和值存储到找到的索引位置处。具体来说,哈希函数需要满足以下两个条件:
散列均匀。这意味着不同的键应该有不同的哈希值,并且此哈希值在数组中分布均匀,也就是不要让大量哈希值都集中到一个区域。
计算速度快。为了避免计算哈希值过于缓慢,需要使用高效的哈希函数。
在 Java 中,Java 8 以前使用的是传统的拉链法解决哈希冲突(即多个键映射到同一个数组下标的情况),而 Java 8 之后为了进一步提升性能,采用了链表和红黑树相结合的方式来处理哈希冲突。
二、数组+链表的实现
HashMap 的内部结构是一个数组,数组中每个元素称为桶。桶里放的元素类型是 Entry 类型的对象,该类包含了键和值。当 HashMap 中加入一个键值对时,它会首先根据键获取其哈希码(即通过根据 hash() 方法计算得到),然后通过这个哈希码在数组中找到相应桶,并把键值对存储在桶中。
如果多个键映射到同一索引位置上,就需要通过链表将它们串联起来,而 JDK 1.8 之前版本中采用的则是最基础的链式结构。但随着链表长度的增加,查询效率会逐渐变低,甚至还可能造成链表成环并导致死循环等情况,因此,Java 8 引入了一种新的机制:当链表长度超过阈值(默认为 8)时,会将链表转化为红黑树,以提高查询效率。在使用红黑树优化后,HashMap 的查询性能会更进一步的提升。
4. HashMap和Hashtable有什么区别?
HashMap和Hashtable区别:
1:HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
2:HashMap允许键和值是null,而Hashtable不允许键或者值是null。
3:Hashtable是同步的,而HashMap不是。因此,HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
4:HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。另一方面,Hashtable提供了对键的列举(Enumeration)。
5:一般认为Hashtable是一个遗留的类。
5. Java如何权衡是使用无序的数组还是有序的数组?
有序数组最大的好处在于查找的时间复杂度是O(log n),而无序数组是O(n)。
有序数组的缺点是插入操作的时间复杂度是O(n),因为值大的元素需要往后移动来给新元素腾位置。相反,无序数组的插入时间复杂度是常量O(1)。"
6. Java集合类框架的最佳实践有哪些?
Java 集合框架是 Java 编程的关键元素。为了有效地使用 Java 集合框架,请考虑利用增强的 for 循环、泛型、避免原始类型以及选择正确的集合等因素。
1:为任务选择正确的集合
每个集合类都有其自己独特的一组品质,并且被设计用于特定的功能。以下是对每种集合的一些描述:
List:ArrayList 类是 Java 中使用最广泛的列表实现,在未知集合有多大时提供可调整大小的数组。
Set:HashSet 类是 Java 中最流行的集合实现,通过基于哈希表的实现提供唯一性。
Queue:LinkedList 类是最流行的 Java 队列实现,允许按特定顺序访问元素。
Map:Java 的 HashMap 类是最流行的映射实现,用于根据不同的键存储和检索数据。
选择系列时要考虑的因素
数据类型:根据要处理和存储的数据类型,不同的集合可能更合适。
排序:在安排重要项目时,列表或队列比集合或地图更好。
重复元素:如果不允许重复元素,则集合或映射可能是比列表或队列更好的选择。
性能:不同集合之间性能差异的特征。通过选择正确的集合,您可以提高代码的性能。
不同集合的用例示例
列表:列表允许存储和修改有序数据,例如待办事项列表或购物清单。
集:集可用于创建唯一的项目,例如电子邮件地址。
队列:队列可用于按特定顺序访问元素,例如按接收顺序处理作业。
地图:地图可用于基于唯一键(例如用户首选项)存储和访问数据。
考虑到数据类型、排序、重复元素和性能要求,为 Java 应用程序选择正确的集合至关重要。这将提高代码的有效性和效率。
2:使用正确的方法和接口
日常开发过程中,我们如何使用集合框架提供的各种方法和接口,以及有关如何使用它们的一些技巧。
(1)选择正确的集合:集合框架提供了多种集合类型来提高代码速度和可读性,例如列表、集合、队列、映射和双端队列。
(2)使用迭代器:迭代器对于浏览集合至关重要,但如果修改,它们可能会很快崩溃并抛出 ConcurrentModificationException。使用写时复制数组列表或并发哈希映射来阻止这种情况。
(3)使用 Lambda 表达式: Java 8 中的 Lambda 表达式允许程序员编写可用作方法参数的代码,并可与 Stream API 的 filter() 和 map() 方法结合使用来处理集合。
(4)使用 Stream API: Stream API 是 Java 8 中的一项强大功能,它支持函数式集合处理、可并行化和惰性处理,从而带来更好的性能。
(5)使用泛型: 泛型是 Java 5 中引入的一项强大功能,允许您编写类型安全的代码。它们在处理集合时特别有用,因为它们允许您指定集合可以包含的元素类型。要使用泛型,使用通配符运算符很重要。
Java集合框架提供了方法和接口来提高代码效率、可读性和可维护性。迭代器、Lambda 表达式、Stream API 和泛型可用于提高性能并避免常见陷阱。
2:最佳实践:
1 根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:假如元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList。
2 有些集合类允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以设置初始容量来避免重新计算hash值或者是扩容。
3 为了类型安全,可读性和健壮性的原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。
4 使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。
5 编程的时候接口优于实现。
6 底层的集合实际上是空的情况下,返回长度是0的集合或者是数组,不要返回null。"
7. HashSet和TreeSet有什么区别?
1、速度和内部实现不同
HashSet:用于搜索,插入和删除等操作。这些操作平均需要花费固定时间。HashSet比TreeSet快。HashSet是使用哈希表实现的。
TreeSet:TreeSet以O(Log n)进行搜索,插入和删除,该值高于HashSet。但是TreeSet保留排序的数据。此外,它支持诸如high()(返回最低的较高元素),floor(),ceiling()等操作。这些操作在TreeSet中也是O(Log n),在HashSet中不受支持。 TreeSet是使用自平衡二进制搜索树(红黑树)实现的。 TreeSet由Java中的TreeMap支持。
2、排序方式不同
HashSet中的元素未排序。 TreeSet按照Java中的Comparable或Comparator方法定义的排序顺序维护对象。默认情况下,TreeSet元素以升序排序。它提供了多种方法来处理有序集,例如first(),last(),headSet(),tailSet()等。
3、空对象不同
HashSet允许空对象。 TreeSet不允许null对象并抛出NullPointerException,为什么,因为TreeSet使用compareTo()方法比较键,而compareTo()会抛出java.lang.NullPointerException。
4、比较方式不同
HashSet使用equals()方法比较Set中的两个对象并检测重复项。 TreeSet使用compareTo()方法实现相同目的。如果equals()和compareTo()不一致,即对于两个相等的对象,equals应该返回true,而compareTo()应该返回零,这将打破Set接口的协定,并允许在Set实现中重复使用,例如TreeSet。
如果要排序的Set,较好将元素添加到HashSet中,然后将其转换为TreeSet,而不是创建TreeSet并向其中添加元素。
8. Enumeration接口和Iterator接口的区别有哪些?
Enumeration速度是Iterator的2倍,同时占用更少的内存。但是,Iterator远远比Enumeration安全,因为其他线程不能够修改正在被iterator遍历的集合里面的对象。同时,Iterator允许调用者删除底层集合里面的元素,这对Enumeration来说是不可能的。
9. 请说明 Collection和Collections的区别?
Collection是集合类的父类,继承它的主要由set和list
Collections是针对集合类的帮助类,它提供了一系列针对集合的搜索,排序,线程安全化等操作
Collection是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供最大化的统一操作方式。
以下接口实现了Collection接口:
map、set、list、vector
Collections是一个包装类。它包含各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
(1)排序(Sort)
(2)反转(Reverse)
(3)替换所有元素(Fill)
(4)拷贝(copy)
(5)返回Collections中最小元素(min)
(6)返回Collections中最小元素(max)
10. 简述 WeakHashMap 的工作原理 ?
WeakHashMap 继承于AbstractMap,实现了Map接口。
和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
不过WeakHashMap的键是“弱键”。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
这个“弱键”的原理呢?大致上就是,通过WeakReference和ReferenceQueue实现的。 WeakHashMap的key是“弱键”,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。实现步骤是:
(01) 新建WeakHashMap,将“键值对”添加到WeakHashMap中。
实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
这就是“弱键”如何被自动从WeakHashMap中删除的步骤了。
和HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap
既然有WeakHashMap,那么有WeakHashSet吗? java collections包是没有直接提供WeakHashSet的。
我们可以通过Collections.newSetFromMap(Map map)方法可以将任何 Map包装成一个Set。源码如下
public static Set newSetFromMap(Map map) {
return new SetFromMap<>(map);
}
/**
- @serial include
*/
private static class SetFromMap extends AbstractSet
implements Set, Serializable {
private final Map m;
// The backing map
private transient Set s;
// Its keySet
SetFromMap(Map map) {
if (!map.isEmpty())
throw new IllegalArgumentException(“Map is non-empty”);
m = map;
s = map.keySet();
}
public void clear() {
m.clear();
}
public int size() {
return m.size();
}
public boolean isEmpty() {
return m.isEmpty();
}
public boolean contains(Object o) {
return m.containsKey(o);
}
public boolean remove(Object o) {
return m.remove(o) != null;
}
public boolean add(E e) {
return m.put(e, Boolean.TRUE) == null;
}
public Iterator iterator() {
return s.iterator();
}
public Object[] toArray() {
return s.toArray();
}
public T[] toArray(T[] a) {
return s.toArray(a);
}
public String toString() {
return s.toString();
}
public int hashCode() {
return s.hashCode();
}
public boolean equals(Object o) {
return o == this || s.equals(o);
}
public boolean containsAll(Collection c) {
return s.containsAll©;
}
public boolean removeAll(Collection c) {
return s.removeAll©;
}
public boolean retainAll(Collection c) {
return s.retainAll©;
}
11. List、Set、Map 和 Queue 之间的区别?
1.List(列表): -允许重复元素。
-具有按照元素插入顺序维护的有序集合。 -可以通过索引访问和操作元素。
-常见实现类有ArrayList、LinkedList和Vector等。
2.Set(集合): -不允许重复元素。
-不维护元素的插入顺序,元素存储的顺序可能发生变化。
-提供了高效的查找和去重功能。
-常见实现类有HashSet、TreeSet和 LinkedHashSet等。
3.Queue(队列):
-以队列的方式存储元素,遵循先进先出(FIFO)的原则。
-可以在队列的两端进行操作,即添加元素到队尾或移除队首元素。
-常见实现类有LinkedList、ArrayDeque和 PriorityQueue等。
4.Map(映射):
-使用键值对(key-value)的方式存储元素。一键是唯一的,不允许重复,每个键关联一个值。 -提供了高效的查找和关联功能。
-常见实现类有HashMap、TreeMap和 LinkedHashMap等。
总结:
-List允许重复元素,维护插入顺序,可以通过索引访问和操作元素。
-Set不允许重复元素,不维护插入顺序,提供高效的查找和去重功能。
-Queue以队列方式存储元素,遵循先进先出原则。 -Map使用键值对存储元素,键唯一,提供高效的查找和关联功能。
这四个接口在Java集合框架中提供了不同的
数据结构和功能,可以根据具体的需求选择合适的接口和实现类
12. Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?
PriorityQueue 保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。
13. 请简述ArrayList 与 LinkedList 的区别?
最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构书链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
14. 简述Java用哪两种方式来实现集合的排序?
你可以使用有序集合,如 TreeSet 或 TreeMap,你也可以使用有顺序的的集合,如 list,然后通过 Collections.sort() 来排序
15. Hashtable 与 HashMap 有什么不同之处?
这两个类有许多不同的地方,下面列出了一部分:
a) Hashtable 是 JDK 1 遗留下来的类,而 HashMap 是后来增加的。
b)Hashtable 是同步的,比较慢,但 HashMap 没有同步策略,所以会更快。
c)Hashtable 不允许有个空的 key,但是 HashMap 允许出现一个 null key。
16. Java 中的 HashSet,内部是如何工作的?
HashSet 的内部采用 HashMap来实现。由于 Map 需要 key 和 value,所以所有 key 的都有一个默认 value。类似于 HashMap,HashSet 不允许重复的 key,只允许有一个null key,意思就是 HashSet 中只允许存储一个 null 对象
17. ArrayList 和 HashMap 的默认大小 ?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList 和 HashMap 类的代码片段:
// from ArrayList.java JDK 1.7
private static final int DEFAULT_CAPACITY = 10;
//from HashMap.java JDK 7
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
18. 请说明 Java 中使用 Collections 的最佳实践?
a)使用正确的集合类,例如,如果不需要同步列表,使用 ArrayList 而不是 Vector。
b)优先使用并发集合,而不是对集合进行同步。并发集合提供更好的可扩展性。
c)使用接口代表和访问集合,如使用List存储 ArrayList,使用 Map 存储 HashMap 等等。
d)使用迭代器来循环集合。
e)使用集合的时候使用泛型
19. 简述Java集合框架机制与原理?
每种编程语言中都有集合,最初的Java版本包含几种集合类:Vector、Stack、HashTable和Array。随着集合的广泛使用,Java1.2提出了囊括所有集合接口、实现和算法的集合框架。在保证线程安全的情况下使用泛型和并发集合类,Java已经经历了很久。它还包括在Java并发包中,阻塞接口以及它们的实现。集合框架的部分优点如下:
(1)使用核心集合类降低开发成本,而非实现我们自己的集合类。
(2)随着使用经过严格测试的集合框架类,代码质量会得到提高。
(3)通过使用JDK附带的集合类,可以降低代码维护成本。
(4)复用性和可操作性。
20. 简述集合框架中的泛型有什么作用和优点 ?
泛型是Java SE 1.5之后的特性, 《Java 核心技术》中对泛型的定义是:
“泛型” 意味着编写的代码可以被不同类型的对象所重用。
“泛型”,顾名思义,“泛指的类型”。我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如使用泛型的好处?
以集合来举例,使用泛型的好处是我们不必因为添加元素类型的不同而定义不同类型的集合,如整型集合类,浮点型集合类,字符串集合类,我们可以定义一个集合来存放整型、浮点型,字符串型数据,而这并不是最重要的,因为我们只要把底层存储设置了Object即可,添加的数据全部都可向
上转型为Object。 更重要的是我们可以通过规则按照自己的想法控制存储的数据类型。
21. Java集合框架的基础接口有哪些?
Collection为集合层级的根接口。一个集合代表一组对象,这些对象即为它的元素。Java平台不提供这个接口任何直接的实现。
Set是一个不能包含重复元素的集合。这个接口对数学集合抽象进行建模,被用来代表集合,就如一副牌。
List是一个有序集合,可以包含重复元素。你可以通过它的索引来访问任何元素。List更像长度动态变换的数组。
Map是一个将key映射到value的对象。一个Map不能包含重复的key:每个key最多只能映射一个value。
一些其它的接口有Queue、Dequeue、SortedSet、SortedMap和ListIterator。
22. 解释Collection不从Cloneable和Serializable接口继承?
Collection接口指定一组对象,对象即为它的元素。如何维护这些元素由Collection的具体实现决定。例如,一些如List的Collection实现允许重复的元素,而其它的如Set就不允许。很多Collection实现有一个公有的clone方法。然而,把它放到集合的所有实现中也是没有意义的。这是因为Collection是一个抽象表现。重要的是实现。
当与具体实现打交道的时候,克隆或序列化的语义和含义才发挥作用。所以,具体实现应该决定如何对它进行克隆或序列化,或它是否可以被克隆或序列化。
在所有的实现中授权克隆和序列化,最终导致更少的灵活性和更多的限制。特定的实现应该决定它是否可以被克隆和序列化。
23. 解释为何Map接口不继承Collection接口?
尽管Map接口和它的实现也是集合框架的一部分,但Map不是集合,集合也不是Map。因此,Map继承Collection毫无意义,反之亦然。
如果Map继承Collection接口,那么元素去哪儿?Map包含key-value对,它提供抽取key或value列表集合的方法,但是它不适合“一组对象”规范
24. Map接口提供了哪些不同的集合视图?
Map接口提供三个集合视图:
(1)Set keyset():返回map中包含的所有key的一个Set视图。集合是受map支持的,map的变化会在集合中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(2)Collection values():返回一个map中包含的所有value的一个Collection视图。这个collection受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个collection时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(3)Set> entrySet():返回一个map钟包含的所有映射的一个集合视图。这个集合受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作,以及对迭代器返回的entry进行setValue外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
25. 简述HashMap和HashTable有何不同?
(1)HashMap允许key和value为null,而HashTable不允许。
(2)HashTable是同步的,而HashMap不是。所以HashMap适合单线程环境,HashTable适合多线程环境。
(3)在Java1.4中引入了LinkedHashMap,HashMap的一个子类,假如你想要遍历顺序,你很容易从HashMap转向LinkedHashMap,但是HashTable不是这样的,它的顺序是不可预知的。
(4)HashMap提供对key的Set进行遍历,因此它是fail-fast的,但HashTable提供对key的Enumeration进行遍历,它不支持fail-fast。
(5)HashTable被认为是个遗留的类,如果你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap。
26. ArrayList和Vector有何异同点?
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
27. Array和ArrayList有何区别?什么时候更适合用Array?
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
Array是指定大小的,而ArrayList大小是固定的。
Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。尽管ArrayList明显是更好的选择,但也有些时候Array比较好用。
(1)如果列表的大小已经指定,大部分情况下是存储和遍历它们。
(2)对于遍历基本数据类型,尽管Collections使用自动装箱来减轻编码任务,在指定大小的基本类型的列表上工作也会变得很慢。
(3)如果你要使用多维数组,使用[][]比List>更容易。
28. 解释Java并发集合类是什么?
Java1.5并发包(java.util.concurrent)包含线程安全集合类,允许在迭代时修改集合。迭代器被设计为fail-fast的,会抛出ConcurrentModificationException。一部分类为:CopyOnWriteArrayList、 ConcurrentHashMap、CopyOnWriteArraySet。
29. 简述Vector,ArrayList, LinkedList的区别 ?
1、Vector、ArrayList都是以类似数组的形式存储在内存中,LinkedList则以链表的形式进行存储。
2、List中的元素有序、允许有重复的元素,Set中的元素无序、不允许有重复元素。
3、Vector线程同步,ArrayList、LinkedList线程不同步。
4、LinkedList适合指定位置插入、删除操作,不适合查找;ArrayList、Vector适合查找,不适合指定位置的插入、删除操作。
5、ArrayList在元素填满容器时会自动扩充容器大小的50%,而Vector则是100%,因此ArrayList更节省空间
30. HashTable, HashMap,TreeMap区别?
1、HashTable线程同步,HashMap非线程同步。
2、HashTable不允许<键,值>有空值,HashMap允许<键,值>有空值。
3、HashTable使用Enumeration,HashMap使用Iterator。
4、HashTable中hash数组的默认大小是11,增加方式的old*2+1,HashMap中hash数组的默认大小是16,增长方式一定是2的指数倍。
5、TreeMap能够把它保存的记录根据键排序,默认是按升序排序。
31. ArrayList和LinkedList的区别 ?
ArrayList初试大小为10,大小不够会调用grow扩容:length = length + (length >> 1)
LinkedList中Node first,last。分别指向头尾
ArrayList和LinkedList在性能上各 有优缺点,都有各自所适用的地方,总的说来可以描述如下:
对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的。对
ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是
统一的,分配一个内部Entry对象。
在ArrayList的 中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
LinkedList不 支持高效的随机元素访问。
ArrayList的空 间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
可以这样说:当操作是在一列
数据的后面添加数据而不是在前面或中间,并且需要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操作是在一列数据的前面或中
间添加或删除数据,并且按照顺序访问其中的元素时,就应该使用LinkedList了。
32. 简述Hashmap和Hashtable的不同 ?
1:继承不同。
public class Hashtable extends Dictionary implements Map
public class HashMap extends AbstractMap implements Map
Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了。
Hashtable中,key和value都不允许出现null值。
在HashMap中,null可以作为键,这样的键只有一个;可以有
一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示
HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,
而应该用containsKey()方法来判断。
两个遍历方式的内部实现上不同。
Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。
6.Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
33. 简述Java Set有哪些实现类?
(1)HashSet
HashSet是set接口的实现类,set下面最主要的实现类就是HashSet(也就是用的最多的),此外还有LinkedHashSet和TreeSet。
HashSet是无序的、不可重复的。通过对象的hashCode和equals方法保证对象的唯一性。
HashSet内部的存储结构是哈希表,是线程不安全的。
(2)TreeSet
TreeSet对元素进行排序的方式:
元素自身具备比较功能,需要实现Comparable接口,并覆盖compareTo方法。
元素自身不具备比较功能,需要实现Comparator接口,并覆盖compare方法。
(3)LinkedHashSet
LinkedHashSet是一种有序的Set集合,即其元素的存入和输出的顺序是相同的
34. 综合简述Java哪些集合类是线程安全的 ?
Vector:就比Arraylist多了个同步化机制(线程安全)。
Stack:栈,也是线程安全的,继承于Vector。
Hashtable:就比Hashmap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
35. 请简述concurrentHashMap和HashTable有什么区别 ?
concurrentHashMap融合了hashmap和hashtable的优势,hashmap是不同步的,但是单线程情况下效率高,hashtable是同步的同步情况下保证程序执行的正确性。
但hashtable每次同步执行的时候都要锁住整个结构
concurrentHashMap锁的方式是细粒度的。concurrentHashMap将hash分为16个桶(默认值),诸如get、put、remove等常用操作只锁住当前需要用到的桶。
concurrentHashMap的读取并发,因为读取的大多数时候都没有锁定,所以读取操作几乎是完全的并发操作,只是在求size时才需要锁定整个hash。
而且在迭代时,concurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,弱一致迭代器。在这种方式中,当iterator被创建后集合再发生改变就不会抛出ConcurrentModificationException,取而代之的是在改变时new新的数据而不是影响原来的数据,iterator完成后再讲头指针替代为新的数据,这样iterator时使用的是原来的数据
36. 简述HasmMap和HashSet的区别 ?
1.HashMap实现了Map接口,而HashSet实现了Set接口。
2.HashMap用于存储键值对,而HashSet用于存储对象。
3.HashMap不允许有重复的键,可以允许有重复的值。HashSet不允许有重复元素。
4.HashMap允许有一个键为空,多个值为空,HashSet允许有一个空值。
5.HashMap中使用put()将元素加入map中,而HashSet使用add()将元素放入set中。
6.HashMap比较快,因为其使用唯一的键来获取对象。
37. 简述为什么HashMap使用的时候指定容量?
HashMap在使用时指定容量的原因是为了在创建HashMap对象时就能够预先确定HashMap的初始大小,从而提高HashMap的性能。
在HashMap中,容量是指HashMap内部存储桶数组的大小。HashMap通过哈希算法将键值对映射到不同的桶中,而桶的数量取决于HashMap的容量。当HashMap中存储的键值对数量增多时,如果桶的数量不足以容纳这些键值对,就会导致哈希冲突增多,影响HashMap的性能。
通过指定合适的初始容量,可以减少哈希冲突的概率,从而提高HashMap的性能。一般情况下,可以根据预估的键值对数量来选择初始容量,以保证HashMap有足够的桶来存储数据,同时避免浪费过多的内存空间。
接下来笔者通过一段具体的Java代码,来演示下在创建HashMap对象时指定容量的例子:
import java.util.HashMap;
public class HashMapDemo {
public static void main(String[] args) {
// 指定容量为16的HashMap对象
HashMap map = new HashMap<>(16);
// 添加键值对
map.put(“key1”, 1);
map.put(“key2”, 2);
map.put(“key3”, 3);
// 输出HashMap的大小
System.out.println("Size of the HashMap: " + map.size());
}
}
在以上的示例中,通过在创建HashMap对象时传入容量参数16,就指定了HashMap的初始容量为16。根据实际需求,我们可以根据预估的键值对数量选择合适的容量值。
38. 简述Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别 ?
Set里的元素是不能重复的,那么用iterator()方法来区分重复与否。equals()是判读两个Set是否相等
equals()和==方法决定引用值是否指向同一对象equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值
39. 简述HashMap 的长度为什么是 2 的 N 次方呢? ?
为了能让 HashMap 存数据和取数据的效率高,尽可能地减少 hash 值的碰撞,也就是说尽量把数据能均匀的分配,每个链表或者红黑树长度尽量相等。
我们首先可能会想到 % 取模的操作来实现。
下面是回答的重点哟:
取余(%)操作中如果除数是 2 的幂次,则等价于与其除数减一的与(&)操作(也就是说hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进制位操作 & ,相对于 % 能够提高运算效率。
这就是为什么 HashMap 的长度需要 2 的 N 次方了。
40. 如何决定使用 HashMap 还是 TreeMap?
对于在 Map 中插入、删除、定位一个元素这类操作,HashMap 是最好的选择,因为相对而言 HashMap 的插入会更快,但如果你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。
41. 简述Hashmap的扩容问题new hashmap(19)它的长度是多少 ?
初始长度是19,当达到默认加载因子的时候会进行扩容
HashMap的扩容公式:initailCapacity * loadFactor = HashMap
其中initailCapacity是初始容量:默认值为16(懒加载机制,只有当第一次put的时候才创建)
其中loadFactor是负载因子:默认值为0.75
当HashMap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,所以这是一个通用的操作,很多人对它的性能表示过怀疑,不过想想我们的“均摊”原理,就释然了,而在hashmap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
那么HashMap什么时候进行扩容呢?当hashmap中的元素个数超过数组大小loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,也就是说,默认情况下,数组大小为16,那么当hashmap中元素个数超过160.75=12的时候,就把数组的大小扩展为216=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知hashmap中元素的个数,那么预设元素的个数能够有效的提高hashmap的性能。比如说,我们有1000个元素new HashMap(1000), 但是理论上来讲new HashMap(1024)更合适,不过上面annegu已经说过,即使是1000,hashmap也自动会将其设置为1024。 但是new HashMap(1024)还不是更合适的,因为0.751000 < 1000, 也就是说为了让0.75 * size > 1000, 我们必须这样new HashMap(2048)才最合适,既考虑了&的问题,也避免了resize的问题。
值得提醒的是初始容量和负载因子也可以自己设定的。 使用的是位运算进行扩容,因为用乘法会影响CPU的性能,计算机不支持乘法运算,最终都会转化为加法运算。
42. 简述Hashtable为什么是线程安全的? ?
Hashtable是线程安全的,其实现方式是在对应的方法上加上synchronized关键字,效率不高,不建议使用。目前,如果要使用线程安全的哈希表的话,推荐使用ConcurrentHashMap
43. 简述Java集合的快速失败机制 “fail-fast” ?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
- 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
- 使用CopyOnWriteArrayList来替换ArrayList
44. 简述怎么确保一个集合不能被修改 ?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变
集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码如下:
List list = new ArrayList<>();
list. add(“x”);
Collection clist = Collections. unmodifiableCollection(list);
clist. add(“y”); // 运行时此行报错
System. out. println(list. size());
45. 简述迭代器 Iterator 是什么 ? Iterator 怎么使用?有什么特点?
Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
因为所有Collection接继承了Iterator迭代器
List list = new ArrayList<>();
Iterator it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改
的时候,就会抛出 ConcurrentModificationException 异常
46. 简述 如何边遍历边移除 Collection 中的元素 ?
边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
Iterator it = list.iterator();
while(it.hasNext()){
// do something
it.remove();
}
一种最常见的错误代码如下
for(Integer i : list){
list.remove(i)
}
运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用foreach(for(Integer i : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被Iterator.remove() 修改。Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它
47. 简述遍历一个 List 有哪些不同的方式?每种方法的实现原理是什么?Java 中 List 遍历的最佳实践是什么 ?
遍历方式有以下几种:
- for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。
- 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支持了 Iterator 模式。
- foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使用时不需要显式声明Iterator 或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。
最佳实践:Java Collections 框架中提供了一个 RandomAccess 接口,用来标记 List 实现是否支持 Random Access。
如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。
如果没有实现该接口,表示不支持 Random Access,如LinkedList。
推荐的做法就是,支持 Random Access 的列表可用 for 循环遍历,否则建议用 Iterator 或foreach 遍历。
48. 简述如何实现数组和 List 之间的转换 ?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。
代码示例:
// list to array
List list = new ArrayList();
list.add(“123”);
list.add(“456”);
list.toArray();
// array to list
String[] array = new String[]{“123”,“456”};
Arrays.asList(array);
49. 插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性 ?
ArrayList和Vector 底层的实现都是使用数组方式存储数据。数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector 中的方法由于加了 synchronized 修饰,因此 Vector 是线程安全容器,但性能上较ArrayList差。
LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList 插入速度较快
50. 简述多线程场景下如何使用 ArrayList ?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的 synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样
List synchronizedList = Collections.synchronizedList(list);
synchronizedList.add(“aaa”);
synchronizedList.add(“bbb”);
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
51. 简述为什么 ArrayList 的 elementData 加上 transient 修饰?
ArrayList 中的数组定义如下:
private transient Object[] elementData;
再看一下 ArrayList 的定义:
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。
transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:
private void writeObject(java.io.ObjectOutputStream s) throws
java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out array length
s.writeInt(elementData.length);
// Write out all elements in the proper order.
for (int i=0; i s.writeObject(elementData[i]);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非 transient 元素,然后
遍历 elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后
的文件大小
52. 简述HashSet如何检查重复?HashSet是如何保证数据不可重复的 ?
向HashSet 中add ()元素时,判断元素是否存在的依据,不仅要比较hash值,同时还要结合
equles 方法比较。
HashSet 中的add ()方法会使用HashMap 的put()方法
HashMap 的 key 是唯一的,由源码可以看出 HashSet 添加进去的值就是作为HashMap 的key,
并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V。所以不会重复(
HashMap 比较key是否相等是先比较hashcode 再比较equals )。
以下是HashSet 部分源码:
private static final Object PRESENT = new Object();
private transient HashMap map;
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
return map.put(e, PRESENT)==null;
}
hashCode()与equals()的相关规定:
- 如果两个对象相等,则hashcode一定也是相同的
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值 - 两个对象相等,对两个equals方法返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
- 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
- hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个
对象无论如何都不会相等(即使这两个对象指向相同的数据)。
==与equals的区别
- ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存
空间的值是不是相同 - ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
53. 简述HashMap在JDK1.7和JDK1.8中有哪些不同?HashMap的底层实现 ?
在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;链表的特点是:寻址困难,但插入和删除容易;所以我们将数组和链表结合在一起,发挥两者各自的优势,使用一种叫做拉链法的方式可以解决哈希冲突。
HashMap JDK1.8之前
JDK1.8之前采用的是拉链法。拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可
HashMap JDK1.8之后
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间
JDK1.7 VS JDK1.8 比较
JDK1.8主要解决或优化了一下问题:
- resize 扩容优化
- 引入了红黑树,目的是避免单条链表过长而影响查询效率,红黑树算法请参考
- 解决了多线程死循环问题,但仍是非线程安全的,多线程时可能会造成数据丢失问题。
54. HashMap是怎么解决哈希冲突的 ?
第一个方面,想要了解哈希冲突,首先我们要了解哈希算法和哈希表。
哈希算法把任意长度的输入通过散列算法变成固定长度的输出,这个输出结果就是一个散列值(用来记录元素的位置)
哈希表又叫做’散列表’,他是通过key直接访问到内存存储位置的数据结构。在具体的实现上,我们通过哈希函数把key映射到表中的某个位置,来获取这个位置的数据,从而区加快数据的查找。
第二个方面,哈希冲突是由于哈希算法被计算的数据是无限的,而计算后的结果范围是有限的,所以总会存在不同的数据计算后得到的值是一样的,那将会存在同一个位置,就会出现哈希冲突。比如 key = “name”,和key = “hobby” 计算后的哈希值一样 。
第三个方面通常解决哈希冲突的方法有4种。
1.开放地址法:也称线性探测法,就是从发生冲突的那个位置,按照一定次序从Hash表中找到一个空闲的位置, 把发生冲突的元素存入到这个位置。而在java种ThreadLocal就用到了线性探测法,来解决Hash冲突。
2.链式寻址法:通过单向链表的方式来解决哈希冲突,Hashmap就是用了这个方法。(但会存在链表过长增加遍历时间)
3.再哈希法:key通过某个哈希函数计算得到冲突的时候,再次使用哈希函数的方法对key哈希一直运算直到不产生冲突为止 (耗时间,性能会有影响)
4.建立公共溢出区:就是把Hash表分为基本表和溢出表两个部分,凡是存在冲突的元素,一律放到溢出表中
55. 简述为什么HashMap中String、Integer这样的包装类适合作为K ?
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况内部已重写了 equals() 、 hashCode() 等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况
56. 简述如果使用Object作为HashMap的Key,应该怎么办呢 ?
重写 hashCode() 和 equals() 方法
- 重写 hashCode() 是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中
排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞; - 重写 equals() 方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用
值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
57. 简述HashMap为什么不直接使用hashCode()处理后的哈希值直接作 为table的下标 ?
hashCode() 方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过 hashCode() 计算出的哈希值可
能不在数组大小范围内,进而无法匹配存储位置;
那怎么解决呢?
- HashMap自己实现了自己的 hash() 方法,通过两次扰动使得它自己的哈希值高低位自行进行异或运算,降低哈希碰撞概率也使得数据分布更平均;
- 在保证数组长度为2的幂次方的时候,使用 hash() 运算之后的值与运算(&)(数组长度 -1)来获取数组下标的方式进行存储,这样一来是比取余操作更加有效率,二来也是因为只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大
小范围不匹配”的问题;
58. 简述HashMap 的长度为什么是2的幂次方 ?
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀,每个链表/红黑树长度大致相同。这个实现就是把数据存到哪个链表/红黑树中的算法。
这个算法应该如何设计呢?
我们首先可能会想到采用%取余的操作来实现。但是,重点来了:“取余(%)操作中如果除数是2的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 并且 采用二进制位操作 &,相对于%能够提高运算效
率,这就解释了 HashMap 的长度为什么是2的幂次方。
那为什么是两次扰动呢?
答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提高对应数组存储下标位置的随机性&均匀性,最终减少Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
59. 简述ConcurrentHashMap 和 Hashtable 的区别 ?
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
底层数据结构: JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
实现线程安全的方式:
- 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
- ② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低
60. 简述TreeMap 和 TreeSet 在排序时如何比较元素?Collections 工 具类中的 sort()方法如何比较元素 ?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元素进 行排序。
Collections 工具类的 sort 方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;
?
comparable接口实际上是出自java.lang包,它有一个 compareTo(Object obj)方法用来排序
comparator接口实际上是出自 java.util 包,它有一个compare(Object obj1, Object obj2)方法用来排序
一般我们需要对一个集合使用自定义排序时,我们就要重写compareTo方法或compare方法,当我们需要对某一个集合实现两种排序方式,比如一个song对象中的歌名和歌手名分别采用一种排序方法的话,我们可以重写compareTo方法和使用自制的Comparator方法或者以两个
Comparator来实现歌名排序和歌星名排序,第二种代表我们只能使用两个参数版的Collections.sort().