Unicode 如何在golang中将utf16文本文件读取为字符串?
我可以将文件读取到字节数组 但是当我把它转换成字符串时 它将utf16字节视为ascii 如何正确转换Unicode 如何在golang中将utf16文本文件读取为字符串?,unicode,go,readline,utf-16,Unicode,Go,Readline,Utf 16,我可以将文件读取到字节数组 但是当我把它转换成字符串时 它将utf16字节视为ascii 如何正确转换 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) o
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))
}
}
输出: 假的 [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 c r i p t i n f o]
更新: 在我测试了这两个示例之后,我已经了解了确切的问题是什么 在windows中,如果我在行尾添加换行符(CR+LF),CR将在行中读取。因为readline函数无法正确处理unicode([OD OA]=ok,[OD 00 OA 00]=not ok) 如果readline函数可以识别unicode,它应该理解[OD 00 OA 00],并返回[]uint16,而不是[]字节 所以我认为我不应该使用bufio.NewReader,因为它不能读取utf16,我看不到bufio.NewReader.ReadLine可以接受参数作为标志来指示读取文本是utf8、utf16le/be或utf32。go库中是否有unicode文本的readline函数?例如:
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)
}
UTF16、UTF8和字节顺序标记由和定义
在Go中,从文件中读取行太麻烦了 人们经常因为bufio.Reader.ReadLine的名字而被它吸引, 但它有一个奇怪的签名,返回(第[]行字节,isPrefix bool, 错误),并且需要大量的工作 ReadSlice和ReadString需要一个分隔符字节,几乎等于 总是明显和难看的'\n',也可以同时返回一行 还有一个EOF
bufio:新的扫描仪接口 为扫描(可能是文本)数据添加一个新的简单界面, 基于一种叫做扫描器的新类型。它有自己的内部机制 缓冲,因此即使不注入 bufio.Reader。输入的格式由“拆分”定义 默认情况下,函数“”将拆分为行
您可以从通常的位置下载二进制和源代码发行版:
下面是一个使用Unicode规则将UTF16文本文件行转换为UTF8编码字符串的程序。代码已经过修改,以利用Go 1.1中新的
bufio.Scanner
界面
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)
}
}
最新版本的
golang.org/x/text/encoding/unicode
更容易做到这一点,因为它包括unicode.BOMOverride
,它将智能地解释BOM
这里是ReadFileUTF16(),它类似于os.ReadFile(),但解码UTF-16
主程序包
进口(
“字节”
“fmt”
“io/ioutil”
“日志”
“字符串”
“golang.org/x/text/encoding/unicode”
“golang.org/x/text/transform”
)
//类似于ioutil.ReadFile(),但解码UTF-16。有用的时候
//从生成UTF-16BE文件的MS Windows系统读取数据,
//但如果发现其他BOM,我们会做正确的事情。
func ReadFileUTF16(文件名字符串)([]字节,错误){
//将文件读入[]字节:
raw,err:=ioutil.ReadFile(文件名)
如果错误!=零{
返回零,错误
}
//制作一个将MS Win默认值转换为UTF8的转换器:
win16be:=unicode.UTF16(unicode.BigEndian,unicode.IgnoreBOM)
//制作一个类似于win16be但符合BOM的变压器:
utf16bom:=unicode.BOMOverride(win16be.NewDecoder())
//制作一个使用utf16bom的读卡器:
unicodeReader:=transform.NewReader(字节数.NewReader(原始),utf16bom)
//解码和打印:
已解码,错误:=ioutil.ReadAll(unicodeReader)
返回已解码,错误
}
func main(){
数据,错误:=ReadFileUTF16(“inputfile.txt”)
如果错误!=零{
log.Fatal(错误)
}
final:=字符串。替换(字符串(数据),“\r\n”、“\n”、-1)
fmt.Println(最终版)
}
这是NewScanerUTF16,它类似于os.Open(),但返回一个扫描程序
主程序包
进口(
“布菲奥”
“fmt”
“日志”
“操作系统”
“golang.org/x/text/encoding/unicode”
“golang.org/x/text/transform”
)
类型utfScanner接口{
读取(p[]字节)(n int,err error)
}
//创建类似于os.Open()的扫描程序,但将文件解码为UTF-16。
//从生成UTF-16BE的MS Windows系统读取数据时非常有用
//文件,但如果找到其他BOM表,则将执行正确的操作。
func NewScannerUTF16(文件名字符串)(utfScanner,错误){
//将文件读入[]字节:
文件,错误:=os.Open(文件名)
如果错误!=零{
返回零,错误
}
//制作一个将MS Win默认值转换为UTF8的转换器:
win16be:=unicode.UTF16(unicode.BigEndian,unicode.IgnoreBOM)
//制作一个类似于win16be但符合BOM的变压器:
utf16bom:=unicode.BOMOverride(win16be.NewDecoder())
//制作一个使用utf16bom的读卡器:
unicodeReader:=transform.NewReader(文件,utf16bom)
返回unicodeReader,无
}
func main(){
s、 错误:=NewScannerUTF16(“inputfile.txt”)
如果错误!=零{
log.Fatal(错误)
}
扫描程序:=bufio.NewScanner(s)
对于scanner.Scan(){
fmt.Println(scanner.Text())//Println将添加回最后的'\n'
}
如果错误:=scanner.err();错误!=nil{
fmt.Fprintln(os.Stderr,“读取输入文件:”,错误)
}
}
仅供参考:我已经将这些功能放入了一个开源模块,并做了进一步的改进。请参见以下最简单的阅读方法:
package main
import (
"bufio"
"fmt"
"log"
"os"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
func main() {
file, err := os.Open("./text.txt")
if err != nil {
log.Fatal(err)
}
scanner := bufio.NewScanner(transform.NewReader(file, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
for scanner.Scan() {
fmt.Printf(scanner.Text())
}
}
由于Windows默认使用小尾端顺序,因此我们使用unicode.UseBOM策略从文本中检索BOM,并使用unicode.LittleEndian作为回退如果您想将任何内容打印为字符串,可以使用
fmt.Sprint
主程序包
进口(
“布菲奥”
“fmt”
“操作系统”
)
func main(){
//把整个文件读一遍
f、 错误:=os.Open(“test.txt”)
如果错误!=零{
fmt.Printf(“打开文件时出错:%v\n”,错误)
返回
}
r:=bufio.NewReader(f)
变量s,u,e=r.ReadLine()
如果e!=nil{
fmt.Println(e)
返回
}
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
package main
import (
"bufio"
"fmt"
"log"
"os"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
func main() {
file, err := os.Open("./text.txt")
if err != nil {
log.Fatal(err)
}
scanner := bufio.NewScanner(transform.NewReader(file, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
for scanner.Scan() {
fmt.Printf(scanner.Text())
}
}