基础14-Java集合框架:掌握List、Set和Map的使用

#王者杯·14天创作挑战营·第4期#

在Java编程中,数据的组织与管理是构建高效、可维护应用程序的核心。Java集合框架(Java Collections Framework)为此提供了强大而灵活的工具集。它是一组接口和类的集合,用于存储、操作和处理对象组。理解并熟练掌握ListSetMap这三大核心接口及其常用实现类,是每个Java开发者必备的技能。本文将深入探讨这些集合的特性、用法、性能差异以及最佳实践,通过丰富的代码示例帮助你全面掌握Java集合框架。


一、Java集合框架概览

Java集合框架位于java.util包中,其设计基于一组核心接口,这些接口定义了集合的行为。主要的接口包括:

  • Collection:所有单值集合(存储单个元素的集合)的根接口,ListSet都继承自它。
  • List:有序集合,允许重复元素,可以通过索引访问。
  • Set:不允许重复元素的集合,通常无序(但有例外)。
  • Queue:用于模拟队列数据结构,通常遵循先进先出(FIFO)原则。
  • Deque:双端队列,支持在两端插入和删除元素。
  • Map:存储键值对(key-value pairs)的集合,键不允许重复。

集合框架的设计遵循“接口优先”的原则,允许我们编写与具体实现无关的代码,从而提高代码的灵活性和可重用性。

1.1 集合框架的层次结构

                              Collection
                                 / \
                                /   \
                               /     \
                            List     Set
                             |        |
                             |        |
                         ArrayList  HashSet
                         LinkedList   LinkedHashSet
                         Vector       TreeSet
                         Stack
                              Map
                               |
                               |
                           HashMap
                            LinkedHashMap
                             TreeMap
                             Hashtable

1.2 使用集合框架的优势

  1. 高性能:经过优化的实现提供了高效的增删改查操作。
  2. 可重用性:提供了一套标准的数据结构和算法。
  3. 类型安全(结合泛型):在编译期检查类型,避免运行时错误。
  4. 易于使用:统一的接口和丰富的API。
  5. 可扩展性:可以轻松地创建自定义集合实现。

二、List接口:有序的序列

List接口代表一个有序的集合,也称为序列。它允许存储重复元素,并且可以通过元素的整数索引(位置)来访问元素。

2.1 List的核心特性

  • 有序性:元素的插入顺序被保留。
  • 可重复:同一个元素可以出现多次。
  • 索引访问:支持通过get(int index)set(int index, E element)等方法进行随机访问。
  • 常用方法
    • add(E e) / add(int index, E element):添加元素。
    • remove(int index) / remove(Object o):移除元素。
    • get(int index):获取指定位置的元素。
    • set(int index, E element):替换指定位置的元素。
    • indexOf(Object o) / lastIndexOf(Object o):查找元素的索引。
    • size():返回元素数量。
    • isEmpty():检查是否为空。
    • contains(Object o):检查是否包含某个元素。

2.2 ArrayList:动态数组

ArrayListList接口最常用的实现,基于动态数组。它提供了快速的随机访问,但在列表中间插入或删除元素时性能较差,因为需要移动后续元素。

2.2.1 基本使用
import java.util.ArrayList;
import java.util.List;

public class ArrayListExample {
    public static void main(String[] args) {
        // 创建一个ArrayList
        List<String> fruits = new ArrayList<>();
        
        // 添加元素
        fruits.add("Apple");
        fruits.add("Banana");
        fruits.add("Orange");
        fruits.add("Apple"); // 允许重复
        
        System.out.println("Fruits: " + fruits); // [Apple, Banana, Orange, Apple]
        
        // 通过索引访问
        String firstFruit = fruits.get(0);
        System.out.println("First fruit: " + firstFruit); // Apple
        
        // 修改元素
        fruits.set(1, "Grape");
        System.out.println("After set: " + fruits); // [Apple, Grape, Orange, Apple]
        
        // 插入元素
        fruits.add(1, "Mango");
        System.out.println("After insert: " + fruits); // [Apple, Mango, Grape, Orange, Apple]
        
        // 删除元素
        fruits.remove("Apple"); // 删除第一个匹配的元素
        System.out.println("After remove: " + fruits); // [Mango, Grape, Orange, Apple]
        fruits.remove(0); // 删除索引为0的元素
        System.out.println("After remove by index: " + fruits); // [Grape, Orange, Apple]
        
        // 查找元素
        int indexOfOrange = fruits.indexOf("Orange");
        System.out.println("Index of Orange: " + indexOfOrange); // 1
        
        // 遍历
        System.out.println("Fruits:");
        for (String fruit : fruits) {
            System.out.println("- " + fruit);
        }
    }
}
2.2.2 性能特点
  • 随机访问O(1) - 非常快。
  • 在末尾添加/删除O(1) - 平均情况下很快(扩容时为O(n))。
  • 在中间或开头插入/删除O(n) - 需要移动元素。
  • 查找O(n) - 需要遍历。

初始容量与扩容ArrayList有一个初始容量(默认为10),当元素数量超过容量时,会自动扩容(通常增加50%),并复制所有元素到新数组。如果知道大致元素数量,建议在创建时指定初始容量以避免频繁扩容。

// 指定初始容量
List<Integer> numbers = new ArrayList<>(100);

2.3 LinkedList:双向链表

LinkedList基于双向链表实现。它在列表的任意位置插入和删除元素都非常高效,但随机访问性能较差。

2.3.1 基本使用
import java.util.LinkedList;
import java.util.List;

public class LinkedListExample {
    public static void main(String[] args) {
        List<String> tasks = new LinkedList<>();
        
        tasks.add("Task 1");
        tasks.add("Task 2");
        tasks.add("Task 3");
        
        System.out.println("Tasks: " + tasks);
        
        // LinkedList还实现了Deque接口,支持双端操作
        ((LinkedList<String>) tasks).addFirst("Priority Task");
        ((LinkedList<String>) tasks).addLast("Final Task");
        
        System.out.println("After addFirst/addLast: " + tasks);
        
        String firstTask = ((LinkedList<String>) tasks).removeFirst();
        String lastTask = ((LinkedList<String>) tasks).removeLast();
        
        System.out.println("Removed first: " + firstTask); // Priority Task
        System.out.println("Removed last: " + lastTask);   // Final Task
        System.out.println("After removeFirst/removeLast: " + tasks);
    }
}
2.3.2 性能特点
  • 在任意位置插入/删除O(1) - 只需修改指针。
  • 随机访问O(n) - 需要从头或尾遍历。
  • 查找O(n)

何时使用LinkedList

  • 需要频繁在列表中间插入或删除元素。
  • 需要实现栈(push/pop)或队列(offer/poll)数据结构。

2.4 Vector和Stack

VectorArrayList的线程安全版本,其方法大多使用synchronized修饰。由于性能开销,现代开发中通常使用ArrayList配合Collections.synchronizedList()CopyOnWriteArrayList来实现线程安全。

Stack继承自Vector,实现了后进先出(LIFO)的栈。但由于其继承自Vector,且有更好的替代品(如ArrayDeque),不推荐使用。

// 不推荐
Stack<String> stack = new Stack<>();
stack.push("A");
stack.push("B");
String top = stack.pop();

// 推荐使用ArrayDeque作为栈
import java.util.ArrayDeque;
ArrayDeque<String> dequeStack = new ArrayDeque<>();
dequeStack.push("A");
dequeStack.push("B");
String topDeque = dequeStack.pop();

2.5 List的遍历方式

import java.util.*;

public class ListTraversal {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("A", "B", "C", "D");

        // 1. 增强for循环(推荐)
        System.out.println("Enhanced for loop:");
        for (String item : list) {
            System.out.println(item);
        }

        // 2. 迭代器(Iterator)
        System.out.println("Iterator:");
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
            // iterator.remove(); // 如果需要在遍历中删除元素
        }

        // 3. 列表迭代器(ListIterator)- 支持双向遍历和修改
        System.out.println("ListIterator (backward):");
        ListIterator<String> listIterator = list.listIterator(list.size());
        while (listIterator.hasPrevious()) {
            String item = listIterator.previous();
            System.out.println(item);
            // listIterator.set("Modified"); // 修改当前元素
            // listIterator.add("New"); // 在当前位置插入
        }

        // 4. 传统for循环(索引)
        System.out.println("Traditional for loop:");
        for (int i = 0; i < list.size(); i++) {
            String item = list.get(i);
            System.out.println(item);
        }

        // 5. Java 8 Stream API
        System.out.println("Stream API:");
        list.stream().forEach(System.out::println);
    }
}

注意:在使用迭代器遍历集合时,如果需要删除元素,必须使用iterator.remove()方法,直接调用list.remove()会导致ConcurrentModificationException


三、Set接口:无重复元素的集合

Set接口表示一个不包含重复元素的集合。它继承自Collection,并添加了对重复元素的限制。Set通常不保证元素的顺序,但有例外。

3.1 Set的核心特性

  • 无重复:不能包含两个相等的元素(根据equals()方法判断)。
  • 无序性:大多数实现不保证元素的顺序(但LinkedHashSetTreeSet例外)。
  • 常用方法:与Collection接口基本相同,如add(), remove(), contains(), size()等。add()方法在元素已存在时返回false,否则返回true

3.2 HashSet:基于哈希表

HashSetSet接口最常用的实现,基于HashMap。它提供最快的查找、添加和删除操作,但不保证元素的顺序。

3.2.1 基本使用
import java.util.HashSet;
import java.util.Set;

public class HashSetExample {
    public static void main(String[] args) {
        Set<String> colors = new HashSet<>();
        
        // 添加元素
        colors.add("Red");
        colors.add("Green");
        colors.add("Blue");
        colors.add("Red"); // 重复元素,添加失败
        
        System.out.println("Colors: " + colors); // 顺序不确定,如 [Red, Blue, Green]
        System.out.println("Size: " + colors.size()); // 3
        
        // 检查元素
        boolean hasRed = colors.contains("Red");
        System.out.println("Contains Red: " + hasRed); // true
        
        // 删除元素
        colors.remove("Green");
        System.out.println("After remove: " + colors); // [Red, Blue]
        
        // 遍历
        System.out.println("Colors:");
        for (String color : colors) {
            System.out.println("- " + color);
        }
    }
}
3.2.2 哈希码与equals方法

HashSet依赖于对象的hashCode()equals()方法来确保唯一性。对于自定义对象,必须正确重写这两个方法。

import java.util.Objects;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写equals方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    // 重写hashCode方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}
public class HashSetWithCustomObject {
    public static void main(String[] args) {
        Set<Person> people = new HashSet<>();
        people.add(new Person("Alice", 25));
        people.add(new Person("Bob", 30));
        people.add(new Person("Alice", 25)); // 与第一个Alice相等,不会被添加
        
        System.out.println("People: " + people);
        // 输出:[Person{name='Alice', age=25}, Person{name='Bob', 30}]
    }
}

性能特点

  • 添加、删除、查找:平均O(1),最坏O(n)(哈希冲突严重时)。

3.3 LinkedHashSet:有序的HashSet

LinkedHashSet继承自HashSet,但它通过维护一个双向链表来记录元素的插入顺序。因此,它既保证了元素的唯一性,又保持了插入顺序。

3.3.1 基本使用
import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetExample {
    public static void main(String[] args) {
        Set<String> insertionOrdered = new LinkedHashSet<>();
        
        insertionOrdered.add("First");
        insertionOrdered.add("Second");
        insertionOrdered.add("Third");
        insertionOrdered.add("First"); // 重复,不添加
        
        System.out.println("Insertion ordered: " + insertionOrdered);
        // 输出:[First, Second, Third] - 保持插入顺序
    }
}

性能特点

  • HashSet基本相同,但略慢(需要维护链表)。
  • 空间开销:比HashSet大。

3.4 TreeSet:基于红黑树

TreeSet实现了SortedSet接口,使用红黑树(一种自平衡二叉搜索树)存储元素。它保证元素按照升序排列(或自定义比较器指定的顺序),并且不允许null值。

3.4.1 基本使用
import java.util.TreeSet;
import java.util.Set;

public class TreeSetExample {
    public static void main(String[] args) {
        Set<Integer> sortedNumbers = new TreeSet<>();
        
        sortedNumbers.add(5);
        sortedNumbers.add(1);
        sortedNumbers.add(3);
        sortedNumbers.add(9);
        sortedNumbers.add(2);
        
        System.out.println("Sorted numbers: " + sortedNumbers);
        // 输出:[1, 2, 3, 5, 9] - 自动排序
        
        // 获取第一个和最后一个元素
        Integer first = ((TreeSet<Integer>) sortedNumbers).first();
        Integer last = ((TreeSet<Integer>) sortedNumbers).last();
        System.out.println("First: " + first + ", Last: " + last); // 1, 9
        
        // 获取子集
        Set<Integer> subset = ((TreeSet<Integer>) sortedNumbers).subSet(2, 7);
        System.out.println("Subset [2, 7): " + subset); // [2, 3, 5]
        
        // 获取头部和尾部集合
        Set<Integer> headSet = ((TreeSet<Integer>) sortedNumbers).headSet(4);
        Set<Integer> tailSet = ((TreeSet<Integer>) sortedNumbers).tailSet(4);
        System.out.println("Head set (<4): " + headSet); // [1, 2, 3]
        System.out.println("Tail set (>=4): " + tailSet); // [5, 9]
    }
}
3.4.2 自定义排序
import java.util.Comparator;
import java.util.TreeSet;

public class CustomSortedSet {
    public static void main(String[] args) {
        // 按字符串长度排序
        TreeSet<String> lengthSorted = new TreeSet<>((s1, s2) -> 
            Integer.compare(s1.length(), s2.length()));
        
        lengthSorted.add("Apple");
        lengthSorted.add("Fig");
        lengthSorted.add("Banana");
        lengthSorted.add("Kiwi");
        
        System.out.println("Length sorted: " + lengthSorted);
        // 输出:[Fig, Kiwi, Apple, Banana] (按长度:3,4,5,6)
        
        // 或者实现Comparator接口
        TreeSet<String> reverseAlphabetical = new TreeSet<>(Comparator.reverseOrder());
        reverseAlphabetical.add("Apple");
        reverseAlphabetical.add("Banana");
        reverseAlphabetical.add("Cherry");
        System.out.println("Reverse order: " + reverseAlphabetical);
        // 输出:[Cherry, Banana, Apple]
    }
}

性能特点

  • 添加、删除、查找O(log n)
  • 排序:自动维护有序性。

3.5 Set的遍历与操作

import java.util.*;

public class SetOperations {
    public static void main(String[] args) {
        Set<Integer> set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
        Set<Integer> set2 = new HashSet<>(Arrays.asList(4, 5, 6, 7, 8));

        // 并集 (Union)
        Set<Integer> union = new HashSet<>(set1);
        union.addAll(set2);
        System.out.println("Union: " + union); // [1, 2, 3, 4, 5, 6, 7, 8]

        // 交集 (Intersection)
        Set<Integer> intersection = new HashSet<>(set1);
        intersection.retainAll(set2);
        System.out.println("Intersection: " + intersection); // [4, 5]

        // 差集 (Difference)
        Set<Integer> difference = new HashSet<>(set1);
        difference.removeAll(set2);
        System.out.println("Difference (set1 - set2): " + difference); // [1, 2, 3]

        // 子集判断
        Set<Integer> subset = new HashSet<>(Arrays.asList(1, 2));
        boolean isSubset = set1.containsAll(subset);
        System.out.println("Is subset: " + isSubset); // true
    }
}

四、Map接口:键值对的映射

Map接口存储键值对(key-value pairs),其中键是唯一的。它不继承自Collection接口,但提供了集合视图(keySet(), values(), entrySet())来访问其内容。

4.1 Map的核心特性

  • 键的唯一性:每个键最多映射到一个值。
  • 值的可重复性:不同的键可以映射到相同的值。
  • 常用方法
    • put(K key, V value):添加或更新键值对。
    • get(Object key):根据键获取值,键不存在时返回null
    • remove(Object key):移除指定键的映射。
    • containsKey(Object key) / containsValue(Object value):检查是否包含键或值。
    • size() / isEmpty():获取大小和检查是否为空。
    • keySet():返回所有键的Set视图。
    • values():返回所有值的Collection视图。
    • entrySet():返回所有键值对的Set<Map.Entry<K,V>>视图。

4.2 HashMap:基于哈希表的Map

HashMapMap接口最常用的实现,基于哈希表。它提供最快的查找、添加和删除操作,但不保证映射的顺序。

4.2.1 基本使用
import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        Map<String, Integer> ages = new HashMap<>();
        
        // 添加键值对
        ages.put("Alice", 25);
        ages.put("Bob", 30);
        ages.put("Charlie", 35);
        ages.put("Alice", 26); // 更新Alice的年龄
        
        System.out.println("Ages: " + ages); // {Alice=26, Bob=30, Charlie=35}
        
        // 获取值
        Integer aliceAge = ages.get("Alice");
        System.out.println("Alice's age: " + aliceAge); // 26
        
        // 检查键
        boolean hasDavid = ages.containsKey("David");
        System.out.println("Has David: " + hasDavid); // false
        
        // 检查值
        boolean hasAge30 = ages.containsValue(30);
        System.out.println("Has age 30: " + hasAge30); // true
        
        // 删除键值对
        ages.remove("Bob");
        System.out.println("After remove Bob: " + ages); // {Alice=26, Charlie=35}
        
        // 遍历
        System.out.println("Entries:");
        for (Map.Entry<String, Integer> entry : ages.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
        
        // 仅遍历键
        System.out.println("Keys:");
        for (String name : ages.keySet()) {
            System.out.println(name);
        }
        
        // 仅遍历值
        System.out.println("Values:");
        for (Integer age : ages.values()) {
            System.out.println(age);
        }
    }
}
4.2.2 处理null值

HashMap允许null键和null值。

ages.put(null, 100); // 允许null键
ages.put("David", null); // 允许null值

Integer nullKeyAge = ages.get(null); // 返回100
Integer davidAge = ages.get("David"); // 返回null
Integer unknownAge = ages.get("Unknown"); // 也返回null

注意get()方法在键不存在或键映射到null值时都返回null。如果需要区分这两种情况,可以使用containsKey()

4.2.3 性能特点
  • 查找、添加、删除:平均O(1),最坏O(n)
  • 初始容量与负载因子:默认初始容量16,负载因子0.75。当元素数量超过容量 * 负载因子时,会进行扩容(rehashing)。

4.3 LinkedHashMap:有序的HashMap

LinkedHashMap继承自HashMap,通过维护一个双向链表来记录元素的插入顺序或访问顺序。

4.3.1 按插入顺序排序
import java.util.LinkedHashMap;
import java.util.Map;

public class LinkedHashMapInsertionOrder {
    public static void main(String[] args) {
        Map<String, Integer> insertionOrdered = new LinkedHashMap<>();
        
        insertionOrdered.put("First", 1);
        insertionOrdered.put("Second", 2);
        insertionOrdered.put("Third", 3);
        
        System.out.println("Insertion ordered: " + insertionOrdered);
        // 输出:{First=1, Second=2, Third=3}
    }
}
4.3.2 按访问顺序排序(LRU缓存)
import java.util.LinkedHashMap;
import java.util.Map;

public class LRUCacheExample {
    // 实现一个简单的LRU缓存
    static class LRUCache<K, V> extends LinkedHashMap<K, V> {
        private final int capacity;

        public LRUCache(int capacity) {
            // accessOrder=true 表示按访问顺序排序
            super(capacity, 0.75f, true);
            this.capacity = capacity;
        }

        @Override
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            // 当元素数量超过容量时,移除最老的条目
            return size() > capacity;
        }
    }

    public static void main(String[] args) {
        LRUCache<String, Integer> cache = new LRUCache<>(3);
        
        cache.put("A", 1);
        cache.put("B", 2);
        cache.put("C", 3);
        System.out.println("Cache: " + cache); // {A=1, B=2, C=3}
        
        cache.get("A"); // 访问A,将其移到末尾
        System.out.println("After get A: " + cache); // {B=2, C=3, A=1}
        
        cache.put("D", 4); // 添加D,触发移除最老的B
        System.out.println("After put D: " + cache); // {C=3, A=1, D=4}
    }
}

4.4 TreeMap:基于红黑树的有序Map

TreeMap实现了SortedMap接口,使用红黑树存储键值对。它保证键按照升序排列(或自定义比较器指定的顺序)。

4.4.1 基本使用
import java.util.TreeMap;
import java.util.Map;

public class TreeMapExample {
    public static void main(String[] args) {
        Map<String, Integer> sortedMap = new TreeMap<>();
        
        sortedMap.put("Banana", 5);
        sortedMap.put("Apple", 3);
        sortedMap.put("Cherry", 8);
        sortedMap.put("Date", 2);
        
        System.out.println("Sorted map: " + sortedMap);
        // 输出:{Apple=3, Banana=5, Cherry=8, Date=2} - 按键的字母顺序排序
        
        // 获取第一个和最后一个条目
        Map.Entry<String, Integer> firstEntry = sortedMap.firstEntry();
        Map.Entry<String, Integer> lastEntry = sortedMap.lastEntry();
        System.out.println("First: " + firstEntry); // Apple=3
        System.out.println("Last: " + lastEntry);   // Date=2
        
        // 获取子映射
        Map<String, Integer> subMap = sortedMap.subMap("Apple", "Date");
        System.out.println("Submap [Apple, Date): " + subMap); // {Apple=3, Banana=5, Cherry=8}
        
        // 获取头部和尾部映射
        Map<String, Integer> headMap = sortedMap.headMap("Cherry");
        Map<String, Integer> tailMap = sortedMap.tailMap("Cherry");
        System.out.println("Head map (<Cherry): " + headMap); // {Apple=3, Banana=5}
        System.out.println("Tail map (>=Cherry): " + tailMap); // {Cherry=8, Date=2}
    }
}
4.4.2 自定义排序
import java.util.Comparator;
import java.util.TreeMap;

public class CustomSortedMap {
    public static void main(String[] args) {
        // 按值排序(需要自定义比较器)
        TreeMap<String, Integer> valueSorted = new TreeMap<>((k1, k2) -> {
            int valueCompare = Integer.compare(valueSorted.get(k1), valueSorted.get(k2));
            return valueCompare != 0 ? valueCompare : k1.compareTo(k2); // 如果值相等,按键排序
        });
        
        // 注意:上面的比较器在put时无法获取值,因此需要另一种方式
        // 更好的方式是使用值的排序,但TreeMap按键排序
        // 如果需要按值排序,可以将条目放入List并排序
        Map<String, Integer> unsorted = new HashMap<>();
        unsorted.put("A", 3);
        unsorted.put("B", 1);
        unsorted.put("C", 4);
        
        // 按值降序排序
        unsorted.entrySet().stream()
                .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
                .forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue()));
        // 输出:C=4, A=3, B=1
    }
}

性能特点

  • 查找、添加、删除O(log n)
  • 不允许null键(但允许null值,除非比较器支持null)。

4.5 Hashtable与Properties

HashtableHashMap的线程安全版本,其方法使用synchronized修饰。与HashMap不同,Hashtable不允许null键和null值。由于性能问题,现代开发中通常使用ConcurrentHashMap替代。

Properties继承自Hashtable,专门用于处理配置文件(.properties文件)。

import java.util.Properties;
import java.io.*;

public class PropertiesExample {
    public static void main(String[] args) throws IOException {
        Properties props = new Properties();
        
        // 设置属性
        props.setProperty("database.url", "jdbc:mysql://localhost:3306/mydb");
        props.setProperty("database.username", "admin");
        props.setProperty("database.password", "secret");
        
        // 保存到文件
        try (FileOutputStream out = new FileOutputStream("config.properties")) {
            props.store(out, "Database Configuration");
        }
        
        // 从文件加载
        Properties loadedProps = new Properties();
        try (FileInputStream in = new FileInputStream("config.properties")) {
            loadedProps.load(in);
        }
        
        System.out.println("Loaded properties: " + loadedProps);
    }
}

4.6 Map的遍历方式

import java.util.*;

public class MapTraversal {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("A", 1);
        map.put("B", 2);
        map.put("C", 3);

        // 1. 遍历entrySet(推荐,同时获取键和值)
        System.out.println("EntrySet:");
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }

        // 2. 遍历keySet
        System.out.println("KeySet:");
        for (String key : map.keySet()) {
            System.out.println(key + " -> " + map.get(key));
        }

        // 3. 遍历values
        System.out.println("Values:");
        for (Integer value : map.values()) {
            System.out.println(value);
        }

        // 4. 使用Iterator
        System.out.println("Iterator (entrySet):");
        Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey() + " -> " + entry.getValue());
            // iterator.remove(); // 如果需要在遍历中删除
        }

        // 5. Java 8 forEach
        System.out.println("forEach:");
        map.forEach((key, value) -> System.out.println(key + " -> " + value));

        // 6. Java 8 Stream API
        System.out.println("Stream API:");
        map.entrySet().stream()
           .forEach(entry -> System.out.println(entry.getKey() + " -> " + entry.getValue()));
    }
}

五、集合的线程安全

Java集合框架中的大多数实现(如ArrayList, HashMap, HashSet)都不是线程安全的。在多线程环境中直接使用它们可能导致数据不一致或ConcurrentModificationException

5.1 同步包装器

Collections类提供了将非线程安全集合转换为线程安全集合的静态方法。

import java.util.*;

public class SynchronizedCollections {
    public static void main(String[] args) {
        List<String> syncList = Collections.synchronizedList(new ArrayList<>());
        Set<String> syncSet = Collections.synchronizedSet(new HashSet<>());
        Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());

        // 使用同步集合
        syncList.add("Item");
        syncSet.add("Element");
        syncMap.put("Key", 1);

        // 注意:遍历时需要手动同步
        synchronized (syncList) {
            for (String item : syncList) {
                System.out.println(item);
            }
        }
    }
}

5.2 并发集合

java.util.concurrent包提供了专门为高并发场景设计的集合类。

5.2.1 ConcurrentHashMap

ConcurrentHashMapHashMap的线程安全版本,通过分段锁(JDK 7)或CAS+synchronized(JDK 8+)实现高并发性能。

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
        
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        // 模拟并发操作
        for (int i = 0; i < 1000; i++) {
            final int taskId = i;
            executor.submit(() -> {
                String key = "Task" + taskId % 10;
                map.merge(key, 1, Integer::sum); // 原子性地增加计数
            });
        }
        
        executor.shutdown();
        try {
            executor.awaitTermination(1, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        System.out.println("Task counts: " + map);
    }
}
5.2.2 CopyOnWriteArrayList

CopyOnWriteArrayList在修改操作(add, set, remove)时创建底层数组的副本,适用于读多写少的场景。

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        // 遍历时可以安全地修改(因为遍历的是快照)
        for (String item : list) {
            System.out.println(item);
            if ("B".equals(item)) {
                list.add("D"); // 不会影响当前遍历
            }
        }
        System.out.println("Final list: " + list); // [A, B, C, D]
    }
}

六、集合的最佳实践与总结

6.1 选择合适的集合

需求推荐集合
需要有序且允许重复ArrayList
频繁在中间插入/删除LinkedList
不允许重复元素HashSet
保持插入顺序且无重复LinkedHashSet
需要排序的元素TreeSet
键值对映射,高性能HashMap
保持插入顺序的键值对LinkedHashMap
需要排序的键值对TreeMap
线程安全的MapConcurrentHashMap
读多写少的ListCopyOnWriteArrayList

6.2 最佳实践

  1. 使用接口而非具体实现List<String> list = new ArrayList<>();
  2. 指定泛型类型:避免原始类型。
  3. 初始化时指定容量:减少扩容开销。
  4. 合理使用Collections工具类:如unmodifiable, synchronized, emptyList等。
  5. 优先使用Java 8+的Stream API:进行复杂的集合操作。
  6. 注意线程安全:在多线程环境中选择合适的并发集合。

6.3 总结

Java集合框架是Java编程的基石之一。ListSetMap三大接口及其丰富的实现类,为各种数据存储和操作需求提供了完美的解决方案。理解它们的特性、性能差异和适用场景,能够帮助你编写出高效、健壮的Java代码。通过不断实践和深入学习,你将能够灵活运用这些工具,应对复杂的编程挑战。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值