随机读写

如果一个文件句柄是指向一个实体文件的,那么就可以对它进行随机数据的访问(包括随机读、写),随机访问表示可以读取文件中的任何一部分数据或者向文件中的任何一个位置处写入数据。实现这种随机读写的功能依赖于一个文件读写位置指针(file pointer)

当一个文件句柄关联到了一个实体文件后,就可以操作这个文件句柄,比如通过这个文件句柄去移动文件的读写指针,当这个指针指向第100个字节位置处时,就表示从100个字节处开始读数据,或者从100个字节处开始写数据。

可以通过seek()函数来设置读写指针的位置,通过tell()函数来获取文件读写指针的位置。如果愿意的话,IO::Seekable模块同样提供了一些等价的面向对象的操作方法,不过它们只是对seek、tell的一些封装,用法和工作机制是完全一样的。

需要注意的是,虽说文件读写指针不是属于文件句柄的,但通过文件句柄去操作读写指针的时候,可以认为指针是属于句柄的。例如,同一个文件的多个文件句柄的读写指针是独立的,如果文件A上同时打开了A1和A2两个文件句柄,那么在A1上设置的读写指针不会影响A2句柄的读写指针。

seek跳转文件指针位置

通过seek()函数,可以让文件的指针随意转到哪个位置。注意还有一个sysseek()函数,它们不同,seek()是工作在buffer上的,sysseek()是底层无buffer的。

seek FILEHANDLE, POSITION, WHENCE

seek()有三个参数:

  • 第一个参数是文件句柄
  • 第二个参数是正负整数或0,它的意义由第三个参数决定
  • 第三个参数是flag,用来表明相对与哪个位置进行跳转的,值可以是0、1和2。如果导入了Fcntl模块的seek标签(即use Fcntl qw(:seek)),则可以使用0、1、2对应的常量SEEK_SET、SEEK_CUR、SEEK_END来替代0、1、2

第三个参数的值决定第二个参数的意义。如下:

seek                           意义
-----------------------------------------------------------------
seek FH, $pos, 0 以相对于文件开头的位置跳转$pos个字节,
seek FH, $pos, SEEK_SET 即直接按照绝对位置的方式设置指针位置。
pos不能是负数,否则表示跳转到文件头的
前面,这会使得seek失败而返回0。例如
`seek FH, 0, 0`表示跳转到开头(第一个
字节前),`seek FH, 10, 0`表示跳转到第
10字节前 seek FH, $pos, 1 以相对于当前指针的位置向前(pos为正数)、
seek FH, $pos, SEEK_CUR 向后(pos为负数)跳转pos个字节,pos=0表
示保持原地不动。例如`seek FH, 60, 1`
表示指针向右(向前)移动60个字节。如果移
动到超出文件的位置并从这里写入数据,将
以空字节`\0`填充直到那个位置 seek FH, $pos, 2 以相对于文件尾部的位置跳转$pos个字节。
seek FH, $pos, SEEK_END 如果pos为负数,表示向文件头方向移动pos
个字节,如果pos为0则表示保持在尾部不动,
如果pos大于0且要写入,则会以空字节`\0`
填充直到那个位置。例如`seek FH, -60, 2`
表示从文件尾部向文件头部移动60个字节

seek在成功跳转成功时返回true,否则返回0值,例如想要跳转到文件头的前面,这时返回0且将指针放置到文件的结尾。

比如用seek来创建一个大文件:

open BIGFILE, ">", "bigfile.txt";
seek BIGFILE, 100*1024, 0; # 100K
syswrite BIGFILE, 'endendend' # 100k + 9bytes
close BIGFILE

跳转超出文件尾部后,如果要真的让文件扩充,需要在结尾的地方写入一点数据,否则不会填充。这就相当于用类似于下面的dd命令创建一个稀疏大文件一样。

dd if=/dev/zero of=bigfile seek=100 count=1 bs=1K

tell()函数获取文件指针位置

tell FILEHANDLE

tell函数获取给定文件句柄当前文件指针的位置。

唯一需要注意的一点是,如果文件句柄指向的文件描述符不是一个实体文件,比如套接字句柄,tell将返回-1。注意不是返回undef,尽管我们可能更期待它返回undef来判断。

$pos = tell MYHANDLE;
print "POS is", $pos > -1 ? $pos : "not file", "\n";

IO::Seekable

IO::Seekable模块提供了seek和tell的封装方法。例如:

$fh->seek($pos, 0);         # SEEK_SET
$fh->seek($pos, SEEK_CUR);
$pos = $fh->tell();

seek在EOF处读

就像实现tail -f一样监控每秒写入到文件尾部的数据并输出。如果使用seek来实现这个功能的话,参考如下:

#!/usr/bin/perl
use strict;
use warnings; die "give me a file" unless(@ARGV and -f $ARGV[0])
open my $taillog, $ARGV[0]; while(1){
while(<$tailog>){print "$.: $_";}
seek $taillog, 0, 1;
sleep 1;
}

上面的程序中,先读取出文件中的数据,然后将文件的指针保持在原地以便下次循环继续从这里开始读取,睡一秒后继续,这个逻辑并不难。

当然,对于上面简单的tail -f来说,根本没使用seek的必要,但是这提供了一种连续从尾部读取数据的思路。

seek在EOF处写

典型的是写日志文件,要不断地向文件尾部追加一行行日志数据。但是,多个进程可能会互相覆盖数据,因为不同进程的写真正是互相独立的,谁也不知道谁的指针在哪里。如果使用的是追加式写入方式,则多进程间不会出现数据覆盖的问题,因为每次append数据之前都会将指针放到文件的最结尾处。但是多个进程的append无法保证每行数据写入的顺序。

如果要保证某进程某次两行数据的写入是紧连在一起的,那么需要使用锁的方式,例如使用flock文件锁。

下面是一个简单的日志写入程序示例:

#!/usr/bin/perl
use strict;
use warnings;
use Fcntl qw(:flock :seek); sub logtofile {
die "give me two args" if @_ < 1;
my $logfile = shift;
my @msg = @_; open LOGFILE, ">>", $logfile or die "open failed: $!"; flock LOGFILE, LOCK_EX;
seek LOGFILE, 0, SEEK_END;
print LOGFILE @msg;
close LOGFILE;
} logtofile "/tmp/mylog.log", "msgA\n", "msgB\n", "msgC\n";

truncate截断文件

如果要截断文件为某个空间大小,直接使用truncate()函数即可(shell下也有truncate命令来截断文件)。

它的第一个参数是文件句柄,第二个参数是截断后的文件大小,单位字节。注意,truncate是从当前指针位置开始向后截断的,其指针前面(左边)的数据不会动但是会计算到截断后的大小。如果指定的截断大小超过文件大小,则会使用空字节\0填充到给定大小(这个行为默认没有定义)。

因为要截断,这个文件句柄的模式必须是可写的,且如果是使用">"模式,将首先被截断为空文件。所以,应该使用+<>>+>>这类模式。为了保证截断效果,如果使用的是后两种open模式,应该在每次截断前使用"seek"将指针指到文件的头部。

例如,截断文件为100字节大小。

open FILE, ">>", "bigfile";
seek FILE, 0, 0;
truncate FILE, 100;
close FILE;

按行截断文件

truncate只能按字节截断文件,不过有时候我们想按照行数来截断文件。

例如,想要保留前10行数据。实现的逻辑很简单,先按行读取10行(判断行号或使用一个行号计数器),然后记录下当前的指针位置,最后使用truncate截断到这个位置。

#!/usr/bin/perl

use strict;
use warnings; die "give me a file" unless @ARGV;
die "give me a line num" unless (defined($ARGV[1]) and $ARGV[1] >= 0); my $file = $ARGV[0];
my $trunc_to = int($ARGV[1]); # 读取到前X行
open READ, $file or die "open failed: $!";
while(<READ>){
last if $. == $trunc_to;
} my $trunc_size = tell READ;
exit if $. < $trunc_to; # total line less than $trunc_to
close READ; # truncate
open WRITE, "+<", $file or die "open failed: $!";
truncate WRITE, $trunc_size or die "truncate failed: $!";
close WRITE;

Perl IO:随机读写文件的更多相关文章

  1. Java基础之读文件——使用通道随机读写文件(RandomReadWrite)

    控制台程序,使用通道随机读写primes_backup.bin文件. import static java.nio.file.StandardOpenOption.*; import java.nio ...

  2. 计算机程序的思维逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    57节介绍了字节流, 58节介绍了字符流,它们都是以流的方式读写文件,流的方式有几个限制: 要么读,要么写,不能同时读和写 不能随机读写,只能从头读到尾,且不能重复读,虽然通过缓冲可以实现部分重读,但 ...

  3. Commons IO方便读写文件的工具类

    Commons IO是apache的一个开源的工具包,封装了IO操作的相关类,使用Commons IO可以很方便的读写文件,url源代码等. 普通地读取一个网页的源代码的代码可能如下 InputStr ...

  4. Java IO如何读写文件

    Java把这些不同来源和目标的数据都统一抽象为数据流:Java语言的输入输出功能是十分强大而灵活的:在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:标准输入输出,文件的操作,网络上 ...

  5. Java编程的逻辑 (60) - 随机读写文件及其应用 - 实现一个简单的KV数据库

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  6. 第五篇:使用无缓冲IO函数读写文件

    前言 本文介绍使用无缓冲IO函数进行文件读写. 所谓的无缓冲是指该IO函数通过调用系统调用实现,其实系统调用内部的读写实现也是使用了缓冲技术的. 读写步骤 1. 打开文件 open 函数 2. 读写文 ...

  7. 使用无缓冲IO函数读写文件

    前言 本文介绍使用无缓冲IO函数进行文件读写. 所谓的无缓冲是指该IO函数通过调用系统调用实现,其实系统调用内部的读写实现也是使用了缓冲技术的. 读写步骤 1. 打开文件 open 函数 2. 读写文 ...

  8. IO流 读写文件

    读写文件 如前所述,一个流被定义为一个数据序列.输入流用于从源读取数据,输出流用于向目标写数据. 下图是一个描述输入流和输出流的类层次图. 下面将要讨论的两个重要的流是 FileInputStream ...

  9. Python IO编程-读写文件

    1.1给出规格化得地址字符串,这些字符串是经过转义的能直接在代码里使用的字符串 需要导入os模块 import os >>>os.path.join('user','bin','sp ...

随机推荐

  1. 《SpringMVC从入门到放肆》十四、SpringMVC分组数据校验

    上一篇我们学习了数据校验,但是在实际项目中,还是有些不够灵活,今天我们就来继续学习一种更灵活的数据校验方法——分组数据校验. 一.什么是分组校验 校验规则是定义在实体中的,而同一个实体可以被多个Con ...

  2. java将数据库中查询到的数据导入到Excel表格

    1.Maven需要的依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> <dependency> ...

  3. 初次接触之R语言

    一.什么是R? 最受欢迎的数据分析和可视化平台之一. 其他分析平台:Excel.SPSS.SAS 二.为什么选择R? 免费.支持WINDOWS/MAC OS/Linux. 开源

  4. 用C#+Selenium+ChromeDriver 生成我的咕咚跑步路线地图

    先上结果: 之前 在公司业务中用过java+Selenium+ChromeDriver ,使用起来非常顺手,可以完美模拟真实的用户浏览行为.最近休息的时候想用C#也试一下,于是有了本文. 实现原理一样 ...

  5. 《Android插件化开发指南》面世

    本书在京东购买地址:https://item.jd.com/31178047689.html 本书Q群:389329264 (一)这是一本什么书 如果只把本书当作纯粹介绍Android插件化技术的书籍 ...

  6. Druid的简介

    Druid的简介 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.JBos ...

  7. JavaScript 实现textarea限制输入字数, 输入框字数实时统计更新,输入框实时字数计算移动端bug解决

    textarea称文本域,又称文本区,即有滚动条的多行文本输入控件,在网页的提交表单中经常用到.与单行文本框text控件不同,它不能通过maxlength属性来限制字数,为此必须寻求其他方法来加以限制 ...

  8. HashMap和HashTable简介和区别

    一.HashMap简介 HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. HashMap是非线程安全的, ...

  9. [Swift]LeetCode33. 搜索旋转排序数组 | Search in Rotated Sorted Array

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  10. [Swift]LeetCode106. 从中序与后序遍历序列构造二叉树 | Construct Binary Tree from Inorder and Postorder Traversal

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...