• 字符编码与文件操作


    一 什么是字符编码

        计算机要想工作必须通电,即用‘电’驱使计算机干活,也就是说‘电’的特性决定了计算机的特性。电的特性即高低电平(人类从逻辑上将二进制数1对应高电平,二进制数0对应低电平),关于磁盘的磁特性也是同样的道理。结论:计算机只认识数字   很明显,我们平时在使用计算机时,用的都是人类能读懂的字符(用高级语言编程的结果也无非是在文件内写了一堆字符),如何能让计算机读懂人类的字符?   必须经过一个过程:   #字符--------(翻译过程)------->数字   #这个过程实际就是一个字符如何对应一个特定数字的标准,这个标准称之为字符编码

    二 以下两个场景下涉及到字符编码的问题:
      
    #1、一个python文件中的内容是由一堆字符组成的,存取均涉及到字符编码问题(python文件并未执行,前两个阶段均属于该范畴)
    
    #2、python中的数据类型字符串是由一串字符组成的(python文件执行时,即第三个阶段)

    三 字符编码的发展史与分类(了解)

    计算机由美国人发明,最早的字符编码为ASCII,只规定了英文字母数字和一些特殊字符与数字的对应关系。最多只能用 8 位来表示(一个字节),即:2**8 = 256,所以,ASCII码最多只能表示 256 个符号

    #阶段一:现代计算机起源于美国,最早诞生也是基于英文考虑的ASCII
    ASCII:一个Bytes代表一个字符(英文字符/键盘上的所有其他字符),1Bytes=8bit,8bit可以表示0-2**8-1种变化,即可以表示256个字符
    
    ASCII最初只用了后七位,127个数字,已经完全能够代表键盘上所有的字符了(英文字符/键盘的所有其他字符),后来为了将拉丁文也编码进了ASCII表,将最高位也占用了
    
    #阶段二:为了满足中文和英文,中国人定制了GBK
    GBK:2Bytes代表一个中文字符,1Bytes表示一个英文字符
    为了满足其他国家,各个国家纷纷定制了自己的编码
    日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里
    
    #阶段三:各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码。如何解决这个问题呢???
    
    #!!!!!!!!!!!!非常重要!!!!!!!!!!!!
    说白了乱码问题的本质就是不统一,如果我们能统一全世界,规定全世界只能使用一种文字符号,然后统一使用一种编码,那么乱码问题将不复存在,
    ps:就像当年秦始皇统一中国一样,书同文车同轨,所有的麻烦事全部解决
    很明显,上述的假设是不可能成立的。很多地方或老的系统、应用软件仍会采用各种各样的编码,这是历史遗留问题。于是我们必须找出一种解决方案或者说编码方案,需要同时满足:
    #1、能够兼容万国字符
    #2、与全世界所有的字符编码都有映射关系,这样就可以转换成任意国家的字符编码
    
    这就是unicode(定长), 统一用2Bytes代表一个字符, 虽然2**16-1=65535,但unicode却可以存放100w+个字符,因为unicode存放了与其他编码的映射关系,准确地说unicode并不是一种严格意义上的字符编码表,下载pdf来查看unicode的详情:
    链接:https://pan.baidu.com/s/1dEV3RYp
    
    很明显对于通篇都是英文的文本来说,unicode的式无疑是多了一倍的存储空间(二进制最终都是以电或者磁的方式存储到存储介质中的)
    
    于是产生了UTF-8(可变长,全称Unicode Transformation Format),对英文字符只用1Bytes表示,对中文字符用3Bytes,对其他生僻字用更多的Bytes去存
    
    
    #总结:内存中统一采用unicode,浪费空间来换取可以转换成任意编码(不乱码),硬盘可以采用各种编码,如utf-8,保证存放于硬盘或者基于网络传输的数据量很小,提高传输效率与稳定性。

     

     字符编码必须统一,不然编译会以报错或提示乱码形式提示 
    首先明确概念
    #1、文件从内存刷到硬盘的操作简称存文件
    #2、文件从硬盘读到内存的操作简称读文件
    
    乱码的两种情况:
    #乱码一:存文件时就已经乱码
    存文件时,由于文件内有各个国家的文字,我们单以shiftjis去存,
    本质上其他国家的文字由于在shiftjis中没有找到对应关系而导致存储失败
    但当我们硬要存的时候,编辑并不会报错(难道你的编码错误,编辑器这个软件就跟着崩溃了吗???),但毫无疑问,不能存而硬存,肯定是乱存了,即存文件阶段就已经发生乱码
    而当我们用shiftjis打开文件时,日文可以正常显示,而中文则乱码了
    
    #用open模拟编辑器的过程
    可以用open函数的write可以测试,f=open('a.txt','w',encodig='shift_jis'
    f.write('你瞅啥\n何を見て\n') #'你瞅啥'因为在shiftjis中没有找到对应关系而无法保存成功,只存'何を見て\n'可以成功
    
    #以任何编码打开文件a.txt都会出现其余两个无法正常显示的问题
    f=open('a.txt','wb')
    f.write('何を見て\n'.encode('shift_jis'))
    f.write('你愁啥\n'.encode('gbk'))
    f.write('你愁啥\n'.encode('utf-8'))
    f.close()

    三、python2与python3字符串类型的区别

         在python2中有两种字符串类型str和unicode

        str类型:

       当python解释器执行到产生字符串的代码时(例如x='上'),会申请新的内存地址,然后将'上'编码成文件开头指定的编码格式

    要想看x在内存中的真实格式,可以将其放入列表中再打印,而不要直接打印,因为直接print()会自动转换编码

    #coding:gbk
    x=''
    y=''
    print([x,y]) #['\xc9\xcf', '\xcf\xc2']
    #\x代表16进制,此处是c9cf总共4位16进制数,一个16进制四4个比特位,4个16进制数则是16个比特位,即2个Bytes,这就证明了按照gbk编码中文用2Bytes
    print(type(x),type(y)) #(<type 'str'>, <type 'str'>)

     unicode类型:

        当python解释器执行到产生字符串的代码时(例如s=u'林'),会申请新的内存地址,然后将'林'以unicode的格式存放到新的内存空间中,所以s只能encode,不能decode

        转换在字符前加u‘,Windows默认的编码是“gbk”格式,而pycharm默认的是--**--Unicode--**--utf-8格式,编码如果不统一,解码时就会显示乱码,或者会报错,保证不乱码的前提是编码要统一,不 然就进行强制转换

    #coding:gbk
    x=u'' #等同于 x=''.decode('gbk')
    y=u'' #等同于 y=''.decode('gbk')
    print([x,y]) #[u'\u4e0a', u'\u4e0b']
    print(type(x),type(y)) #(<type 'unicode'>, <type 'unicode'>)

     二 在python3 中也有两种字符串类型str和bytes

        str是unicode

    #coding:gbk
    x='' #当程序执行时,无需加u,''也会被以unicode形式保存新的内存空间中,
    
    print(type(x)) #<class 'str'>
    
    #x可以直接encode成任意编码格式
    print(x.encode('gbk')) #b'\xc9\xcf'
    print(type(x.encode('gbk'))) #<class 'bytes'>

    很重要的一点是:看到python3中x.encode('gbk') 的结果\xc9\xcf正是python2中的str类型的值,而在python3是bytes类型,在python2中则是str类型

    三、文件处理:

     

      计算机系统分为:计算机硬件,操作系统,应用程序三部分。

    我们用python或其他语言编写的应用程序若想要把数据永久保存下来,必须要保存于硬盘中,这就涉及到应用程序要操作硬件,众所周知,应用程序是无法直接操作硬件的,这就用到了操作系统。操作系统把复杂的硬件操作封装成简单的接口给用户/应用程序使用,其中文件就是操作系统提供给应用程序来操作硬盘虚拟概念,用户或应用程序通过操作文件,可以将自己的数据永久保存下来。

    有了文件的概念,我们无需再去考虑操作硬盘的细节,只需要关注操作文件的流程:

    二 在python中

    #1. 打开文件,得到文件句柄并赋值给一个变量
    1 f=open('a.txt','r',encoding='utf-8') #默认打开模式就为r
    2 
    3 #2. 通过句柄对文件进行操作
    4 data=f.read()
    5 
    6 #3. 关闭文件
    7 f.close()
    
    

    文件的操作:读、写、修改、添加(f.read、f.close、f.open、f.(with.open)要定义打开的模式不然默认为 mode='r'  后面跟 os f )# r取消转义

      格式例如:                  

    1  with open(r'E:\PY\venv\01.txt',mode='r',encoding='utf-8') as f:

      示例1:

    1 1 f=ope(file=’C:\Windows.txt‘,mode='r'.encoding='utf-8'2 2 data=f.read()
    3 3 f.close()

    上述就是一个打开文本文件的一系列操作:获取文件>>>打开文件>>>关闭文件的过程

    # mode='r' 表示只读(也可以选择为其他模式)

    #encoding='utf-8' 表示将硬盘的01010101按照utf-8的规则去编码

    #f.read() 表示读取要获取的内容,内容已经转换完毕

    f.close # 表示关闭文件

      示例2:

    1 1 f=ope(file=’C:\Windows.txt‘,mode='rb')# mode='rb' 表示只读(也可以选择为其他模式)
    2 2 data=f.read()
    3 3 f.close()

    示列1和示例2的区别在于对文件操作时没有定义“encoding='utf-8'”字符码格式。这样获取的文件内容就会不一样

      强调第一点: 

      打开一个文件包含两部分资源:操作系统级打开的文件+应用程序的变量。在操作完毕一个文件时,必须把与该文件的这两部分资源一个不落地回收,回收方法为:
    1、f.close() #回收操作系统级打开的文件
    2、del f #回收应用程序级的变量
    
    其中del f一定要发生在f.close()之后,否则就会导致操作系统打开的文件还没有关闭,白白占用资源,
    而python自动的垃圾回收机制决定了我们无需考虑del f,这就要求我们,在操作完毕文件后,一定要记住f.close()
    
    虽然我这么说,但是很多同学还是会很不要脸地忘记f.close(),对于这些不长脑子的同学,我们推荐傻瓜式操作方式:使用with关键字来帮我们管理上下文
    with open('a.txt','w') as f:
        pass
     
    with open('a.txt','r') as read_f,open('b.txt','w') as write_f:
        data=read_f.read()
        write_f.write(data)

    #强调第二点:


      f=open(...)是由操作系统打开文件,那么如果我们没有为open指定编码,那么打开文件的默认编码很明显是操作系统说了算了,操作系统会用自己的默认编码去打开文件,在windows下是gbk,在linux下是utf-8。
    这就用到了上节课讲的字符编码的知识:若要保证不乱码,文件以什么方式存的,就要以什么方式打开。

    f=open('a.txt','r',encoding='utf-8')
    复制代码

    二 打开文件的模式

    文件句柄 = open('文件路径', '模式')

     1 #1. 打开文件的模式有(默认为文本模式):
     2 r ,只读模式【默认模式,文件必须存在,不存在则抛出异常】
     3 w,只写模式【不可读;不存在则创建;存在则清空内容】
     4 a, 之追加写模式【不可读;不存在则创建;存在则只追加内容】
     5 
     6 #2. 对于非文本文件,我们只能使用b模式,"b"表示以字节的方式操作(而所有文件也都是以字节的形式存储的,使用这种模式无需考虑文本文件的字符编码、图片文件的jgp格式、视频文件的avi格式)
     7 rb 
     8 wb
     9 ab
    10 注:以b方式打开时,读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码
    11 
    12 #3. 了解部分
    13 "+" 表示可以同时读写某个文件
    14 r+, 读写【可读,可写】
    15 w+,写读【可读,可写】
    16 a+, 写读【可读,可写】
    17 
    18 
    19 x, 只写模式【不可读;不存在则创建,存在则报错】
    20 x+ ,写读【可读,可写】
    21 xb

     三 、操作文件的方法   

      注意:文件操作时,以“w”或“wb”模式打开,则只能写,并且在打开的同时会先将内容清空(’f.write‘:文件不存在时会自动创建新的文件,文件存在的时候会自动清除内容,谨慎使用)

    文件的追加: f.write 默认添加在尾部

    文件的读写,打开关闭模式:





    上述代码执行的结果:# readlinse()输出的结果是一个列表




    常用的文件操作方法:
     1 #掌握
     2 f.read() #读取所有内容,光标移动到文件末尾
     3 f.readline() #读取一行内容,光标移动到第二行首部
     4 f.readlines() #读取每一行内容,存放于列表中
     5 
     6 f.write('1111\n222\n') #针对文本模式的写,需要自己写换行符
     7 f.write('1111\n222\n'.encode('utf-8')) #针对b模式的写,需要自己写换行符
     8 f.writelines(['333\n','444\n']) #文件模式
     9 f.writelines([bytes('333\n',encoding='utf-8'),'444\n'.encode('utf-8')]) #b模式
    10 
    11 #了解
    12 f.readable() #文件是否可读
    13 f.writable() #文件是否可读
    14 f.closed #文件是否关闭
    15 f.encoding #如果文件打开模式为b,则没有该属性
    16 f.flush() #立刻将文件内容从内存刷到硬盘
    17 f.name

    四 文件内光标移动

    一: read(3):

      1. 文件打开方式为文本模式时,代表读取3个字符

      2. 文件打开方式为b模式时,代表读取3个字节

    二: 其余的文件内光标移动都是以字节为单位如seek,tell,truncate(截断)

    注意:

      1. seek有三种移动方式0,1,2,其中1和2必须在b模式下进行,但无论哪种模式,都是以bytes为单位移动的

      2. truncate是截断文件,所以文件的打开方式必须可写,但是不能用w或w+等方式打开,因为那样直接清空文件了,所以truncate要在r+或a或a+等模式下测试效果

     1 with open (r'E:\PY\venv\01.txt',encoding='utf-8',mode='r') as f:  # 在rt模式下 read内的数字 表示的是字符的个数
     2     print(f.read(11))
     3 with open(r'E:\PY\venv\01.txt', encoding='utf-8', mode='rb') as f:
     4     ser=f.read(11)  # rb 模式读取的是字节数 不用‘encoding='utf-8'’
     5     print(ser)
     6     # print(ser.decode('utf-8'))
     7     # f.seek(offset.whence)
     8     # offset:相对偏移量,光标的移动位数
     9     # whence:'0,1,2'
    10     #  0:参照文件的开头 t和b 都可以使用
    11     #  1:参照光标所在的当前位置 只能在b模式下用
    12     #  2: 参照文件末尾 只能在b模式下使用

         # 在rt模式下 read内的数字 表示的是字符的个数,除此之外的都是字节('r','rt','rb')

    with open (r'E:\PY\venv\01.txt',mode='rb') as f: 不需要转成 encoding='utf-8'
        print(f.read(1))
        set=f.seek(6,0)     # 一个文字对应3个字节,一个英文对应一个字节
        print(set)
        print(f.seek(6,0))  #rb模式下的都是移动字节数,出了r模式外
        print(f.seek(0,0))
        print(f.read(1))
    
        print(f.read(9))
        print(f.seek(9,0))
        print(f.read(3))
        print(f.seek(0,0))
        print(f.read())

    末尾开始往后读取字4个节数:

        # with open(r'test','rb') as f:
        #     print(f.read())
        #     f.seek(-4,2)
        #     print(f.read().decode('utf-8'))

    文件的添加:

       with open(r'test','r+',encoding='utf-8') as f:
      #     f.seek(3,0)
      #     f.write('过')

    五 文件的修改:
       修改文件
       1.
    先将数据由硬盘读到内存(读文件)
        2.在内存中完成修改(字符串的替换)
        3.
    再覆盖原来的内容(写文件)
     with open(r'test02.txt','w',encoding='utf-8') as f:
         res = data.replace('egon','jason')  # 直接替换
         print(data)
             f.write(res)

    优点:任意时间硬盘上只有一个文件 不会占用过多硬盘空间
    缺点:当文件过大的情况下,可能会造成内存溢出

    文件修改方式2
       # 创建一个新文件
       # 循环读取老文件内容到内存进行修改  将修改好的内容写到新文件中
       # 将老文件删除  将新文件的名字改成老文件名

    import os
    
    with open(r'test02.txt','r',encoding='utf-8') as read_f,\
            open(r'test02.swap','a',encoding='utf-8') as write_f:
        for line in read_f:
            new_line = line.replace('jason','egon')
            write_f.write(new_line) ## 添加新内容
    os.remove('test02.txt')
    os.rename('test02.swap','test02.txt')  ## 重命名
    优点:内存中始终只有一行内容 不占内存
    缺点:再某一时刻硬盘上会同时存在两个文件

    两种修改方式:f.replace   f.write,利用for循环

        

    import os
    with open (r'E:\PY\venv\01.txt',mode='r',encoding='utf-8')as f:
        setr=f.read()
        print(setr)
        print(type(setr))
    with open (r'E:\PY\venv\01.txt',mode='w',encoding='utf-8')  as f:
       date=setr.replace('一杯','两杯')
       print(setr)
       print(f.write(date))

     1 import os
     2 
     3 with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f:
     4     data=read_f.read() #全部读入内存,如果文件很大,会很卡
     5     data=data.replace('alex','SB') #在内存中完成修改
     6 
     7     write_f.write(data) #一次性写入新文件
     8 
     9 os.remove('a.txt')
    10 os.rename('.a.txt.swap','a.txt')
     
     
    
    
  • 相关阅读:
    批量修改文件编码
    RAII机制
    C++20新特性一:模块Module
    vue 使用v-for遍历对象属性
    Chrome 91 本地跨域无法携带cookies问题解决
    Vue 函数式组件的使用技巧
    URL编码解决中文字符乱码(encodeURIComponent和decodeURIComponent)
    vue的provide/inject实现响应式数据监听
    vue3之watch监听
    Vue3: 知识总结: hooks
  • 原文地址:https://www.cnblogs.com/Gaimo/p/11141383.html
Copyright © 2020-2023  润新知