起因
今天在学习RandomAccessFile
这个类时,看到里面有一个方法
public void seek(long pos) throws IOException {if (pos < 0) {throw new IOException("Negative seek offset");} else {seek0(pos);}}
这个方法没有对文件的长度的校验,如果一个文件实际长度只有4byte
,如果seek(1024)
会发生什么?首先了解一下什么是空洞文件
什么是空洞文件(hole file)?
💁♂️在Linux中,lseek的系统调用是可以改变在文件上面的偏移量的,而且还允许其超出文件的长度。偏移量一旦超出了文件的长度,下一次进行文件IO写入操作文件的时候便会延续偏移量的位置继续写入,进而在文件中间产生了空洞的部分,这部分会以”\0”填充,而从原来的文件结尾到新写入数据间的这段空间就被称为“文件空洞
”。
在Linux中,EOF(文件结束符)并不是一个字符,而是在读取到文件末尾的时候返回的一个信号值,也就是-1。
文件空洞部分实际上是不会占用任何的物理空间的,直到在某个时刻对空洞部分进行写入文件内容的时候才会为它分配对应的空间。但是在空洞文件形成的时候,逻辑上面的文件大小是分配了空洞部分的大小的。
实验
接下来可以通过一个实验来验证空洞文件的形成,并且使用cat和cp两种方式对空洞文件进行操作,看看他们对应的不同效果。
首先使用dd命令产生一个空洞文件,具体可以查看dd的使用方法。
- if - 输入文件
- of - 输出文件
- seek - 设置输出文件的偏移量
- skip - 设置输入文件的偏移量
- bs - 是ibs和obs的合集
接下来从/dev/urandom中读取内容,写入到hole.file文件中,在写入之前,对hole.file文件设置了999的偏移量,bs设置了4096的大小,同时设置写入的block数量为1,这样将会产生一个逻辑长度为1000数据块的文件。
~$ dd if=/dev/urandom of=hole.file bs=4096 seek=999 count=1
1+0 records in
1+0 records out
4096 bytes (4.1 kB, 4.0 KiB) copied, 0.000449329 s, 9.1 MB/s
产生之后,使用ls命令查看文件大小是4.0MB
nsh@nsh-dev:~$ ls -lh hole.file
-rw-rw-r-- 1 nsh nsh 4.0M 3月 31 16:15 hole.file
使用du命令查看文件大小是4.0KB
nsh@nsh-dev:~$ du -sh hole.file
4.0K hole.file
相差如此悬殊!!🧐
使用od命令来查看hole.file文件的二进制内容
nsh@nsh-dev:~$ od -c hole.file
0000000 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
17470000 V 203 352 215 322 u 243 245 x } 222 321 243 332 ( 335
会看到其实整个hole.file文件的开始是用许多的”\0”来填充的,由于上面设置了偏移量比较大,可以设置小一点的seek来观察。
接下来使用cat来重定向文件到一个新的文件;再使用cp命令将文件拷贝一份出来
nsh@nsh-dev:~$ cat hole.file >hole.cat
nsh@nsh-dev:~$ cp hole.file hole.cp
使用ls命令和du命令查看两个新生文件的大小
nsh@nsh-dev:~$ ls -lh hole.*
-rw-rw-r-- 1 nsh nsh 4.0M 3月 31 16:26 hole.cat
-rw-rw-r-- 1 nsh nsh 4.0M 3月 31 16:26 hole.cp
-rw-rw-r-- 1 nsh nsh 4.0M 3月 31 16:15 hole.file
nsh@nsh-dev:~$ du -sh hole.*
4.0M hole.cat
4.0K hole.cp
4.0K hole.file
会看到使用cat重定向得到的文件的大小使用ls和du都是4.0MB,而使用cp命令得到的文件大小信息和原来的文件是一样的。
总结
为什么ls和du命令得到的文件大小会相差如此巨大呢?
- ls获取得到的是文件的逻辑大小;
- du获取得到的是文件的实际占用物理块的大小。也就是说,当产生空洞文件的时候,文件系统并不会将空洞文件部分对应也分配好空间,那样是相当浪费的,而且还会被一些黑客利用,在系统中产生大量的空洞文件来耗尽系统的存储资源。
作用
空洞文件看上去好像是一个不太靠谱不太安全的操作,但其实在很多情况都很有用:
- 像在我们平时使用迅雷下载的时候,刚开始下载但是本地的下载文件就已经好几百兆了,这就利用了空洞文件。为了能够并行下载,创建空洞文件可以让多线程在不同的seek上面开始写入文件,如果不是空洞文件就只能串行写入了。
- 在创建虚拟机的时候,我们会使用img工具生成一个例如50GB大小的镜像文件,但是其实在安装完系统之后,镜像的大小可能只有4GB,也就是说img并不会马上就占用掉物理存储空间的50GB,而是在未来使用过程中不断增加的。