在软件开发中,数据结构的选择直接影响程序的性能和可维护性。Java作为一门成熟的编程语言,在java.util包中提供了丰富的数据结构实现,帮助开发者高效地组织和处理数据。这些数据结构可以分为线性结构(如数组、列表、队列)和非线性结构(如树、图)两大类,每种结构都有其特定的应用场景和性能特征。
提示:选择数据结构时,首先要明确数据的访问模式(随机访问还是顺序访问)、数据量大小以及是否需要排序等特性。
数组是最基础的数据结构,Java中的数组具有以下特点:
java复制// 声明并初始化一个整型数组
int[] scores = new int[5];
// 直接初始化数组
String[] names = {"Alice", "Bob", "Charlie"};
数组在内存中是连续存储的,这使得它的随机访问时间复杂度为O(1)。但数组的大小一旦确定就不能改变,这是它的主要局限性。在实际开发中,数组常用于:
注意:数组越界是常见错误,访问数组元素时务必确保索引在0到length-1范围内。
ArrayList是Java中最常用的动态数组实现,它解决了普通数组大小固定的问题:
java复制List<String> fruits = new ArrayList<>();
fruits.add("Apple"); // 添加元素
fruits.add(0, "Banana"); // 在指定位置插入
String first = fruits.get(0); // 获取元素
fruits.remove(1); // 删除元素
ArrayList内部使用Object数组存储数据,当容量不足时会自动扩容(通常增加50%)。这使得:
LinkedList基于双向链表实现,特别适合频繁插入删除的场景:
java复制List<Integer> numbers = new LinkedList<>();
numbers.add(10);
numbers.addFirst(5); // 头部插入
numbers.addLast(15); // 尾部插入
int first = numbers.getFirst(); // 获取头部元素
LinkedList的每个元素(节点)都包含指向前后节点的引用,这使得:
HashSet是基于HashMap实现的集合,它使用哈希表来存储元素:
java复制Set<String> uniqueNames = new HashSet<>();
uniqueNames.add("Alice");
uniqueNames.add("Alice"); // 重复元素不会被添加
boolean exists = uniqueNames.contains("Bob"); // 检查存在性
HashSet的特点包括:
TreeSet基于红黑树实现,保持元素有序:
java复制Set<Integer> sortedNumbers = new TreeSet<>();
sortedNumbers.add(3);
sortedNumbers.add(1);
sortedNumbers.add(2);
// 元素将自动排序:[1, 2, 3]
TreeSet的特性:
HashMap是最常用的Map实现,基于哈希表+链表/红黑树:
java复制Map<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 25);
ageMap.put("Bob", 30);
int aliceAge = ageMap.get("Alice"); // 获取值
ageMap.remove("Bob"); // 删除键值对
HashMap的关键特性:
TreeMap基于红黑树实现,保持键的有序性:
java复制Map<String, Integer> sortedMap = new TreeMap<>();
sortedMap.put("Orange", 2);
sortedMap.put("Apple", 5);
// 键将按字母顺序排列
TreeMap的特点:
Stack类实现后进先出(LIFO)结构:
java复制Stack<Integer> stack = new Stack<>();
stack.push(10); // 压栈
stack.push(20);
int top = stack.peek(); // 查看栈顶元素(不移除)
int popped = stack.pop(); // 出栈
Stack的常用场景:
Queue表示先进先出(FIFO)队列:
java复制Queue<String> queue = new LinkedList<>();
queue.offer("First"); // 入队
queue.offer("Second");
String head = queue.peek(); // 查看队首
String removed = queue.poll(); // 出队
Java中还提供了:
二叉树通常需要自定义节点类:
java复制class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
// 创建二叉树
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
二叉树常用算法包括:
Java标准库没有内置图结构,通常有以下实现方式:
java复制int[][] graph = new int[V][V];
graph[0][1] = 1; // 0和1之间有边
java复制List<List<Integer>> adjList = new ArrayList<>();
adjList.add(Arrays.asList(1, 2)); // 节点0的邻居
adjList.add(Arrays.asList(0, 2)); // 节点1的邻居
Vector是线程安全的动态数组,但性能较低:
java复制Vector<String> vec = new Vector<>();
vec.add("Element");
与ArrayList的主要区别:
Hashtable是早期的键值对实现:
java复制Hashtable<String, Integer> table = new Hashtable<>();
table.put("key", 123);
与HashMap的主要区别:
选择数据结构时考虑以下因素:
| 数据结构 | 获取 | 插入 | 删除 | 查找 | 内存 |
|---|---|---|---|---|---|
| 数组 | O(1) | O(n) | O(n) | O(n) | 小 |
| ArrayList | O(1) | O(n) | O(n) | O(n) | 小 |
| LinkedList | O(n) | O(1) | O(1) | O(n) | 大 |
| HashSet | - | O(1) | O(1) | O(1) | 中 |
| TreeSet | - | O(log n) | O(log n) | O(log n) | 大 |
| HashMap | O(1) | O(1) | O(1) | O(1) | 中 |
| TreeMap | O(log n) | O(log n) | O(log n) | O(log n) | 大 |
对于已知大小的集合,指定初始容量避免扩容开销:
java复制// 已知有1000个元素
List<String> list = new ArrayList<>(1000);
Map<String, Integer> map = new HashMap<>(1024);
使用迭代器时避免结构性修改:
java复制List<String> names = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String name = it.next();
if (name.equals("B")) {
it.remove(); // 正确方式
// names.remove(name); // 错误!会抛出ConcurrentModificationException
}
}
替代传统同步集合的现代方案:
java复制List<String> syncList = Collections.synchronizedList(new ArrayList<>());
java复制ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
CopyOnWriteArrayList<String> threadSafeList = new CopyOnWriteArrayList<>();
这个异常通常发生在遍历集合时修改集合:
java复制List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // 抛出异常
}
}
解决方案:
集合可能引起内存泄漏的常见场景:
解决方法:
提升集合性能的方法: