Java 字串反轉完整實作指南與效能分析
字串反轉是經典面試題,也是實務中常見操作 (如回文判斷、加密演算法、資料轉換)。本文介紹五種主流實作方式,從簡單易懂到高效能生產級,並提供 JMH 基準測試框架讓你在專案中實測選型。
五種實作方式
1. StringBuilder.reverse() - 生產首選
public static String reverseStringBuilder(String str) {
if (str == null) return null;
return new StringBuilder(str).reverse().toString();
}
- JDK 內建、高度優化、原生碼實作 (JDK 9+ 使用
StringLatin1.reverse/StringUTF16.reverse)
- 線程安全 (非共享)、可變緩衝區、單次分配
- 推薦等級:⭐⭐⭐⭐⭐
2. 字元陣列雙指針交換 - 手寫高效能
public static String reverseCharArray(String str) {
if (str == null) return null;
char[] arr = str.toCharArray();
int left = 0, right = arr.length - 1;
while (left < right) {
char tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++; right--;
}
return new String(arr);
}
- 原地交換、零額外物件 (除輸出字串)、緩存友好
- 適合嵌入式/極致效能場景、教學展示演算法
- 推薦等級:⭐⭐⭐⭐
3. 遞歸 + substring - 教學用、效能差
public static String reverseRecursive(String str) {
if (str == null || str.length() <= 1) return str;
return str.charAt(str.length() - 1) + reverseRecursive(str.substring(0, str.length() - 1));
}
- 每層遞歸建立新字串、
substring 複製陣列 (JDK 7+)、O(n²) 時間、O(n) 棧深度
- 長字串導致 StackOverflowError、大量 GC 壓力
- 推薦等級:⭐ (僅教學)
4. Stream API - 函數式風格
public static String reverseStream(String str) {
if (str == null) return null;
return str.chars()
.mapToObj(c -> (char) c)
.reduce("", (acc, c) -> c + acc, (a, b) -> b + a); // 字串拼接極慢
}
// 改進版:收集到陣列再反轉
public static String reverseStreamImproved(String str) {
if (str == null) return null;
return str.chars()
.mapToObj(c -> (char) c)
.collect(StringBuilder::new, (sb, c) -> sb.append(c), StringBuilder::append)
.reverse()
.toString();
}
- 函數式表達、並行潛力 (但反轉無並行增益)
- 原版字串拼接 O(n²),改進版接近 StringBuilder
- 推薦等級:⭐⭐⭐
5. ByteBuffer / 位元組層面 - 特殊編碼場景
public static String reverseBytes(String str, Charset charset) {
if (str == null) return null;
byte[] bytes = str.getBytes(charset);
ByteBuffer buf = ByteBuffer.wrap(bytes);
byte[] reversed = new byte[bytes.length];
for (int i = bytes.length - 1; i >= 0; i--) {
reversed[bytes.length - 1 - i] = bytes[i];
}
return new String(reversed, charset);
}
- 處理特定編碼位元組序列反轉 (非字元反轉)
- 多位元組字元 (UTF-8 中文) 會產生無效位元組序列,**慎用**
- 推薦等級:⭐ (特殊需求)
JMH 基準測試框架
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
public class StringReverseBenchmark {
@Param({"10", "100", "1000", "10000"})
public int length;
private String testString;
@Setup
public void setup() {
// 生成隨機字串
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append((char) ('a' + (i % 26)));
}
testString = sb.toString();
}
@Benchmark
public String stringBuilder() {
return new StringBuilder(testString).reverse().toString();
}
@Benchmark
public String charArray() {
char[] arr = testString.toCharArray();
int l = 0, r = arr.length - 1;
while (l < r) { char t = arr[l]; arr[l] = arr[r]; arr[r] = t; l++; r--; }
return new String(arr);
}
@Benchmark
public String streamImproved() {
return testString.chars()
.mapToObj(c -> (char) c)
.collect(StringBuilder::new, (sb, c) -> sb.append(c), StringBuilder::append)
.reverse().toString();
}
}
典型測試結果參考 (JDK 17, x86_64)
| 字串長度 | StringBuilder (ns) | CharArray (ns) | Stream Improved (ns) | 遞歸 (ns) |
| 10 | ~80 | ~60 | ~200 | ~5000 |
| 100 | ~300 | ~250 | ~1200 | ~500000 (OOM 風險) |
| 1000 | ~2500 | ~2200 | ~11000 | StackOverflow |
| 10000 | ~22000 | ~20000 | ~110000 | N/A |
特殊情況處理
| 場景 | 處理方式 |
| Unicode 補充字元 (Emoji、罕用字) | 用 codePoints() 處理 code point 而非 char:
str.codePoints().toArray() → 反轉 int[] → new String(cp, 0, cp.length) |
| 不可變字串常量池 | 反轉後為新物件,不影響原字串 |
| null/空字串 | 開頭防御性檢查 if (str == null || str.isEmpty()) return str; |
| 極大字串 (>100MB) | 分塊處理、記憶體映射、或外部排序算法,避免雙倍記憶體 |
總結建議
- 生產代碼:
new StringBuilder(str).reverse().toString() - 簡潔、高效、JDK 保證
- 效能極致/教學:字元陣列雙指針 - 可控、零額外分配、展示演算法
- 函數式風格:Stream + StringBuilder 收集器 - 團隊風格統一時
- 避免:遞歸、字串拼接 (+)、ByteBuffer 字元反轉
- Unicode 安全:含 Emoji 時必須用 code point 級別反轉