有學過Java的人應該會知道StringBuilder
或是StringBuffer
這兩個在java.lang
套件下的類別,常被用來處理需要一直被改變內容的字串。由於Java程式語言一個字串(String)有著不可變物件(Immutable Object)的特性,如果直接使用String類別來進行字串的處理,在改變字串的過程中,每次字串內容的變化將會產生出新的String物件來表示,也導致其效能不是很好,需要使用內建的StringBuilder
或是StringBuffer
這兩個類別來專門處理可變的字串,處理完後再使用其toString()
方法將最終結果建立為字串物件。那麼既然Java需要使用StringBuilder
或是StringBuffer
來處理字串,基於JavaScript的Node.js也需要嗎?
Node.js使用Chrome的V8 JavaScript引擎來執行JavaScript程式,而實際上V8實作的字串功能,效能也已經可以說是極好的了,所以官方也無內建StringBuilder
或是StringBuffer
之類的模組。也就是說,在Node.js中,可以不需要特別使用StringBuilder
或是StringBuffer
來提升字串效能。
然而,StringBuilder
或是StringBuffer
帶來的價值僅僅只是字串處理效能而已嗎?不!Java的StringBuilder
和StringBuffer
皆自帶了常用的增、刪、改、查之方法,可以大幅度地減少重複撰寫的程式。
舉例來說,若要將某一段字串插入到某個字串中,如果不使用StringBuilder
或StringBuffer
,寫出來的Java程式看起來會像這樣:
String text = "This is apple.";
String insertString = "an ";
text = text.substring(0, 8) + insertString + text.substring(8);
System.out.println(text); // This is an apple.
利用字串物件的substring
方法將text
字串變數所表示的字串切割成頭、尾兩個部份,再用+
運算子將他們串接,並在兩者中間插入新字串。由這一系列的動作來完成我們所謂的「字串插入」。
但如果使用StringBuilder
或StringBuffer
,這個Java程式會變得十分簡單,如下:
StringBuilder text = new StringBuilder("This is apple.");
String insertString = "an ";
text.insert(8, insertString);
System.out.println(text.toString());
直接使用StringBuilder
物件的insert
方法,將指定字串插入至指定位置。程式看起來也更容易閱讀。
當然,程式設計者也可以將使用substring
方法來完成「字串插入」的動作自己另外寫成方法來呼叫,但這樣做的話,之後字串處理的過程中,如果遇到需要刪除或是取代的時候不也要再實作一次嗎?直接使用StringBuilder
或StringBuffer
會省事很多。
從剛剛就一直在說StringBuilder
和StringBuffer
,這兩者究竟差在哪裡呢?其實,這兩者的用法完全一樣,只差在StringBuilder
類別在多執行緒的條件下是不安全的,而StringBuffer
類別則是擁有thread-safe的特性。若無多執行緒需求的話,使用StringBuilder
類型會比較好,因為不用花費效能去處理同步問題。
Java的內容講了一堆,回到Node.js的主題上吧!Node.js是單執行緒的結構,也就是說,同一時間不會有CPU同時執行某段程式的問題。若要在Node.js上實作如Java內建的StringBuilder
或StringBuffer
,可以不必考慮thread-safe的問題,因此只需要有StringBuilder
就好。
node-stringbuilder
node-stringbuilder
是一個使用Node.js 8之後才支援的N-API所開發的模組,使用C語言和UTF-16編碼,在一段長度可變的記憶體內來處理字串的相關操作,支援string
、Buffer
、ReadStream
、number
、boolean
和其他物件的輸入。同時它也內建字串搜尋的功能,使用「Boyer-Moore-MagicLen」演算法。且每個物件皆能複製出新的獨立物件來進行不一樣的字串處理。
npmjs.com
npm 安裝指令
使用方法
初始化
使用require
函數來引入node-stringbuilder
模組。
const StringBuilder = require("node-stringbuilder");
接著使用new
運算子,或是使用模組的from
方法來建立StringBuilder
物件。
const sb1 = new StringBuilder();
// or
const sb2 = StringBuilder.from();
建構參數可以傳入預設的文字內容和長度容量。
const sb = StringBuilder.from("First", 4096);
長度容量可以分成好幾個區塊,每個長度容量的區塊是128(個字元)。長度容量可以區塊為單位進行擴充或是縮小。
// To expand
const newCapacity = 65536;
sb.expandCapacity(newCapacity);
// To shrink
sb.shrinkCapacity();
當有文字加入至StringBuilder
物件中時,StringBuilder
會去檢查它的長度容量是否足夠容納這新進來的文字。如果不行,它就會自動進行擴展,當然,擴展長度容量的時候會有一些延遲,如果頻繁地執行擴展的動作,甚至會拖慢程式的運行速度。因此,如果程式設計者可以預測這個StringBuilder
物件大概會存到多長的文字,建議在產生StringBuilder
物件的階段就先行設定。
串接(Append)
串接文字至原本的文字之後。
sb.append("string").append(123).append(false).append(fs.createReadStream(path));
串接文字至原本的文字之後,並進行換行。
sb.appendLine("string");
串接文字至原本的文字之後,並重複進行數次。
sb.appendRepeat("string", 3);
非同步(asnyc)的方式串接檔案內容至原本的文字之後。
await sb.appendReadStream(fs.createReadStream(path));
插入(Insert)
插入文字至原本文字中的任意位置。
sb.insert("string"); // To the head.
sb.insert(5, "string");
取代(Replace)
取代原本文字中的指定位置範圍的某段文字。
sb.replace(4, 15, "string");
取代部份原本文字中已存在的子字串。
sb.replacePattern("old", "new");
sb.replacePattern(
"old",
"new",
offset,
limit
);
取代所有原本文字中已存在的子字串。
sb.replaceAll("old", "new");
刪除(Delete)
刪除原本文字中的指定位置範圍的某段文字。
sb.delete(4, 15);
刪除原本文字中指定索引位置的字元。
sb.deleteCharAt(4);
清空StringBuilder
物件,但保留現有的長度容量。
sb.clear();
子字串(Substring)
僅保留原本文字中的指定位置範圍的某段文字。
sb.substring(1, 5); // input the start and end index
sb.substr(1, 5); // input the start index and length
反轉(Reverse)
sb.reverse();
大小寫轉換(Upper/Lower Case)
將文字轉換成大寫或是小寫。
sb.upperCase();
sb.lowerCase();
修剪(Trim)
移除文字前後所有空白、換行相關的字元。
sb.trim();
重複(Repeat)
將目前的文字重複數次。
sb.repeat(1);
擴展長度容量
擴展StringBuilder
物件的長度容量。
sb.expandCapacity(4096).append("string");
擴展StringBuilder
物件的長度容量,並取得擴展之後的容量。
const capacity = sb.expandCapacity(4096, true);
縮小長度容量
根據目前的文字縮小StringBuilder
物件的長度容量。
sb.shrinkCapacity().clone().append("string");
根據目前的文字縮小StringBuilder
物件的長度容量,並取得縮小之後的容量。
const capacity = sb.shrinkCapacity(true);
取得目前文字長度
const length = sb.length();
取得長度容量
const capacity = sb.capacity();
計算字數
const words = sb.count();
建立字串
將StringBuilder
物件內指定位置範圍的文字轉成字串。
const str = sb.toString(4, 10);
將StringBuilder
物件內指定位置範圍的文字轉成UTF-8編碼的Buffer
。
const buffer = sb.toBuffer(4, 10);
將StringBuilder
物件內的文字都轉成字串。
const str = sb.toString(4, 10);
將StringBuilder
物件內的文字都轉成UTF-8編碼的Buffer
。
const buffer = sb.toBuffer();
取得StringBuilder
物件內指定索引位置的文字字元。
const c = sb.charAt(4);
字串搜尋
從文字前端開始搜尋子字串。
const indexArray = sb.indexOf("string");
const indexArray2 = sb.indexOf("string", offset, limit);
從文字前端開始搜尋符合正規表示式的子字串。
const indexArray = sb.indexOf(/string/g);
從文字尾端開始搜尋子字串。
const indexArray = sb.lastIndexOf("string");
字串比對
比對兩文字是否完全相等。
const equal = sb.equals("string");
比對兩文字是否在忽略大小寫的條件下相等。
const equal = sb.equalsIgnoreCase("string");
比對文字是否以某字串開頭或結尾。
const start = sb.startsWith("string");
const end = sb.endsWith("string");
複製StringBuilder
物件
const newSB = sb.clone();
效能的影響
由於使用C語言這樣的底層程式實作模組的關係,node-stringbuilder
的效能大概只有在「串接(Append)」和「字串比對(完全相等)」時會比JS程式還要來的差。雖說如此,這兩者卻也是平常開發產品時最常用的功能,因此如果不需要進行除了「串接(Append)」和「字串比對(完全相等)」之外,其他的字串操作的話,直接使用JavaScript程式原生的方式去產生字串是比較好的。