9.11笔记字符集 IO流,文件字节输出输入流,文件字符输入输出流

p157-p160

/*
字符集
之前学了file类,但是还不能读写文件内容.所以需要学习IO流,来真正操作文件. 在学习IO流之前,应该先学习字符集
计算机不能直接存字符,底层只能存0和1. 人类为了存储字符,给了每个字符一个编码,这个合集就是字符集,比如ASCII就是一个字符集.
对美国人来说,因为字符数量很少,所以编号的总数也很少,用1个字节就可以表示128个字符信息,完全够用.
但是对中文来说就不够了,所以有了GBK中文码表,在GBK中一个中文以两个字节的形式存储,包括几万个汉字,兼容ASCII表,但不包含世界上全部文字
Unicode码表是如今的编码标准,一般情况下计算机会先通过UTF-8编码到二进制再存到计算机. Unicode兼容ASCII码表,在Unicode中汉字以三个字节储存.
技术人员一般都会用UTF-8编码,编码时和解码时的字符集要一致,否则出现中文字符乱码. 英文和数字在任何国家都不会乱码.
因为不管什么字符集都要兼容ASCII码表,所以英文总是占1字节. 中文如果是UTF-8就是3字节,GBK是2字节.

编码和解码
利用String里面的API,可以实现编码和解码.
byte[] bytes = s.getBytes("UTF-32");,即可将字符串s编码,获得一个字节数组. 入参可以选择要用哪种字符集编码,比如UTF-32,GBK等,默认用UTF-8
String s1 = new String(bytes,"utf-32"); 新建字符串,输入字节数组,即可解码. 入参可以选择解码字符集,必须和编码字符集相同才能正确解码
*/

/*
IO流
IO流用来读写数据,I是input,O是output,所以IO流也叫输入输出流
按照读写方法不同,分为字符流和字节流. 字符流就是每次读取或者写入一个字符,比较适合文本文档的操作. 字节流更适合读写音视频等文件,可以读写任何文件.
所以IO流总共分为四类: Reader字符输入流,Writer字符输出流,InputStream字节输入流,OutputStream字节输出流. 在java中,这是四个抽象类
因为方法在内存里运行,所以输入输出是对于内存来说的,input等于是放进内存里,所以input是读取硬盘内容.output等于从内存里输出,也就是把内容写入硬盘
*/



/*
InputStream
要想使用字节输入流,需要创建一个继承了InputStream抽象类的类的对象,这个类是FileInputStream,最好使用多态写法.下面是创建对象的代码
InputStream fileInputStream = new FileInputStream("输入文件的绝对路径或者相对路径都可以");
这个类里提供了读取方法

read() 返回一个int类型的整数,表示的是一个字节. 每次read都会往后推一个字节,比如abc,第一次read会返回a的int97,第二次返回b98,以此类推
但是如果文件中有中文,比如"abc中文",此时abc都读完以后应该读中,但是中是三个字节表示,read只能读一个字节,就会出现三个乱码
如果全读完了,继续read,那么会返回-1. 这种方法效率很低,就像是一次只取一滴水,一次只返回1个字节,如果遇到中文编码还会乱码,一般不会使用.

现在希望一次不是能取一滴水,而是能取一桶水. 方法是先定义一个byte[]的数组当桶,然后把数组传进read()方法里面,这样每次读取就会把桶装满.
byte[] buffer = new byte[3];
int read = fileInputStream.read(buffer);
此时read方法会把buffer这个桶装满,里面会装上三个字节的数据.然后返回一个int整数,表示这次装进去了多少水(几个字节)
再用new String(buffer)把这个数组传进去解码,就可以获得一个读取出来解码后的字符串了.
比如文件里只有2个字节的内容,此时定义的buffer桶要装3个,那么read方法就会把文件里这两个字节放进数组的前两位索引上,然后返回2,代表这次read了2字节
真实使用的时候可能文件比较大,一桶一次要装1024滴水,那么很有可能留下一个隐患,就是每次水桶用过可能倒不干净.
比如文件一共有1524个字节,此时第一次取一桶水,不会出现问题. 但是第二次取,文件当中只有500个字节了,read会把这500个字节传入数组的0-499索引
而后面的全部索引因为没有改动,还保留着之前文件的501-1024字节内容,再次解码这桶水,结果就不是我们想要的结果了.
为了避免这种情况的出现,在new String解码的时候,我们需要告诉他需要解码到第几位,而这个位数正是调用read(buffer)方法返回的整数
int read = fileInputStream.read(buffer);
new String(buffer,0,read) 这代表从0索引开始解码,到read索引结束,这样就不会出现解码到之前没倒掉的水的问题了.
再次改进代码,使用循环来读取信息,代码如下:
FileInputStream fileInputStream = new FileInputStream("javasepromax\\DAY09-oop-demo\\abc.txt");
    byte[] buffer = new byte[1024];
    int len;
    //如果这里没内容可读了,那么read方法会返回-1,不等于-1证明后面有内容
    while((len = fileInputStream.read(buffer))!=-1){
        //从0解码到len
        System.out.print(new String(buffer,0,len));
    }
虽然这个方法改善了读取性能,但是还是无法避免中文乱码.因为如果前面1022个字节都是字母,而1023+1024+1025是一个汉字,那么就会出现乱码
问题.   要解决这个问题,只需要一次性把文件读完即可,也就是让这个桶一次性装全部的水
File file = new File("javasepromax\\DAY09-oop-demo\\abc.txt"); 先找到这个文件,创建一个file对象
FileInputStream fileInputStream = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
此时直接让桶的大小等于这个对象的大小,注意需要把文件的length返回的long类型强转成int类型.
但此处有一个风险,那就是数组是装在内存里的,文件是装在硬盘里. 如果文件过大,可能这个数组占满内存都装不下.
但实际开发也不会需要处理这么大的文件,所以还是选择强转
fileInputStream.read(buffer);一次装满
System.out.println(new String(buffer)); 不用考虑别的直接解码即可

Java也想到了这个方法,在JDK9时在FileInputStream里提供了readAllBytes()的方法,直接调用即可,不用自己定义桶了
File file = new File("javasepromax\\DAY09-oop-demo\\abc.txt"); 先找到这个文件,创建一个file对象
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = fileInputStream.readAllBytes(); 直接全读取了,返回一个大桶装满了水
System.out.println(new String(bytes)); 解码这个大桶


 */

/*
OutputStream
和InputStream类似,OutputStream也是一个抽象类,我们一般使用他的子类fileOutputStream来创建对象
fileOutputStream相当于建立起了一个内存到硬盘的通道,以便让我们写入数据,同样使用多态,创建对象的代码如下:
OutputStream fileOutputStream = new FileOutputStream("javasepromax\\DAY09-oop-demo\\bac.txt");
此时入参的地址,可以是一个已经存在的文件,也可以还未被创建. 如果未被创建,在写入数据的时候会自动创建这个文件.
注意,如果这样创建对象,每次这个流第一次写入数据时都会先清空这个文件之前的内容,再写入,等于是覆盖式管道.
如果不想清空, 需要在后面入参一个true
OutputStream fileOutputStream = new FileOutputStream("javasepromax\\DAY09-oop-demo\\bac.txt",true);

写入数据的主要方法是write(),入参是一个字节,比如'a',97,等等. 比如:
fileOutputStream.write(97);
既然可以入参字节,也就可以入参字节数组. 也就是说一次直接倒进去一桶水,而不是一滴一滴的倒
byte[] buffer = ['a','b','c'];
fileOutputStream.write(buffer);
但是这样是不能传输汉字进去的,因为一个汉字是3字节,而write正常一次只能1字节,所以我们要先利用getBytes方法对汉字字符串进行编码,
得到字符串的字节数组,再传入write方法即可
byte[] buffer = "我是中国人".getBytes();
fileOutputStream.write(buffer);

与fileInputStream类似的,write方法可以选择数组的索引来选择性写入文件,如:
fileOutputStream.write(buffer,0,9); 意思是只把buffer数组里0索引-9索引的内容写入文件,实际上就是正好3个汉字
如果一直这样write下去,那么所有的字都会写在一行,所以我们需要换行. 换行的方法是write("\r\n".getBytes())

这个流写完数据以后需要进行刷新才能生效,需要调用flush()方法. 因为流是需要占用内存的,如果打开不关闭的话会影响运行性能.
如果要关闭流,需要close()方法,close()方法在关闭流之前会先flush刷新一次.
 */


/*
文件拷贝
有两种方法,对比较小的文件来说,先创建一个fileInputStream,把文件放进去直接readAll,再把获得的数组直接write进fileOutputStream即可
    File file = new File("E:\\源文件");
    FileInputStream fileInputStream = new FileInputStream(file);
    byte[] bytes = fileInputStream.readAllBytes();
    FileOutputStream fileOutputStream = new FileOutputStream("E:\\log\\拷贝的文件");
    fileOutputStream.write(bytes);
    fileOutputStream.close();
    fileInputStream.close();
如果文件比较大,可以选择分批次复制,也就是定义个桶,一桶一桶来复制.
    File file = new File("E:\\源文件");
    FileInputStream fileInputStream = new FileInputStream(file);
    FileOutputStream fileOutputStream = new FileOutputStream("E:\\log\\拷贝的文件");
    byte[] bytes = new byte[1024];
    int len;
    while((len=fileInputStream.read(bytes))!=-1){
        fileOutputStream.write(bytes,0,len);
    }
    fileOutputStream.close();
    fileInputStream.close();
 这样写代码有可能出现问题和漏洞,比如如果出现一些BUG导致程序运行中挂掉,那么流的close关闭是无法执行的,流就会占用内存
 比较好的办法是利用trycatch-finally,因为finally后面的代码一定会被执行,所以一定可以关掉这个流.
 但是因为这两个流是在try代码块内部定义的,所以无法直接访问,建议是把这两个流定义在外面,指向null,然后创建的时候再调用这两个流
 但是这样还是有问题,因为代码块内的代码可能会在这两个流创建之前就出bug,这样导致finally里的close方法会出现空指针异常
 所以在finally里面还要对这两个流进行非空校验,最终代码如下:
    FileInputStream fileInputStream = null; //先定义出两个流,指向null,方便finally代码块也能调用.
    FileOutputStream fileOutputStream = null;//如果定义在try里面,finally代码块无法调用
    try {
        File file = new File("E:\\源文件.zip");
        fileInputStream = new FileInputStream(file);
        fileOutputStream = new FileOutputStream("E:\\log\\拷贝的文件2.zip");
        byte[] bytes = new byte[1024];
        int len;
        while((len=fileInputStream.read(bytes))!=-1){
            fileOutputStream.write(bytes,0,len);
        }
        fileOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //即使上面出现bug,finally里面也会被执行.
        //进行非空校验是为了避免还没生成这两个流,代码就已经出问题了
        //比如代码第一行就出问题,然后这两个流还没被赋值.如果直接关闭就会空指针异常
        if (fileInputStream!=null){
            fileInputStream.close();
        }
        if (fileOutputStream!=null){
            fileOutputStream.close();
        }
    }

另外补充一点,即使代码里有return返回,也一定会执行finally里的代码内容.
所以如果finally里面写了return,那么无论如何return的都是finally里面的内容.

但是这样写逻辑过于繁琐,实际上这两个类都属于是资源类,他们实现了Closeable可关闭接口,实际上是可以自动关闭的.
我们使用trycatch时,其实可以在try后面加上一个(),在里面定义这些资源.当跑完整块代码以后,trycatch会自动帮我们把这些资源关闭
这样无需再用finally去手动关闭,自然也无需非空校验,大大简化了代码的复杂程度. 代码优化如下:
try (
            FileInputStream fileInputStream = new FileInputStream("E:\\源文件");
            FileOutputStream fileOutputStream = new FileOutputStream("E:\\log\\拷贝的文件2.zip");
    ) {
        File file = new File("E:\\二乔.zip");

        byte[] bytes = new byte[1024];
        int len;
        while ((len = fileInputStream.read(bytes)) != -1) {
            fileOutputStream.write(bytes, 0, len);
        }
        fileOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
值得注意的是,try()里面只能定义和初始化资源,如果不是资源会报错. 资源就是实现了可关闭接口的类.
另外,在JDK9以后优化了这一功能,如果这两个资源已经被定义好,可以直接放在try()里,并用分号链接.比如:
try(资源1;资源2){}catch(){} 当代码完成后,会自动释放资源1和资源2
 */


/*
字符流
如果我们要读取文件里的字符,直接用IO流里面的字节流会出现一些问题.比如一个汉字是3个字节,如果被分割或者分别读取,就会出现错误
字符流善于读写文本,按照字符来进行操作,不会出现因为汉字就乱码的情况,所以需要学习字符流.
字符流里面有两个抽象类,Reader和Writer. 主要的实现类和之前的字节流类似,是fileReader和fileWriter

fileReader
read()方法,每次读取一个字符,如果取到了就会返回一个int值,把他强转成char打印即可看到内容.如果没有取到就会返回-1
和字节流类似的,我们可以定义一个字符数组,(注意,字节流创建的是byte[],而字符流是char[])当做一个桶,然后一次读取多个字符.
    Reader reader = new FileReader("javasepromax\\DAY09-oop-demo\\abc.txt");
    char[] chars = new char[1024];
    int len;
    while((len = reader.read(chars))!=-1){
        String s = new String(chars, 0, len);
        System.out.print(s);
    }

fileWriter
write()方法,可以写字符串,也可以字符,也可以字符数组
创建fileWriter时候直接创建,会创建一个覆盖式管道,每次运行会把之前内容全清空再写入.
如果想添加不动原来文件的内容,需要后面填写一个true参数.
FileWriter fileWriter = new FileWriter("javasepromax\\DAY09-oop-demo\\abc.txt", true);
写入以后也需要flush()和close()
  */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值