java.util.StringTokenizer:僅支援單一字串、單一分隔符集合,不支援 Reader 串流String.split() / Pattern.split():同樣針對完整字串,需一次載入記憶體public class ReaderToken implements Iterator {
private Reader reader; // 輸入串流
private String[] splits; // 多組分隔符模式
private StringBuffer hasRead; // 已讀未切出的緩衝區
private String[] splitsSf; // 實際匹配到的分隔符文本(含萬用字元展開)
private int currentSplit = -1; // 當前匹配到的分隔符索引
}
public ReaderToken(Reader reader, String[] splits) {
this.reader = reader;
this.splits = splits;
this.splitsSf = new String[splits.length];
this.hasRead = new StringBuffer();
}
private int indexOf() {
currentSplit = -1;
int[] pos = new int[splits.length]; // 各分隔符當前匹配位置
Arrays.fill(pos, 0);
Arrays.fill(splitsSf, "");
try {
while (true) {
// 檢查是否有分隔符已完整匹配
for (int j = 0; j < pos.length; j++) {
if (pos[j] >= splits[j].length()) {
currentSplit = j;
return currentSplit;
}
}
int read = reader.read();
if (read == -1 || read == 65535) return currentSplit; // EOF
char ch = (char) read;
hasRead.append(ch);
// 逐分隔符推進狀態機
for (int i = 0; i < pos.length; i++) {
char expected = splits[i].charAt(pos[i]);
if (expected == ch) {
pos[i]++;
splitsSf[i] += ch;
} else if (expected == '*') { // 萬用字元:匹配任意字元直到下一個指定字元
char next = splits[i].charAt(pos[i] + 1);
if (next == ch) {
pos[i] += 2;
}
splitsSf[i] += ch;
} else if (expected == '?') { // 單一任意字元
pos[i]++;
splitsSf[i] += ch;
} else { // 失配:重置該分隔符狀態
pos[i] = 0;
splitsSf[i] = "";
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return -1;
}
public boolean hasNext() {
return indexOf() > -1;
}
public Object next() {
int tokenEnd = hasRead.length() - splitsSf[currentSplit].length();
StringBuffer token = new StringBuffer(hasRead.substring(0, tokenEnd));
hasRead = new StringBuffer(hasRead.substring(tokenEnd)); // 保留分隔符後的剩餘字元
return token.toString();
}
public void remove() { /* 不支援 */ }
public String getToken() { return splits[currentSplit]; } // 原始模式
public String getTokenInReader() { return splitsSf[currentSplit]; } // 實際匹配文本
public Object getEnd() { return hasRead; } // 流結束殘留內容
String[] patterns = { "a?a", "ERROR:*END" }; // ?=單一任意字元,*=貪婪匹配直到下一字元
String input = "cefaaasdbdERROR: something went wrong ENDmore data";
ReaderToken tokenizer = new ReaderToken(new StringReader(input), patterns);
while (tokenizer.hasNext()) {
System.out.println("Token: " + tokenizer.next());
System.out.println("Pattern: " + tokenizer.getToken());
System.out.println("Matched: " + tokenizer.getTokenInReader());
}
System.out.println("Remainder: " + tokenizer.getEnd());
Token: cef Pattern: a?a Matched: aaa Token: sdbd Pattern: ERROR:*END Matched: ERROR: something went wrong END Remainder: more data
| 特性 | 說明 |
|---|---|
| 串流處理 | 直接操作 Reader,無需載入完整內容至記憶體 |
| 多重分隔符 | 建構子傳入 String[],並行匹配,最先完成者優先 |
| 萬用字元 | *(貪婪直到下一指定字元)、?(單一任意字元) |
| 回傳分隔符 | getTokenInReader() 取得實際匹配文本,便於後續語意判斷 |
| 標準介面 | 實作 Iterator,可直接用於 for (Object t : tokenizer)(需適配器) |
splits 改為 Pattern[],使用 Matcher 逐字元推進(較複雜)mark/reset 支援:需 PushbackReader 或緩衝區管理hasRead 使用 char[] 環狀緩衝區,避免頻繁 StringBuffer 複製TokenizeException 替代 printStackTrace()ReaderToken 以狀態機逐字元掃描實現串流分詞,彌補了 JDK 標準庫在「串流、多重分隔符、萬用字元、分隔符回傳」四大維度的缺口。程式碼簡潔、無第三方依賴,適合嵌入式解析器、日誌分析器、協議解碼器等場景直接使用或二次開發。