在Java中,`List` 是一个接口,它属于 `java.util` 包,并且是 `Collection` 接口的一个子接口。`List` 接口定义了一种有序集合(元素存入和取出的顺序是相同的),可以包含重复的元素。以下是一些实现了 `List` 接口的主要类:
1. **`ArrayList`**:
- 基于动态数组实现的列表,支持快速随机访问。
2. **`LinkedList`**:
- 基于双向链表实现的列表,提供了快速的插入和删除操作。
3. **`Vector`**:
- 线程安全的动态数组实现,类似于 `ArrayList`,但在多线程环境下使用。
4. **`Stack`**:
- 继承自 `Vector` 的栈实现,LIFO(后进先出)数据结构。
5. **`CopyOnWriteArrayList`**:
- 线程安全的变体,适用于读多写少的场景,写操作时会复制整个底层数组。
6. **`ArrayBlockingQueue`** 和 **`LinkedBlockingQueue`**:
- 两种线程安全的有界阻塞队列实现,实现了 `Queue` 接口,但也可以作为 `List` 使用。
7. **`PriorityQueue`**:
- 一种优先队列实现,元素按照自然顺序或构造函数中提供的 `Comparator` 进行排序。
8. **`ConcurrentSkipListSet`**:
- 一种并发的、可排序的集合,虽然它实现了 `Set` 接口,但底层是基于跳表实现的,可以作为有序列表使用。
9. **`SynchronizedList`**:
- 一个包装器类,可以将任何其他列表变成线程安全的列表。
10. **`AbstractSequentialAccessList`** 和 **`AbstractList`**:
- 这两个是抽象类,提供了 `List` 接口的部分实现,用于简化自定义列表类的开发。
11. **`Collections.checkedList()`**:
- 一个静态工厂方法,可以返回一个列表,它自动检查列表中添加或获取的元素是否符合指定的类型。
12. **`Collections.synchronizedList()`**:
- 一个静态工厂方法,可以返回一个线程安全的列表。
这些类提供了不同的功能和性能特性,以满足不同的使用场景。例如,如果你需要频繁的随机访问,`ArrayList` 是一个好选择;如果你需要频繁的插入和删除操作,`LinkedList` 可能更合适。在选择具体的 `List` 实现时,应考虑你的应用程序需求,包括性能、线程安全性、内存使用等因素。
在Java中,`Set` 是一个接口,它属于 `java.util` 包,并且是 `Collection` 接口的一个子接口。`Set` 接口定义了一种不允许包含重复元素的集合。以下是一些实现了 `Set` 接口的主要类:
1. **`HashSet`**:
- 基于哈希表实现的集合,它允许快速查找、插入和删除操作。由于 HashSet
是基于 HashMap
实现的,HashSet允许存null,但是只允许存储一个null值。
2. **`LinkedHashSet`**:
- 类似于 `HashSet`,但它维护了元素的插入顺序。
3. **`TreeSet`**:
- 基于红黑树实现的集合,可以按照自然顺序或构造函数中提供的 `Comparator` 对元素进行排序。
4. **`CopyOnWriteArraySet`**:
- 线程安全的 `Set` 集合,适用于读多写少的场景,写操作时会复制整个底层数组。
5. **`ConcurrentSkipListSet`**:
- 并发的、可排序的 `Set` 集合实现,基于跳表(Skip List)数据结构。
6. **`EnumSet`**:
- 专门为 `enum` 类型设计的 `Set` 集合实现,基于位向量(bit vector)。
7. **`SynchronizedSet`**:
- 一个包装器类,可以将任何 `Set` 集合变成线程安全的集合。
8. **`AbstractSet`**:
- 一个抽象类,提供了 `Set` 接口的部分实现,用于简化自定义 `Set` 类的实现。
9. **`Collections.checkedSet()`**:
- 一个静态工厂方法,可以返回一个 `Set` 集合,它自动检查添加或获取的元素是否符合指定的类型。
10. **`Collections.synchronizedSet()`**:
- 一个静态工厂方法,可以返回一个线程安全的 `Set` 集合。
这些 `Set` 集合类提供了不同的特性和性能特点,以满足不同的使用场景。例如,`HashSet` 提供了最快的查找速度,而 `TreeSet` 提供了有序的元素集合。在选择具体的 `Set` 实现时,应考虑你的应用程序需求,包括性能、线程安全性、内存使用等因素。
在Java中,对 `List` 和 `Set` 进行迭代时执行增删操作需要特别小心,因为它们可能会影响到迭代器的状态。以下是一些关于在 `for` 循环中对 `List` 和 `Set` 进行增删操作的注意事项:
### 对于 `List`:
1. **使用迭代器显式删除**:
当使用 `Iterator` 时,可以使用 `remove()` 方法来安全地删除元素。
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer item = iterator.next();
if (item % 2 == 0) { // 假设我们要删除所有偶数
iterator.remove();
}
}
2. **避免在 `for-each` 循环中直接删除**:
使用 `for-each` 循环时,不要直接删除元素,因为这可能会导致 `ConcurrentModificationException`。
// 错误用法:可能导致 ConcurrentModificationException
for (Integer item : list) {
if (item % 2 == 0) {
list.remove(item); // 不要在 for-each 循环中这样做
}
}
3. **使用 `ListIterator`**:
`ListIterator` 提供了 `add()` 和 `remove()` 方法,可以在 `while` 循环中使用它来安全地添加或删除元素。
```java
ListIterator<Integer> listIterator = list.listIterator();
while (listIterator.hasNext()) {
Integer item = listIterator.next();
if (/* some condition */) {
listIterator.remove();
// 或者 listIterator.add(someElement);
}
}
```
### 对于 `Set`:
1. **使用迭代器删除**:
类似于 `List`,使用 `Iterator` 的 `remove()` 方法可以安全地删除 `Set` 中的元素。
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4));
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {
Integer item = iterator.next();
if (item % 2 == 0) {
iterator.remove();
}
}
2. **避免在 `for-each` 循环中直接删除**:
同样,不要在 `for-each` 循环中直接删除 `Set` 元素,以避免 `ConcurrentModificationException`。
// 错误用法:可能导致 ConcurrentModificationException
for (Integer item : set) {
if (item % 2 == 0) {
set.remove(item); // 不要在 for-each 循环中这样做
}
}
3. **添加元素**:
如果你需要在迭代过程中添加元素到 `Set`,可以在循环外部进行,或者使用 `addAll()` 方法添加一个元素集合。
总结来说,当需要在迭代过程中修改 `List` 或 `Set` 时,应该使用迭代器的 `remove()` 方法来删除元素,并且避免在 `for-each` 循环中直接修改集合。对于添加操作,最好是迭代结束后再执行,或者使用集合的 `addAll()` 方法来添加多个元素。这样可以确保集合的一致性,避免 `ConcurrentModificationException` 异常。
在Java中,`Map` 接口定义了键值对的集合,它属于 `java.util` 包。以下是一些实现了 `Map` 接口的主要类:
1. **`HashMap`**:
- 基于哈希表实现的 Map,它允许空键和空值。`HashMap` 不保证映射的顺序。
JDK1.7 HashMap底层是由数组+链表构成
JDK1.7HashMap 桶上面的链表是采用头插法的方式添加元素,例如我先存A元素,再存B元素,再存C元素,那么我取元素的时候是先取C元素,通过C元素找到B元素,通过B元素找到A元素。
顺序是C-》B-》A
那么在桶数组扩容时,会创建一个新的桶数组,并且把元素存储到新的桶数组的某个桶中,那么我取的顺序是C-》B-》A,由于是头插法,那么最初存储的顺序是A-》B-》C。
头插法转移:使用头插法将元素转移到新数组的过程中,每次从原链表中取出头节点,并将其插入到新数组对应桶的头部。这意味着,第一个被转移的元素会成为新链表的尾元素。导致链表反转,链表反转单线程下还好,不会出现问题,如果是多线程,两个线程有可能同时对一个桶数组进行扩容,那么可能导致A引用B,B引用C,C引用A,从而导致死循环造成 CPU 使用率飙升
虽然 JDK 1.8 通过改进扩容机制(使用尾插法)解决了链表反转导致的死循环问题,但在特定条件下,如在链表转换为红黑树的过程中,如果不当使用(例如,在 computeIfAbsent
方法中递归调用自身),HashMap
仍然可能出现死循环,导致 CPU 使用率飙升,所以在多线程环境下建议使用ConcurrentHashMap。
JDK1.8 HashMap底层是由数组+链表+红黑树
桶数组存储的是Node节点,JDK1.8链表长度大于7就会升级为红黑树
Node里面存储了hash值,key,value,以及下一个节点的引用。链表中元素的数量超过一定阈值(默认是8),会升级成红黑树,这个时候桶中存储的是红黑树的根节点,而不是链表的头节点。
调用put方法添加元素的时候
会根据key来获取hash值
通过
(n - 1) & hash 来确定元素存储到哪个桶
n是容量。
如果这个桶上面还没有Node
如果桶上有Node了,并且hash值和key都相同
那么就修改这个Node的value属性
如果hash值相同,但是key不同,并且Node的next值为空,那么就新建一个Node,并且把这个新建的Node引用给Node.next
如果p.next有值,那么就会再次循环,知道找到最后一个Node(也就是Node.next==null)如果循环到第7次的时候,那么就会把单向链表升级成红黑树
桶数组扩容是新创建一个数组,具体请看HashMap中的resize()
2. **`Hashtable`**:
- 与 `HashMap` 类似,但它是同步的,不允许空键和空值。
3. **`LinkedHashMap`**:
- 类似于 `HashMap`,但它维护了插入顺序或者访问顺序,取决于其构造函数的参数。
4. **`TreeMap`**:
- 基于红黑树实现的 Map,可以按照自然顺序或构造函数提供的 `Comparator` 对键进行排序。
import java.util.Comparator;
public class PriceComparator implements Comparator<Double> {
@Override
public int compare(Double o1, Double o2) {
// 这里定义比较逻辑,例如按价格升序排序
return Double.compare(o1, o2);
}
}
Map<Double, Order> orderMap = new TreeMap<>(new PriceComparator());
orderMap.put(new Order().getPrice(), new Order());
// 插入更多订单...
当你遍历TreeMap时,元素将按照你定义的比较器的顺序排列。
for (Map.Entry<Double, Order> entry : orderMap.entrySet()) {
double price = entry.getKey();
Order order = entry.getValue();
// 处理按价格排序的订单
}
// 获取价格小于某个值的所有订单
SortedMap<Double, Order> headMap = orderMap.headMap(某个价格);
// 获取价格在两个值之间的所有订单
SortedMap<Double, Order> subMap = orderMap.subMap(最小价格, true, 最大价格, true);
// 获取价格大于某个值的所有订单
SortedMap<Double, Order> tailMap = orderMap.tailMap(某个价格);
此外,TreeMap
和TreeSet
在多线程环境下不是线程安全的,但如果是由单个线程创建并填充的,然后只读而不被多个线程修改,则没有理由进行同步或锁定。如果需要在多线程环境下使用,可以采取以下方法之一同步/锁定对集合的访问:
- 使用
Collections.synchronizedSortedMap
或Collections.synchronizedSortedSet
来包装TreeMap
或TreeSet
。 - 通过手动同步某个对象,如集合本身。
- 通过使用锁,例如
ReentrantReadWriteLock
,来提供更高的并发性能。
在选择线程安全的替代品时,应该根据具体需求和场景进行权衡和选择。如果需要保持元素顺序的场景,ConcurrentSkipListMap
是一个合适的选择,它提供了与TreeMap
相似的排序特性,并且适用于大规模数据的并发访问
5. **`ConcurrentHashMap`**:
- 线程安全的 Map 实现,适用于并发程序。
6. **`IdentityHashMap`**:
- 基于哈希表实现的 Map,它使用对象的 `equals()` 和 `hashCode()` 方法来确定键值对的相等性。
7. **`EnumMap`**:
- 专门为 `enum` 类型设计的 Map 实现。
8. **`WeakHashMap`**:
- 一种使用弱引用作为键的 Map 实现,当键不再被使用时,可以被垃圾回收器回收。
9. **`SortedMap`**:
- `Map` 接口的子接口,所有实现了 `SortedMap` 的类都能按照某种顺序对键进行排序。
10. **`NavigableMap`**:
- `SortedMap` 接口的子接口,提供了在有序映射中导航的方法。
11. **`Properties`**:
- 继承自 `Hashtable` 的类,用于处理属性文件。
12. **`SynchronizedMap`**:
- 一个包装器类,可以将任何 `Map` 变成线程安全的 Map。
13. **`Collections.checkedMap()`**:
- 一个静态工厂方法,可以返回一个 Map,它自动检查添加或获取的键值对是否符合指定的类型。
14. **`Collections.synchronizedMap()`**:
- 一个静态工厂方法,可以返回一个线程安全的 Map。
15. **`AbstractMap`**:
- 一个抽象类,提供了 `Map` 接口的部分实现,用于简化自定义 Map 类的实现。
这些类提供了不同的功能和性能特性,以满足不同的使用场景。例如,如果你需要快速查找键值对,`HashMap` 是一个好选择;如果你需要有序的键值对集合,`TreeMap` 可能更合适。在选择具体的 `Map` 实现时,应考虑你的应用程序需求,包括性能、线程安全性、内存使用等因素。
在Java中,迭代`Map`集合时,直接在迭代过程中添加或删除元素通常是不安全的,因为这可能会导致`ConcurrentModificationException`异常。这是因为迭代器依赖于集合的原始状态来正确地遍历元素,如果在迭代过程中修改了集合,迭代器的状态就会与实际的集合状态不一致。
然而,有几种方法可以在迭代过程中修改`Map`:
1. **使用迭代器的`remove`方法:**
迭代器提供了一个`remove`方法,可以在迭代过程中安全地删除当前元素。
Map<KeyType, ValueType> map = ...;
Iterator<Map.Entry<KeyType, ValueType>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<KeyType, ValueType> entry = iterator.next();
if (需要删除的条件) {
iterator.remove(); // 安全删除当前元素
}
}
2. **使用`computeIfPresent`方法:**
如果你需要在迭代过程中更新或删除元素,可以使用`computeIfPresent`方法。这个方法接受两个参数:一个条件函数和一个更新函数。如果条件函数返回`true`,更新函数就会被调用。
Map<KeyType, ValueType> map = ...;
map.forEach((key, value) -> {
if (需要更新或删除的条件) {
map.computeIfPresent(key, (k, v) -> {
// 返回null来删除元素,或者返回新的值来更新元素
return null; // 删除元素
// 或者 return newValue; // 更新元素
});
}
});
3. **复制集合并在迭代后修改原集合:**
如果你需要在迭代过程中添加元素,可以先复制当前集合,然后在复制的集合上进行迭代和修改,最后用修改后的集合替换原集合。
Map<KeyType, ValueType> originalMap = ...;
Map<KeyType, ValueType> copyMap = new HashMap<>(originalMap);
for (Map.Entry<KeyType, ValueType> entry : copyMap.entrySet()) {
// 进行迭代和修改操作
}
originalMap.clear(); // 清空原集合
originalMap.putAll(copyMap); // 将修改后的集合赋值给原集合
4. **使用`removeIf`方法:**
如果你只需要删除满足特定条件的元素,可以使用`removeIf`方法,它接受一个`Predicate`作为参数。
Map<KeyType, ValueType> map = ...;
map.values().removeIf(value -> value == null); // 删除所有值为null的元素
请注意,即使使用上述方法,也应该谨慎操作,以避免在并发环境中产生不可预见的行为。在多线程环境中,应该使用线程安全的`ConcurrentMap`接口实现,如`ConcurrentHashMap`。