IO流

一.IO流概述

1.IO流的概述:

      IO流用来处理设备之间的数据传输
      Java对数据的操作是通过流的方式
      Java用于操作流的对象都在IO包中

2.IO流的分类:

A:按数据流向分:输入流 读入数据
                            输出流 写出数据

B:按数据类型分:字节流 :可以读写任何类型的文件(音频 视频  文本文件等)

                            字节流又可以分为:字节输入流(InputStream 读)和字节输出流(OutputStream 写)

                            字节流的抽象基类是  InputStream ,OutputStream。
                             字符流: 只能读写文本文件

                            字符流又可以分为:字符输入流(Reader 读)和字符输出流(Writer 写)

                            字符流的抽象基类是  Reader , Writer。

注:字符流和字节流的4个抽象基类派生出来的子类名字都是以父类名字为结尾。

比如文件输出流和文件输入流的名字分别为FileoutputStream和InputStream
问:什么情况下使用哪种流呢?
答:如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。

      如果你什么都不知道,就用字节流

字节流的关系:

二.FileOutputStream

1.文件输出流,写出数据

   A: 构造方法
FileOutputStream(File file)//可以用文件名来封装字节输出流
FileOutputStream(String name)//可以通过字符串来封装字节输出流
   B:案例演示
FileOutputStream写出数据
注意事项:
创建字节输出流对象了做了几件事情?
  a:调用系统资源创建a.txt文件
  b:创建了一个字节输出流对象
  c:把对象指向这个文件
问:为什么一定要close()?
a: 通知系统释放关于管理所创建文件的资源
b: 让Io流对象变成垃圾,等待垃圾回收器对其回收

c:当字节输入流为空时,就要进行非空判断,不然会出现空指针异常

2.FileOutputStream的三个write()方法

                A:public void write(int b):写一个字节  超过一个字节 砍掉前面的字节
                B:public void write(byte[] b):写一个字节数组
                C:public void write(byte[] b,int off,int len):写一个字节数组的一部分

案例:使用以上方法

public class Test1 {
    public static void main(String[] args) throws IOException {
       FileOutputStream fos=new FileOutputStream(new File("a.txt"));
       fos.write(98);
       byte[]bytes="Hello,everyone".getBytes();
       fos.write(bytes);
       fos.write(bytes,0,2);
       //用完一定要注意释放资源
       fos.close();
    }
}

在此工程下创建了文件a.txt

内容:

bHello,everyoneHe
3.FileOutputStream写出数据实现换行和追加写入

A换行: windows下的换行符只用是 \r\n
  Linux \n
  Mac       \r

在我们使用过程中,用\r\n

B追加:将构造函数中的参数默认的为false改为true就可以完成追加

案例:实现FileOutputStream写出数据实现换行和追加写入


public class Test2 {
    public static void main(String[] args)throws IOException{
        //如果你再运行一遍,会覆盖原来的值
        FileOutputStream fos=new FileOutputStream(new File("b.txt"));
        fos.write(100);
        //那么在实际中,我们会采取在构造文件时,将默认的false改为true既可以实现拼接
        //追加内容
        FileOutputStream fos1=new FileOutputStream(new File("c.txt"),true);
        fos1.write(97);
        fos1.write("\r\n".getBytes());
        fos1.close();
    }
}
b.txt内容:

d


c.txt内容:

a
a
注1:将该段代码执行了两次,b.txt每次都会覆盖,因为默认构造函数的参数为false,而c.txt因为将构造函数的参数改为true,所以每次都是在原来的基础上追加。执行两次,所以输出两个a,且使用了换行符,一般我们在java中使用换行符就使用上述格式就可以。

注2:当我们在写入部分字节汉数组时,要注意区分字的编码格式,不同的编码格式,则所占的字节数不同。

GBK编码,一个汉字占2个字节,

UTF-8编码,一个汉字占3个字节,

案例A:”春天来了,你好”请输出前两个汉字。

我使用的是UTF-8,因为包含头,不包含尾,所以范围为0-6,格式如下:

 byte[] bytes1 = "春天来了,你好".getBytes();
        //GBK编码一般汉字是2个字节,UTF-8编码,一个汉字是3个字节
        //获取前两个汉字,包含头,不包含尾
        fos.write(bytes1,0,6);
案例B:FileoutPutStream写出数据加入异常处理

测试类:

public class Test4 {
    public static void main(String[] args) {
        FileOutputStream file=null;
        try {
            // file=new FileOutputStream(new File("love.txt"));
            // file=new FileOutputStream("qq.txt");
             file.write("你好".getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                    file.close();
            } catch (IOException e) {
                e.printStackTrace();
            }catch (NullPointerException e){
                //NullPointerException是由catch捕捉到的
                e.printStackTrace();
            }
        }
    }
}

结果:







注:我们发现在15行和20都会出现空指针异常,在用FileoutputStream写出数据时,我们必须要处理异常,一般处理异常有两种方式,一种是直接抛出去让JVM处理,但是我们一般不采用这种方式,而是用自己的方法,就是用try{}catch{}来实现,tryl里边大多写的是可能发生异常的代码,而将可能发生的异常写在catch中,让catch去捕捉。一般情况都会打印堆栈信息。

三.FileInputStream

1.FileInputStream读取数据一次一个字节

方法:              int read():一次读取一个字节,返回值为int型
                       如果没有数据返回的就是-1

注:每次都要释放资源

案例:读取数据一次一个字节来复制文件

public class Test3 {
    public static void main(String[] args) throws  Exception {
        FileInputStream fis=new FileInputStream(new File("D://aa.exe"));
        FileOutputStream fos=new FileOutputStream(new File("E://aa.exe"));
        int len=0;
        while ((len=fis.read())!=-1){
            fos.write(len);
        }
        //释放资源
        fis.close();
        fos.close();
    }
}

结果:发现E盘下出现aa.exe

2.FileInputStream读取数据一次一个字节数组


方法:          byte[] bytes = new byte[1024] ;//定义一个缓冲区
                   int len = 0 ;// 作用: 记录读取到的有效的字节个数
                  while((len = fis.read(bytes)) != -1){
                 System.out.print(new String(bytes , 0 , len)); }

案例:因为用一次一个字节来读取文件,这样速度很慢,所以我们采用一次读取一个字节数组,这个字节数组相当于一个缓冲区,自己可以定义它的大小,可以定义为1024,或者1024的倍数。

分析过程:a: 创建字节输入流对象和字节输出流对象
                b: 频繁的读写操作
   c: 释放资源

public class Test4 {
    public static void main(String[] args) throws IOException {
        //我们可以看出一次复制一个字节文件这个比较耗时
        //因此我们采用一次读取一个字节素组来复制文件
        FileInputStream fis=new FileInputStream(new File("D://aa.exe"));
        FileOutputStream fos=new FileOutputStream(new File("E://aa.exe"));
        //创建一个数据缓冲区
        byte[]bytes=new byte[1024];
        int len=0;
        //获取当前时间
        Instant strat=Instant.now();
        int i=0;
        while((len=fis.read(bytes))!=-1){
            System.out.println("运行第"+i+"次");
            fos.write(bytes,0,len);
            //刷新流
            fos.flush();
            i++;
        }
        //获取结束的当前时间
        Instant end=Instant.now();
        Duration between = Duration.between(strat, end);
        long l = between.toMillis();
        System.out.println("程序运行耗时"+l+"毫秒");
        System.out.println("程序总体使用字节数组缓冲区"+i+"次");
    }
}
结果:

四.BufferedOutputStream和BufferedInputStream

1.A:BufferedOutputStream和BufferedInputStream为字节缓冲区流,在上述的例子中,我们不难发现,一次读写一个字节数组比一次读写一个字节要速度快的多,这是因为使用了数组的缘故,java本身也设计的时候也考虑到缓冲区的思想,因此,提供了字符缓冲区流。

B:BufferedOutputStream的构造方法(高效的字节输出流)
  BufferedOutputStream(OutputStream out)//因为OutputStream是字节输出流的顶层父类,是一个抽象基类,因此必须传递它的子类对象

 方法作用:创建一个新的缓冲区输出流,以将数据写入指定的底层输出流

C:BufferedInputStream的构造方法(高效的字节输入流)

  BufferedInputStream(InputStream in)//因为InputStream是字节输入流的顶层父类,是一个抽象基类,因此必须传递它的子类对象

方法作用:创建一个BufferedInputStream,并保存其参数,即输入流 in,以便将来使用。

案例1:复制一个视频文件来比较使用了字节缓冲区流是否使程序运行加快

测试类:

public class Test1 {
    public static void main(String[] args) throws IOException {
        // BufferedInputStream    BufferedOutputStream 底层引入了这个缓冲区的思想
        //定义程序的开始时间
        Instant start = Instant.now();
        //test();//消耗时间为3059毫秒
        test1();//消耗时间为2398毫秒
        //记录程序结束的时间
        Instant end = Instant.now();
        Duration between = Duration.between(start, end);
        long l = between.toMillis();
        System.out.println("消耗时间为" + l + "毫秒");

    }

    private static void test1() throws IOException {

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D://abc.exe"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("E://abc.exe"));
        //定义一个字节数组
        byte[] bytes = new byte[1024];
        int len = 0;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
            bos.flush();
        }
        bis.close();
        bos.close();
    }

    private static void test() throws IOException {
        FileInputStream fis = new FileInputStream("D://abc.exe");
        FileOutputStream fos = new FileOutputStream("E://abc.exe");
        //定义一个字节数组
        byte[] bytes = new byte[1024];
        //记录读取到的有效的字节个数
        int len = 0;
        //一次读取一个字节数组
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
            //刷新
            fos.flush();
        }
        fis.close();
        fos.close();
    }
}
结果:用字节缓冲区流使用时间为2398,没有用字节缓冲区流使用时间为3059毫秒,因为这个视频文件不是很大,所以这两个的运行时间差不太多,但是当你的文件很大,那么使用缓冲区流的优点将会体现出来。当然这个缓冲区流是java提供,在程序中使用一次访问一个字节数组,也是使用了缓冲区的思想,当然在一次访问一个字节数组时,缓冲区流也会比较快,在这里就不再一一演示了。

案例2:将一个文件夹下的所有文件及文件夹都复制到另外一个盘下。

分析:首先我选择将D://ccc文件复制到E://ccc

         文件夹下边有文件也有文件夹,因此我们必须把文件的复制和文件夹的复制分开,创建为两个方法,一个复制文件夹,一个复制文件,在文件夹里去判断是否为文件是则继续调用自己,不是则调用文件复制方法,将文件复制到该目录下。利用递归的方法去实现。

A:首先必须先把D://ccc单级文件夹复制到E://ccc

a:封装源文件夹

b:封装目标文件夹

c:如果发现E盘没有该文件夹就创建该文件夹

d:复制文件夹,自己创建一个复制文件的方法,每当需要复制文件夹时就去调用该方法

B:创建一个复制文件的方法

a:先将该文件夹下的所有文件夹和文件找出,存储到一个文件数组中

b:去遍历这个文件数组,进行判断,如果是文件则调用文件复制的方法,把该复制到目标文件夹下,如果是文件夹,则进行递归调用,只是要修改目标文件夹的位置,目标文件夹在原目标文件夹的下一层,文件夹的名称为源文件夹的名称相同,因此可以进行字符串拼接的方法,重新封装一个新的目标文件夹,然后在目标文件夹下创建该文件夹

c:继续进行递归调用复制文件夹的方法

C:创建一个复制文件的方法

a:先读取原目标文件

b:封装新的目标文件,利用字符串拼接的方法,重新构建一个新的目标文件目录,在原目标目录的基础上再添加一个现原文件那夹的名称即可

c:为了提高运行的速度,因此一般都会采用一次访问一个字节的方法来复制文件

d:注意进行文件的刷新和资源释放,自己利用try catch方法来处理异常,一般不采用去直接抛给JVM去处理异常

分析完成之后,下边就是具体的实现类

测试类:

public class Test {
    public static void main(String[] args) {
        //复制单级文件夹
        //封装原文件目录
        File afile=new File("D://ccc");
        //封装目标文件目录
        File dfile=new File("E://ccc");
        //如果目标目录不存在就自己创建一个目标目录
        if(!dfile.exists()){
            dfile.mkdirs();
        }
        copyfile(afile,dfile);
    }

    private static void copyfile(File afile, File dfile) {
        File[] files = afile.listFiles();
        for(File f:files){
            if(f.isFile()){
             //如果是文件,开始复制
                copysinglefile(f,dfile);
            }else {
                //不是目标文件,可能是文件夹
                //递归调用
                File bbfile=new File(dfile+"\\"+f.getName());
                //该文件夹如果不存在就重新创建
              if(!bbfile.exists()){
                  bbfile.mkdirs();
              }
             copyfile(f,bbfile);
            }
        }

    }

    private static void copysinglefile(File f, File dfile) {
        try{
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream(f));
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(dfile+"\\"+f.getName()));
        byte[]bytes=new byte[1024];
        int len=0;
        while ((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
            bos.flush();
        }
            bis.close();
            bos.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}


结果:

D:\ccc\a
E:\ccc\a
D:\ccc\a\d
E:\ccc\a\d
D:\ccc\b
E:\ccc\b
D:\ccc\b\aa
E:\ccc\b\aa

















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值