一、Java 集合框架概述

Java 集合框架(Java Collections Framework)是Java语言中一个非常重要且强大的工具库,它位于java.util包下,包含了一系列用于存储和操作对象的接口和类。这个框架为开发者提供了高效、灵活的数据结构实现,大大简化了集合操作。

该框架主要分为两大体系:

1.Collection 接口体系 Collection 接口是所有集合类的根接口之一,它用于存储和处理单个元素对象。在实际开发中,我们通常使用它的三个主要子接口:

  • List(列表):

    • 特点:元素有序(存入顺序和取出顺序一致),允许重复元素
    • 典型实现:
      • ArrayList:基于动态数组实现,随机访问效率高
      • LinkedList:基于双向链表实现,插入删除效率高
      • Vector:线程安全的动态数组实现
  • Set(集合):

    • 特点:元素无序(具体实现类可能有特定排序规则),不允许重复元素
    • 典型实现:
      • HashSet:基于哈希表实现,查询效率高
      • TreeSet:基于红黑树实现,元素自动排序
      • LinkedHashSet:维护插入顺序的HashSet
  • Queue(队列):

    • 特点:通常遵循先进先出(FIFO)原则
    • 典型实现:
      • LinkedList:可用作队列实现
      • PriorityQueue:优先级队列,元素按优先级排序
      • ArrayDeque:双端队列实现

2.Map 接口体系 Map接口存储的是键值对(key-value)映射,其中:

    • 键(key)不允许重复,每个键最多映射到一个值
    • 值(value)可以重复

    常用实现类包括:

    • HashMap:基于哈希表实现,查询效率高
    • TreeMap:基于红黑树实现,按键的自然顺序或Comparator排序
    • LinkedHashMap:维护插入顺序的HashMap
    • Hashtable:线程安全的Map实现(较老的实现)

    这些集合类在Java开发中的应用场景非常广泛:

    • ArrayList常用于需要频繁随机访问元素的场景
    • LinkedList适合频繁插入删除操作的场景
    • HashSet用于需要快速判断元素是否存在的场景
    • HashMap是缓存实现、对象映射等的首选数据结构

    二、常用集合类详解

    一、List接口实现类

    1. ArrayList

    特点与实现细节

    ArrayList是基于动态数组实现的List接口,其内部使用Object[] elementData数组存储元素。主要特点包括:

    1. 底层实现

      • 使用可动态扩容的Object数组
      • 数组类型为transient Object[] elementData,支持序列化优化
    2. 查询效率

      • 随机访问时间复杂度O(1),直接通过索引计算内存地址
      • 示例:list.get(100)可以直接定位到第101个元素
    3. 增删效率

      • 末尾添加元素平均时间复杂度O(1)
      • 中间插入/删除需要移动后续元素,最坏情况O(n)
      • 示例:在包含10000个元素的列表首位插入元素,需要移动9999个元素
    4. 线程安全

      • 非线程安全,多线程并发修改会抛出ConcurrentModificationException
      • 替代方案:
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        

        或者使用CopyOnWriteArrayList
    5. 扩容机制

      • 默认初始容量10
      • 扩容公式:newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍)
      • 扩容时调用Arrays.copyOf()创建新数组并复制元素
      • 最大容量Integer.MAX_VALUE - 8(部分JVM实现保留头信息)

    使用场景

    适合以下场景:

    • 需要频繁随机访问元素
    • 主要在列表末尾进行添加操作
    • 元素数量相对稳定或可预估
    • 内存空间较为充足

    基础操作示例

    // 创建ArrayList的四种方式
    List<String> list1 = new ArrayList<>();          // 默认构造:初始容量10
    List<String> list2 = new ArrayList<>(20);        // 指定初始容量
    List<String> list3 = new ArrayList<>(existingCollection);  // 从集合创建
    List<String> list4 = List.of("A", "B", "C");     // Java9+不可变列表
    
    // 容量优化实践
    list1.ensureCapacity(100);     // 提前分配足够空间
    // 添加大量元素...
    list1.trimToSize();           // 调整为实际大小
    
    // 批量操作示例
    List<String> letters = Arrays.asList("A", "B", "C", "D", "E");
    list1.addAll(letters);        // 添加集合
    list1.removeAll(Arrays.asList("A", "E")); // 删除指定元素
    list1.retainAll(Arrays.asList("B", "C")); // 保留交集
    
    // Java8+新特性
    list1.removeIf(e -> e.length() > 5);    // 条件删除
    list1.replaceAll(String::toUpperCase);  // 元素转换
    list1.sort(Comparator.naturalOrder()); // 排序
    
    // 并行流处理
    list1.parallelStream()
        .filter(s -> s.startsWith("A"))
        .forEach(System.out::println);
    

    性能优化建议

    1. 预估大小并设置初始容量,避免多次扩容
    2. 批量操作优先使用addAll()而不是循环添加
    3. 频繁删除时考虑使用迭代器remove()方法
    4. 只读场景可使用Collections.unmodifiableList()包装

    2. LinkedList

    特点与实现细节

    LinkedList是基于双向链表实现的List和Deque接口,其内部使用Node<E>节点存储元素。主要特点包括:

    1. 底层实现

      • 每个节点包含:E itemNode<E> nextNode<E> prev
      • 维护Node<E> firstNode<E> last指针
      • 内存占用比ArrayList多50%以上(每个元素需要前后节点引用)
    2. 查询效率

      • 按索引访问需要遍历,时间复杂度O(n)
      • 优化:索引在前半部分从前往后,后半部分从后往前
      • 示例:10000个元素的列表,访问第5000个元素需要遍历5000次
    3. 增删效率

      • 已知位置插入/删除只需修改相邻节点引用,O(1)时间
      • 实际应用中仍可能因查找位置产生O(n)开销
      • 首尾操作效率最高,特别适合实现队列和栈
    4. 线程安全

      • 非线程安全,多线程环境需要外部同步
      • 替代方案:使用ConcurrentLinkedDeque

    使用场景

    适合以下场景:

    • 频繁在列表两端进行增删操作
    • 需要实现栈、队列或双端队列功能
    • 元素数量变化较大且无法预估
    • 内存充足但访问模式主要是顺序访问

    特有方法示例

    LinkedList<String> linkedList = new LinkedList<>();
    
    // 队列操作(FIFO)
    linkedList.offer("A");       // 添加元素到队尾
    linkedList.offerLast("B");   // 同offer()
    String head = linkedList.poll();  // 移除并返回队首
    String peek = linkedList.peek();  // 查看队首不移除
    
    // 双端队列操作
    linkedList.offerFirst("C");  // 添加到队首
    linkedList.pollLast();       // 移除并返回队尾
    
    // 栈操作(LIFO)
    linkedList.push("D");       // 压栈(添加到队首)
    String top = linkedList.pop(); // 弹栈(移除队首)
    
    // 其他特有方法
    Iterator<String> descIt = linkedList.descendingIterator(); // 反向迭代
    String first = linkedList.peekFirst();    // 查看队首
    String last = linkedList.peekLast();      // 查看队尾
    linkedList.removeFirstOccurrence("A");    // 删除第一次出现的元素
    linkedList.removeLastOccurrence("B");     // 删除最后一次出现的元素
    

    性能比较

    操作 ArrayList LinkedList
    get(index) O(1) O(n)
    add(element) O(1)均摊 O(1)
    add(index,element) O(n) O(n)
    remove(index) O(n) O(n)
    remove(element) O(n) O(n)
    iterator.remove() O(n) O(1)

    3. Vector

    特点与实现细节

    Vector是Java早期线程安全的动态数组实现,现已不推荐使用。主要特点包括:

    1. 线程安全

      • 所有public方法都使用synchronized修饰
      • 同步粒度较粗,性能较差
      • 现代替代方案:
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        

        或者使用CopyOnWriteArrayList
    2. 扩容机制

      • 默认初始容量10
      • 默认扩容为原容量的2倍
      • 可通过capacityIncrement构造参数调整扩容步长
      • 设置为0时则每次翻倍扩容
    3. 遗留方法

      • elements():返回Enumeration接口实现
        Enumeration<String> en = vector.elements();
        while(en.hasMoreElements()) {
            String e = en.nextElement();
        }
        

      • addElement(E)/removeElement():与add/remove功能相同
    4. 子类Stack

      • 继承Vector实现的栈结构
      • 提供push/pop/peek等栈操作
      • 由于继承Vector导致性能问题,推荐使用Deque接口实现:
        Deque<String> stack = new ArrayDeque<>();
        

    与现代集合对比

    特性 Vector ArrayList + Collections.synchronizedList
    线程安全 是(方法级同步) 是(对象级同步)
    迭代器安全 需要手动同步
    性能 较差 较好
    扩容策略 2倍或自定义 1.5倍
    遗留接口 支持Enumeration 不支持

    使用建议

    1. 新代码应避免直接使用Vector
    2. 需要线程安全列表时优先考虑:
      // 读多写少场景
      List<String> list1 = new CopyOnWriteArrayList<>();
      
      // 通用场景
      List<String> list2 = Collections.synchronizedList(new ArrayList<>());
      

    3. 需要栈功能时使用ArrayDeque
      Deque<String> stack = new ArrayDeque<>();
      stack.push("A");
      String top = stack.pop();
      

    二、Set接口实现类

    HashSet

    实现原理

    HashSet是基于HashMap实现的集合类,其核心存储机制实际上是使用HashMap的key来存储元素(value统一使用一个静态常量对象PRESENT占位)。

    详细存储结构

    // HashSet内部实际上使用HashMap存储
    private transient HashMap<E,Object> map;
    
    // 所有元素对应的value都是这个固定对象
    private static final Object PRESENT = new Object();
    

    哈希冲突处理机制

    在哈希冲突处理方面:

    1. JDK7及之前版本完全采用链表法解决冲突

      • 当多个元素哈希到同一个数组索引时,形成单向链表
      • 新元素插入链表头部(头插法)
    2. JDK8进行了优化:

      • 当链表长度超过8且数组长度达到64时,会将链表转为红黑树结构
        • 转换阈值:TREEIFY_THRESHOLD = 8
        • 最小数组长度要求:MIN_TREEIFY_CAPACITY = 64
      • 当红黑树节点数小于6时,会退化为链表
        • 退化阈值:UNTREEIFY_THRESHOLD = 6

    扩容机制

    初始容量默认为16(与HashMap一致),负载因子为0.75。当元素数量超过(容量×负载因子)时,会自动扩容为原来的2倍。

    扩容过程示例:

    1. 初始容量16,当元素数量达到12(16×0.75)时触发扩容
    2. 新容量变为32,重新hash所有元素
    3. 扩容后元素重新分布到更大的数组中

    元素要求

    为了正确使用HashSet,自定义类必须正确重写hashCode()和equals()方法:

    class Person {
        String name;
        int age;
        
        // 使用Objects工具类生成hashCode
        @Override
        public int hashCode() {
            return Objects.hash(name, age);  // 确保相同对象的hashCode相同
        }
        
        // 实现equals方法
        @Override
        public boolean equals(Object o) {
            // 1. 检查是否是同一对象
            if (this == o) return true;
            // 2. 检查是否为null或类型不匹配
            if (o == null || getClass() != o.getClass()) return false;
            // 3. 类型转换后比较关键字段
            Person person = (Person) o;
            return age == person.age && Objects.equals(name, person.name);
        }
    }
    

    hashCode()方法最佳实践

    1. 使用相同的字段计算hashCode和equals
    2. 使用Objects.hash()简化实现
    3. 避免hashCode()方法过于简单(如只用一个字段)
    4. 避免hashCode()方法过于复杂(影响性能)

    equals()方法实现要点

    1. 自反性:x.equals(x)必须返回true
    2. 对称性:x.equals(y)与y.equals(x)结果相同
    3. 传递性:如果x.equals(y)且y.equals(z),则x.equals(z)
    4. 一致性:多次调用结果相同
    5. 非空性:x.equals(null)返回false

    TreeSet

    排序方式

    TreeSet支持两种排序方式:

    1. 自然排序:元素实现Comparable接口

    class Student implements Comparable<Student> {
        private String name;
        private int score;
        
        // 按姓名升序排列
        @Override
        public int compareTo(Student s) {
            return this.name.compareTo(s.name);
        }
    }
    

    自然排序实现要点:

    • 必须实现Comparable接口
    • compareTo方法必须与equals方法保持一致
    • 推荐实现Comparable的类同时重写equals和hashCode方法

    2. 定制排序:创建TreeSet时传入Comparator

    // 先按字符串长度排序,长度相同则按字典序
    TreeSet<String> ts = new TreeSet<>(
        (s1, s2) -> s1.length() != s2.length() ? 
            s1.length() - s2.length() : s1.compareTo(s2)
    );
    
    // 更复杂的比较器示例
    Comparator<Employee> comp = Comparator
        .comparing(Employee::getDepartment)
        .thenComparingInt(Employee::getSalary)
        .reversed();
    TreeSet<Employee> empSet = new TreeSet<>(comp);
    

    Comparator常用方法:

    • comparing():指定比较属性
    • thenComparing():添加次要比较条件
    • reversed():反转排序顺序
    • nullsFirst()/nullsLast():处理null值

    时间复杂度分析

    基于红黑树实现的时间复杂度:

    • 插入(add):O(log n)
    • 删除(remove):O(log n)
    • 查找(contains):O(log n)

    有序操作:

    • first()/last():O(1) - 直接获取最小/最大节点
    • lower()/higher():O(log n) - 查找前驱/后继节点
    • headSet()/tailSet():O(1) - 创建子集视图

    LinkedHashSet

    继承关系

    • 继承自HashSet
    • 底层使用LinkedHashMap存储元素

    顺序维护机制

    • 通过双向链表维护元素的插入顺序
    • 每个Entry节点包含before和after指针
    // LinkedHashSet内部维护的Entry结构
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
    

    性能特点

    1. 插入和查询性能略低于HashSet

      • 插入:需要额外维护链表指针
      • 查询:与HashSet相同,但缓存局部性稍差
    2. 迭代效率高于HashSet

      • 直接按链表顺序遍历,时间复杂度O(n)
      • HashSet迭代需要遍历整个哈希表,性能较差
    3. 内存占用高于HashSet

      • 每个元素需要额外存储before和after指针
      • 内存开销增加约20-30%

    使用场景

    当需要Set的特性又需要维护插入顺序时使用,例如:

    // 保持访问顺序的URL记录
    LinkedHashSet<String> visitedUrls = new LinkedHashSet<>();
    
    // 需要按添加顺序处理的唯一元素集合
    LinkedHashSet<Task> taskQueue = new LinkedHashSet<>();
    
    // 实现LRU缓存
    class LRUCache<K,V> extends LinkedHashMap<K,V> {
        private final int capacity;
        
        public LRUCache(int capacity) {
            super(capacity, 0.75f, true);
            this.capacity = capacity;
        }
        
        @Override
        protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
            return size() > capacity;
        }
    }
    

    典型应用场景

    1. 需要维护元素插入顺序的集合
    2. 实现简单的LRU缓存机制
    3. 需要快速判断元素存在性又要保持顺序的场景
    4. 需要去重且保留原始顺序的数据处理

    三、Map接口实现类

    HashMap 

    核心参数详解:
    1. DEFAULT_INITIAL_CAPACITY = 16

      • 默认初始容量,必须是2的幂次方(1<<4),这样设计是为了优化哈希计算
      • 实际应用中可以指定初始容量(如new HashMap(32)),但构造器会将其调整为最接近的2的幂
    2. DEFAULT_LOAD_FACTOR = 0.75f

      • 空间和时间成本的折衷值,表示哈希表填充程度达到75%时触发扩容
      • 示例:容量16的HashMap在存储第12个元素(16*0.75)时会扩容
    3. 树化相关参数

      • TREEIFY_THRESHOLD=8:链表长度超过8时可能转为红黑树
      • UNTREEIFY_THRESHOLD=6:树节点数小于等于6时转回链表
      • MIN_TREEIFY_CAPACITY=64:哈希表总容量需达到64才会树化
      • 设计考虑:避免在哈希表较小时过早树化,节省内存
    put方法完整流程:
    1. 哈希计算

      // 扰动函数:高16位异或低16位,增强散列性
      static final int hash(Object key) {
          int h;
          return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }
      

    2. 初始化检查

      • 检测Node<K,V>[] table是否为空
      • 首次put时调用resize()初始化数组(默认16容量)
    3. 定位桶位置

      • 通过(table.length - 1) & hash计算索引(等价于取模但效率更高)
    4. 冲突处理场景

      • 场景1:桶为空 → 直接新建节点插入
      • 场景2:键已存在 → 覆盖原有value
      • 场景3:树节点 → 调用putTreeVal插入红黑树
      • 场景4:链表 → 尾插法插入,同时检查:
        • 如果链表长度≥8且表容量≥64 → 调用treeifyBin树化
        • 否则仅扩容
    5. 扩容机制

      • 触发条件:size > threshold(capacity * loadFactor)
      • 扩容操作:新建2倍大小的数组,重新分配节点(可能拆分树)
    并发问题深度分析:
    1. JDK7死循环场景

      // 多线程同时扩容时,头插法可能导致环形链表
      void transfer(Entry[] newTable) {
          Entry<K,V> e = table[bucketIndex];
          while(null != e) {
              Entry<K,V> next = e.next;  // 线程A执行到此挂起
              e.next = newTable[bucketIndex]; // 线程B完成转移后形成环
              newTable[bucketIndex] = e;
              e = next;
          }
      }
      

    2. JDK8改进与局限

      • 改用尾插法避免环形链表
      • 但仍存在数据覆盖问题(如两个线程同时put时可能丢失修改)
    3. 线程安全方案

      • ConcurrentHashMap:分段锁+CAS(JDK7)或 synchronized+CAS(JDK8)
      • Collections.synchronizedMap:使用互斥锁包装整个Map

    TreeMap 

    红黑树规则验证:
    1. 染色规则示例

      • 插入新节点默认为红色(减少黑高调整)
      • 若父节点为红,则通过旋转(左旋/右旋)和变色维持平衡
    2. 平衡性维护

      // 插入后的修复操作(包含三种case处理)
      private void fixAfterInsertion(Entry<K,V> x) {
          while (x != null && x != root && x.parent.color == RED) {
              if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                  Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                  if (colorOf(y) == RED) {  // Case1: 叔叔节点为红
                      setColor(parentOf(x), BLACK);
                      setColor(y, BLACK);
                      setColor(parentOf(parentOf(x)), RED);
                      x = parentOf(parentOf(x));
                  } else {
                      if (x == rightOf(parentOf(x))) {  // Case2: 形成三角关系
                          x = parentOf(x);
                          rotateLeft(x);
                      }
                      // Case3: 直线关系
                      setColor(parentOf(x), BLACK);
                      setColor(parentOf(parentOf(x)), RED);
                      rotateRight(parentOf(parentOf(x)));
                  }
              }
              // 对称处理右子树情况...
          }
          root.color = BLACK;
      }
      

    导航方法应用场景:
    1. 区间查询

      // 查找最接近100的键
      Map.Entry<Integer, String> floor = map.floorEntry(100);  // ≤100的最大键
      Map.Entry<Integer, String> ceiling = map.ceilingEntry(100); // ≥100的最小键
      

    2. 排行榜实现

      // 获取大于90分的最低记录
      Map.Entry<Student, Integer> higher = gradeMap.higherEntry(
          new Student("", 90));  // 需实现Comparable
      


    LinkedHashMap 

    访问顺序模式实现:
    1. LRU缓存示例

      // 固定大小的LRU缓存
      new LinkedHashMap<String, Object>(100, 0.75f, true) {
          @Override
          protected boolean removeEldestEntry(Map.Entry eldest) {
              return size() > MAX_ENTRIES;
          }
      };
      

    2. 链表维护机制

      • Entry扩展:增加before/after指针形成双向链表
      • 访问回调afterNodeAccess方法将访问的节点移到链表尾部
      • 插入回调afterNodeInsertion可触发 eldest 条目移除(需重写removeEldestEntry

    Hashtable 对比分析

    详细差异对比表:
    特性 Hashtable HashMap
    线程安全 方法级synchronized锁 非线程安全
    Null处理 不允许null键值 允许null键值
    初始容量 11(质数) 16(2的幂)
    扩容方式 2n+1(保持质数) 2n(保持2的幂)
    迭代器 枚举器+Iterator(fail-fast) Iterator(fail-fast)
    哈希计算 直接使用hashCode() 扰动函数优化哈希
    继承体系 Dictionary抽象类 AbstractMap
    使用建议:
    • 遗留系统:Hashtable在Java 1.0引入,新代码建议使用ConcurrentHashMap
    • 特殊需求:当需要保证强一致性的旧代码维护时使用Hashtable
    • 性能考量:Hashtable的全表锁在高并发时性能差,平均吞吐量比ConcurrentHashMap低5~10倍

    三、集合使用注意事项

    线程安全问题

    Java 集合框架中的大多数实现类(如 ArrayList、LinkedList、HashSet、HashMap 等)都是线程不安全的,这意味着在多线程并发环境下,如果多个线程同时访问和修改同一个集合实例,可能会导致数据不一致或其他不可预期的行为。

    解决方案

    1. 使用 Collections.synchronizedXXX() 方法包装非线程安全集合:

      List<String> syncList = Collections.synchronizedList(new ArrayList<>());
      Map<String, String> syncMap = Collections.synchronizedMap(new HashMap<>());
      

    2. 使用并发包(java.util.concurrent)中的线程安全集合:

      • ConcurrentHashMap:线程安全的哈希表实现
      • CopyOnWriteArrayList:写时复制的线程安全列表
      • CopyOnWriteArraySet:写时复制的线程安全集合

    应用场景

    • 在 Web 应用中处理并发请求时
    • 多线程数据处理程序中
    • 缓存实现中需要共享数据访问时

    集合元素的类型安全

    泛型集合可以避免类型转换错误,但需要注意:

    // 正确用法
    List<String> stringList = new ArrayList<>();
    stringList.add("Java");
    String s = stringList.get(0); // 无需强制类型转换
    
    // 错误用法可能引发 ClassCastException
    List rawList = new ArrayList();
    rawList.add("Java");
    Integer i = (Integer)rawList.get(0); // 运行时错误
    

    hashCode() 和 equals() 方法规范

    对于要存储在 HashSet 或作为 HashMap 键的对象,必须正确实现这两个方法:

    public class Student {
        private String id;
        private String name;
        
        @Override
        public int hashCode() {
            return Objects.hash(id, name);
        }
        
        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            Student other = (Student)obj;
            return Objects.equals(id, other.id) && 
                   Objects.equals(name, other.name);
        }
    }
    

    重要规则

    1. 如果 a.equals(b) 返回 true,那么 a.hashCode() 必须等于 b.hashCode()
    2. 相等的对象必须具有相同的哈希码
    3. 不相等的对象可以有相同的哈希码(哈希冲突)

    集合遍历的最佳实践

    不同的集合类型适合不同的遍历方式:

    1. ArrayList

      // 索引遍历 - 效率最高
      for (int i = 0; i < list.size(); i++) {
          String item = list.get(i);
      }
      

    2. LinkedList

      // 迭代器遍历 - 效率最高
      Iterator<String> it = linkedList.iterator();
      while (it.hasNext()) {
          String item = it.next();
      }
      

    3. 所有集合的通用遍历方式:

      // 增强for循环(内部使用迭代器)
      for (String item : collection) {
          // 处理元素
      }
      

    遍历时修改集合的注意事项

    List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
    Iterator<String> it = list.iterator();
    while (it.hasNext()) {
        String item = it.next();
        if ("B".equals(item)) {
            it.remove(); // 正确方式 - 使用迭代器的remove方法
            // list.remove(item); // 错误 - 会抛出ConcurrentModificationException
        }
    }
    

    集合初始化容量优化

    对于基于数组实现的集合,合理设置初始容量可以避免频繁扩容:

    // 已知最终大小约为1000时
    List<String> list = new ArrayList<>(1000);
    Map<String, Integer> map = new HashMap<>(1000);
    
    // HashMap容量计算(考虑负载因子0.75)
    int expectedSize = 1000;
    int initialCapacity = (int) (expectedSize / 0.75 + 1);
    Map<String, Integer> optimizedMap = new HashMap<>(initialCapacity);
    

    扩容代价

    • ArrayList 每次扩容需要复制整个数组
    • HashMap 扩容需要重新计算所有元素的哈希位置

    TreeSet/TreeMap 的排序要求

    元素必须满足以下条件之一:

    1. 实现 Comparable 接口
    2. 构造集合时提供 Comparator
    // 方式1:元素实现Comparable
    class Person implements Comparable<Person> {
        private String name;
        
        @Override
        public int compareTo(Person o) {
            return this.name.compareTo(o.name);
        }
    }
    
    TreeSet<Person> set1 = new TreeSet<>();
    
    // 方式2:提供Comparator
    TreeSet<String> set2 = new TreeSet<>(
        (s1, s2) -> s1.length() - s2.length()
    );
    

    集合判空的最佳实践

    // 不推荐 - 可能NPE
    if (collection.size() == 0) { ... }
    
    // 推荐方式
    if (collection == null || collection.isEmpty()) { ... }
    
    // Java8+ 更安全的做法
    Optional.ofNullable(collection)
        .filter(c -> !c.isEmpty())
        .ifPresent(c -> {
            // 处理非空集合
        });
    

    注意isEmpty() 方法通常比 size() == 0 更高效,因为某些集合实现可能有更优化的判空方式。

    Logo

    Agent 垂直技术社区,欢迎活跃、内容共建。

    更多推荐