八股文–集合
1.list集合类中元素有序,可重复,支持索引
arraylist:如果有参构造扩容按1.5倍;无参则第一次为10,后面按1.5倍,线程不安全,效率高
vector :如果有参构造扩容按2倍;无参则第一次为10,后面按2倍,线程安全,效率低
LinkedList:双向链表,增删效率较高,通过链表追加,改查效率较低。线程不安全。
2.set集合类中元素无序,不可重复,没有索引
hashset:底层是hashmap
hashset添加一个元素时,先得到hash值然后转成索引值;找到存储数据表table看看这个索引位置是否存放元素,没有的话直接加入,如果有,调用equals比较,如果相同,放弃添加,如果不相同,则添加到最后。
在java8中,如果一条链表的元素到达TREEIFY_THRESHOLD(默认值为8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化。
注意:源码中hashset获取的hash值不是单纯的hashcode值
该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
//table 就是 HashMap 的一个数组,类型是 Node[]
//if 语句表示如果当前table 是null, 或者 大小=0
//就是第一次扩容,到16个空间.临界值16*0.75=12,如果到了临界值12就会扩容到16*2=32,新的临界值为24
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
//并把这个位置的对象,赋给 p
//(2)判断p 是否为null
//(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)
//(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
//一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
Node<K,V> e; K k; //
//如果当前索引位置对应的链表的第一个元素和准备添加的key的hash值一样
//并且满足 下面两个条件之一:
//(1) 准备加入的key 和 p 指向的Node 结点的 key 是同一个对象
//(2) p 指向的Node 结点的 key 的equals() 和准备加入的key比较后相同
//就不能加入
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//再判断 p 是不是一颗红黑树,
//如果是一颗红黑树,就调用 putTreeVal , 来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较
//(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
// 注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
// , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
// 注意,在转成红黑树时,要进行判断, 判断条件
// if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
// resize();
// 如果上面条件成立,先table扩容.
// 只有上面条件不成立时,才进行转成红黑树
//(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
for (int binCount = 0; ; ++binCount) {死循环
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//size 就是我们每加入一个结点Node(k,v,h,next), size++
if (++size > threshold)
resize();//扩容
afterNodeInsertion(evict);
return null;
}
LinkedHashSet:插入和取出顺序一致,维护数组+双向链表,第一次扩容,到16个空间
3.map
hashmap:是以key-value的方式存储数据(HashMap$Node类型)
key不能重复,但值可以重复,允许使用null值;如果添加相同的key,则会覆盖原来到底key-value;和hashset一样不保证映射顺序,因为底层是以hash表的方式存储的。
hashmap没有实现同步,线程不安全,方法没有做同步互斥操作,没有synchronized。
和hashset一样第一次扩容,到16个空间.临界值160.75=12,如果到了临界值12就会扩容到162=32,新的临界值为24
jdk7.0的hashmap底层实现数组+链表;jdk8.0的hashmap底层实现数组+链表+红黑树
hashtable:存放的键值对都不能为null;线程安全
底层有数组 Hashtable$Entry[] 初始化大小为 11
扩容: 按照自己的扩容机制来进行即可.
执行 方法 addEntry(hash, key, value, index); 添加K-V 封装到Entry
当 if (count >= threshold) 满足时,就进行扩容
按照 int newCapacity = (oldCapacity << 1) + 1; 的大小扩容.
Properties:
Properties
是Java中的一个类,它继承自Hashtable
,用于处理属性文件(.properties)的操作。属性文件是一种常用的配置文件格式,用于存储键值对,通常用于配置应用程序的参数和设置。
以下是Properties
类的一些关键特性和用法:
-
键值对存储:
Properties
类以键值对的形式存储数据,其中键和值都是字符串类型。每个键值对都被视为一个属性。 -
加载属性文件:
Properties
类提供了load()
方法,用于从属性文件中加载属性。属性文件通常是以文本形式存储,每个键值对占据一行,以等号或冒号分隔键和值。通过load()
方法,可以将属性文件中的键值对加载到Properties
对象中。 -
存取属性:
Properties
类提供了一系列方法用于存取属性值。常用的方法包括:getProperty(String key)
:根据键获取对应的值。setProperty(String key, String value)
:设置键值对,将指定的键值对添加或替换到Properties
对象中。getProperty(String key, String defaultValue)
:根据键获取对应的值,如果键不存在,则返回默认值。
-
持久化属性:
Properties
类提供了store()
方法,用于将属性保存到属性文件中。通过调用store()
方法,可以将Properties
对象中的键值对写入到属性文件,以供后续读取和使用。 -
默认属性:
Properties
对象可以指定一个默认的Properties
对象。当通过getProperty()
方法获取属性值时,如果当前Properties
对象中不存在指定的键,它会尝试从默认的Properties
对象中获取对应的值。 -
遍历属性:可以通过
propertyNames()
方法获取Properties
对象中所有的键,然后使用循环来遍历属性并获取对应的值。
Properties
类在读取和写入属性文件方面非常方便,它提供了简单的接口和方法,可以轻松地加载、存储和操作属性。它常用于应用程序的配置文件处理、国际化资源的加载等场景。
ConccurentHashMap:
与普通的HashMap不同,ConcurrentHashMap支持并发访问,并且可以在多个线程同时访问它而不需要显式的同步操作。这使得ConcurrentHashMap非常适合多线程环境下的高并发应用。
线程安全性:ConcurrentHashMap的设计目标是提供线程安全的操作。它使用了一种称为"分段锁"的机制,内部将哈希表分成多个段(segments),每个段都有一个锁来保护对该段的访问。这样,不同的线程可以同时访问不同的段,从而提高并发性能。
分段机制:ConcurrentHashMap通过将哈希表分成多个段来提高并发性能。默认情况下,ConcurrentHashMap的段数是16,可以通过构造函数参数进行配置。不同的键值对根据哈希值被映射到不同的段中。
线程安全迭代器:ConcurrentHashMap提供了一种特殊的迭代器,称为"弱一致性"迭代器(Weakly Consistent Iterator)。这种迭代器能够以线程安全的方式遍历ConcurrentHashMap,但不保证迭代期间反映出的最新修改。
支持高并发:由于使用了分段锁和多线程并发访问机制,ConcurrentHashMap在高并发环境下表现良好。它能够提供较高的读写吞吐量,并且在并发访问时仍然保持较好的性能。
4.hashmap、hashtable、conccurenthashmap三者的区别
HashMap、Hashtable和ConcurrentHashMap是Java中用于存储键值对的三种不同的实现。它们在功能和性能方面有一些区别。
-
线程安全性:
- HashMap:HashMap是非线程安全的,如果多个线程同时访问和修改HashMap,可能会导致数据不一致或抛出异常。
- Hashtable:Hashtable是线程安全的,它的所有操作都是同步的。通过使用互斥锁来实现线程安全。然而,这种同步机制可能会在高并发环境下造成性能下降。
- ConcurrentHashMap:ConcurrentHashMap也是线程安全的,但它采用了更高效的方式来实现线程安全性。它使用了分段锁(Segment Locking)的机制,可以让多个线程同时访问不同的段,从而提高并发性能。
-
性能:
- HashMap:HashMap在单线程环境下具有较好的性能,但在并发环境下不是线程安全的。
- Hashtable:Hashtable的所有方法都是同步的,这在高并发环境下会导致性能下降。
- ConcurrentHashMap:ConcurrentHashMap在高并发环境下具有良好的性能,通过分段锁机制可以实现更高的并发度,提供较好的吞吐量。
-
允许null键和null值:
- HashMap:HashMap允许使用null作为键和值。
- Hashtable:Hashtable不允许使用null作为键或值,如果尝试存储null,会抛出NullPointerException。
- ConcurrentHashMap:ConcurrentHashMap允许使用null作为键或值。
-
迭代器的一致性:
- HashMap:HashMap的迭代器是快速失败(fail-fast)的,如果在迭代过程中对HashMap进行结构上的修改,会抛出ConcurrentModificationException。
- Hashtable:Hashtable的迭代器也是快速失败的。
- ConcurrentHashMap:ConcurrentHashMap的迭代器是弱一致性(weakly consistent)的,它不会抛出ConcurrentModificationException,并且能够反映出迭代开始时的状态,但不能保证在整个迭代过程中反映出最新的修改。
综上所述,HashMap在单线程环境下具有较好的性能,但不是线程安全的;Hashtable是线程安全的,但性能较差;ConcurrentHashMap是线程安全且性能较好的,并且提供更高的并发度。选择使用哪种实现取决于具体的使用场景和要求。
5.treemap和treeset:
TreeMap
和TreeSet
是Java集合框架中的两个实现类,它们都基于红黑树(Red-Black Tree)数据结构。它们的特点是元素按照自然顺序或自定义比较器进行有序存储和检索。
下面是对TreeMap
和TreeSet
的详细解释:
-
TreeMap:
- TreeMap是基于键值对的有序映射表实现。它通过红黑树来保持键的顺序,可以根据键的自然顺序或自定义比较器进行排序。
- TreeMap的键不能为null,因为它需要按照键进行排序。
- TreeMap提供了一系列与
SortedMap
接口一致的方法,如put()
、get()
、remove()
等。此外,它还提供了一些特定于自身的方法,如firstKey()
、lastKey()
、subMap()
等。 - TreeMap的时间复杂度为O(logN),其中N为元素的数量。它适用于需要按照键进行排序的场景,但对于频繁的插入和删除操作,性能相对较低。
-
TreeSet:
- TreeSet是基于有序集合的实现,它通过红黑树来维护有序性,可以根据元素的自然顺序或自定义比较器进行排序。
- TreeSet中的元素不能为null,因为它需要进行有序存储。
- TreeSet实现了
SortedSet
接口,提供了一系列与SortedSet
接口一致的方法,如add()
、remove()
、contains()
等。它还提供了一些特定于自身的方法,如first()
、last()
、headSet()
、tailSet()
等。 - TreeSet的时间复杂度为O(logN),其中N为元素的数量。它适用于需要对元素进行有序存储和查找的场景,但对于频繁的插入和删除操作,性能相对较低。
无论是TreeMap还是TreeSet,它们都具有以下特点:
- 元素有序:它们使用红黑树来维护元素的有序性,因此元素在集合中是有序的。
- 支持自定义排序:通过实现
Comparable
接口或提供自定义的Comparator
,可以对元素进行自定义排序。 - 高效的查找操作:由于采用了红黑树数据结构,它们提供了高效的查找操作,时间复杂度为O(logN)。
- 不适合频繁的插入和删除:由于需要维护树的平衡性,频繁的插入和删除操作会导致性能下降。
总之,TreeMap和TreeSet提供了有序存储和检索的功能,