有學過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」演算法。且每個物件皆能複製出新的獨立物件來進行不一樣的字串處理。
GitHub:
npm:
安裝
直接使用npm指令進行安裝:
npm install node-stringbuilder
用法
初始化
使用「require」函數來引入「node-stringbuilder」模組。
const StringBuilder = require('node-stringbuilder');
接著使用「new」運算子,或是使用模組的「from」方法來建立StringBuilder物件。
var sb1 = new StringBuilder();
// or
var sb2 = StringBuilder.from();
建構參數可以傳入預設的文字內容和長度容量。
var sb = StringBuilder.from('First', 4096);
長度容量可以分成好幾個區塊,每個長度容量的區塊是128(個字元)。長度容量可以區塊為單位進行擴充或是縮小。
// To expand
var 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物件的長度容量,並取得擴展之後的容量。
var capacity = sb.expandCapacity(4096, true);
縮小長度容量
根據目前的文字縮小StringBuilder物件的長度容量。
sb.shrinkCapacity().clone().append('string');
根據目前的文字縮小StringBuilder物件的長度容量,並取得縮小之後的容量。
var capacity = sb.shrinkCapacity(true);
取得目前文字長度
var length = sb.length();
取得長度容量
var capacity = sb.capacity();
計算字數
var words = sb.count();
建立字串
將StringBuilder物件內指定位置範圍的文字轉成字串。
var str = sb.toString(4, 10);
將StringBuilder物件內指定位置範圍的文字轉成UTF-8編碼的Buffer。
var buffer = sb.toBuffer(4, 10);
將StringBuilder物件內的文字都轉成字串。
var str = sb.toString(4, 10);
將StringBuilder物件內的文字都轉成UTF-8編碼的Buffer。
var buffer = sb.toBuffer();
取得StringBuilder物件內指定索引位置的文字字元。
var c = sb.charAt(4);
字串搜尋
從文字前端開始搜尋子字串。
var indexArray = sb.indexOf('string');
var indexArray2 = sb.indexOf('string', offset, limit);
從文字前端開始搜尋符合正規表示式的子字串。
var indexArray = sb.indexOf(/string/g);
從文字尾端開始搜尋子字串。
var indexArray = sb.lastIndexOf('string');
字串比對
比對兩文字是否完全相等。
var equal = sb.equals('string');
比對兩文字是否在忽略大小寫的條件下相等。
var equal = sb.equalsIgnoreCase('string');
比對文字是否以某字串開頭或結尾。
var start = sb.startsWith('string');
var end = sb.endsWith('string');
複製StringBuilder物件
var newSB = sb.clone();
效能的影響
由於使用C語言這樣的底層程式實作模組的關係,「node-stringbuilder」的效能大概只有在「串接(Append)」和「字串比對(完全相等)」時會比JS程式還要來的差。雖說如此,這兩者卻也是平常開發產品時最常用的功能,因此如果不需要進行除了「串接(Append)」和「字串比對(完全相等)」之外,其他的字串操作的話,直接使用JavaScript程式原生的方式去產生字串是比較好的。