1.1. 字符編碼
1.1.1. 字符編碼的作用
計算機(jī)只認(rèn)識0和1組成的二進(jìn)制序列,因此任何文件中的內(nèi)容(比如"hello neuedu","你好,東軟睿道"這些字符串)要想被計算機(jī)識別或者想存儲在計算機(jī)上都需要轉(zhuǎn)換為二進(jìn)制序列。那么字符與二進(jìn)制序列怎么進(jìn)行相互轉(zhuǎn)換呢?于是人們嘗試建立一個表格來存儲一個字符與一個二進(jìn)制序列的對應(yīng)關(guān)系。
- 編碼 將字符轉(zhuǎn)換為對應(yīng)的二進(jìn)制序列的過程叫做字符編碼
- 解碼 將二進(jìn)制序列轉(zhuǎn)換為對應(yīng)的字符的過程叫做字符解碼
1.2. 字符編碼的簡單發(fā)展歷史
1.2.1. ASCII碼誕生
最早建立這個字符與十進(jìn)制數(shù)字對應(yīng)的關(guān)系的是美國,這張表被稱為ASCII碼(American Standard Code for Information Interface, 美國標(biāo)準(zhǔn)信息交換代碼)。ASCII碼是基于拉丁字母的一套電腦編程系統(tǒng),主要用于顯示現(xiàn)代英語和其他西歐語言。它被設(shè)計為用1個字節(jié)來表示一個字符,所以ASCII碼表最多只能表示2**8=256個字符。實際上ASCII碼表中只有128個字符,剩余的128個字符是預(yù)留擴(kuò)展用的。

1.2.2. GBK等各國編碼誕生
隨著計算機(jī)的普及和發(fā)展,很過國家都開始使用計算機(jī)。大家發(fā)現(xiàn)ASCII碼預(yù)留的128個位置根本無法存儲自己國家的文字和字符,因此各個國家開始制定各自的字符編碼表,其中中國的的字符編碼表有GB2312和GBK。
1.2.3. Unicode誕生(萬國碼)
后來隨著世界互聯(lián)網(wǎng)的形成和發(fā)展,各國的人們開始有了互相交流的需要。但是這個時候就存在一個問題,每個國家所使用的字符編碼表都是不同的。這個時候,人們希望有一個世界統(tǒng)一的字符編碼表來存放所有國家所使用的文字和符號,這就是Unicode。Unicode又被稱為 統(tǒng)一碼、萬國碼、單一碼,它是為了解決傳統(tǒng)的字符編碼方案的局限性而產(chǎn)生的,它為每種語言中的每個字符設(shè)定了統(tǒng)一并且為之一的二進(jìn)制編碼。Unicode規(guī)定所有的字符和符號最少由2個字節(jié)(16位)來表示,所以Unicode碼可以表示的最少字符個數(shù)為2**16=65536。
1.2.4. UTF-8誕生
為什么已經(jīng)有了Unicode還要UTF-8呢?因為美國人不樂意了,由于當(dāng)時存儲設(shè)備是非常昂貴的,而Unicode中規(guī)定所有字符最少要由2個字節(jié)表示。美國人認(rèn)為像原來ASCII碼中的字符用1個字節(jié)就可以了,因此決定創(chuàng)建一個新的字符編碼來節(jié)省存儲空間。UTF-8是對Unicode編碼的壓縮和優(yōu)化,它不在要求最少使用2個字節(jié),而是將所有字符和符號進(jìn)行分類:
- ascii碼中的內(nèi)容用1個字節(jié)保存
- 歐洲的字符用2個字節(jié)保存
- 東亞的字符用3個字節(jié)保存
- ...
UTF-8是目前最常用,也是被推薦使用的字符編碼。
1.3. 字符串和字節(jié)序列的轉(zhuǎn)換
1.3.1. 字符串轉(zhuǎn)字節(jié)序列
bytes = '張三'.encode()
print(bytes)
print(type(bytes))
bytes = '張三'.encode('utf-8')
print(bytes)
print(type(bytes))
bytes = '張三'.encode('gbk')
print(bytes)
print(type(bytes))
輸出
b'\xe5\xbc\xa0\xe4\xb8\x89'
<class 'bytes'>
b'\xe5\xbc\xa0\xe4\xb8\x89'
<class 'bytes'>
b'\xd5\xc5\xc8\xfd'
<class 'bytes'>
encode默認(rèn)按utf-8編碼
1.3.2. 字節(jié)序列轉(zhuǎn)字符串
bytes = b'\xe5\xbc\xa0\xe4\xb8\x89'
msg1 = bytes.decode()
print(msg1)
print(type(msg1))
msg1 = bytes.decode('utf-8')
print(msg1)
print(type(msg1))
msg1 = bytes.decode('gbk')
print(msg1)
print(type(msg1))
輸出
張三
<class 'str'>
張三
<class 'str'>
寮犱笁
<class 'str'>
1.4. 文件的概念和作用
- 計算機(jī)的 文件,就是存儲在硬盤上的 數(shù)據(jù)
1.5. 文件的存儲方式
- 在計算機(jī)中,文件是以 二進(jìn)制 的方式保存在磁盤上的
1.5.1. 文本文件和二進(jìn)制文件
-
文本文件(字符串)
- 可以使用 文本編輯軟件 查看
- 本質(zhì)上還是二進(jìn)制文件
- 例如:python 的源程序
-
二進(jìn)制文件
- 保存的內(nèi)容 不是給人直接閱讀的,而是 提供給其他軟件使用的
- 例如:圖片文件、音頻文件、視頻文件等等
- 二進(jìn)制文件不能使用 文本編輯軟件 查看
1.6. 文件的基本操作
1.6.1. 操作文件的套路
在 計算機(jī) 中要操作文件的套路非常固定,一共包含三個步驟:
- 打開文件
- 讀、寫文件
- 讀 將文件內(nèi)容讀入內(nèi)存
- 寫 將內(nèi)存內(nèi)容寫入文件
- 關(guān)閉文件
1.6.2. 操作文件的函數(shù)/方法
- 在
Python中要操作文件需要記住 1 個函數(shù)和 3 個方法
| 序號 | 函數(shù)/方法 | 說明 |
|---|---|---|
| 01 | open | 打開文件,并且返回文件操作對象 |
| 02 | read | 將文件內(nèi)容讀取到內(nèi)存 |
| 03 | write | 將指定內(nèi)容寫入文件 |
| 04 | close | 關(guān)閉文件 |
-
open函數(shù)負(fù)責(zé)打開文件,并且返回文件對象 -
read/write/close三個方法都需要通過 文件對象 來調(diào)用
1.6.3. read 方法 —— 讀取文件
-
open函數(shù)的第一個參數(shù)是要打開的文件名(文件名區(qū)分大小寫)- 如果文件 存在,返回 文件操作對象
- 如果文件 不存在,會 拋出異常
-
read方法可以一次性 讀入 并 返回 文件的 所有內(nèi)容 -
close方法負(fù)責(zé) 關(guān)閉文件- 如果 忘記關(guān)閉文件,會造成系統(tǒng)資源消耗,而且會影響到后續(xù)對文件的訪問 新建一個demo.txt文件,輸入neuedu,放到和readfile.py同級目錄下
readfile.py
# 1\. 打開 - 文件名需要注意大小寫
file = open("demo.txt")
print(file)
# 2\. 讀取
text = file.read()
print(text)
# 3\. 關(guān)閉
file.close()
輸出
<_io.TextIOWrapper name='demo.txt' mode='r' encoding='cp936'>
neuedu
新建一個demo2.txt文件,輸入東軟睿道,保存為utf-8模式,放到和readfile.py同級目錄下 readfile.py
# 1\. 打開 - 文件名需要注意大小寫
file = open("demo2.txt")
print(file)
# 2\. 讀取
text = file.read()
print(text)
# 3\. 關(guān)閉
file.close()
輸出
<_io.TextIOWrapper name='demo2.txt' mode='r' encoding='cp936'>
涓滆蔣鐫塊亾
可見發(fā)生了中文亂碼問題。
上面代碼發(fā)生的過程是將存儲在文件中的字符串,讀取到變量中(內(nèi)存中),這里涉及的事情的先后順序是這樣的,理解這些非常重要:
1.最開始,我們用某個編輯軟件(記事本程序),編輯了"東軟睿道"四個字符,按utf-8編碼方式保存到磁盤上,此處放生了編碼過程(字符串--->字節(jié))。 2.接下來我們通過上面的python代碼,打開(open)文件,此時將存儲在文件中的字符串(字節(jié),二進(jìn)制),讀取到變量中(內(nèi)存中),轉(zhuǎn)換成字符串。這會發(fā)生一個解碼過程(字節(jié)--->字符串)。Python默認(rèn)是按照什么編碼方式解碼的呢,輸出信息給出了答案
<_io.TextIOWrapper name='demo2.txt' mode='r' encoding='cp936'>
open函數(shù)返回的是TextIOWrapper類型的文件對象,cp936是默認(rèn)的文本編碼格式(gb2312) 說明文件打開時默認(rèn)按gb2312編碼方式進(jìn)行解碼。編碼和解碼的方式不同,故而發(fā)生亂碼。
解決辦法有2個: 1.記事本編輯完保存時,按'gb2312'方式保存到磁盤(ANSI)
2.文件打開時指定編碼方式為utf-8:
# 1\. 打開 - 文件名需要注意大小寫
file = open("demo2.txt",encoding='utf-8')
print(file)
# 2\. 讀取
text = file.read()
print(text)
# 3\. 關(guān)閉
file.close()
輸出
<_io.TextIOWrapper name='demo2.txt' mode='r' encoding='utf-8'>
東軟睿道
1.6.4. write 方法 —— 寫入文件
寫入文件示例
# 打開文件
f = open("abc.txt", "w")
print(f)
f.write("hello neuedu!\n")
f.write("今天天氣真好")
# 關(guān)閉文件
f.close()
查看abc.txt
hello neuedu!
今天天氣真好
如果看到的的中文是亂碼,確認(rèn)是否是以記事本程序打開,如果是用pycharm打開,看到的是亂碼,和讀文件是同樣的道理,python默認(rèn)是以gb2312的方式將中文編碼,寫入到文件中,pycharm默認(rèn)以utf-8格式解碼打開,故而看到是亂碼,要想以utf-8方式將中文編碼,寫入到文件中,需要:
f = open("abc.txt", "w",encoding='utf-8')
注意,要想向文件中寫入數(shù)據(jù),open方法必須寫上第二個參數(shù)w。
f = open("abc.txt", "w")
open方法默認(rèn)以讀的方式打開,以下2行代碼是等價的。
f = open("abc.txt")
f = open("abc.txt","r")
所以,讀文件的時候我們可以省略第二個參數(shù)r,寫文件不可以省略。
1.7. read(),readline(),readlines()區(qū)別與用法
0.準(zhǔn)備 假設(shè)a.txt的內(nèi)容如下所示:
Hello
Welcome
What is the luck...
-
read([size])方法 read([size])方法從文件當(dāng)前位置起讀取size個字節(jié),若無參數(shù)size,則表示讀取至文件結(jié)束為止,它的返回值為字符串對象
f = open("a.txt") lines = f.read() print(lines) print(type(lines)) f.close()輸出結(jié)果:
Hello Welcome What is the luck... <type 'str'> #字符串類型2.readline()方法 從字面意思可以看出,該方法每次讀出一行內(nèi)容,所以,讀取時占用內(nèi)存小,比較適合大文件,該方法返回一個字符串對象。
f = open("a.txt") line = f.readline() print(type(line)) while line: print(line) line = f.readline() f.close()輸出結(jié)果:
<type 'str'> Hello Welcome What is the luck...3.readlines()方法讀取整個文件所有行,保存在一個列表(list)變量中,每行作為一個元素,但讀取大文件會比較占內(nèi)存。
f = open("a.txt") lines = f.readlines() print(type(lines)) for line in lines: print(lines) f.close()輸出結(jié)果:
<type 'list'> Hello Welcome What is the luck...4.最簡單、最快速的逐行處理文本的方法:直接for循環(huán)文件對象
f = open("a.txt") for line in f: print(line) f.close()1.8. 使用 with open() as 讀寫文件
由于文件讀寫時都有可能產(chǎn)生IOError,一旦出錯,后面的f.close()就不會調(diào)用。所以,為了保證無論是否出錯都能正確地關(guān)閉文件,我們可以使用try ... finally來實現(xiàn):
try:
f = open('a.txt', 'r')
print(f.read())
finally:
if f:
f.close()
每次都這么寫實在太繁瑣,所以,Python引入了with語句來自動幫我們調(diào)用close()方法:
with open('a.txt', 'r') as f:
print(f.read())
這和前面的try ... finally是一樣的,但是代碼更加簡潔,并且不必調(diào)用f.close()方法。
1.9. 文件指針
文件指針 標(biāo)記 從哪個位置開始讀取數(shù)據(jù)
- 第一次打開 文件時,通常 文件指針會指向文件的開始位置
- 當(dāng)執(zhí)行了 read 方法后,文件指針 會移動到 讀取內(nèi)容的末尾
思考 如果執(zhí)行了一次 read 方法,讀取了所有內(nèi)容,那么再次調(diào)用 read 方法,還能夠獲得到內(nèi)容嗎?
答案 不能 第一次讀取之后,文件指針移動到了文件末尾,再次調(diào)用不會讀取到任何的內(nèi)容
控制文件指針移動 方法:f.seek(offset,whence) offset代表文件指針的偏移量,單位是字節(jié)bytes whence代表參照物,有三個取值 (1)0:參照文件的開頭 (2)1:參照當(dāng)前文件指針?biāo)诘奈恢?(3)2:參照文件末尾
PS:快速移動到文件末尾f.seek(0,2) 強(qiáng)調(diào):其中whence=1和whence=2只能在b 模式下使用 f.tell()函數(shù)可以得到當(dāng)前文件指針的位置
with open(r'rrf.txt','rb')as f:
# f.readlines()
# f.seek(6,0) #從開頭移動6個字節(jié)
# print(f.readline().decode() )
# print(f.tell() )
# with open(r'rrf.txt', 'rb')as f:
# f.readline()
# f.seek(9,1) #從當(dāng)前指針位置移動9個字節(jié)
# print(f.readline() .decode() )
with open(r'rrf.txt', 'rb')as f:
f.seek(-5,2) #指針在末尾,往前讀5個字節(jié)
print(f.read() .decode() )
print(f.tell())
rrf.txt
abcdefghijklmnopqrstuvwxyz
1.10. 打開文件的方式總結(jié)
-
open函數(shù)默認(rèn)以 只讀方式 打開文件,并且返回文件對象
語法如下:
f = open("文件名", "訪問方式")
| 訪問方式 | 說明 |
|---|---|
| r | 以只讀方式打開文件。文件的指針將會放在文件的開頭,這是默認(rèn)模式。如果文件不存在,拋出異常 |
| w | 以只寫方式打開文件。如果文件存在會被覆蓋。如果文件不存在,創(chuàng)建新文件 |
| a | 以追加方式打開文件。如果該文件已存在,文件指針將會放在文件的結(jié)尾。如果文件不存在,創(chuàng)建新文件進(jìn)行寫入 |
| r+ | 以讀寫方式打開文件。文件的指針將會放在文件的開頭。如果文件不存在,拋出異常 |
| w+ | 以讀寫方式打開文件。如果文件存在會被覆蓋。如果文件不存在,創(chuàng)建新文件 |
| a+ | 以讀寫方式打開文件。如果該文件已存在,文件指針將會放在文件的結(jié)尾。如果文件不存在,創(chuàng)建新文件進(jìn)行寫入 |
思考 在使用python的open函數(shù)對文件進(jìn)行打開時,
- 用什么模式打開才最合適?
- 是用r? 還是w? 還是a? ,后面還要不要加個+???
- 讀寫圖片等二進(jìn)制文件怎么辦?
with open('test.txt','打開模式選啥?????') as w:
w.write("要寫入的內(nèi)容")
總結(jié)
-
已經(jīng)確定要打開的文件已存在(如果不存在會報錯)。
- 只讀文本文件 ? 用r
- 只讀非文本文件(圖片等)? 用rb
- 要既讀又寫? 在后邊添個+號增加權(quán)限, 用r+ 或者 rb+
-
不確定要打開的文件是否存在,如果該文件已存在則打開文件,并從開頭開始編輯,即原有內(nèi)容會被替換。如果該文件不存在,創(chuàng)建新文件。
- 只寫文本文件 ? 用w
- 只寫非文本文件(圖片等)? 用wb
- 要既讀又寫? 在后邊添個+號增加權(quán)限, 用w+ 或者 wb+
-
不確定要打開的文件是否存在,而且想要追加寫入(不替換原有內(nèi)容,新內(nèi)容寫入到已有內(nèi)容后)。如果該文件不存在,創(chuàng)建新文件。
- 只寫文本文件 ? 用a
- 只寫非文本文件(圖片等)? 用ab
- 要既讀又寫? 在后邊添個+號增加權(quán)限, 用a+ 或者 ab+
1.11. 課上練習(xí)
1.11.1. 文件操作
1). 創(chuàng)建文件data.txt, 文件共100000行, 每行存放一個1~100之間的整數(shù). 2). 找出文件中數(shù)字出現(xiàn)次數(shù)最多的10個數(shù)字, 寫入文件mostNum.txt;
import random
f = open('data.txt', 'w+')
for i in range(100000):
f.write(str(random.randint(1,100)) + '\n')
print(f.read())
f.close()
from collections import Counter
dict={}
f = open('data.txt', 'r+')
for i in f:
if i not in dict:
dict[i] = 1
else:
dict[i] = dict[i] + 1
d = Counter(dict)
with open('mostNum.txt', 'w+') as k:
for i in d.most_common(10):
k.write(f'{i[0].strip()}--------{i[1]}\n')
k.seek(0, 0)
print(k.read())
f.close()
利用Counter類里的most_common方法來直接比較字典的value值取value值最大的前十個元素。
1.11.2. 添加行號
1). 編寫程序, 將a.txt操作生成文件a_num.txt文件, 2). 其中文件內(nèi)容與a.txt一致, 但是在每行的行首加上行號。
with open('a.txt', 'r+') as f1, open('a_num.txt', 'w+') as f2:
for i, j in enumerate(f1):
f2.write(str(i)+' '+j.strip()+'\n')
f2.seek(0, 0)
print(f2.read())
a.txt
Hello
Welcome
What is the luck...
a_num.txt
0 Hello
1 Welcome
2 What is the luck...
1.11.3. 非文本文件的讀取
如果讀取圖片, 音樂或者視頻(非文本文件), 需要通過二進(jìn)制的方式進(jìn)行讀取與寫入;b
- 讀取二進(jìn)制文件 rb:rb+:wb:wb+:ab:ab+:
- 讀取文本文件 rt:rt+:wt:wt+:at:at+ 等價于 r:r+:w:w+:a:a+
先讀取二進(jìn)制文件內(nèi)容, 保存在變量content里面
f1 = open("a.png", mode='rb')
content = f1.read()
f1.close()
# print(content)
f2 = open('b.png', mode='wb')
f2.write(content)
f2.close()