1. 字符串操作优化
1.1 使用StringBuilder进行字符串拼接
// ❌ 问题代码:在循环中使用"+"拼接字符串
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都创建新的StringBuilder对象
}
// ✅ 优化方案:显式使用StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i);
}
String result = sb.toString();
案例代码
public class StringConcatOptimize {
// 测试循环次数:10万次(足够放大性能差异)
private static final int LOOP_COUNT = 100000;
public static void main(String[] args) {
// 1. 方式1:循环中使用"+"拼接字符串(低效)
long plusConcatTime = testPlusConcat();
// 2. 方式2:显式使用StringBuilder拼接(高效)
long stringBuilderTime = testStringBuilderConcat();
// 打印结果
System.out.println("===== 字符串拼接性能对比 =====");
System.out.println("循环中使用\\"+\\"拼接耗时:" + plusConcatTime + " 毫秒");
System.out.println("使用StringBuilder拼接耗时:" + stringBuilderTime + " 毫秒");
}
/**
* 测试循环中用"+"拼接字符串的耗时
*/
private static long testPlusConcat() {
String result = "";
long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_COUNT; i++) {
// 每次循环都会创建新的StringBuilder,拼接后转String
result += i;
}
long endTime = System.currentTimeMillis();
// 打印结果长度(验证拼接完成)
System.out.println("\\"+\\"拼接结果长度:" + result.length());
return endTime – startTime;
}
/**
* 测试显式使用StringBuilder拼接字符串的耗时
*/
private static long testStringBuilderConcat() {
// 初始化StringBuilder(可指定初始容量进一步优化)
StringBuilder sb = new StringBuilder(LOOP_COUNT * 5); // 预估每个数字占5个字符,减少扩容
long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_COUNT; i++) {
// 直接在原有缓冲区追加,无新对象创建
sb.append(i);
}
String result = sb.toString();
long endTime = System.currentTimeMillis();
// 打印结果长度(验证拼接完成)
System.out.println("StringBuilder拼接结果长度:" + result.length());
return endTime – startTime;
}
}
- 核心差异:+ 拼接每次循环创建新的 StringBuilder/String 对象,而显式 StringBuilder 仅创建一个对象,无额外开销。
- 优化技巧:
- 循环拼接字符串时,必须显式使用 StringBuilder(多线程场景用 StringBuffer);
- 初始化 StringBuilder 时尽量指定预估初始容量,避免内部数组扩容。
- 例外场景:单次拼接(如 str = a + b + c)无需手动用 StringBuilder,编译器会自动优化为一次 StringBuilder 拼接,性能无差异。
1.2 字符串常量池的利用
// ❌ 问题代码:不必要的对象创建
String str1 = new String("hello");
String str2 = new String("hello");
// ✅ 优化方案:利用常量池
String str1 = "hello";
String str2 = "hello";
// str1 == str2 为 true
案例代码
public class StringObjectOptimize {
// 测试循环次数:100万次(放大内存/性能差异)
private static final int LOOP_COUNT = 1000000;
public static void main(String[] args) {
// ========== 1. 验证对象唯一性 ==========
System.out.println("===== 1. 对象唯一性对比 =====");
// 方式1:new String 创建(每次新对象)
String strNew1 = new String("hello");
String strNew2 = new String("hello");
System.out.println("new String(\\"hello\\") == new String(\\"hello\\"): " + (strNew1 == strNew2)); // false
System.out.println("new String 内容相等性: " + strNew1.equals(strNew2)); // true(内容相同)
// 方式2:常量池创建(复用同一对象)
String strPool1 = "hello";
String strPool2 = "hello";
System.out.println("\\"hello\\" == \\"hello\\": " + (strPool1 == strPool2)); // true
System.out.println("常量池 内容相等性: " + strPool1.equals(strPool2)); // true
// 额外验证:常量池 vs new String
System.out.println("\\"hello\\" == new String(\\"hello\\"): " + (strPool1 == strNew1)); // false
// ========== 2. 性能/内存开销对比 ==========
System.out.println("\\n===== 2. 性能耗时对比 =====");
// 测试 new String 创建100万次耗时
long newStringTime = testNewStringCreation();
// 测试 常量池 创建100万次耗时
long poolStringTime = testPoolStringCreation();
System.out.println("new String 创建100万次耗时:" + newStringTime + " 毫秒");
System.out.println("常量池 创建100万次耗时:" + poolStringTime + " 毫秒");
}
/**
* 测试 new String 创建大量对象的耗时
*/
private static long testNewStringCreation() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_COUNT; i++) {
// 每次循环都创建新对象,消耗内存+GC资源
String str = new String("hello");
// 避免JIT优化掉无操作的对象创建
str.hashCode();
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 测试 常量池 复用对象的耗时
*/
private static long testPoolStringCreation() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_COUNT; i++) {
// 每次循环都复用常量池中的同一个对象,无新对象创建
String str = "hello";
// 避免JIT优化
str.hashCode();
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
}
- 核心区别:new String("hello") 每次创建新对象(堆内存),而 “hello” 复用常量池中的对象,避免重复创建。
- 优化原则:
- 日常开发中,除非有特殊需求(如需要独立的对象引用),否则绝对不要用 new String("xxx"),直接用 “xxx” 赋值即可;
- 常量池不仅节省内存,还能大幅提升性能,尤其在高频创建相同字符串的场景。
- 扩展知识点:如果已有字符串对象想入池,可使用 str.intern() 方法(JDK7+ 后会将对象引用放入常量池)。
1.3 正则表达式缓存Pattern
// ❌ 问题代码:重复编译正则表达式
public boolean validateEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"); // 每次调用都编译Pattern
}
// ✅ 优化方案:缓存Pattern对象
javaprivate static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
public boolean validateEmailOptimized(String email) {
return EMAIL_PATTERN.matcher(email).matches();
}
案例代码
import java.util.regex.Pattern;
public class RegexOptimize {
// 测试调用次数:100万次(放大性能差异)
private static final int CALL_COUNT = 1000000;
// 测试用的邮箱(模拟真实验证场景)
private static final String TEST_EMAIL = "test123@example.com";
// ✅ 优化方案:缓存编译后的Pattern对象(仅编译一次)
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
public static void main(String[] args) {
// 1. 方式1:重复编译正则(低效)
long unoptimizedTime = testUnoptimizedValidate();
// 2. 方式2:缓存Pattern(高效)
long optimizedTime = testOptimizedValidate();
// 打印结果
System.out.println("===== 正则表达式验证性能对比 =====");
System.out.println("重复编译正则耗时:" + unoptimizedTime + " 毫秒");
System.out.println("缓存Pattern耗时:" + optimizedTime + " 毫秒");
}
/**
* 测试:每次调用都编译正则表达式(问题代码)
*/
private static long testUnoptimizedValidate() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < CALL_COUNT; i++) {
// 每次调用matches()都会重新编译正则表达式
validateEmail(TEST_EMAIL);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 问题代码:重复编译正则
*/
public static boolean validateEmail(String email) {
if (email == null) return false;
return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$");
}
/**
* 测试:缓存Pattern对象(优化方案)
*/
private static long testOptimizedValidate() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < CALL_COUNT; i++) {
// 复用已编译的Pattern,仅创建Matcher
validateEmailOptimized(TEST_EMAIL);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 优化代码:复用编译后的Pattern
*/
public static boolean validateEmailOptimized(String email) {
if (email == null) return false;
// 仅创建Matcher,无正则编译开销
return EMAIL_PATTERN.matcher(email).matches();
}
}
- 核心差异:重复编译方式每次调用都执行正则编译(高耗时),缓存 Pattern 仅编译一次,后续仅做匹配操作(低耗时)。
- 最佳实践:
- 高频调用的正则验证,必须将 Pattern 声明为static final常量缓存;
- 即使是低频调用,缓存 Pattern 也不会有任何副作用,是通用的优化原则。
- 扩展提示:
- Pattern 是线程安全的,可以多线程共享;
- Matcher 是非线程安全的,需在方法内创建,避免多线程共用。
2 集合操作优化
2.1 初始化时指定集合大小
// ❌ 问题代码:不指定初始容量
// 默认初始容量是 10,当元素超过容量时会触发扩容(创建新数组 + 复制元素),扩容次数越多,性能损耗越大。
List<String> list = new ArrayList<>();
// 默认初始容量是 16、负载因子 0.75,当元素数达到 容量*负载因子(12)时触发扩容(重新哈希 + 迁移数据),扩容成本远高于 ArrayList。
Map<String, Integer> map = new HashMap<>();
// ✅ 优化方案:指定初始容量(预估大小)
List<String> list = new ArrayList<>(1000);
// HashMap:初始容量 = 预估元素数 / 负载因子(默认 0.75),向上取整(避免扩容)。
Map<String, Object> map = new HashMap<>(1024, 0.75f);
案例代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CollectionCapacityOptimize {
// 测试数据量:100万条(足够触发多次扩容)
private static final int DATA_SIZE = 1000000;
public static void main(String[] args) {
// ========== 测试 ArrayList 性能 ==========
// 1. 不指定初始容量(默认10)
long arrayListNoCapTime = testArrayListWithoutCapacity();
// 2. 指定初始容量(预估100万)
long arrayListWithCapTime = testArrayListWithCapacity();
// ========== 测试 HashMap 性能 ==========
// 1. 不指定初始容量(默认16)
long hashMapNoCapTime = testHashMapWithoutCapacity();
// 2. 指定初始容量(预估100万,负载因子默认0.75)
long hashMapWithCapTime = testHashMapWithCapacity();
// 打印结果
System.out.println("===== ArrayList 性能对比 =====");
System.out.println("不指定初始容量耗时:" + arrayListNoCapTime + " 毫秒");
System.out.println("指定初始容量耗时:" + arrayListWithCapTime + " 毫秒");
System.out.println("\\n===== HashMap 性能对比 =====");
System.out.println("不指定初始容量耗时:" + hashMapNoCapTime + " 毫秒");
System.out.println("指定初始容量耗时:" + hashMapWithCapTime + " 毫秒");
}
/**
* 测试 ArrayList 不指定初始容量的插入耗时
*/
private static long testArrayListWithoutCapacity() {
List<String> list = new ArrayList<>(); // 默认容量10
long startTime = System.currentTimeMillis();
for (int i = 0; i < DATA_SIZE; i++) {
list.add("data_" + i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 测试 ArrayList 指定初始容量的插入耗时
*/
private static long testArrayListWithCapacity() {
// 直接指定预估容量,避免扩容
List<String> list = new ArrayList<>(DATA_SIZE);
long startTime = System.currentTimeMillis();
for (int i = 0; i < DATA_SIZE; i++) {
list.add("data_" + i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 测试 HashMap 不指定初始容量的插入耗时
*/
private static long testHashMapWithoutCapacity() {
Map<String, Integer> map = new HashMap<>(); // 默认容量16,负载因子0.75
long startTime = System.currentTimeMillis();
for (int i = 0; i < DATA_SIZE; i++) {
map.put("key_" + i, i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 测试 HashMap 指定初始容量的插入耗时
* 公式:预估容量 / 负载因子(向上取整),避免扩容
*/
private static long testHashMapWithCapacity() {
// 计算最优初始容量:1000000 / 0.75 ≈ 1333334(避免扩容)
int initialCapacity = (int) (DATA_SIZE / 0.75f);
Map<String, Integer> map = new HashMap<>(initialCapacity, 0.75f);
long startTime = System.currentTimeMillis();
for (int i = 0; i < DATA_SIZE; i++) {
map.put("key_" + i, i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
}
- 核心优化点:指定初始容量能避免集合扩容时的 “数组复制 / 哈希重计算” 等耗时操作,数据量越大,优化效果越显著。
- 计算技巧:
- ArrayList:直接指定预估元素数量即可;
- HashMap:初始容量 = 预估元素数 / 负载因子(默认 0.75),向上取整(避免扩容)。
- 使用场景:当能预估集合的最终元素数量时,务必指定初始容量;若无法预估,则使用默认值即可。
2.2 使用EntrySet遍历Map
// ❌ 问题代码:使用keySet()遍历Map,低效遍历
for (String key : map.keySet()) {
Object value = map.get(key); // 每次都要查找
}
// ✅ 优化方案:使用entrySet()直接获取键值对,高效遍历
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
}
案例代码
import java.util.HashMap;
import java.util.Map;
public class MapTraversalCompare {
public static void main(String[] args) {
// 1. 准备测试数据:创建一个包含100万条数据的Map
int dataSize = 1000000;
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < dataSize; i++) {
map.put("key_" + i, i);
}
// 2. 方式1:使用keySet()遍历(低效)
long startTime1 = System.currentTimeMillis();
for (String key : map.keySet()) {
// 每次遍历都要通过key重新从Map中查找value,存在重复查找
Integer value = map.get(key);
// 执行一个简单操作(避免JIT优化影响结果)
doSomething(key, value);
}
long endTime1 = System.currentTimeMillis();
System.out.println("使用keySet()遍历耗时:" + (endTime1 – startTime1) + " 毫秒");
// 3. 方式2:使用entrySet()遍历(高效)
long startTime2 = System.currentTimeMillis();
for (Map.Entry<String, Integer> entry : map.entrySet()) {
// 直接从entry中获取键值对,无需重复查找
String key = entry.getKey();
Integer value = entry.getValue();
// 执行相同的简单操作
doSomething(key, value);
}
long endTime2 = System.currentTimeMillis();
System.out.println("使用entrySet()遍历耗时:" + (endTime2 – startTime2) + " 毫秒");
}
// 空方法,仅用于模拟遍历过程中的业务操作,避免JIT优化
private static void doSomething(String key, Integer value) {
// 无实际逻辑,仅占住执行步骤
}
}
- 核心差异:keySet() 遍历需要通过 key 重复查找 value(两次遍历),entrySet() 直接获取键值对(一次遍历),后者减少了哈希查找的开销。
- 性能结论:数据量越大,entrySet() 的效率优势越明显,日常开发中遍历 Map 优先使用 entrySet()。
- 额外提示:如果仅需要遍历 key 或仅需要遍历 value,可直接用 map.keySet() 或 map.values(),无需用 entrySet();但同时需要键值时,entrySet() 是最优选择。
2.3 遍历集合修改
// ❌ 问题代码:增强for循环遍历集合时直接删除元素会触发并发修改异常
List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
for (String s : list) {
if ("b".equals(s)) {
list.remove(s); // 抛出ConcurrentModificationException
}
}
// ✅ 优化方案:使用迭代器的remove方法安全删除元素
// 1. 获取集合对应的迭代器对象
Iterator<String> it = list.iterator();
// 2. 循环判断是否还有下一个元素
while (it.hasNext()) {
// 3. 获取当前迭代的元素(必须先调用next()才能调用remove())
String currentElement = it.next();
if ("b".equals(currentElement)) {
// 4. 调用迭代器的remove方法:会同步更新modCount和expectedModCount
it.remove(); // 安全删除:不会触发并发修改异常
}
}
// 方案2:使用Java8+ removeIf
list.removeIf(s -> "b".equals(s));
// 方案3:创建新集合(适合删除元素多的情况)
List<String> newList = list.stream().filter(s -> !"b".equals(s)).collect(Collectors.toList());
案例代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ConcurrentModificationException; // 补充导入缺失的异常类
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
public class SafeRemoveElement { // 类名与文件名SafeRemoveElement.java一致
public static void main(String[] args) {
// ========== 1. 错误示例:增强for循环直接删除元素(触发异常) ==========
System.out.println("===== 1. 错误方式:增强for循环直接删除 =====");
List<String> errorList = new ArrayList<>(Arrays.asList("a", "b", "c"));
try {
for (String s : errorList) {
if ("b".equals(s)) {
errorList.remove(s); // 触发ConcurrentModificationException
}
}
} catch (ConcurrentModificationException e) {
System.out.println("异常信息:" + e.getClass().getSimpleName());
System.out.println("删除后集合状态:" + errorList); // 集合可能处于不一致状态
}
// ========== 2. 正确方案1:使用迭代器的remove()方法 ==========
System.out.println("\\n===== 2. 正确方案1:迭代器remove() =====");
List<String> iteratorList = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> it = iteratorList.iterator();
while (it.hasNext()) {
String current = it.next(); // 必须先next()才能remove()
if ("b".equals(current)) {
it.remove(); // 安全删除,同步更新修改次数
}
}
System.out.println("删除后集合:" + iteratorList); // [a, c]
// ========== 3. 正确方案2:Java8+ removeIf()(简洁高效) ==========
System.out.println("\\n===== 3. 正确方案2:removeIf() =====");
List<String> removeIfList = new ArrayList<>(Arrays.asList("a", "b", "c"));
removeIfList.removeIf(s -> "b".equals(s)); // 一行代码完成安全删除
System.out.println("删除后集合:" + removeIfList); // [a, c]
// ========== 4. 正确方案3:流式处理创建新集合 ==========
System.out.println("\\n===== 4. 正确方案3:流式处理创建新集合 =====");
List<String> streamList = new ArrayList<>(Arrays.asList("a", "b", "c"));
// 过滤掉不需要的元素,收集为新集合(原集合不变)
List<String> newList = streamList.stream()
.filter(s -> !"b".equals(s)) // 保留非"b"的元素
.collect(Collectors.toList());
System.out.println("原集合:" + streamList); // [a, b, c](未修改)
System.out.println("新集合:" + newList); // [a, c]
}
}
- 核心问题:
- 增强for循环本质是迭代器遍历,集合的remove方法会修改modCount(修改次数),而迭代器的expectedModCount(预期修改次数)未同步,导致校验失败抛出异常(ConcurrentModificationException)。
- 迭代器的remove()方法会同步更新modCount和expectedModCount,是遍历过程中删除元素的安全方式。
- 最优方案选择:
- 简单删除场景:优先用removeIf()(代码最简洁);
- 复杂遍历逻辑(需额外处理元素):用迭代器的remove();
- 需保留原集合 / 删除元素多:用流式处理创建新集合。
- 注意事项:迭代器的remove()每次只能删除next()获取的当前元素,且next()和remove()需成对调用。
2.4 选择合适的数据结构
// ❌ 问题代码:在不合适的场景使用LinkedList
List<String> list = new LinkedList<>();
for (int i = 0; i < 10000; i++) {
list.get(i); // LinkedList的随机访问是O(n)
}
// ✅ 优化方案:根据访问模式选择数据结构
// 1. 高频随机访问(get/set按索引操作)场景 → 选ArrayList
// ArrayList底层是动态扩容的数组,随机访问时通过索引直接定位内存地址,时间复杂度O(1)
// 缺点:数组中间插入/删除需要移动元素,时间复杂度O(n)
List<String> list = new ArrayList<>();
// 2. 高频插入/删除(尤其是中间位置)场景 → 选LinkedList
// LinkedList底层是双向链表,插入/删除仅需修改节点指针,时间复杂度O(1)(找到节点后)
// 缺点:随机访问需要遍历链表,时间复杂度O(n)
List<String> list2 = new LinkedList<>();
// 3. 需要按key自然排序/自定义排序的Map场景 → 选TreeMap
// TreeMap底层是红黑树(平衡二叉树),会自动对key排序,增删查的时间复杂度O(log n)
// 缺点:性能略低于HashMap,无排序需求时不建议使用
Map<String, Integer> sortedMap = new TreeMap<>();
// 4. 无需排序、追求极致读写性能的Map场景 → 选HashMap
// HashMap底层是数组+链表/红黑树(JDK1.8+),增删查的平均时间复杂度O(1)
// 注意:HashMap是无序的,线程不安全;需线程安全可选ConcurrentHashMap(优于Hashtable)
Map<String, Integer> fastMap = new HashMap<>();
案例代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class CollectionSelectOptimize {
// 测试数据量:10000条(放大性能差异)
private static final int DATA_SIZE = 10000;
public static void main(String[] args) {
// ========== 场景1:高频随机访问(get)→ ArrayList vs LinkedList ==========
System.out.println("===== 场景1:高频随机访问(get)=====");
// 初始化数据
List<String> arrayList = new ArrayList<>();
List<String> linkedList = new LinkedList<>();
for (int i = 0; i < DATA_SIZE; i++) {
arrayList.add("data_" + i);
linkedList.add("data_" + i);
}
// 测试ArrayList随机访问
long arrayListGetTime = testRandomAccess(arrayList);
// 测试LinkedList随机访问
long linkedListGetTime = testRandomAccess(linkedList);
System.out.println("ArrayList 随机访问耗时:" + arrayListGetTime + " 毫秒");
System.out.println("LinkedList 随机访问耗时:" + linkedListGetTime + " 毫秒");
// ========== 场景2:高频中间插入 → ArrayList vs LinkedList ==========
System.out.println("\\n===== 场景2:高频中间插入 ======");
// 重置集合并初始化基础数据(关键修复:先填充数据,再测试插入)
arrayList = new ArrayList<>();
linkedList = new LinkedList<>();
// 初始化基础数据,保证中间插入的索引合法
for (int i = 0; i < DATA_SIZE; i++) {
arrayList.add("data_" + i);
linkedList.add("data_" + i);
}
// 测试ArrayList中间插入
long arrayListInsertTime = testMiddleInsert(arrayList);
// 测试LinkedList中间插入
long linkedListInsertTime = testMiddleInsert(linkedList);
System.out.println("ArrayList 中间插入耗时:" + arrayListInsertTime + " 毫秒");
System.out.println("LinkedList 中间插入耗时:" + linkedListInsertTime + " 毫秒");
// ========== 场景3:Map读写性能 → HashMap vs TreeMap ==========
System.out.println("\\n===== 场景3:Map读写性能(无序vs排序)=====");
// 测试HashMap(无序,高性能)
long hashMapTime = testMapOperation(new HashMap<>());
// 测试TreeMap(排序,性能略低)
long treeMapTime = testMapOperation(new TreeMap<>());
System.out.println("HashMap 增删查耗时:" + hashMapTime + " 毫秒");
System.out.println("TreeMap 增删查耗时:" + treeMapTime + " 毫秒");
// 验证TreeMap的排序特性
testTreeMapSort();
}
/**
* 测试随机访问(get按索引)性能
*/
private static long testRandomAccess(List<String> list) {
long startTime = System.currentTimeMillis();
// 随机访问:遍历所有索引的元素(模拟高频get操作)
for (int i = 0; i < DATA_SIZE; i++) {
list.get(i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 测试中间位置插入性能(修复:动态计算中间索引,避免越界)
*/
private static long testMiddleInsert(List<String> list) {
long startTime = System.currentTimeMillis();
// 动态计算中间索引(避免硬编码5000导致越界)
int middleIndex = list.size() / 2;
// 高频中间插入:循环插入1000条数据
for (int i = 0; i < 1000; i++) {
list.add(middleIndex, "insert_" + i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 测试Map的增删查综合性能
*/
private static long testMapOperation(Map<String, Integer> map) {
long startTime = System.currentTimeMillis();
// 1. 新增数据
for (int i = 0; i < DATA_SIZE; i++) {
map.put("key_" + i, i);
}
// 2. 查找数据
for (int i = 0; i < DATA_SIZE; i++) {
map.get("key_" + i);
}
// 3. 删除数据
for (int i = 0; i < DATA_SIZE / 2; i++) {
map.remove("key_" + i);
}
long endTime = System.currentTimeMillis();
return endTime – startTime;
}
/**
* 验证TreeMap的自动排序特性
*/
private static void testTreeMapSort() {
Map<String, Integer> treeMap = new TreeMap<>();
treeMap.put("key_3", 3);
treeMap.put("key_1", 1);
treeMap.put("key_2", 2);
System.out.println("\\nTreeMap 自动排序结果:" + treeMap.keySet()); // [key_1, key_2, key_3]
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("key_3", 3);
hashMap.put("key_1", 1);
hashMap.put("key_2", 2);
System.out.println("HashMap 无序结果:" + hashMap.keySet()); // 顺序不固定
}
}
- 原理分析:ArrayList的随机访问时间复杂度为O(1),而LinkedList为O(n)。HashMap的插入和查找为O(1),TreeMap为O(log n)。根据具体场景选择最合适的数据结构是性能优化的基础。
- 核心原则:数据结构的选择必须匹配访问模式,选错会导致百倍级性能损耗:
- 高频随机访问(get/set)→ 选 ArrayList;
- 高频插入 / 删除(尤其是中间位置)→ 选 LinkedList;
- 无需排序、追求极致读写 → 选 HashMap;
- 需要按 Key 排序 → 选 TreeMap(接受性能略降)。
- 避坑点:不要盲目使用 LinkedList,90% 的业务场景中 ArrayList 更高效(即使有少量插入 / 删除);
- 扩展提示:
- 线程安全场景:List 选 CopyOnWriteArrayList,Map 选 ConcurrentHashMap;
- 固定长度集合:优先用数组(比 ArrayList 更省内存)。
3 循环优化
3.1 提取循环内不变的计算
// ❌ 问题代码:每次循环都调用size(),重复计算
for (int i = 0; i < list.size(); i++) { // 每次循环都调用size()
// …
}
// ✅ 优化方案:缓存循环条件值 ,取不变部分
int size = list.size(); // 缓存size值
for (int i = 0; i < size; i++) {
// …
}
原理分析:在循环条件中调用方法会导致每次迭代都进行方法调用,即使方法结果不变。缓存结果可以减少方法调用开销
3.2 增强for循环与迭代器
// 增强for循环(内部使用迭代器)
for (String item : list) {
// 简洁高效
}
// 需要删除元素时使用迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (shouldRemove(item)) {
iterator.remove(); // 安全的删除操作
}
}
原理分析:增强for循环:编译器自动转换为迭代器,简化代码同时保持性能
3.3 使用System.arraycopy()复制数组
// ❌ 问题代码:手动循环复制数组
int[] source = new int[1000];
int[] dest = new int[1000];
for (int i = 0; i < source.length; i++) {
dest[i] = source[i]; // 逐个元素复制
}
// ✅ 优化方案:使用System.arraycopy()
javaint[] source = new int[1000];
int[] dest = new int[1000];
System.arraycopy(source, 0, dest, 0, source.length);
案例代码
public class ArrayCopyOptimize {
// 测试数据量:1000万条(放大性能差异)
private static final int ARRAY_SIZE = 10_000_000;
public static void main(String[] args) {
// 1. 初始化源数组(填充测试数据)
int[] source = new int[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++) {
source[i] = i;
}
// 2. 测试1:手动循环复制数组(低效)
long manualCopyTime = testManualArrayCopy(source);
// 3. 测试2:System.arraycopy()复制数组(高效)
long systemCopyTime = testSystemArrayCopy(source);
// 打印结果
System.out.println("===== 数组复制性能对比 =====");
System.out.println("手动循环复制耗时:" + manualCopyTime + " 毫秒");
System.out.println("System.arraycopy()复制耗时:" + systemCopyTime + " 毫秒");
System.out.println("性能提升倍数:" + (double) manualCopyTime / systemCopyTime + " 倍");
// 额外验证:Java 8+ Arrays.copyOf(底层也是System.arraycopy)
long arraysCopyOfTime = testArraysCopyOf(source);
System.out.println("Arrays.copyOf()复制耗时:" + arraysCopyOfTime + " 毫秒");
}
/**
* 测试:手动for循环复制数组
*/
private static long testManualArrayCopy(int[] source) {
int[] dest = new int[ARRAY_SIZE];
long startTime = System.currentTimeMillis();
// 逐个元素复制,Java层循环开销大
for (int i = 0; i < source.length; i++) {
dest[i] = source[i];
}
long endTime = System.currentTimeMillis();
// 验证复制结果(避免JIT优化掉无操作的复制)
verifyCopy(source, dest);
return endTime – startTime;
}
/**
* 测试:System.arraycopy()复制数组(native方法)
*/
private static long testSystemArrayCopy(int[] source) {
int[] dest = new int[ARRAY_SIZE];
long startTime = System.currentTimeMillis();
// 本地方法复制,直接操作内存块
System.arraycopy(source, 0, dest, 0, source.length);
long endTime = System.currentTimeMillis();
// 验证复制结果
verifyCopy(source, dest);
return endTime – startTime;
}
/**
* 测试:Arrays.copyOf(底层封装了System.arraycopy)
*/
private static long testArraysCopyOf(int[] source) {
long startTime = System.currentTimeMillis();
// Arrays.copyOf = 新建数组 + System.arraycopy
int[] dest = java.util.Arrays.copyOf(source, source.length);
long endTime = System.currentTimeMillis();
// 验证复制结果
verifyCopy(source, dest);
return endTime – startTime;
}
/**
* 验证复制结果是否一致(避免JIT优化影响测试结果)
*/
private static void verifyCopy(int[] source, int[] dest) {
if (source.length != dest.length) {
throw new RuntimeException("复制失败:数组长度不一致");
}
// 随机验证一个元素(无需全量验证,节省时间)
int randomIndex = (int) (Math.random() * source.length);
if (source[randomIndex] != dest[randomIndex]) {
throw new RuntimeException("复制失败:元素值不一致");
}
}
}
- 核心差异:手动循环是 Java 层解释执行(逐元素赋值),System.arraycopy() 是 JVM 本地方法(内存块复制),后者无循环开销,性能大幅提升;
- 最优实践:
- 复制已有数组:优先用 System.arraycopy()(灵活控制起始索引和长度);
- 新建数组并复制:用 Arrays.copyOf()(代码更简洁,底层也是 System.arraycopy);
- 绝对避免手动循环复制数组(除非有特殊的元素处理逻辑);
- 扩展提示:
- System.arraycopy()支持数组部分复制(比如只复制前 100 个元素),灵活性更高;
- 该方法是不安全的(目标数组容量不足会抛ArrayIndexOutOfBoundsException),使用前需校验数组长度。
4. 对象创建优化
4.1 避免不必要的对象创建
// ❌ 问题代码:过度使用包装类型,不必要的包装类
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // 每次都会创建Long对象,自动装箱拆箱
}
// ✅ 优化方案:使用基本数据类型,使用基本类型
long sum = 0L; // 使用基本类型
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // 无装箱拆箱开销
}
代码案例
public class WrapperTypeOptimize {
// 测试循环次数:Integer.MAX_VALUE(约21亿次,放大装箱拆箱开销)
// 注:完整循环21亿次耗时过久,这里用1000万次演示(可自行调整)
private static final long LOOP_COUNT = 10_000_000L;
public static void main(String[] args) {
// 1. 测试:使用包装类型 Long(低效,频繁装箱拆箱)
long wrapperTime = testWrapperType();
// 2. 测试:使用基本类型 long(高效,无装箱拆箱)
long primitiveTime = testPrimitiveType();
// 打印结果
System.out.println("===== 包装类型 vs 基本类型 性能对比 =====");
System.out.println("使用 Long 包装类型耗时:" + wrapperTime + " 毫秒");
System.out.println("使用 long 基本类型耗时:" + primitiveTime + " 毫秒");
System.out.println("性能差距倍数:" + (double) wrapperTime / primitiveTime + " 倍");
}
/**
* 测试:使用 Long 包装类型(频繁装箱拆箱)
*/
private static long testWrapperType() {
Long sum = 0L; // 包装类型初始值
long startTime = System.currentTimeMillis();
for (long i = 0; i < LOOP_COUNT; i++) {
// sum += i 等价于:sum = Long.valueOf(sum.longValue() + i)
// 每次循环都创建新的 Long 对象,触发装箱拆箱
sum += i;
}
long endTime = System.currentTimeMillis();
// 验证结果(避免JIT优化掉无操作的计算)
System.out.println("Long 计算结果:" + sum);
return endTime – startTime;
}
/**
* 测试:使用 long 基本类型(无装箱拆箱)
*/
private static long testPrimitiveType() {
long sum = 0L; // 基本类型初始值
long startTime = System.currentTimeMillis();
for (long i = 0; i < LOOP_COUNT; i++) {
// 直接数值运算,无任何额外开销
sum += i;
}
long endTime = System.currentTimeMillis();
// 验证结果
System.out.println("long 计算结果:" + sum);
return endTime – startTime;
}
}
- 核心差异:包装类型运算时会触发频繁的「自动装箱 / 拆箱」,创建大量临时对象,而基本类型是纯数值运算,无额外开销;
- 最优实践:
- 纯数值计算场景(如累加、统计、数学运算):必须使用基本类型(long/int/double 等);
- 仅在需要 “对象特性” 时使用包装类型(如集合存储:List、泛型限制、null 值表示);
- 扩展提示:
- JDK5+ 的自动装箱 / 拆箱是 “语法糖”,方便开发但会隐藏性能损耗,高频循环中需警惕;
- 包装类型有缓存池(如 Long 缓存 -128~127),但超出范围后仍会创建新对象,无法解决高频循环的性能问题。
4.2 重用重量级对象
// ❌ 问题代码:重复创建SimpleDateFormat
public String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date); // 每次调用都创建新对象
}
// ✅ 优化方案:使用静态常量或ThreadLocal
// 方案1:静态常量(单线程安全)
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd");
// 方案2:ThreadLocal(多线程安全)
private static final ThreadLocal<SimpleDateFormat> SDF_TL =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDateOptimized(Date date) {
return SDF.format(date);
}
案例代码
// ❌ 问题代码:每次调用方法都创建新的SimpleDateFormat对象,存在两大问题
// 1. 性能损耗:频繁创建/销毁对象,增加JVM内存分配和GC压力
// 2. 若改为全局共享且多线程调用,会出现线程安全问题(SimpleDateFormat内部Calendar对象共享)
public String formatDate(Date date) {
// 每次调用都新建对象,10000次调用就创建10000个SimpleDateFormat实例
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date); // 单次调用看似没问题,但高频调用性能差
}
// ✅ 优化方案1:静态常量(仅适用于单线程/无并发场景)
// 优点:全局复用一个对象,消除重复创建开销
// 缺点:多线程环境下会出现线程安全问题(如日期解析错误、数据错乱)
private static final SimpleDateFormat SDF_SINGLE_THREAD = new SimpleDateFormat("yyyy-MM-dd");
// ✅ 优化方案2:ThreadLocal(推荐,适用于多线程/高并发场景)
// 核心原理:每个线程拥有独立的SimpleDateFormat实例,避免线程间共享
// 优点:既复用对象(每个线程一个),又保证线程安全;性能接近静态常量
// 注意:JDK8+推荐使用withInitial方式初始化,懒加载且代码更简洁
private static final ThreadLocal<SimpleDateFormat> SDF_THREAD_LOCAL =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 优化方案1的调用方法(单线程专用)
public String formatDateSingleThread(Date date) {
if (date == null) {
throw new IllegalArgumentException("日期参数不能为空");
}
return SDF_SINGLE_THREAD.format(date);
}
// 优化方案2的调用方法(多线程安全)
public String formatDateMultiThread(Date date) {
if (date == null) {
throw new IllegalArgumentException("日期参数不能为空");
}
// 从当前线程的ThreadLocal中获取专属的SimpleDateFormat实例
SimpleDateFormat sdf = SDF_THREAD_LOCAL.get();
try {
return sdf.format(date);
} finally {
// 非必须:若线程复用(如线程池),可移除当前线程的实例以释放内存
// SDF_THREAD_LOCAL.remove();
}
}
// ✅ 进阶优化:JDK8+ 推荐使用 java.time 包(线程安全、API更友好)
// DateTimeFormatter 是不可变对象,天然线程安全,无需ThreadLocal/静态常量
private static final java.time.format.DateTimeFormatter DATE_FORMATTER =
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd");
public String formatDateJdk8(Date date) {
if (date == null) {
throw new IllegalArgumentException("日期参数不能为空");
}
// 将旧Date转换为LocalDate,使用新API格式化
java.time.LocalDate localDate = date.toInstant()
.atZone(java.time.ZoneId.systemDefault())
.toLocalDate();
return localDate.format(DATE_FORMATTER);
}
// 测试方法(模拟多线程调用)
public static void main(String[] args) {
DateFormatOptimization demo = new DateFormatOptimization();
// 模拟10个线程并发调用
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 每个线程格式化100次随机日期
for (int j = 0; j < 100; j++) {
long randomTime = ThreadLocalRandom.current().nextLong(0, System.currentTimeMillis());
Date randomDate = new Date(randomTime);
// 推荐使用JDK8+的安全方案
String result = demo.formatDateJdk8(randomDate);
System.out.printf("线程[%s] 格式化结果:%s%n", Thread.currentThread().getName(), result);
}
}).start();
}
}
}
原理分析:SimpleDateFormat等重量级对象的创建成本较高,且不是线程安全的。通过重用对象可以减少创建开销和GC压力
5 资源管理优化
5.1 使用try-with-resources
// ❌ 传统方式
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("file.txt"));
// 处理文件
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
// 处理异常
}
}
}
// ✅ 优化方案:try-with-resources (Java 7+) – 自动资源管理语法糖
// 核心原理:
// 1. 括号内声明的资源必须实现 AutoCloseable 接口(所有IO流都已实现)
// 2. JVM会在try块执行完毕(正常/异常结束)后,自动调用资源的close()方法
// 3. 底层会自动生成类似传统方式的finally块,但代码更简洁,且能正确处理多个资源的关闭顺序
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
// 无需手动关闭资源:JVM会自动调用brAuto.close(),且异常处理更优雅
}
原理分析:try-with-resources:自动调用资源的close()方法,确保资源释放避免内存泄漏
5.2 未使用缓冲I/O
❌ 问题代码:逐字节读取大文件,性能极低
try (FileInputStream fis = new FileInputStream("largefile.txt")) {
int data;
// read()方法每次仅从磁盘读取1个字节,返回字节的ASCII值(0-255),读到末尾返回-1
// 问题:大文件场景下会产生大量磁盘IO操作(磁盘寻道+读取),IO次数与文件大小成正比
// 磁盘IO是慢速操作,频繁的单次字节读取会导致严重的性能瓶颈
while ((data = fis.read()) != –1) { // 每次读取1个字节
// 处理data
}
}
// ✅ 优化方案:使用带缓冲区的输入流批量读取,大幅提升读取效率
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("largefile.txt"))) {
// 创建8KB(8192字节)的字节缓冲区:一次读取多字节到内存,减少磁盘IO次数
// 8KB是JDK默认的缓冲区大小,也是平衡内存占用和IO效率的最优值之一
byte[] buffer = new byte[8192]; // 8K缓冲区
int bytesRead; // 记录每次实际读取的字节数(最后一次可能小于缓冲区大小)
// read(buffer):一次性从缓冲区读取最多8192字节到内存数组中
// 底层逻辑:BufferedInputStream会先把磁盘数据批量读取到其内部缓冲区
// 后续read操作优先从内存缓冲区获取数据,缓冲区空了才会触发一次磁盘IO填充缓冲区
// 返回值:实际读取的字节数,读到文件末尾返回-1
while ((bytesRead = bis.read(buffer)) != –1) {
// 处理buffer
}
}
原理分析:通过引入内存缓冲区(默认 8KB),将逐字节读取时的高频次磁盘 IO 操作,合并为以缓冲区大小为单位的低频次批量读取,大幅减少慢速的磁盘 IO 次数,从而提升大文件读取效率。
6. 并发优化
6.1 使用局部变量
// 局部变量存储在栈上,访问速度更快
public void process(List<String> items) {
int size = items.size(); // 局部变量
// 使用size而不是反复调用items.size()
}
原理分析:局部变量在栈上分配,方法结束时自动回收,没有GC开销。而成员变量在堆上分配,访问时需要额外的寻址操作
6.2 选择合适的并发容器
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.*;
/**
* 并发容器选型与使用示例
* 核心原则:根据"读写频率、是否有序、是否阻塞"选择匹配的并发容器
*/
public class ConcurrentContainerSelection {
public static void main(String[] args) throws InterruptedException {
// ====================== 场景1:高频读、低频写(如配置列表、基础数据缓存) ======================
// ❌ 错误:ArrayList非线程安全,并发读写会数据错乱
// List<String> unsafeList = new ArrayList<>();
// ✅ 正确:CopyOnWriteArrayList(写时复制),读无锁,写时复制新数组(适合读多写少)
Set<String> safeSet = new CopyOnWriteArraySet<>(); // Set版同理
safeSet.add("config1");
// 读操作无锁,性能极高
for (String s : safeSet) {
System.out.println("读取配置:" + s);
}
// ====================== 场景2:高频并发读写的Map(如缓存、业务数据存储) ======================
// ❌ 错误:HashMap非线程安全,并发PUT/GET会导致死循环/数据丢失
// Map<String, Integer> unsafeMap = new HashMap<>();
// ✅ 正确:ConcurrentHashMap(JDK1.8+ 用CAS+Synchronized替代分段锁,高并发性能最优)
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// 并发PUT:支持高并发,无需手动加锁
concurrentMap.put("user1", 1001);
// 并发GET:无锁,性能接近普通HashMap
System.out.println("获取用户ID:" + concurrentMap.get("user1"));
// ====================== 场景3:需要有序的并发Map/Set ======================
// ✅ ConcurrentSkipListMap:按Key自然排序,高并发有序Map(替代线程不安全的TreeMap)
Map<String, Integer> sortedConcurrentMap = new ConcurrentSkipListMap<>();
sortedConcurrentMap.put("b", 2);
sortedConcurrentMap.put("a", 1);
// 遍历结果会按Key排序:a→b
for (Map.Entry<String, Integer> entry : sortedConcurrentMap.entrySet()) {
System.out.println("有序键值对:" + entry.getKey() + "=" + entry.getValue());
}
// ====================== 场景4:生产-消费模型(队列) ======================
// 场景4.1:有界队列(控制生产速度,避免内存溢出)
Queue<String> boundedQueue = new ArrayBlockingQueue<>(100); // 容量100
// 场景4.2:无界队列(适合异步任务处理)
Queue<String> unboundedQueue = new LinkedBlockingQueue<>();
// 场景4.3:优先级并发队列(按元素优先级消费)
Queue<Integer> priorityQueue = new PriorityBlockingQueue<>();
priorityQueue.add(3);
priorityQueue.add(1);
System.out.println("优先级队列取出:" + priorityQueue.take()); // 输出1(优先取最小值)
// 场景4.4:延迟队列(定时任务,如订单超时关闭)
DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
// 添加延迟任务:3秒后可被消费
delayQueue.add(new DelayedTask("订单123超时关闭", 3000));
// 消费任务:take()会阻塞直到任务到期
new Thread(() -> {
try {
DelayedTask task = delayQueue.take();
System.out.println("执行延迟任务:" + task.getTaskName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 等待3秒看延迟任务执行
Thread.sleep(3000);
}
/**
* 延迟任务示例(实现Delayed接口)
*/
static class DelayedTask implements Delayed {
private final String taskName;
private final long expireTime; // 任务到期时间(毫秒)
public DelayedTask(String taskName, long delayMillis) {
this.taskName = taskName;
this.expireTime = System.currentTimeMillis() + delayMillis;
}
public String getTaskName() {
return taskName;
}
// 剩余延迟时间(核心方法)
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(expireTime – System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
// 优先级比较(DelayQueue排序用)
@Override
public int compareTo(Delayed o) {
return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
}
}
}
原理分析:选择并发容器:针对不同并发场景选择最适合的容器,减少锁竞争
6.3 使用线程池管理线程
// ❌ 问题代码:直接创建线程
for (int i = 0; i < 100; i++) {
new Thread(() -> {
// 执行任务
}).start(); // 频繁创建销毁线程
}
// ✅ 优化方案:使用线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 执行任务
});
}
executor.shutdown();
原理分析:线程的创建和销毁是昂贵的操作。线程池可以重用线程,减少创建开销,同时控制并发数量,避免资源耗尽。
6.4 在高并发场景中,过度同步会降低性能。
场景 1:锁粒度优化(最核心)
问题:同步整个方法,包含大量无需同步的逻辑,导致锁竞争激烈。
public class SyncOptimization {
// 共享状态(仅该变量需要线程安全)
private int count = 0;
// 非共享状态(无需同步)
private final Map<String, String> configMap = new HashMap<>();
// ❌ 过度同步:同步整个方法,包含无需同步的configMap操作,锁粒度太大
public synchronized void processData(String key) {
// 1. 无需同步的逻辑(却被加锁,导致线程串行)
String config = configMap.get(key);
doSomeNonSyncWork(config);
// 2. 仅这行需要同步(更新共享变量)
count++;
}
// ✅ 优化:缩小锁粒度,仅同步核心共享变量的修改逻辑
public void processDataOptimized(String key) {
// 1. 无需同步的逻辑:并行执行,无锁竞争
String config = configMap.get(key);
doSomeNonSyncWork(config);
// 2. 仅对共享变量修改加锁,锁粒度最小化
synchronized (this) { // 更优:使用专用锁对象(如private final Object lock = new Object())
count++;
}
}
// 模拟无需同步的耗时操作
private void doSomeNonSyncWork(String config) {
// 业务逻辑:如解析配置、数据转换等(无共享状态修改)
}
}
场景 2:使用并发容器替代同步容器 / 手动加锁
问题:用Hashtable/Collections.synchronizedMap(全表锁),高并发下所有操作串行。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentContainerOptimization {
// ❌ 过度同步:synchronizedMap是全表锁,put/get都加锁,高并发性能差
private static final Map<String, Object> SYNC_MAP = Collections.synchronizedMap(new HashMap<>());
// ✅ 优化:使用ConcurrentHashMap(分段锁/CAS优化,仅锁定当前桶,并发度高)
private static final Map<String, Object> CONCURRENT_MAP = new ConcurrentHashMap<>();
public Object getValue(String key) {
// ConcurrentHashMap的get操作无锁(volatile保证可见性),put仅锁定当前桶
return CONCURRENT_MAP.get(key);
}
public void putValue(String key, Object value) {
CONCURRENT_MAP.put(key, value);
}
}
场景 3:使用原子类替代 synchronized(简单数值操作)
问题:用synchronized保护简单的数值增减,锁开销大于业务逻辑
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicClassOptimization {
// ❌ 过度同步:简单计数用synchronized,锁开销高
private int count = 0;
public synchronized void increment() {
count++;
}
// ✅ 优化:使用AtomicInteger(CAS无锁操作,性能提升10倍+)
private final AtomicInteger atomicCount = new AtomicInteger(0);
public void incrementOptimized() {
// CAS(Compare-And-Swap)无锁更新,无上下文切换开销
atomicCount.incrementAndGet();
}
}
场景 4:读写分离(读多写少场景)
问题:读写都加同一把锁,读操作被写操作阻塞,高频读场景性能差。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockOptimization {
// 共享数据(读多写少,如配置缓存)
private Map<String, String> dataMap = new HashMap<>();
// 读写锁:读锁可共享(多个读线程并行),写锁排他(仅一个写线程)
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// ❌ 过度同步:读写都用synchronized,读线程串行
public synchronized String get(String key) {
return dataMap.get(key);
}
public synchronized void put(String key, String value) {
dataMap.put(key, value);
}
// ✅ 优化:读写分离,读锁共享,写锁排他
public String getOptimized(String key) {
readLock.lock();
try {
// 多个读线程可并行执行,无锁竞争
return dataMap.get(key);
} finally {
readLock.unlock();
}
}
public void putOptimized(String key, String value) {
writeLock.lock();
try {
// 写操作排他,保证数据一致性
dataMap.put(key, value);
} finally {
writeLock.unlock();
}
}
}
场景 5:避免不必要的同步(无共享状态)
问题:方法无共享状态,却加了synchronized,完全无意义且损耗性能
public class UnnecessarySyncOptimization {
// ❌ 过度同步:方法无共享状态,synchronized完全多余
public synchronized String processSingleThreadData(String input) {
// 仅处理方法内局部变量,无任何共享状态修改
return input.toUpperCase();
}
// ✅ 优化:移除不必要的同步,方法天然线程安全
public String processSingleThreadDataOptimized(String input) {
return input.toUpperCase();
}
}
7 数据库操作优化
7.1 使用PreparedStatement
// ❌ Statement容易导致SQL注入
Statement stmt = connection.createStatement();
String sql = "SELECT * FROM users WHERE id = " + userId;
// ✅ PreparedStatement(预编译、防注入)
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, userId);
原理分析:PreparedStatement,SQL预编译提高执行效率,参数化防止SQL注入
7.2 批量处理
// 批量插入数据
try (PreparedStatement pstmt = connection.prepareStatement(
"INSERT INTO users (name, age) VALUES (?, ?)")) {
for (User user : userList) {
pstmt.setString(1, user.getName());
pstmt.setInt(2, user.getAge());
pstmt.addBatch(); // 添加到批量
// 每1000条执行一次
if (i % 1000 == 0) {
pstmt.executeBatch();
}
}
pstmt.executeBatch(); // 执行剩余的
}
原理分析:批量处理:减少数据库连接次数和网络IO,提高数据操作效率
8 算法优化
8.1 空间换时间
// 使用缓存计算结果
private Map<Integer, BigInteger> cache = new HashMap<>();
public BigInteger factorial(int n) {
if (cache.containsKey(n)) {
return cache.get(n);
}
BigInteger result = // 计算阶乘
cache.put(n, result);
return result;
}
原理分析:空间换时间:使用缓存存储计算结果,避免重复计算提高性能
8.2 避免重复计算
// ❌ 重复计算
if (user != null && user.getDepartment() != null
&& user.getDepartment().getCompany() != null) {
// …
}
// ✅ 提前返回
if (user == null) return;
Department dept = user.getDepartment();
if (dept == null) return;
Company company = dept.getCompany();
if (company == null) return;
原理分析:避免重复计算:提前返回和提取公共计算,减少不必要的逻辑判断
9 内存优化
9.1 及时释放引用
public void processLargeData() {
byte[] largeData = loadLargeData();
try {
// 处理数据
} finally {
largeData = null; // 帮助GC
}
}
原理分析:及时释放引用:显式置null帮助垃圾收集器识别可回收对象
9.2 使用软引用/弱引用缓存
// 使用软引用实现缓存
private SoftReference<Map<String, Object>> cacheRef;
public Object getFromCache(String key) {
Map<String, Object> cache = cacheRef.get();
if (cache == null) {
cache = loadCache();
cacheRef = new SoftReference<>(cache);
}
return cache.get(key);
}
原理分析:软引用缓存:内存不足时自动释放缓存,平衡性能和内存使用
优化原则总结
注意:优化前务必进行性能测试和基准测试,确保优化真正有效。不同的应用场景可能需要不同的优化策略。
网硕互联帮助中心







评论前必须登录!
注册