java Collection和Map接口实现类的详解

在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(某个价格);

此外,TreeMapTreeSet在多线程环境下不是线程安全的,但如果是由单个线程创建并填充的,然后只读而不被多个线程修改,则没有理由进行同步或锁定。如果需要在多线程环境下使用,可以采取以下方法之一同步/锁定对集合的访问:

  1. 使用Collections.synchronizedSortedMapCollections.synchronizedSortedSet来包装TreeMapTreeSet
  2. 通过手动同步某个对象,如集合本身。
  3. 通过使用锁,例如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`。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值