使用io.Copy()稀疏文件很大

时间:2017-03-26 22:19:51

标签: file go io copy sparse-file

我想将文件从一个地方复制到另一个地方,问题是我处理了很多稀疏文件。

是否有任何(简单)方法复制稀疏文件而不会在目的地变得庞大?

我的基本代码:

out, err := os.Create(bricks[0] + "/" + fileName)
in, err := os.Open(event.Name)
io.Copy(out, in)

2 个答案:

答案 0 :(得分:6)

一些背景理论

请注意io.Copy()管道原始字节 - 一旦您考虑将数据从io.Reader管道传输到提供io.Writer和{{Read([]byte)的{​​{1}},这是可以理解的。 1}},相应地。 因此,Write([]byte)能够完全处理任何提供的来源 字节和绝对任何消耗它们的接收器。

另一方面,文件中孔的位置是“旁道”信息,“{经典”系统调用如io.Copy()隐藏其用户。 read(2)无法以任何方式传达此类旁道信息。

IOW,最初,文件稀疏性只是让用户背后的数据有效存储。

所以,不,io.Copy()无法处理稀疏文件本身。

该怎么办

您需要更深入一级并使用io.Copy()包和一些手动修补来实现所有这些。

要处理漏洞,您应该使用syscall系统调用的SEEK_HOLESEEK_DATA特殊值,这些值虽然在正式非标准范围内,但all支持major platforms

不幸的是,不存在对那些“从”位置的支持 在库存lseek(2)包中(从Go 1.8.1开始) 也不在golang.org/x/sys树中。

但不要害怕,有两个简单的步骤:

  1. 首先,股票syscall实际上映射到syscall.Seek() 在相关平台上。

  2. 接下来,您需要确定lseek(2)SEEK_HOLE的正确值 SEEK_DATA用于您需要支持的平台。

      

    请注意,它们在不同平台之间可以不同

    说,在我的Linux系统上,我可以做简单的

    $ grep -E 'SEEK_(HOLE|DATA)' </usr/include/unistd.h 
    #  define SEEK_DATA     3       /* Seek to next data.  */
    #  define SEEK_HOLE     4       /* Seek to next hole.  */
    

    ...找出这些符号的值。

  3. 现在,比如说,您在包中创建了一个特定于Linux的文件 包含类似

    的内容
    // +build linux
    
    const (
        SEEK_DATA = 3
        SEEK_HOLE = 4
    )
    

    然后将这些值与syscall.Seek()一起使用。

    要传递给syscall.Seek()和朋友的文件描述符 可以使用Fd()方法从打开的文件中获取 os.File值。

    阅读时使用的模式是检测包含数据的区域,并从中读取数据 - 有关示例,请参阅this

    请注意,这涉及读取稀疏文件;但是如果你想将它们实际上转移稀疏 - 也就是说,保持它们的属性, - 情况就更复杂了:它似乎更不便携,所以一些研究和实验到期了。

    在Linux上,您似乎可以尝试使用fallocate(2) FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE尝试在洞口打洞 你正在写的文件的结尾;如果合法地失败了 (使用syscall.EOPNOTSUPP),你只需要在你正在阅读的洞所覆盖的目标文件中挖出尽可能多的归零块 - 希望 操作系统将做正确的事情,并将它们自己转换为一个洞。

    请注意,某些文件系统根本不支持漏洞 - 作为一个概念。 一个例子是FAT系列中的文件系统。 我要引导你的是,无法创建稀疏文件 实际上是你的情况下目标文件系统的属性。

    您可能会感兴趣Go issue #13548 "archive/tar: add support for writing tar containing sparse files"

    还有一点需要注意:您还可以考虑检查复制源文件的目标目录是否与源文件位于同一文件系统中,如果这样,则使用syscall.Rename()(在POSIX系统上) 或os.Rename()只是将文件移动到不同的目录中 实际上是在复制它的数据。

答案 1 :(得分:0)

您无需诉诸系统调用。

package main

import "os"

func main() {
    f, _ := os.Create("/tmp/sparse.dat")
    f.Write([]byte("start"))
    f.Seek(1024*1024*10, 0)
    f.Write([]byte("end"))
}

然后您将看到:

$ ls -l /tmp/sparse.dat
-rw-rw-r-- 1 soren soren 10485763 Jun 25 14:29 /tmp/sparse.dat
$ du /tmp/sparse.dat
8   /tmp/sparse.dat

是的,您不能原样使用io.Copy。相反,您需要实现io.Copy的替代方案,该替代方案从src中读取一个块,并检查是否全部为'\0'。如果是这样,只需dst.Seek(len(chunk), os.SEEK_CUR)跳过dst中的那部分。该特定实现留给读者练习:)