16 - 文件操作-StringIO-BytesIO
1 文件操作
读写文件是最常见的IO操作(一般说IO操作,指的是文件IO,如果是网络,一般都会直接说网络IO),在磁盘上读写文件的功能都是由操作系统提供的,操作系统不允许普通的程序直接操作磁盘(大部分程序都需要间接的通过操作系统来完成对硬件的操作),所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。在操作系统中,文件常用的操作有:
功能 | 介绍 |
---|---|
open | 打开 |
read | 读取 |
write | 写 |
close | 关闭 |
readline | 行读取 |
readlines | 多行读取 |
seek | 文件指针操作 |
tell | 指针操作 |
1.1 open函数介绍
在python中,我们使用open函数来打开一个文件,然后返回一个文件对象(流对象)和文件描述符。打开文件失败,则返回异常。
第三方模块codecs包中提供的open函数,具备内置函数open的所有功能,并且在内部它对目标文件进行了编码的与判断处理,当你不知道目标文件的编码类型时,使用codecs.open来说更可靠。
1.2 打开操作
想要读取文件,那么就需要先行打开文件。下面来看一下open函数的用法
open(['file', "mode='r'", 'buffering=-1', 'encoding=None', 'errors=None', 'newline=None', 'closefd=True', 'opener=None'],)
file
:要打开的文件名(默认为当前路径,否则要指定绝对路径)。mode
:表示打开文件的格式。buffering
: 设置缓冲区的大小(二进制模式和文本模式不同)encoding
:打开文件的编码格式(文本模式)errors
: 转码错误时是否提示异常newline
: 文本模式中对新行的处理方式closefd
: 删除描述符时,文件对象是否释放
Python 2.x中包含file和open两个操作文件的函数,Python 3.x 中只有open,操作方法相同
f = file('/etc/passwd','r')
1.2.1 mode模式
文件打开的方式有如下几种:
描述字符 | 意义 | 备注 |
---|---|---|
r | 只读打开 | 文件的默认打开方式,文件不存在会报异常 |
w | 只写打开 | 文件不存在会创建,存在的话,会覆盖源文件(非追加) |
x | 创建并只写一个新文件 | 文件已存在则报错 |
a | 只追加写模式 | 在文件末尾追加,文件不存在,新建并追加 |
b | 二进制模式 | 字节流 ,将文件就按照字节理解,与字符编码无关。二进制模式操作时,字节操作使用bytes类型 |
t | 文本模式 | 字符流 ,将文件的字节按照某种字符编码理解,按照字符操作,缺省模式 |
+ | 读写打开一个文件 | 给原来只读、只写方式打开提供缺失的读或者写能力 |
默认使用文本只读模式打开,特殊文件需要用文本模式传输的话,建议使用b
+符
,为r、w、a、x提供缺失的读或者写的功能,但是,获取文件对象依旧按照r、w、a、x自己的特征。但+不能单独使用,可以认为它是为前面的模式字符做的增强功能。
描述字符 | 意义 |
---|---|
r+ | 读模式附加写功能 |
w+ | 写模式附加读功能 |
a+ | 追加写模式附加读功能 |
x+ | 打开一个新文件可读可写 |
以上为文本模式,二进制模式时只需要添加b即可.即r+b,或者rb+都可
In [2]: f = open('hello.txt','r')
---------------------------------------------------------------------------
FileNotFoundError Traceback (most recent call last)
<ipython-input-2-27a5c78b3969> in <module>
----> 1 f = open('hello.txt','r')
FileNotFoundError: [Errno 2] No such file or directory: 'hello.txt'
In [3]: f = open('123.txt')
In [4]: f.read()
Out[4]: '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00abc'
In [5]: f.close()
In [6]: f = open('123.txt','w')
In [7]: f.read()
---------------------------------------------------------------------------
UnsupportedOperation Traceback (most recent call last)
<ipython-input-7-571e9fb02258> in <module>
----> 1 f.read()
UnsupportedOperation: not readable
In [8]:
注意:
- 我们把这个 f 成为文件句柄(用来标识一个文件的内存对象),包含文件名,字符集,大小,硬盘上的起始位置等等。
- f 在这里就变成了一个文件迭代器,我们可以通过使用for line in f ,去循环的读取文件的每一行内容
# for 循环遍历文件,打印文件的每一行
In [11]: f = open('123.txt')
In [12]: for i in f:
...: print(i,end='')
...:
hello
world
my
name
is
daxin
thanks
In [13]:
注意:这里for line in fd,其实可以从fd.readlines()中读取,但是如果文件很大,那么就会一次性读取到内存中,非常占内存,而这里fd存储的是对象,只有我们读取一行,它才会把这行读取到内存中,建议使用这种方法。
1.2.2 文件指针
为什么文件只能读一次,第二次读就是空的了?是否可以重复读取呢?在对文件操作中,会存在一个指针用于指定当前光标的位置
,当我们读完一次后, 文件指针就指向了文件的末尾了,那么如果想要重复读取,那么只需要对指针的位置进行调整即可。
- mode = r , 指针起始在0
- mode = a , 指针其实在EOF(end of file:文件末尾)
f.tell() # 显示当前文件指针的位置
f.seek(offset, whence=0, /) # 移动文件指针的位置
- offset:偏移多少字节
- whence:从哪里开始
在文本模式下:
- whence
0
:缺省值,表示从头开始,offset只能正整数 - whence
1
:表示从当前位置开始,offset只能设置为0 - whence
2
:表示从EOF位置开始,offset只能设置为0
文本模式支持从开头向后偏移的方式。whence为1时,只支持偏移0(
seek(0,0)
),相当于原地不同,没什么卵用。whence为2时,表示从EOF开始,但也只支持偏移0(seek(0,2)
),相当于将文件指针移动到EOF。
二进制模式下:
- whence
0
: 缺省值,表示从头开始,offset只能正整数 - whence
1
: 表示从当前位置,offset可正可负 - whence
2
: 表示从EOF开始,offset可正可负
二进制模式支持任意起点的偏移,从头、从尾部、从中间位置开始,向后seek可以超界,但是向前seek的时候,不能超界,否则抛异常
1.2.3 缓冲区
open函数的第三个参数为buffering,叫作缓冲区,缓冲区是内存中的一小部分空间,用于存放准备写入磁盘的数据,操作系统在进行磁盘写入的时候,由于磁盘速度要远远慢于CPU以及内存,如果每次写入数据时,都要直接写到磁盘上的话,那么效率会非常的低,所以在操作系统层面会对写入操作进行优化,比如缓冲区默认大小为4K或者4K的倍数(为什么是4K,和磁盘扇区有关,这里就不详述了),当写入的数据小于4K,会先存在缓冲区,当缓冲区满了,或者超过一定时间没有写入,操作系统会将数据一次性的刷入(flush)磁盘。
buffering的默认值为-1,表示使用缺省大小的buffer,如果是二进制模式,由io.DEFAULT_BUFFER_SIZE
决定,默认是4096
或者8192
,如果是文本模式,设备类型为终端设备时,使用行缓存方式
,如果不是,则使用二进制模式的策略。使用其他值时,有如下含义:
0
:只在二进制模式使用,表示关闭buffer1
:只在文本模式使用,表示使用行缓冲。(见到换行符就flush)大于1
:用于指定buffer的大小
二进制场景:
In [3]: import io
In [4]: f = open('123.txt','w+b')
In [5]: print(io.DEFAULT_BUFFER_SIZE)
8192
In [6]: f.write("daxin".encode())
Out[6]: 5
In [7]: f.seek(0)
Out[7]: 0
In [8]: f.read()
Out[8]: b'daxin'
In [9]: f.flush() # 默认情况下缓冲区大小为8192字节,所以我们写入的不会被立即刷入到磁盘上,如果我们要手动刷入可以手动执行flush命令
In [10]: f.close() # close会触发flush操作
In [11]: f = open('123.txt','w+b',4) # 缓冲区为4个字节
In [12]: f.write('dax'.encode()) # 3个字节不会刷入磁盘
Out[12]: 3
In [13]: f.write('in'.encode()) # 超过缓冲区大小会直接连同本次写入的数据一起刷入磁盘
Out[13]: 2
In [14]: f.seek(0)
Out[14]: 0
In [15]: f.read()
Out[15]: b'daxin'
In [16]: f.flush()
In [17]: f.close()
文本字符串场景:
In [18]: f = open('123.txt','w+') # 没有指定缓冲区的大小,采用默认值-1(由于不是终端设备,所以采用二进制的策略 由io.DEFAULT_BUFFER_SIZE控制
In [19]: !cat 123.txt
In [20]: f.write('daxin')
Out[20]: 5
In [21]: !cat 123.txt
In [22]: f.write('\n') # 遇到换行符的时候并没有刷入磁盘
Out[22]: 1
In [23]: !cat 123.txt
In [29]: f = open('123.txt','w+',1) # 指定缓冲区的大小为1(行)
In [30]: f.write('daxin')
Out[30]: 5
In [31]: f.write(' hello')
Out[31]: 6
In [33]: !cat 123.txt # 没有遇到换行符,所以不会刷入磁盘
In [34]: f.write('\n') # 遇到换行符,然后刷入磁盘
Out[34]: 1
In [35]: !cat 123.txt
daxin hello
In [36]:
buffering | 说明 |
---|---|
buffering=1 | 文本和二进制模式时,都是io.DEFAULT_BUFFER_SIZE控制 |
buffering=0 | 文本模式时不支持,二进制模式时表示关闭缓冲区 |
buffering=1 | 文本模式表示行换成,二进制模式表示一个字节 |
buffering>1 | 文本模式时,是io.DEFAULT_BUFFER_SIZE字节,flush后把当前字符串也写入磁盘。二进制模式时,表示缓冲区的大小,直到超过这个值时,才会刷入磁盘 |
一般来说,文本模式一般都用默认缓冲区大小,二进制模式可以指定buffer的大小。除非我们明确知道,否则选择默认缓冲区大小是一个比较好的选择,另外在日常编程中,明确知道需要写磁盘了,都会手动调用一次flush,而不是等到自动flush或者close的时候。
1.2.4 encoding编码
当我们操作二进制文件的时候,数据都是十六进制的,并没有什么编码的一说。而在使用文本模式时,就需要指定编码类型了,常用的中文编码比如,GBK、UTF-8、Unicode等,文件在内存中都是Unicode格式的,而存储在磁盘上就是按照本地编码的格式存储了,windows上默认的是GBK(ANSI,cp936),当我们存储的文本数据不是默认的格式时,在打开时就会产生乱码,这时可以使用指定编码格式打开文件。
encoding=None,默认值时,表示使用操作系统默认编码类型
- windows: GBK(0xB0A1)
- Linux: UTF-8(0xE5 95 8A)
1.2.5 其他参数
errors
: 什么样的编码错误将被捕获。
- None/strict:有编码错误将抛出ValueError异常
- ignore:表示忽略
newline
: 文本模式中,换行的转换。可以为None、''(空串)、'\r'、'\n'、'\r\n'。
- 读时,None表示'\r'、'\n'、'\r\n'都被转换为'\n';''(空串)表示不会自动转换通用换行符;其他合法字符表示换行符就是指定字符,就会按照指定字符分行
- 写时,None表示'\n'都会被替换为系统指定缺省分隔符(os.linesep);'\n'表示'\n'不替换;其他的合法字符表示'\n'会被替换为指定的字符
closefd
: 关闭文件描述符.
- True: 表示关闭它
- False: 会在文件关闭后保持这个文件描述符
fileobj.fileno()查看
1.3 读写操作
read(size=-1)
: 表示读取的多少个字符或字节;负数或者None表示读取到EOF(end of file),中文文本时,表示size个字符。
>>> f = open('123.txt','r+')
>>> f.read(5)
'daxin'
>>> f.read(5)
' hell'
>>> f.read(1)
'o'
>>> f.read(1)
'\n'
>>> f.read(1)
'你'
readline(size=-1)
: 一行行读取文件内容。size设置一次能读取行内几个字符或者字节。负数或者None表示读取到EOF(end of file)
>>> f = open('123.txt','r+')
>>> f.readline(3)
'a d'
>>> f.readline()
'axin hello world\n'
>>> f.readline(-10)
'c\n'
readlines(hint=-1)
: 读取所有行并返回列表。当指定hint为负数时,则会读取所有行,当指定为正数时,表示读取hint个字符所在的行
。比如第一行有6个字符,第二行有3个字符,当我们使用readlines(7)时,就会返回两行,而如果使用readlines(5)时就会只返回第一行。
In [58]: f.readlines()
Out[58]: ['1\n', '2\n', '3\n', '4\n', '5\n', '6\n', '7\n', '8\n', '9']
In [94]: f = open('123.txt','r+')
In [95]: f.readlines(9)
Out[95]: ['你好啊我了个去\n', '为什么\n']
In [98]: f.readlines(3)
Out[98]: ['你好\n', '在']
In [99]:
write(s)
: 把字符串写入到文件中并返回写入字符串的个数。
writelines(lines)
: 将字符串列表写入文件中。
In [60]: f = open('123.txt','r+')
In [65]: f.seek(0,2)
Out[65]: 25
In [67]: f.write('10')
Out[67]: 2
In [68]: f.seek(25)
Out[68]: 25
In [69]: f.read()
Out[69]: '10'
In [71]: f.writelines(['11','12'])
In [72]: f.seek(25)
Out[72]: 25
In [73]: f.read()
Out[73]: '101112'
1.4 关闭操作
当我们打开一个文件时,那么在系统中就会存在一个文件描述符对应这个文件,并且在文件系统中,一个程序可以使用的文件描述符的数量是有限的,如果我们使用完毕后不进行释放,那么很有可能就耗光文件描述符的数量(Linux中默认的文件描述符为1024,使用ulimit -a查看),产生异常,所以在使用完毕后,我们就需要手动的进行关闭
f.close()
关闭文件的操作,会同时调用flush(),把缓冲区的数据刷入磁盘。当文件已经关闭,再次关闭没有任何效果
1.5 上下文管理
上面说我们每次打开一个文件都需要手动的进行关闭,那么如果忘记了,这个文件会一直占用着对应的文件描述符,如果多次打开一个文件不关闭,那么每次都会占用新的文件秒师傅,直到文件描述符用完。上下文管理就是为了解决这种问题的,它有如下特点:
- 使用 with .. as 关键字。
- with语句块执行完毕的时候,会自动关闭文件对象
- 不会开启新的作用域(不会产生诸如函数的内部变量等)
2.6以上版本支持with读取,with open('/tmp/hello.txt') as fd:
,然后所有打开文件的操作都需要缩进,包含在with下才,这样不需要明确的指定close,当新的代码没有缩进的时候,文件会自动关闭。
In [103]: with open('123.txt','r+') as f:
...: f.read()
...:
In [105]: f.closed
Out[105]: True
或者:
In [100]: f = open('123.txt','r+')
In [101]: with f:
...: f.read()
...:
In [102]: f.closed
Out[102]: True
1.6 文件对象的其他方法
文件对象的内置方法还有很多种,如下所示:
fd.closed(): 判断文件是否被关闭,若被打开提示False,没有的话提示True
fd.flush(): 把修改的内容,强制刷新到文件中去
fd.isatty: 判断是否是一个终端文件
fd.mode: 查看文件的打开模式
fd.name: 查看文件的名称
fd.next: 迭代的方法,和readline很像,区别是,next读到末尾会报错,readline会继续返回空
fd.read: 一次性读取所有内容,以字符串的方式存取
fd.readable(): 判断文件是否可读
fd.readlines: 一次性读取所有内容,以列表的方式存取(适合操作小文件)
fd.readline(): 每次读取一行内容
fd.seek(0): 调整文件读取的指针位置
fd.seekable(): 判断文件是否可以调整指针位置(tty,磁盘等文件是不能被seek的),可以被seek则返回真,否则返回假
fd.tell(): 查询文件目前读取位置(以字符为单位)
fd.truncate(): 截取文件,从开头开始截取,不指定指针位置的话,那么会清空文件
fd.write: 把一个字符串写入到文件中去
fd.writelines():把字符串列表写入文件中
fd.xreadlines():读一行打印一行,针对大文件非常适用 -----> Python 2.x 中适用,3.x中已经取消
fd.encoding: 查看文件的编码
fd.writeable(): 判断文件是否可以写
fd.fileno(): 返回文件在操作系统上的文件描述符(一个进程的运行默认会打开三个特殊的文件描述符:0表示 stdin、1表示 stdout,2表示stderr)
fd.name: 文件名称
2 StringIO模块
用于在内存空开辟一个文本模式的buffer,可以像文件对象一样操作它,当close方法被调用时,这个buffer会被释放.
使用前需要先进行导入, from io import StringIO
In [106]: from io import StringIO
In [107]: s = StringIO() # 实例化一个StringIO对象用于读写,相当于 f = open('123.txt','r+')
In [108]: s.write('123')
Out[108]: 3
In [109]: s.readline()
Out[109]: ''
In [110]: s.seek(0)
Out[110]: 0
In [111]: s.readline()
Out[111]: '123'
getvalue()
: 获取全部内容,根文件指针没有关系.
In [106]: from io import StringIO
In [107]: s = StringIO()
In [108]: s.write('123')
Out[108]: 3
In [109]: s.readline()
Out[109]: ''
In [110]: s.seek(0)
Out[110]: 0
In [111]: s.readline() # 指针已经切换到文件末尾
Out[111]: '123'
In [112]: s.getvalue() # 无视指针,可以读取所有
Out[112]: '123'
一般来说,磁盘的操作比内存的操作要慢得多,内存足够的情况下,一般的优化思路是少落地,减少磁盘IO的过程,可以大大提高程序的运行效率.
3 BytesIO模块
用于在内存中开辟一个二进制模式的buffer,可以像文件对象一样操作它,当close方法被调用时,这个buffer会被释放.
调用前需要先导入: from io import BytesIO
In [114]: from io import BytesIO
In [115]: b = BytesIO() # 实例化一个BytesIO对象用于存储二进制数据
In [116]: b.write(b'abc')
Out[116]: 3
In [117]: b.seek(0)
Out[117]: 0
In [118]: b.read()
Out[118]: b'abc'
In [119]: b.getvalue() # 同样无视指针的存在
Out[119]: b'abc'
针对StringIO和BytesIO来说,还有如下方法:
- readable(): 可读吗?
- writeable(): 可写吗?
- seekable(): 可以调整指针吗?
4 file-like对象
类文件对象,可以向文件对象一个操作,比如socket对象,输入输出对象(stdin,stdout)等都是类文件对象.
In [120]: from sys import stdin,stdout
In [122]: stdout.write('123')
123
In [123]: type(stdout)
Out[123]: colorama.ansitowin32.StreamWrapper
In [124]:
每个程序初始化运行时,都会打开3个文件:
- stdin: 标准输入
- stdout: 标准输出
- stderr: 标准错误输出**
16 - 文件操作-StringIO-BytesIO的更多相关文章
- Python I/O编程 --读写文件、StringIO/ BytesIO
I/O编程 Input/Output 输入/输出 Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水 Input Stream就是数据从外面(磁盘.网络)流进内存,Ou ...
- python 16 文件操作(二)
转自 http://www.cnblogs.com/BeginMan/p/3169020.html 一.文件系统 从系统角度来看,文件系统是对文件存储器空间进行组织和分配,负责文件存储并对存入的文件进 ...
- Python之IO编程——文件读写、StringIO/BytesIO、操作文件和目录、序列化
IO编程 IO在计算机中指Input/Output,也就是输入和输出.由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘.网络等,就需要IO接口.从 ...
- media静态文件统一管理 操作内存的流 - StringIO | BytesIO PIL:python图片操作库 前端解析二进制流图片(了解) Admin自动化数据管理界面
一.media ''' 1. 将用户上传的所有静态文件统一管理 -- settings.py -- MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 2. 服务 ...
- GO学习-(16) Go语言基础之文件操作
Go语言文件操作 本文主要介绍了Go语言中文件读写的相关操作. 文件是什么? 计算机中的文件是存储在外部介质(通常是磁盘)上的数据集合,文件分为文本文件和二进制文件. 打开和关闭文件 os.Open( ...
- python目录和文件操作
一.python中对文件.文件夹操作时经常用到的os模块和shutil模块常用方法. 要操作目录,必须引入os模块 import os1.得到当前工作目录,即当前Python脚本工作的目录路径: os ...
- [转]Python文件操作
前言 这里的“文件”不单单指磁盘上的普通文件,也指代任何抽象层面上的文件.例如:通过URL打开一个Web页面“文件”,Unix系统下进程间通讯也是通过抽象的进程“文件”进行的.由于使用了统一的接口,从 ...
- Python基础:Python函数、文件操作、递归
函数参数 函数参数包括位置参数,关键字参数,动态参数(*args, **args)三种. 传参的过程是形式参数的赋值. *args传入的参数是元组形式,**args传入的参数是字典形式. 示例代码如下 ...
- Python 常见文件操作的函数示例(转)
转自:http://www.cnblogs.com/txw1958/archive/2012/03/08/2385540.html # -*-coding:utf8 -*- ''''' Python常 ...
随机推荐
- jmeter 兼容bug 记录一笔
这个问题我也遇到过,然后网上搜到了这篇文章! 先说下问题: 我在做性能测试时,使用JMeter搞了100个并发,以100TPS的压力压测十分钟,但压力一直出现波动,而且出现波动时JMeter十分卡,如 ...
- 【uoj#282】长度测量鸡 结论题
题目描述 给出一个长度为 $\frac{n(n+1)}2$ 的直尺,要在 $0$ 和 $\frac{n(n+1)}2$ 之间选择 $n-1$ 个刻度,使得 $1\sim \frac{n(n+1)}2$ ...
- hihoCoder #1639 图书馆
题目大意 给定 $n$($1\le n\le 1000$)个正整数 $a_1, a_2, \dots, a_n$($a_i \le 10^{12}$),令 $s$ 为这 $n$ 个数之和.求 $$ \ ...
- Codeforces Round #409 (rated, Div. 2, based on VK Cup 2017 Round 2)
A 每次可以换一个或不换,暴力枚举位置即可 B 模拟 C 二分答案.. 边界可以优化r=totb/(tota-p),二分可以直接(r-l>=EPS,EPS不要太小,合适就好),也可以直接限定二分 ...
- POJ P1185 炮兵阵地 【状压dp】
炮兵阵地 Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 29502 Accepted: 11424 Description 司令 ...
- Codeforces 744C. Hongcow Buys a Deck of Cards(状压DP)
这题的难点在于状态的设计 首先显然是个状压,需要一维表示卡的状态,另一维如果设计成天数,难以知道当前的钱数,没法确定是否能够购买新的卡,如果设计成钱数,会发现状态数过多,空间与时间都无法承受.但是可以 ...
- 【BZOJ 2754 喵星球上的点名】
Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 2512 Solved: 1092[Submit][Status][Discuss] Descript ...
- NOIP2017
NOIP2017游记 记得开始学OI是今年的6月,那时候纯粹是抱着好玩的心态来学的,但是渐渐地,我发现我好像喜欢上了OI,喜欢敲键盘时的声音,喜欢手指触碰键盘时的手感,喜欢这个奥赛班与其他科目学习气氛 ...
- 10.Android UiAutomator Junit 断言函数的使用
一.断言函数介绍 1.断言函数: 确定被测试的方法是否按照预期的效果正常工作 比如说: if (假设成立){ 通过测试 }else{ 报错并终止当前用例测试 } 2.断言函数用例结构: 一个完整的测试 ...
- 配置:heartbeat+nginx+mysqld+drbd高可用笔记(OK)
参考资料:http://www.centoscn.com/CentosServer/cluster/2015/0605/5604.html 背景需求: 使用heartbeat来做HA高可用,并且把 ...