7.1 通过I/O实现文件的读取与写入



一、初识I/O

1.1 理解输入(Input)与输出(Output)

1.I/O特点

  • I/O,所有程序都必须有;
  • 输入机制:一个Java程序,要接受来自键盘命令的输入,或者读取外部载体的数据(包括磁盘、光盘)
  • 输出机制:通过运算,在显示器上输出"Hello world",或者将结果保存在本地磁盘、光盘等存储设备中

2.java.io包

java的 IO是通过java.io包下的类和接口来支持的。其中,java.io包下的类的分类:

  • 按照出入方向:输入(Input)与输出(Output)
  • 按照内容类型:字节流、字符流

1.2 File类及其常用方法

1.File类特点

File类:java.io包下的一个类,代表与平台无关的文件和目录

用途:程序中操作文件和目录,都可以通过File类来完成。可以新建、删除、重命名文件和目录

缺点:File类只可以操作文件本身,但不能修改文件的内容。

要想访问修改文件内的内容,就需要输入/输出流。

2.createNewFile()方法来创建文件

  • 成功创建文件
public class FileSample {

    //演示最常见的File类的方法
    public static void main(String[] args) {
        File f = new File("d:/b.txt");                  //表示多级路径,大多数平台都是用反斜杠/
        //File f = new File("d:\\b.txt");             //windows平台也可用这个,但麻烦
        try {
            boolean r1 = f.createNewFile();          //创建文件  //如果上述地址不存在,就会出现异常
            System.out.println(f.getPath()+"文件创建是否成功:"+r1);        //获取文件的路径,以检查文件是否创建成功
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

--->
d:\b.txt文件创建是否成功:true
  • 因为windows平台上,c盘是系统盘,需要管理员授权的。所以,禁止程序直接写入C盘根路径。
public class FileSample {

    //演示最常见的File类的方法
    public static void main(String[] args) {
        File f = new File("c:/b.txt");                  
        try {
            boolean r1 = f.createNewFile();         
            System.out.println(f.getPath()+"文件创建是否成功:"+r1);        
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

--->
java.io.IOException: 拒绝访问。
    at java.io.WinNTFileSystem.createFileExclusively(Native Method)
    at java.io.File.createNewFile(File.java:1023)
    at com.imooc.io.FileSample.main(FileSample.java:13)
  • 如果再运行一次File f = new File("d:/b.txt"); 来创建同名文件,也会报错。因为已存在。
  • 如果指定一个不存在的目录,也会抛出异常。

3.获得文件的基本参数的方法:

public class FileSample {
    
    public static void main(String[] args) {
        File f = new File("d:/b.txt");      
        try {
            boolean r1 = f.createNewFile();         
            System.out.println(f.getPath()+"文件创建是否成功:"+r1);  
            
            System.out.println(f.getPath() + "是否存在:" + f.exists());
            System.out.println(f.getPath()+"是否是目录:"+f.isDirectory());
            System.out.println(f.getPath() + "是否是文件:" + f.isFile());        //不是文件,就是目录,二选一
            System.out.println(f.getPath()+"的大小:"+f.length());              //字节数量
            System.out.println(f.getPath()+"的文件名:"+f.getName());
            boolean r2 = f.delete();
            System.out.println(f.getPath()+"文件是否删除成功:"+r2);
            System.out.println(f.getPath() + "是否存在:" + f.exists());

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

--->
d:\b.txt文件创建是否成功:true
    
d:\b.txt是否存在:true
d:\b.txt是否是目录:false
d:\b.txt是否是文件:true
d:\b.txt的大小:0
d:\b.txt的文件名:b.txt
d:\b.txt文件是否删除成功:true
d:\b.txt是否存在:false

4.上述是对文件的操作,下面是对目录的创建:mkdirs()方法:

public class FileSample {

    public static void main(String[] args) {
        File d = new File("d:/电影/华语/大陆");
        try {
            boolean r3 = d.mkdirs();
            System.out.println("[" + d.getPath() + "]目录是否创建成功:" + r3);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

--->
[d:\电影\华语\大陆]目录是否创建成功:true

如果再运行一次上述代码,就会出现:因为该目录已经存在的。

[d:\电影\华语\大陆]目录是否创建成功:false

二、通过字节、字符流实现文件的读取与写入

上面完成了File类操作文件本身。接下来,介绍读取与写入文件的内容。

2.1 流与四种抽象类

1.流

把要处理的数据,统一称为流。

读取原文件,写入目标新文件

2.四种抽象类

分类字节流字符流
输入方向InputStreamReader
输出方向OutputStreamWriter
对象二进制数据,例如图片文本文件

2.2 通过字节输入流读取文件

1.InputStream类的特点

  • InputStream是所有字节输入流的父类
  • InputStream提供核心方法:read(),用于读取字节数据
  • 其中,专用的FileInputStream类,专用于读取二进制文件

相关接口和具体实现类之间的关系图示:

2.InputStream类的通用开发模式

(1)实例化InputStream对象

(2)利用read方法循环读取字节数据,并进行处理

(3)调用close方法,关闭InputStream对象

3.用字节输入流,读取E盘根目录下的demo.txt文件

  • 新建一个文件

  • 查看源码,验证相关接口和具体实现类之间的关系

  • 用字节输入流,读取E盘根目录下的demo.txt文件
public class FileCopySample {

    public static void main(String[] args) {
        //用字节输入流,读取E盘根目录下的demo.txt文件
        File source = new File("e:/demo.txt");         //指定一个路径
        InputStream fis=null;  //为什么放在外面?因为流有开就有关.fis对象的生存周期,既要在try块打开,也要在finally块关闭

        try {
            //实例化一个InputStream对象
            fis = new FileInputStream(source);     //将数据源头的对象传入构造方法
            byte[] bs = new byte[1024];      //专用于保存字节信息的数组  //在内存中创建一个1024字节大小(1k内存)的数组
            int len;
            while ((len = fis.read(bs)) != -1) {   //一次性读取1024字节的数据,存储到字节数组bs中 
                System.out.println(len);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fis != null) {                  //当前对象是已实例化过的
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

--->
7        //对应E盘根目录下的demo.txt文件:abcdefg内容的长度

注意:read()方法读完文件时,会返回-1,表示文件已读取完毕

  • 如果用字节输入流,读取E盘根目录下的demo.jpg文件:
public static void main(String[] args) {
    File source = new File("e:/demo.jpg");         //指定一个路径
    byte[] bs = new byte[1024];
    ...
        
--->
1024        //一块一块的读取,局部读取
1024
...
1024
127            //不够整除了,有个小余数
  • 换个更大的方式,去读取:
public static void main(String[] args) {
    File source = new File("e:/demo.jpg");       //指定一个路径
    byte[] bs = new byte[1024*1024];            //如果是1m的读取
    ...
        
--->
1048576
1048576
1048576
1048576
1048576
473215

2.3 通过字节输出流实现文件复制

1.OuputStream类的特点

  • OuputStream是所有字节输出流的父类
  • OuputStream提供核心方法:write(),用于向指定输出流输出字节数组
  • 其中,专用的FileOutputStream类,专用于写入二进制文件

相关接口和具体实现类之间的关系图示:

其中,Flushable接口的作用:立即将缓存中的数据,写入到文件或其他输出流中。

2.OuputStream类的通用开发模式

(1)实例化OuputStream对象

(2)利用write方法循环写入字节数据

(3)调用close方法,关闭OuputStream对象

3.实现文件复制的功能

public class FileCopySample {

    public static void main(String[] args) {
        
        File source = new File("e:/demo.jpg");        
        File target = new File("e:/demo1.jpg");         //定义要输出到哪个文件
        InputStream fis=null;       
        OutputStream fos = null;

        try {
            fis = new FileInputStream(source);          //读取指定文件
            fos = new FileOutputStream(target);         //写入目标文件

            byte[] bs = new byte[1024];             
            int len;

            while ((len = fis.read(bs)) != -1) {    //如果还有内容
                System.out.println(len);
                fos.write(bs,0,len);                //起始点       //最后一次的长度,往往不是1024,所以用len
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

            if (fos != null) {
                try {
                    fos.close();                    //关闭输出流
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }
            if (fis != null) {                         //如果当前对象是已实例化过的
                try {
                    fis.close();                        //关闭输入流
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

--->
1024        
1024
...
1024
127            

查看电脑路径,成功复制了图片:

上述代码,不仅可以复制图片,还可以复制文档。

2.4 通过字符输入输出流实现文本读取与写入

上述字节的输入输出流,是针对所有字节的操作。但是,实际开发中,多是对文本内容的操作。

1.字符输入输出流的特点

  • Reader是所有字符输入流的抽象父类
  • Write是所有字符输出流的抽象父类
  • FileReader:读取文本文件
  • FileWriter:写入文本文件

2.写入文本文件

public class TextFileSample {

    //实例方法,写入文本文件
    public void writeTextFile(){
        Writer writer = null;

        try{
            File file = new File("e:/test.txt");
            if (!file.exists()) {
                file.createNewFile();
             }
            writer = new FileWriter(file);
            writer.write("这是一个新文件");

        }catch(IOException e){
            e.printStackTrace();
        }finally{
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TextFileSample sample = new TextFileSample();
        sample.writeTextFile();
    }
}

--->
已经成功创建了test.txt文本文件,内容为:“这是一个新文件”
  • 如果对已有文件进行写入,相当于直接覆盖:
  writer.write("这是一个新文件");

--->
内容为:“这是一个新文件new”
  • 如果想在文本内容末尾追加:
 writer.write("这是一个新文件new");
 writer.append(":Append内容");

--->
内容为:“这是一个新文件new:Append内容”
  • 查看源码,验证相关接口和具体实现类之间的关系

3.读取文本文件

public class TextFileSample {

    //实例方法,读取文本文件
    public void readTextFile(){
        Reader reader=null;
        try {
            File file = new File("e:/test.txt");
            reader = new FileReader(file);        //实例化FileReader对象
            int ch = 0;

            while ((ch = reader.read())!= -1) {    //调用对象的read方法,读取每一个字符的编码数字
                System.out.println(ch);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TextFileSample sample = new TextFileSample();   
        sample.readTextFile();
    }
}

--->
36825        //UTF-8编码集中的编码
26159
...
100
20869
23481
  • UTF-8编码集相当于一个包含全世界语言的代号大字典,其中的编码其实就是个int代号。它可以与所代表的对应字符进行转换。
System.out.print((char) ch);        //将UTF-8编码集中的编码转型为对应的字符
--->
这是一个新文件new:Append内容
  • 查看源码,验证相关接口和具体实现类之间的关系

三、I/O辅助类

3.1 辅助类:将字节流转换成字符流

1.两个转换流的类

不管是网络传输,还是读取文件,最根本的还是二进制的数据。

但是,实际人肉眼读取文本文件,需要对字符进行处理。

因此,需要转换:

  • 将字节流转换成字符流
  • InputStreamReader:将字节输入流--->字符输入流
  • OutputStreamWriter:将字节输出流--->字符输出流

字符是字节的子集,所以I/O不提供字符再转回到字节。

2.利用InputStreamReader读取文本文件

public class TextFileSample {
   
    public void isrSample(){
        FileInputStream fis=null;
        InputStreamReader isr=null;

        try {
            File file = new File("e:/test.txt");
            fis = new FileInputStream(file);
            isr = new InputStreamReader(fis, "UTF-8");
            while (isr.ready()) {                //如果后面有没有其他字符
                System.out.println(isr.read());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        TextFileSample sample = new TextFileSample();
        sample.isrSample();
    }
}

--->
36825        //也成功读取到了UTF-8编码集中的编码
26159
...
100
20869
23481
  • 将UTF-8编码集中的编码转型为对应的字符,肉眼能认识的:
System.out.print((char) isr.read());        //将UTF-8编码集中的编码转型为对应的字符

--->
这是一个新文件new:Append内容
  • 如果我们不想直接输出,而是将其保存下来:
public void isrSample(){
        FileInputStream fis=null;
        InputStreamReader isr=null;

        try {
            File file = new File("e:/test.txt");
            fis = new FileInputStream(file);        
            isr = new InputStreamReader(fis, "UTF-8");
            StringBuffer buffer = new StringBuffer();
            while (isr.ready()) {    
                buffer.append((char) isr.read());    //保存进buffer对象
            }
            System.out.println(buffer.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (isr != null) {
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

实际应用中,InputStreamReader类其实用的不多,而是直接用已经封装好的FileReader类。

因为,FileReader类是InputStreamReader类的子类。

3.利用OutputStreamWriter写入文本文件

public class TextFileSample {
   
    public void oswSample(){
        FileOutputStream fos = null;
        OutputStreamWriter osw = null;

        try {
            File file = new File("E:/test.txt");
            //创建文件
            if (!file.exists()) {
                file.createNewFile();
            }

            fos = new FileOutputStream(file);
            osw = new OutputStreamWriter(fos, "UTF-8");
            osw.write("这是一个新文件!");

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (osw != null) {
                    osw.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        TextFileSample sample = new TextFileSample();
        sample.oswSample();
    }
}

--->
这是一个新文件!

3.2 辅助类:缓冲区与应用

默认文件的读取与写入,都是逐个字节/字符完成的。但这种处理方法并不高效。那应该怎么优化呢?

缓冲区,应运而生。

1.概念

将读取或写入的数据整块在内存中缓存,一次性批量读取、写入,便可以有效提高数据交互效率。

优化输入输出的执行过程

比如:去菜市场买菜:

  • 方式1:跑第一趟买一个萝卜,第二趟买一个白菜,第三趟买一块肉
  • 方式2:拿个筐去,跑一趟搞定

2.分类

  • BufferedInputStreamBufferedOutputStream用于缓冲字节的输入、输出流
  • BufferedReaderBufferedWriter用于缓冲字符的输入、输出流

3.利用BufferedReader实现文本的整行读取

public class TextFileSample {
    public void readerBuffer(){
        Reader reader=null;
        BufferedReader br = null;
        try {
            File file = new File("e:/FileSample.java");
            reader = new FileReader(file);
            br = new BufferedReader(reader);
            String line = null;                         //整行的文本
            while ((line = br.readLine()) != null) {      //每次读一行,直到下一行没有新数据
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();

        }finally {
            try {
                if (br != null) {
                    br.close();
                }
                if (reader != null) {
                    reader.close();                     //依次关闭,养成良好的编码习惯
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        TextFileSample sample = new TextFileSample();
        sample.readerBuffer();
    }
}

--->
public class FileSample {                        //成功一行为单位,读取了java文件中的内容
    public static void main(String[] args) {
      ...

四、初识其他流的应用

4.1 初识其他常用的字节流

不用刻意记,什么时候用到什么时候介绍:

4.2 通过URLConnection对象获取网络资源

如何利用URLConnection对象从网络获取文件、下载到本地硬盘?

该图片保存在远程服务器上,用浏览器加载:https://manongbiji.oss-cn-beijing.aliyuncs.com/images/weixin.jpg

1.如何通过网络,获取某一个文件的字节呢?

URL(Unified Resource Location),统一资源定位符,大白话就是,网址。

public class URLConnectionSample {

    public static void main(String[] args) {
        InputStream is = null;
        try {
            URL url = new URL("https://manongbiji.oss-cn-beijing.aliyuncs.com/images/weixin.jpg");  //实例化一个网址对象
            URLConnection connection = url.openConnection();   //本机与远程服务器,建立通信的管道
            is = connection.getInputStream();

            byte[] bs = new byte[1024];
            int len = 0;
            while ((len = is.read(bs)) != -1) {
                System.out.println(len);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

--->
942            //源文件,读取成功!
1024        //根据网络的状态、服务器通信的情况,可能会出现没有把数组填满的情况。
376
...
847

2.写入到本地硬盘

public class URLConnectionSample {

    public static void main(String[] args) {
        InputStream is = null;
        OutputStream os = null;
        try {
            URL url = new URL("https://manongbiji.oss-cn-beijing.aliyuncs.com/images/weixin.jpg"); 
            URLConnection connection = url.openConnection();   
            is = connection.getInputStream();
            os = new FileOutputStream("e:/weixin.jpg");         //保存到本地的文件中

            byte[] bs = new byte[1024];
            int len = 0;
            while ((len = is.read(bs)) != -1) {
                os.write(bs, 0, len);       //从网上获得一点字节数据,这里就写入一点字节数据。直到字节数组bs全部写完
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

成功保存到了本地硬盘:

总结:

开发时,如果要获取一些数据,就是想办法获得各种各样的输入流。

当进行写操作时,输出时,就要通过OutputStreamWriter对象来进行数据的输出。这就是I/O操作的常规过程。

五、总结

读进脑子里

写出来

声明:Jerry's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 7.1 通过I/O实现文件的读取与写入


Follow excellence, and success will chase you.