io.Copy() 稀疏文件很大
Sparse files are huge with io.Copy()
我想将文件从一个地方复制到另一个地方,但问题是我要处理很多稀疏文件。
有没有什么(简单的)方法可以复制稀疏文件而不会在目的地变得很大?
我的基本代码:
out, err := os.Create(bricks[0] + "/" + fileName)
in, err := os.Open(event.Name)
io.Copy(out, in)
一些背景理论
请注意,io.Copy()
通过管道传输原始字节 - 一旦您认为它将数据从 io.Reader
传输到 io.Writer
提供 Read([]byte)
和Write([]byte)
,对应。
因此,io.Copy()
绝对能够处理任何提供的来源
字节和绝对消耗它们的任何接收器。
另一方面,文件中漏洞的位置是 "side-channel" 信息,"classic" 系统调用(例如 read(2)
)向用户隐藏了这些信息。
io.Copy()
无法以任何方式传达此类边信道信息。
IOW,最初,文件稀疏性只是在用户背后高效存储数据的想法。
所以,不,io.Copy()
本身无法处理稀疏文件。
怎么办
您需要更深入地使用 syscall
包和一些手动修补来实现所有这些。
要处理漏洞,您应该为 lseek(2)
系统调用使用 SEEK_HOLE
和 SEEK_DATA
特殊值,虽然它们在形式上是非标准的,但受 all major platforms.
遗憾的是,不存在对那些 "whence" 位置的支持
既不在库存 syscall
包中(从 Go 1.8.1 开始)
也不在 golang.org/x/sys
树中。
但不要害怕,有两个简单的步骤:
首先,股票syscall.Seek()
实际映射到lseek(2)
在相关平台上。
接下来,您需要找出 SEEK_HOLE
和
SEEK_DATA
您需要支持的平台。
Note that they are free to be different between different platforms!
说,在我的 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. */
...计算出这些符号的值。
现在,比方说,您在包中创建一个 Linux 特定文件
包含类似
的东西
// +build linux
const (
SEEK_DATA = 3
SEEK_HOLE = 4
)
然后将这些值与 syscall.Seek()
一起使用。
要传递给 syscall.Seek()
和朋友的文件描述符
可以使用 Fd()
方法从打开的文件中获取
os.File
个值。
读取时使用的模式是检测包含数据的区域,并从中读取数据——参见 this 的一个示例。
请注意,这涉及读取稀疏文件;但是如果你真的想 transfer 它们稀疏——也就是说,保留它们中的 属性,——情况就更复杂了:它看起来甚至更少便携,因此需要进行一些研究和实验。
在 Linux 上,您似乎可以尝试将 fallocate(2)
与
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE
尝试在
您正在写入的文件的末尾;如果那合法地失败了
(使用 syscall.EOPNOTSUPP
),你只需将你正在读取的漏洞所覆盖的尽可能多的归零块铲到目标文件中——希望如此
OS 会做正确的事,并自行将它们变成一个洞。
请注意,一些文件系统根本不支持漏洞——作为一个概念。
一个例子是 FAT 家族中的文件系统。
我要引导您的是,无法创建稀疏文件可能
实际上是你的目标文件系统的属性。
您可能会对 Go issue #13548 "archive/tar: add support for writing tar containing sparse files" 感兴趣。
另外注意:您还可以考虑检查复制源文件的目标目录是否与源文件位于同一文件系统中,如果是这样,请使用 syscall.Rename()
(在 POSIX系统)
或 os.Rename()
将文件移动到不同的目录 w/o
实际上是在复制它的数据。
您不需要求助于系统调用。
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
读取一个块,检查它是否全部 '[=15=]'
。如果是,只需 dst.Seek(len(chunk), os.SEEK_CUR)
跳过 dst
中的那部分。该特定实现留作 reader :)
的练习
我想将文件从一个地方复制到另一个地方,但问题是我要处理很多稀疏文件。
有没有什么(简单的)方法可以复制稀疏文件而不会在目的地变得很大?
我的基本代码:
out, err := os.Create(bricks[0] + "/" + fileName)
in, err := os.Open(event.Name)
io.Copy(out, in)
一些背景理论
请注意,io.Copy()
通过管道传输原始字节 - 一旦您认为它将数据从 io.Reader
传输到 io.Writer
提供 Read([]byte)
和Write([]byte)
,对应。
因此,io.Copy()
绝对能够处理任何提供的来源
字节和绝对消耗它们的任何接收器。
另一方面,文件中漏洞的位置是 "side-channel" 信息,"classic" 系统调用(例如 read(2)
)向用户隐藏了这些信息。
io.Copy()
无法以任何方式传达此类边信道信息。
IOW,最初,文件稀疏性只是在用户背后高效存储数据的想法。
所以,不,io.Copy()
本身无法处理稀疏文件。
怎么办
您需要更深入地使用 syscall
包和一些手动修补来实现所有这些。
要处理漏洞,您应该为 lseek(2)
系统调用使用 SEEK_HOLE
和 SEEK_DATA
特殊值,虽然它们在形式上是非标准的,但受 all major platforms.
遗憾的是,不存在对那些 "whence" 位置的支持
既不在库存 syscall
包中(从 Go 1.8.1 开始)
也不在 golang.org/x/sys
树中。
但不要害怕,有两个简单的步骤:
首先,股票
syscall.Seek()
实际映射到lseek(2)
在相关平台上。接下来,您需要找出
SEEK_HOLE
和SEEK_DATA
您需要支持的平台。Note that they are free to be different between different platforms!
说,在我的 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. */
...计算出这些符号的值。
现在,比方说,您在包中创建一个 Linux 特定文件 包含类似
的东西// +build linux
const (
SEEK_DATA = 3
SEEK_HOLE = 4
)
然后将这些值与 syscall.Seek()
一起使用。
要传递给 syscall.Seek()
和朋友的文件描述符
可以使用 Fd()
方法从打开的文件中获取
os.File
个值。
读取时使用的模式是检测包含数据的区域,并从中读取数据——参见 this 的一个示例。
请注意,这涉及读取稀疏文件;但是如果你真的想 transfer 它们稀疏——也就是说,保留它们中的 属性,——情况就更复杂了:它看起来甚至更少便携,因此需要进行一些研究和实验。
在 Linux 上,您似乎可以尝试将 fallocate(2)
与
FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE
尝试在
您正在写入的文件的末尾;如果那合法地失败了
(使用 syscall.EOPNOTSUPP
),你只需将你正在读取的漏洞所覆盖的尽可能多的归零块铲到目标文件中——希望如此
OS 会做正确的事,并自行将它们变成一个洞。
请注意,一些文件系统根本不支持漏洞——作为一个概念。 一个例子是 FAT 家族中的文件系统。 我要引导您的是,无法创建稀疏文件可能 实际上是你的目标文件系统的属性。
您可能会对 Go issue #13548 "archive/tar: add support for writing tar containing sparse files" 感兴趣。
另外注意:您还可以考虑检查复制源文件的目标目录是否与源文件位于同一文件系统中,如果是这样,请使用 syscall.Rename()
(在 POSIX系统)
或 os.Rename()
将文件移动到不同的目录 w/o
实际上是在复制它的数据。
您不需要求助于系统调用。
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
读取一个块,检查它是否全部 '[=15=]'
。如果是,只需 dst.Seek(len(chunk), os.SEEK_CUR)
跳过 dst
中的那部分。该特定实现留作 reader :)