3 回答

TA貢獻1712條經驗 獲得超3個贊
您的方法的根本問題是類型轉換[]byte
為的結果中string
沒有任何U+FFFD
s :此類型轉換僅將字節從源逐字復制到目標。
就像字節切片一樣,Go 中的字符串沒有義務包含 UTF-8 編碼的文本;它們可以包含任何數據,包括與文本無關的不透明二進制數據。
但是對字符串的一些操作——即將它們類型轉換為[]rune
并使用它們迭代range
——確實將字符串解釋為 UTF-8 編碼的文本。這正是你被絆倒的地方:你的range
調試循環試圖解釋字符串,每次解碼正確編碼的代碼點的嘗試失敗時,都會range
產生一個替換字符,U+FFFD
.
重申一下,通過類型轉換獲得的字符串不包含您希望被正則表達式替換的字符。
至于如何從您的數據中實際生成有效的 UTF-8 編碼字符串,您可以采用兩步過程:
將你的字節切片類型轉換為字符串——就像你已經做的那樣。
使用任何將字符串解釋為 UTF-8 的方法——替換將在此過程中動態出現的 U+FFFD——在您進行迭代時。
像這樣的東西:
var sb strings.Builder
for _, c := range string(b) {
if c == '\uFFFD' {
sb.WriteByte('.')
} else {
sb.WriteRune(c)
}
}
return sb.String()
關于性能的說明:由于將 a 類型轉換[]byte為string復制內存——因為字符串是不可變的,而切片不是——類型轉換的第一步可能會浪費處理大量數據和/或緊密工作的代碼的資源處理循環。
在這種情況下,可能值得使用適用于字節片的包的DecodeRune
功能。encoding/utf8
它的文檔中的一個例子可以很容易地適應上面的循環。

TA貢獻2003條經驗 獲得超2個贊
您可能希望strings.ToValidUTF8()用于此:
ToValidUTF8 返回字符串 s 的副本,其中每次運行無效的 UTF-8 字節序列都被替換字符串替換,該替換字符串可能為空。
它“似乎”完全符合您的需要。測試它:
a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
s := strings.ToValidUTF8(string(a), ".")
fmt.Println(s)
輸出(在Go Playground上試試):
a.b.
我寫“貌似”是因為如您所見,a和b: 之間只有一個點,因為可能有 2 個字節,但有一個無效序列。
請注意,您可以避免[]byte=>string轉換,因為有一個bytes.ToValidUTF8()等價的操作并返回 a []byte:
a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
a = bytes.ToValidUTF8(a, []byte{'.'})
fmt.Println(string(a))
輸出將是相同的。在Go Playground上試試這個。
如果您對多個(無效序列)字節可能會縮小為一個點感到困擾,請繼續閱讀。
另請注意,要檢查可能包含或不包含文本的任意字節切片,您可以簡單地使用hex.Dump()which 生成如下輸出:
a := []byte{'a', 0xff, 0xaf, 'b', 0xbf}
fmt.Println(hex.Dump(a))
輸出:
00000000 61 ff af 62 bf |a..b.|
您的預期輸出a..b.包含其他(有用的)數據,例如十六進制偏移量和字節的十六進制表示。
要獲得“更好”的輸出圖片,請嘗試使用更長的輸入:
a = []byte{'a', 0xff, 0xaf, 'b', 0xbf, 50: 0xff}
fmt.Println(hex.Dump(a))
00000000 61 ff af 62 bf 00 00 00 00 00 00 00 00 00 00 00 |a..b............|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000030 00 00 ff |...|

TA貢獻1871條經驗 獲得超13個贊
并且非常清楚地解釋了從字符串中掃描 unicode 符文的問題。
只需添加以下注釋:如果您的意圖是僅查看 ASCII 范圍內的字符(可打印字符 < 127)并且您并不真正關心其他 unicode 代碼點,您可以更直率:
// create a byte slice with the same byte length as s
var bs = make([]byte, len(s))
// scan s byte by byte :
for i := 0; i < len(s); i++ {
switch {
case 32 <= s[i] && s[i] <= 126:
bs[i] = s[i]
// depending on your needs, you may also keep characters in the 0..31 range,
// like 'tab' (9), 'linefeed' (10) or 'carriage return' (13) :
// case s[i] == 9, s[i] == 10, s[i] == 13:
// bs[i] = s[i]
default:
bs[i] = '.'
}
}
fmt.Printf("rs: %s\n", bs)
這個功能會給你一些接近“文本”部分的東西hexdump -C。
- 3 回答
- 0 關注
- 161 瀏覽
添加回答
舉報