2013-04-03 37 views
13

tôi có thể đọc các tập tin để byte mảngCách đọc tệp văn bản utf16 thành chuỗi trong golang?

nhưng khi tôi chuyển nó sang chuỗi

nó đối xử với các byte UTF16 như ascii

Làm thế nào để chuyển đổi nó một cách chính xác?

package main 

import ("fmt" 
"os" 
"bufio" 
) 

func main(){ 
    // read whole the file 
    f, err := os.Open("test.txt") 
    if err != nil { 
     fmt.Printf("error opening file: %v\n",err) 
     os.Exit(1) 
    } 
    r := bufio.NewReader(f) 
    var s,b,e = r.ReadLine() 
    if e==nil{ 
     fmt.Println(b) 
     fmt.Println(s) 
     fmt.Println(string(s)) 
    } 
} 

đầu ra:

sai

[255 254 91 0 83 0 99 0 114 0 105 0 112 0 116 0 32 0 73 0 110 0 102 0 111 0 93 0 13 0]

S cript tôi nfo]


Cập nhật:

Sau khi tôi thử nghiệm hai ví dụ, tôi đã hiểu vấn đề chính xác là gì bây giờ.

Trong cửa sổ, nếu tôi thêm ngắt dòng (CR + LF) ở cuối dòng, CR sẽ được đọc trong dòng. Vì hàm readline không thể xử lý chính xác unicode ([OD OA] = ok, [OD 00 OA 00] = không ok).

Nếu chức năng readline có thể nhận dạng unicode, nó phải hiểu [OD 00 OA 00] và trả về [] uint16 thay vì [] byte.

Vì vậy, tôi nghĩ rằng tôi không nên sử dụng bufio.NewReader vì nó không thể đọc utf16, tôi không thấy bufio.NewReader.ReadLine có thể chấp nhận tham số làm cờ để cho biết văn bản đọc là utf8, utf16le/be hoặc utf32. Có chức năng readline cho văn bản unicode trong thư viện đi?

Trả lời

13

UTF16, UTF8, và Byte Order Marks được xác định bởi Unicode Consortium: UTF-16 FAQ, UTF-8 FAQByte Order Mark (BOM) FAQ.


Issue 4802: bufio: reading lines is too cumbersome

dòng đọc từ một tập tin là quá cồng kềnh trong Go.

Mọi người thường được vẽ tới bufio.Reader.ReadLine vì tên của nó, nhưng có chữ ký lạ, trả về (line [] byte, isPrefix bool, lỗi err) và yêu cầu nhiều công việc.

ReadSlice và ReadString đòi hỏi một byte delimiter, đó là gần như luôn rõ ràng và khó coi '\ n', và cũng có thể quay trở lại cả một dòng và EOF


Revision: f685026a2d38

bufio: giao diện Máy quét mới

Thêm giao diện mới, đơn giản cho sc dữ liệu anning (có thể là văn bản), dựa trên loại mới được gọi là Máy quét. Nó có bộ đệm nội bộ riêng, do đó, nên có hiệu quả đáng kể ngay cả khi không tiêm bufio.Reader. Định dạng của đầu vào được xác định bởi một hàm "split ", theo mặc định chia thành các dòng.


go1.1beta1 released

Bạn có thể tải các bản phân phối nhị phân và nguồn từ chỗ cũ nhé: https://code.google.com/p/go/downloads/list?q=go1.1beta1


Dưới đây là một chương trình trong đó sử dụng các quy tắc Unicode để chuyển đổi UTF16 dòng tệp văn bản để chuỗi UTF8 được mã hóa. Mã đã được sửa đổi để tận dụng lợi thế của giao diện bufio.Scanner mới trong Go 1.1.

package main 

import (
    "bufio" 
    "bytes" 
    "encoding/binary" 
    "fmt" 
    "os" 
    "runtime" 
    "unicode/utf16" 
    "unicode/utf8" 
) 

// UTF16BytesToString converts UTF-16 encoded bytes, in big or little endian byte order, 
// to a UTF-8 encoded string. 
func UTF16BytesToString(b []byte, o binary.ByteOrder) string { 
    utf := make([]uint16, (len(b)+(2-1))/2) 
    for i := 0; i+(2-1) < len(b); i += 2 { 
     utf[i/2] = o.Uint16(b[i:]) 
    } 
    if len(b)/2 < len(utf) { 
     utf[len(utf)-1] = utf8.RuneError 
    } 
    return string(utf16.Decode(utf)) 
} 

// UTF-16 endian byte order 
const (
    unknownEndian = iota 
    bigEndian 
    littleEndian 
) 

// dropCREndian drops a terminal \r from the endian data. 
func dropCREndian(data []byte, t1, t2 byte) []byte { 
    if len(data) > 1 { 
     if data[len(data)-2] == t1 && data[len(data)-1] == t2 { 
      return data[0 : len(data)-2] 
     } 
    } 
    return data 
} 

// dropCRBE drops a terminal \r from the big endian data. 
func dropCRBE(data []byte) []byte { 
    return dropCREndian(data, '\x00', '\r') 
} 

// dropCRLE drops a terminal \r from the little endian data. 
func dropCRLE(data []byte) []byte { 
    return dropCREndian(data, '\r', '\x00') 
} 

// dropCR drops a terminal \r from the data. 
func dropCR(data []byte) ([]byte, int) { 
    var endian = unknownEndian 
    switch ld := len(data); { 
    case ld != len(dropCRLE(data)): 
     endian = littleEndian 
    case ld != len(dropCRBE(data)): 
     endian = bigEndian 
    } 
    return data, endian 
} 

// SplitFunc is a split function for a Scanner that returns each line of 
// text, stripped of any trailing end-of-line marker. The returned line may 
// be empty. The end-of-line marker is one optional carriage return followed 
// by one mandatory newline. In regular expression notation, it is `\r?\n`. 
// The last non-empty line of input will be returned even if it has no 
// newline. 
func ScanUTF16LinesFunc(byteOrder binary.ByteOrder) (bufio.SplitFunc, func() binary.ByteOrder) { 

    // Function closure variables 
    var endian = unknownEndian 
    switch byteOrder { 
    case binary.BigEndian: 
     endian = bigEndian 
    case binary.LittleEndian: 
     endian = littleEndian 
    } 
    const bom = 0xFEFF 
    var checkBOM bool = endian == unknownEndian 

    // Scanner split function 
    splitFunc := func(data []byte, atEOF bool) (advance int, token []byte, err error) { 

     if atEOF && len(data) == 0 { 
      return 0, nil, nil 
     } 

     if checkBOM { 
      checkBOM = false 
      if len(data) > 1 { 
       switch uint16(bom) { 
       case uint16(data[0])<<8 | uint16(data[1]): 
        endian = bigEndian 
        return 2, nil, nil 
       case uint16(data[1])<<8 | uint16(data[0]): 
        endian = littleEndian 
        return 2, nil, nil 
       } 
      } 
     } 

     // Scan for newline-terminated lines. 
     i := 0 
     for { 
      j := bytes.IndexByte(data[i:], '\n') 
      if j < 0 { 
       break 
      } 
      i += j 
      switch e := i % 2; e { 
      case 1: // UTF-16BE 
       if endian != littleEndian { 
        if i > 1 { 
         if data[i-1] == '\x00' { 
          endian = bigEndian 
          // We have a full newline-terminated line. 
          return i + 1, dropCRBE(data[0 : i-1]), nil 
         } 
        } 
       } 
      case 0: // UTF-16LE 
       if endian != bigEndian { 
        if i+1 < len(data) { 
         i++ 
         if data[i] == '\x00' { 
          endian = littleEndian 
          // We have a full newline-terminated line. 
          return i + 1, dropCRLE(data[0 : i-1]), nil 
         } 
        } 
       } 
      } 
      i++ 
     } 

     // If we're at EOF, we have a final, non-terminated line. Return it. 
     if atEOF { 
      // drop CR. 
      advance = len(data) 
      switch endian { 
      case bigEndian: 
       data = dropCRBE(data) 
      case littleEndian: 
       data = dropCRLE(data) 
      default: 
       data, endian = dropCR(data) 
      } 
      if endian == unknownEndian { 
       if runtime.GOOS == "windows" { 
        endian = littleEndian 
       } else { 
        endian = bigEndian 
       } 
      } 
      return advance, data, nil 
     } 

     // Request more data. 
     return 0, nil, nil 
    } 

    // Endian byte order function 
    orderFunc := func() (byteOrder binary.ByteOrder) { 
     switch endian { 
     case bigEndian: 
      byteOrder = binary.BigEndian 
     case littleEndian: 
      byteOrder = binary.LittleEndian 
     } 
     return byteOrder 
    } 

    return splitFunc, orderFunc 
} 

func main() { 
    file, err := os.Open("utf16.le.txt") 
    if err != nil { 
     fmt.Println(err) 
     os.Exit(1) 
    } 
    defer file.Close() 
    fmt.Println(file.Name()) 

    rdr := bufio.NewReader(file) 
    scanner := bufio.NewScanner(rdr) 
    var bo binary.ByteOrder // unknown, infer from data 
    // bo = binary.LittleEndian // windows 
    splitFunc, orderFunc := ScanUTF16LinesFunc(bo) 
    scanner.Split(splitFunc) 

    for scanner.Scan() { 
     b := scanner.Bytes() 
     s := UTF16BytesToString(b, orderFunc()) 
     fmt.Println(len(s), s) 
     fmt.Println(len(b), b) 
    } 
    fmt.Println(orderFunc()) 

    if err := scanner.Err(); err != nil { 
     fmt.Println(err) 
    } 
} 

Output:

utf16.le.txt 
15 "Hello, 世界" 
22 [34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 0 22 78 76 117 34 0] 
0 
0 [] 
15 "Hello, 世界" 
22 [34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 0 22 78 76 117 34 0] 
LittleEndian 

utf16.be.txt 
15 "Hello, 世界" 
22 [0 34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 78 22 117 76 0 34] 
0 
0 [] 
15 "Hello, 世界" 
22 [0 34 0 72 0 101 0 108 0 108 0 111 0 44 0 32 78 22 117 76 0 34] 
BigEndian 
+0

Bây giờ tôi hiểu được vấn đề không được chuyển đổi, nó nằm trong dòng đọc. Vì vậy, câu hỏi được cập nhật. –

+0

Đây là chương trình sửa đổi để khắc phục sự cố của bạn. – peterSO

+0

Cảm ơn chương trình của bạn, tôi sẽ sửa đổi nó dựa trên bản sửa đổi của bạn, bởi vì ngắt dòng vẫn có nhiều [link] tiêu chuẩn (http://en.wikipedia.org/wiki/Newline). Kể từ khi không có gói trong đi đọc utf16, tôi nghĩ rằng tôi cũng nên báo cáo vấn đề này để google, bởi vì ngày nay, ngôn ngữ lập trình hiện đại sẽ có thể xử lý unicode chính xác, đặc biệt là trong ứng dụng internet. –

4

Ví dụ:

package main 

import (
     "errors" 
     "fmt" 
     "log" 
     "unicode/utf16" 
) 

func utf16toString(b []uint8) (string, error) { 
     if len(b)&1 != 0 { 
       return "", errors.New("len(b) must be even") 
     } 

     // Check BOM 
     var bom int 
     if len(b) >= 2 { 
       switch n := int(b[0])<<8 | int(b[1]); n { 
       case 0xfffe: 
         bom = 1 
         fallthrough 
       case 0xfeff: 
         b = b[2:] 
       } 
     } 

     w := make([]uint16, len(b)/2) 
     for i := range w { 
       w[i] = uint16(b[2*i+bom&1])<<8 | uint16(b[2*i+(bom+1)&1]) 
     } 
     return string(utf16.Decode(w)), nil 
} 

func main() { 
     // Simulated data from e.g. a file 
     b := []byte{255, 254, 91, 0, 83, 0, 99, 0, 114, 0, 105, 0, 112, 0, 116, 0, 32, 0, 73, 0, 110, 0, 102, 0, 111, 0, 93, 0, 13, 0} 
     s, err := utf16toString(b) 
     if err != nil { 
       log.Fatal(err) 
     } 

     fmt.Printf("%q", s) 
} 

(Cũng here)

Output:


"[Script Info]\r" 
+0

Tôi cũng khuyên bạn nên sử dụng 'mã hóa/nhị phân' để đọc nó dưới dạng [] uint16 để bắt đầu. – cthom06

+0

@ cthom06: Tôi không khuyến khích điều đó. – zzzz

+0

@ cthom06 Tại sao? Hãy cẩn thận rằng các ký tự trong UTF16 không phải lúc nào cũng được mã hóa trên hai byte (điều đó chỉ đúng đối với BMP). –

6

Phiên bản mới nhất của golang.org/x/text/encoding/unicode làm cho nó dễ dàng hơn để làm điều này vì nó bao gồm unicode.BOMOverride, mà một cách thông minh sẽ hiểu BOM.

Đây là ReadFileUTF16(), giống như os.ReadFile() nhưng giải mã UTF-16.

package main 

import (
    "bytes" 
    "fmt" 
    "io/ioutil" 
    "log" 
    "strings" 

    "golang.org/x/text/encoding/unicode" 
    "golang.org/x/text/transform" 
) 

// Similar to ioutil.ReadFile() but decodes UTF-16. Useful when 
// reading data from MS-Windows systems that generate UTF-16BE files, 
// but will do the right thing if other BOMs are found. 
func ReadFileUTF16(filename string) ([]byte, error) { 

    // Read the file into a []byte: 
    raw, err := ioutil.ReadFile(filename) 
    if err != nil { 
     return nil, err 
    } 

    // Make an tranformer that converts MS-Win default to UTF8: 
    win16be := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) 
    // Make a transformer that is like win16be, but abides by BOM: 
    utf16bom := unicode.BOMOverride(win16be.NewDecoder()) 

    // Make a Reader that uses utf16bom: 
    unicodeReader := transform.NewReader(bytes.NewReader(raw), utf16bom) 

    // decode and print: 
    decoded, err := ioutil.ReadAll(unicodeReader) 
    return decoded, err 
} 

func main() { 
    data, err := ReadFileUTF16("inputfile.txt") 
    if err != nil { 
     log.Fatal(err) 
    } 
    final := strings.Replace(string(data), "\r\n", "\n", -1) 
    fmt.Println(final) 

} 

Đây là NewScannerUTF16 giống như os.Open() nhưng trả về máy quét.

package main 

import (
    "bufio" 
    "fmt" 
    "log" 
    "os" 

    "golang.org/x/text/encoding/unicode" 
    "golang.org/x/text/transform" 
) 

type utfScanner interface { 
    Read(p []byte) (n int, err error) 
} 

// Creates a scanner similar to os.Open() but decodes the file as UTF-16. 
// Useful when reading data from MS-Windows systems that generate UTF-16BE 
// files, but will do the right thing if other BOMs are found. 
func NewScannerUTF16(filename string) (utfScanner, error) { 

    // Read the file into a []byte: 
    file, err := os.Open(filename) 
    if err != nil { 
     return nil, err 
    } 

    // Make an tranformer that converts MS-Win default to UTF8: 
    win16be := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM) 
    // Make a transformer that is like win16be, but abides by BOM: 
    utf16bom := unicode.BOMOverride(win16be.NewDecoder()) 

    // Make a Reader that uses utf16bom: 
    unicodeReader := transform.NewReader(file, utf16bom) 
    return unicodeReader, nil 
} 

func main() { 

    s, err := NewScannerUTF16("inputfile.txt") 
    if err != nil { 
     log.Fatal(err) 
    } 

    scanner := bufio.NewScanner(s) 
    for scanner.Scan() { 
     fmt.Println(scanner.Text()) // Println will add back the final '\n' 
    } 
    if err := scanner.Err(); err != nil { 
     fmt.Fprintln(os.Stderr, "reading inputfile:", err) 
    } 

} 
Các vấn đề liên quan