Golang中如何结合使用zip、xml、io和fs库
Golang中如何结合使用zip、xml、io和fs库 你好,我是Go语言的新手,想从一个zip压缩包中的XML文件里读取一个结构体。我在整合这些部分时遇到了困难——虽然能让代码运行起来,但感觉应该有更直接的方法。
首先,我认为需要使用 zip.OpenReader 来获取一个 zip.ReadCloser。最终,我需要获得一些 []byte 数据,以便传递给 xml.Unmarshal。
中间部分我有两个想法:
- 使用
zip.ReadCloser.Open来打开一个文件——但这会返回一个fs.File,据我所知,它没有简单的方法可以将其全部内容作为字节读取。而且这似乎需要复制字节,而这是不必要的,因为zip文件已经在内存中解压了。 - 遍历
zip.ReadCloser.File来搜索我需要的文件名。然后zip.Reader.File确实有一个简单的方法来读取它,即调用Open然后使用io.ReadAll。但似乎很不幸,我需要自己进行文件的线性遍历。
所以我主要是想寻求一些建议,是否有我没有发现的更聪明的方法来做到这一点?
提前感谢。
以下是一些代码,为了简洁起见,我省略了所有的错误处理和 defer 关闭操作。
func one() {
// var r *zip.ReadCloser
r, _ := zip.OpenReader("na.zip")
// var f fs.File
f, _ := r.Open("custom.xml")
// var bytes []byte
// ** 有什么办法可以避免我自己实现这个函数吗?
bytes, _ := readfsfile(f)
// var custom Custom
custom := Custom{}
xml.Unmarshal(bytes, &custom)
fmt.Println(custom)
}
func two() {
// var r *zip.ReadCloser
r, _ := zip.OpenReader("na.zip")
// var file zip.Reader.File
// ** 有什么办法可以避免这种遍历吗?
for _, file := range r.File {
if file.Name == "custom.xml" {
// var reader io.ReadCloser
reader, _ := file.Open()
// var bytes []byte
bytes, _ := io.ReadAll(reader)
// var custom Custom
custom := Custom{}
xml.Unmarshal(bytes, &custom)
fmt.Println(custom)
}
}
}
更多关于Golang中如何结合使用zip、xml、io和fs库的实战教程也可以访问 https://www.itying.com/category-94-b0.html
是的,有时文档不会直接说明这一点。但你可以查看源代码。如果它实现了该接口的方法。或者,像本例中这样,返回包含嵌入方法的接口。
更多关于Golang中如何结合使用zip、xml、io和fs库的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
我个人更喜欢第二个选项,因为第一个选项限制了你必须提前知道文件路径。
在我的情况下,我确实提前知道压缩包内的文件路径,它总是一样的,所以这没问题。
你好。据我所见,第一个选项中的 Open 方法也返回一个 ReadCloser,因此你可以像第二个选项一样使用它的 Read 方法。
f, _ := r.Open("file in archive")
defer f.Close()
data, _ := io.ReadAll(f)
我个人更倾向于第二个选项,因为第一个选项限制了你必须预先知道文件路径。
太棒了,谢谢。我确实遗漏了一些东西!
仅仅因为 fs.File 拥有 Read(p []byte) (n int, err error) 方法,就可以说它是一个 Reader(因此可以传递给 io.ReadAll),这样理解对吗?我读到过一些相关内容,但显然没有完全理解。我原本期望文档会提到“实现了 Reader”之类的话。
如果你想稍微简化一下,这里有个快速提示。看看 xml.Unmarshal 做了什么:
func Unmarshal(data []byte, v any) error {
return NewDecoder(bytes.NewReader(data)).Decode(v)
}
如果你追踪 xml.NewDecoder 的函数签名,你会发现它接受一个 io.Reader:
func NewDecoder(r io.Reader) *Decoder
你的 io.ReadCloser 就是一个 reader。所以你可以直接把它传递给 NewDecoder,而无需先将所有字节读入内存:
if file.Name == "custom.xml" {
// var reader io.ReadCloser
reader, _ := file.Open()
// var custom Custom
custom := Custom{}
err := xml.NewDecoder(reader).Decode(&custom)
// 处理 err
fmt.Println(custom)
}
在Go中处理zip中的XML文件,你现有的两种方法都是正确的。以下是更简洁的实现方式:
// 方法1:使用fs.File接口
func readXMLFromZip(zipPath, filename string) (*Custom, error) {
r, err := zip.OpenReader(zipPath)
if err != nil {
return nil, err
}
defer r.Close()
f, err := r.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
var custom Custom
if err := xml.Unmarshal(data, &custom); err != nil {
return nil, err
}
return &custom, nil
}
// 方法2:直接访问zip.File(推荐)
func readXMLFromZipDirect(zipPath, filename string) (*Custom, error) {
r, err := zip.OpenReader(zipPath)
if err != nil {
return nil, err
}
defer r.Close()
for _, file := range r.File {
if file.Name == filename {
rc, err := file.Open()
if err != nil {
return nil, err
}
defer rc.Close()
data, err := io.ReadAll(rc)
if err != nil {
return nil, err
}
var custom Custom
if err := xml.Unmarshal(data, &custom); err != nil {
return nil, err
}
return &custom, nil
}
}
return nil, fmt.Errorf("file %s not found in zip", filename)
}
// 方法3:使用fs.WalkDir遍历(Go 1.16+)
func readXMLFromZipWalk(zipPath, filename string) (*Custom, error) {
r, err := zip.OpenReader(zipPath)
if err != nil {
return nil, err
}
defer r.Close()
var targetFile fs.File
fs.WalkDir(r, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() && path == filename {
targetFile, err = r.Open(path)
return fs.SkipAll
}
return nil
})
if targetFile == nil {
return nil, fmt.Errorf("file %s not found", filename)
}
defer targetFile.Close()
data, err := io.ReadAll(targetFile)
if err != nil {
return nil, err
}
var custom Custom
if err := xml.Unmarshal(data, &custom); err != nil {
return nil, err
}
return &custom, nil
}
// 方法4:使用zip.Reader(避免自动解压所有文件)
func readXMLFromZipReader(zipPath, filename string) (*Custom, error) {
f, err := os.Open(zipPath)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
r, err := zip.NewReader(f, fi.Size())
if err != nil {
return nil, err
}
for _, file := range r.File {
if file.Name == filename {
rc, err := file.Open()
if err != nil {
return nil, err
}
defer rc.Close()
data, err := io.ReadAll(rc)
if err != nil {
return nil, err
}
var custom Custom
if err := xml.Unmarshal(data, &custom); err != nil {
return nil, err
}
return &custom, nil
}
}
return nil, fmt.Errorf("file %s not found", filename)
}
方法2(直接遍历r.File)是最常用且性能最好的方式,因为zip库内部已经维护了文件索引,遍历是O(n)操作且n通常很小。io.ReadAll是读取fs.File的标准方法,不需要自己实现读取函数。

