2013-11-25 46 views
0

我正在寫一個帶有TCP套接字的小型通信協議。 我能夠讀寫整數等基本數據類型,但我不知道如何從一個字節片段讀取UTF-8編碼的字符串。從io.Reader讀取UTF-8編碼字符串

協議客戶端是用Java編寫的,服務器是Go。

由於每個I讀:GO符是32位的長和UTF-8字符是1至4個字節長,讓不能夠簡單地投下字節切片爲字符串。

我想知道我怎麼能讀寫這個UTF-8流。

注意 我有字節緩衝區的時間長度來讀取字符串。

回答

4

一些理論第一:

  • 在Go甲rune表示Unicode代碼點—分配到以Unicode一個特定字符的數字。這是uint32的別名。
  • UTF-8是Unicode 編碼 —的表示用於存儲和傳輸的裝置的Unicode碼點的格式。 UTF-8可能會使用1到4個字節來編碼單個代碼點。

這怎麼映射上的Go數據類型:

  • 兩個[]bytestring店一系列字節(圍棋一個byteuint8的別名)。

    的主要區別是,字符串是不變的,所以當你可以

    b := make([]byte, 2) 
    b[0] = byte('a') 
    b[1] = byte('z') 
    

    你不能

    var s string 
    s[0] = byte('a') 
    

    後者事實甚至不能強調要明確設置字符串長度(如在虛構的s := make(string, 10)中)。

  • 而在圍棋字符串包含抽象的字節(你可以自由在其中存儲,比如說,字符使用Windows-1252編碼),一定去陳述和類型轉換解釋字符串作爲UTF-8進行編碼,在特別是:
    • string[]rune之間的類型轉換將該字符串解析爲一系列UTF-8編碼的代碼點並生成一個片段。反向類型轉換採用符文切片中的Unicode代碼點並生成UTF-8編碼的字符串。
    • range遍歷字符串遍歷包含字符串的Unicode碼點,只是字節。

圍棋還提供string[]byte和背部之間的類型轉換。現在回想一下,字符串是隻讀的,而字節片不是。這意味着像

b := make([]byte, 1000) 
io.ReadFull(r, b) 
s := sting(b) 

總是複製數據,無論一個結構,如果你轉換一個切片一個字符串或背部。這浪費了空間,但是是類型安全的並且強化了語義。

現在回到你手頭的任務。

如果你有合理的小字符串的工作,並不在內存壓力下,只是轉換由io.Read()(或其他)充滿了你的字節切片字符串。請務必重複使用您正在使用的切片讀取數據,以緩解垃圾收集器—的壓力,也就是說,不要爲每次新​​讀取分配新切片,因爲您要複製放入其中的數據由閱讀代碼關閉到一個字符串。

最後,如果你絕對必須不復制數據(比如,你在處理多兆字節的字符串,你有嚴格的內存需求),你可以嘗試通過不安全工作發揮搞鬼與內存— here是一個例子,你可能如何移植內存從字節切片到字符串。請注意,如果你恢復到這樣的狀態,你必須非常清楚地知道它可以隨任何新版本的Go一起打破,而且它甚至不能保證工作。

+0

只是爲了澄清:當我將字節片「轉換」爲字符串時,不只是「投」字節,而是構造一個新的字符串? – Mikhas

+2

@Mikhas,這是正確的:數據字節被複制。這是Go的幾個地方之一,它的開發者爲了簡化類型轉換而做出了實用的選擇,允許這種*隱藏成本*。再想一想:如果Go只是將一個字節片段的數據「投」到一個字符串中,那麼原始片段不會以某種方式在投射後自動捕獲並存在,並且您將能夠更改字符串的內容*保證爲只讀*通過將與該字符串共享數據的片。這會違反語義(這就是'implantSlice'黑客所做的)。 – kostix

+0

@Mikhas,[另請參閱](http://stackoverflow.com/a/12754757/720999) – kostix