java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java集合框架

Java 容器集合框架详解

作者:数开小羊

Java容器(Collection Framework)是一个用于存储和操作对象的框架,它提供了多种数据结构和算法,使得开发者可以方便地管理数据,本文介绍Java 容器集合框架详解,感兴趣的朋友一起看看吧

本篇文章涵盖 ArrayList/LinkedList 源码细节HashMap 完整原理(含红黑树、扩容、扰动函数)Set 底层实现迭代器 fail-fast 机制并发容器简介Comparable/Comparator 深入Collections 工具类全解Properties 详解 以及 实际开发建议。所有代码均可直接运行。

一、容器概览与设计目的

1.1 为什么需要容器(集合框架)

1.2 容器框架的核心接口

Iterable (根接口,提供 for-each 支持)
│
└── Collection
     ├── List
     ├── Set
     └── Queue
Map (独立分支)

二、List 接口详解

2.1 ArrayList 源码核心(JDK 1.8)

2.1.1 底层结构

transient Object[] elementData;   // 存储元素的数组
private int size;                 // 实际元素个数

2.1.2 构造方法

// 无参构造:初始为空数组,第一次 add 时扩容到 10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 指定初始容量
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
    }
}

2.1.3 扩容机制

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5 倍
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

2.1.4 add 和 remove 的时间复杂度

操作时间复杂度说明
尾部添加 add(e)均摊 O(1)偶尔扩容 O(n)
指定位置添加 add(i, e)O(n)需要移动元素
尾部删除 remove(size-1)O(1)直接置 null
指定位置删除 remove(i)O(n)移动元素
随机访问 get(i)O(1)数组特性

2.2 LinkedList 源码核心

2.2.1 底层双向链表节点

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

2.2.2 关键字段

transient int size = 0;
transient Node<E> first;
transient Node<E> last;

2.2.3 插入与删除效率

2.2.4 LinkedList 作为双端队列

实现了 Deque 接口,可当作栈、队列使用。

Deque<String> stack = new LinkedList<>();
stack.push("A");     // 压栈
String top = stack.pop();   // 弹栈
Deque<String> queue = new LinkedList<>();
queue.offer("A");    // 入队
String head = queue.poll(); // 出队

2.3 Vector 与 Stack(不推荐使用)

三、Set 接口详解

3.1 HashSet 底层 —— 实际上是一个 HashMap

public class HashSet<E> extends AbstractSet<E> implements Set<E>, ... {
    private transient HashMap<E,Object> map;
    private static final Object PRESENT = new Object();
    public boolean add(E e) {
        return map.put(e, PRESENT) == null;
    }
}

3.2 重写 hashCode 和 equals 的必要性

3.3 LinkedHashSet

3.4 TreeSet

Set<String> treeSet = new TreeSet<>();          // 自然排序
Set<String> treeSet2 = new TreeSet<>(Comparator.reverseOrder()); // 倒序

四、Map 接口详解(重点)

4.1 HashMap 核心原理(JDK 1.8)

4.1.1 数据结构

4.1.2 重要常量

常量默认值说明
DEFAULT_INITIAL_CAPACITY16默认初始容量
MAXIMUM_CAPACITY1 << 30最大容量
DEFAULT_LOAD_FACTOR0.75f默认加载因子
TREEIFY_THRESHOLD8链表长度 ≥ 8 且数组长度 ≥ 64 转红黑树
UNTREEIFY_THRESHOLD6树节点 ≤ 6 转回链表
MIN_TREEIFY_CAPACITY64链表树化最小数组长度

4.1.3 put 方法流程

  1. 计算 key 的 hash 值:(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)(扰动函数,使高位参与运算,减少碰撞)。
  2. 计算桶索引:(n - 1) & hash(等价于 hash % n,但位运算更快)。
  3. 如果该桶为空,直接创建节点放入。
  4. 如果桶不为空,判断第一个节点是否与当前 key 相等(hash 相等且 equals),是则覆盖。
  5. 否则,如果是树节点,执行红黑树插入。
  6. 否则,遍历链表,若找到相同 key 则覆盖,否则在尾部插入新节点。
  7. 插入后检查链表长度是否 ≥ TREEIFY_THRESHOLD,是则转为红黑树。
  8. 检查 ++size > threshold,是则扩容。

4.1.4 扩容机制

// 扩容后重新分配元素(简化示意)
if ((e.hash & oldCap) == 0) {
    // 保持原位置
} else {
    // 移动到 oldIndex + oldCap
}

4.1.5 加载因子为什么是 0.75

4.1.6 红黑树的作用

4.2 LinkedHashMap

// 构造 accessOrder=true 表示按访问顺序排序
LinkedHashMap<K,V> lru = new LinkedHashMap<K,V>(16, 0.75f, true);

4.3 TreeMap

4.4 ConcurrentHashMap(简单介绍)

五、迭代器与 fail-fast 机制

5.1 迭代器工作原理

每个集合有一个内部类实现 Iterator 接口,维护以下字段:

5.2 modCount 与 fail-fast

5.3 避免 ConcurrentModificationException

// 正确删除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("B")) {
        it.remove();
    }
}
// Java 8 推荐的删除方式
list.removeIf(s -> s.equals("B"));

5.4 安全失败(fail-safe)机制

六、Collections 工具类全解

方法描述示例
sort(List)自然排序Collections.sort(list)
sort(List, Comparator)定制排序Collections.sort(list, (a,b)->a-b)
reverse(List)反转顺序Collections.reverse(list)
shuffle(List)随机打乱Collections.shuffle(deck)
swap(List, i, j)交换元素Collections.swap(list,0,1)
rotate(List, distance)循环移动Collections.rotate(list, 2)
fill(List, obj)全部填充Collections.fill(list, null)
copy(dest, src)复制Collections.copy(dest, src)
replaceAll(list, oldVal, newVal)替换Collections.replaceAll(list, 0, -1)
binarySearch(List, key)二分查找Collections.binarySearch(list, 5)
max(Collection), min(Collection)最大最小值Collections.max(set)
frequency(Collection, obj)出现次数Collections.frequency(list, "A")
disjoint(c1, c2)是否无交集Collections.disjoint(set1, set2)
synchronizedXxx(collection)转为线程安全List<String> syncList = Collections.synchronizedList(new ArrayList<>())
unmodifiableXxx(collection)转为不可修改List<String> unmod = Collections.unmodifiableList(list)

七、Comparable 与 Comparator 深入

7.1 Comparable

public class Employee implements Comparable<Employee> {
    private int salary;
    @Override
    public int compareTo(Employee o) {
        return Integer.compare(this.salary, o.salary);
    }
}

7.2 Comparator

Comparator<Employee> byName = (e1, e2) -> e1.getName().compareTo(e2.getName());
Comparator<Employee> bySalaryDesc = (e1, e2) -> Integer.compare(e2.getSalary(), e1.getSalary());
// 链式排序
Comparator<Employee> chain = Comparator.comparing(Employee::getDept)
                                       .thenComparing(Employee::getSalary);

7.3 区别总结

特性ComparableComparator
位置定义在类内部定义在外部独立
修改类需要修改原类无需修改原类
排序方式只能一种自然顺序可多种自定义顺序
方法compareTo(T o)compare(T o1, T o2)

八、Properties 详解

8.1 加载配置文件

Properties props = new Properties();
// 从类路径加载
try (InputStream in = Thread.currentThread().getContextClassLoader()
        .getResourceAsStream("config.properties")) {
    props.load(in);
}
// 从文件系统加载
try (FileInputStream fis = new FileInputStream("config.properties")) {
    props.load(fis);
}
// 读取属性,提供默认值
String dbUrl = props.getProperty("db.url", "jdbc:mysql://localhost:3306/test");

8.2 存储属性到文件

props.setProperty("app.name", "MyApp");
try (FileOutputStream out = new FileOutputStream("config.properties")) {
    props.store(out, "Application Configuration");
}

8.3 XML 格式属性文件

// 加载 XML
props.loadFromXML(in);
// 存储为 XML
props.storeToXML(out, "Comment");

九、实际开发中的选择建议

场景推荐集合
存储顺序重要,允许重复ArrayList
频繁在中间插入删除LinkedList
去重,不关心顺序HashSet
去重,需要保持插入顺序LinkedHashSet
去重,需要自动排序TreeSet
键值对,快速查找HashMap
键值对,需要保持插入顺序LinkedHashMap
键值对,需要按 Key 排序TreeMap
线程安全的键值对ConcurrentHashMap
读多写少的列表CopyOnWriteArrayList
配置文件Properties

十、常见面试题与陷阱

十一、代码演练

示例 1:使用 Comparator 实现多字段排序

class Person {
    String name;
    int age;
    // 构造器、getter、toString 省略
}
List<Person> persons = Arrays.asList(
    new Person("张三", 20),
    new Person("李四", 18),
    new Person("张三", 22)
);
persons.sort(Comparator.comparing(Person::getName)
                       .thenComparingInt(Person::getAge));

示例 2:使用 HashMap 统计单词频率

String text = "hello world hello java";
Map<String, Integer> freq = new HashMap<>();
for (String word : text.split(" ")) {
    freq.merge(word, 1, Integer::sum);
}
System.out.println(freq);  // {hello=2, world=1, java=1}

示例 3:利用 LinkedHashMap 实现 LRU 缓存

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private final int maxSize;
    public LRUCache(int maxSize) {
        super(16, 0.75f, true);
        this.maxSize = maxSize;
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
        return size() > maxSize;
    }
}

十二、后续进阶

也可以深入学习 源码分析并发集合,进一步理解 Java 容器的设计精髓。

到此这篇关于Java 容器集合框架详解的文章就介绍到这了,更多相关java集合框架内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文