Golang中如何结合使用zip、xml、io和fs库

Golang中如何结合使用zip、xml、io和fs库 你好,我是Go语言的新手,想从一个zip压缩包中的XML文件里读取一个结构体。我在整合这些部分时遇到了困难——虽然能让代码运行起来,但感觉应该有更直接的方法。

首先,我认为需要使用 zip.OpenReader 来获取一个 zip.ReadCloser。最终,我需要获得一些 []byte 数据,以便传递给 xml.Unmarshal

中间部分我有两个想法:

  1. 使用 zip.ReadCloser.Open 来打开一个文件——但这会返回一个 fs.File,据我所知,它没有简单的方法可以将其全部内容作为字节读取。而且这似乎需要复制字节,而这是不必要的,因为zip文件已经在内存中解压了。
  2. 遍历 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

6 回复

是的,有时文档不会直接说明这一点。但你可以查看源代码。如果它实现了该接口的方法。或者,像本例中这样,返回包含嵌入方法的接口。

更多关于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的标准方法,不需要自己实现读取函数。

回到顶部