Java - IO 流

Posted 鮀城小帅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java - IO 流相关的知识,希望对你有一定的参考价值。

1. File类的使用

1.1 File类

  • java.io.File 类:文件和文件目录路径的抽象表示形式,与平台无关。
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入 / 输出流。
  • 想要在 Java 程序中表示一个真实存在的文件或目录,那么必须有一个 File 对象,但是 Java 程序中的一个File 对象,可能没有一个真实存在的文件或目录。
  • File 对象可以作为参数传递给流的构造器。

1.2 File类的使用

  1. File 类的一个对象,代表一个文件或一个文件目录( 俗称:文件夹 )。
  2. File 类声明在 java.io 包下。
  3. File 类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容必须使用 IO 流来完成。
  4. 后续 File 类的对象常会作为参数传递到流的构造器中,指明读取或写入的 " 终点 "。

1.3 File的实例化

(1)如何创建 file 类的实例
        File(String filePath):以 filePath 为路径创建 File 对象,可以是绝对路径或者相对路径
        File(String parentPath,String childPath): 以 parentPath 为父路径,childPath为子路径创建 File 对象。
        File(File parentFile,String childPath):根据一个父 File 对象和子文件路径创建 File 对象
(2)相对路径:相较于某个路径下,指明的路径。
        绝对路径:包含盘符在内的文件或文件目录的路径。
(3)路径分隔符
        windows:\\\\
        unix:/
(4)Java 程序支持跨平台运行,因此路径分隔符要慎用。
(5)为了解决这个隐患,File 类提供了一个常量:

public static final String separator。
//根据操作系统,动态的提供分隔符。
File file1= new File("d:\\\\Work\\\\info.txt");
File file2= new File("d:"+ File.separator+ "Work"+ File.separator+ "info.txt");
File file3= new File("d:/Wo

1.4 File类的常用方法1

public String getAbsolutePath():获取绝对路径
public String getPath():获取路径
public String getName():获取名称
public String getParent():获取上层文件目录路径。若无,返回 null
public long length():获取文件长度(即:字节数)。不能获取目录的长度。
public long lastModified():获取最后一次的修改时间,毫秒值
如下的两个方法适用于文件目录:
public String[] list():获取指定目录下的所有文件或者文件目录的名称数组
public File[] listFiles():获取指定目录下的所有文件或者文件目录的 File 数组
public boolean renameTo(File dest): 把文件重命名为指定的文件

案例:文件重命名

    /**
     * File 类的重命名功能
     * public boolean renameTo(File dest): 把文件重命名为指定的文件
     路径 比如:file1.renameTo(file2) 为例:
     * 要想保证返回 true, 需要 file1 在硬盘中是存在的,且 file2 不能在
     硬盘中存在。
     */
    @Test
    public void test1()
        File file = new File("E:\\\\work_space\\\\iostream\\\\fileFolder\\\\hello.txt");
        File file2 = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello2.txt");
        boolean renameTo = file.renameTo(file2);
        System.out.println(renameTo);
    

1.5 File类的常用方法2

public boolean isDirectory():判断是否是文件目录
public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏

File初始化说明:

  • 当硬盘中真有一个真实的文件或目录存在时,创建 File 对象时,各个属性会显式赋值。
  • 当硬盘中没有真实的文件或目录对应时,那么创建对象时,除了指定的目录和路径之外,其他的属性都是取成员变量的默认值。

创建硬盘中对应的文件或文件目录;

public boolean createNewFile():创建文件。若文件存在,则不创建,返回 false;
public boolean mkdir():创建文件目录。如果此文件目录存在,就不创建了。
                        如果此文件目录的上层目录不存在,也不创建。
public boolean mkdirs():创建文件目录。如果上层文件目录不存在,一并创建。

删除磁盘中的文件或文件目录

public boolean delete():删除文件或者文件夹。

删除注意事项:Java 中的删除不走回收站。

案例:文件不存在就创建,文件存在才能删除

    @Test
    public void test2() throws IOException 
        File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello.txt");
        if(!file.exists())
            // 文件不存在就创建
            file.createNewFile();
            System.out.println("创建成功");
        else
            // 文件存在,可进行删除
            file.delete();
            System.out.println("删除成功");
        
    

2. IO流原理与流的分类

2.1 IO流原理

I/O 是 Input/Output 的缩写,I/O 技术是非常实用的技术,用于处理设备之间的数据传输。如读 / 写文件,网络通讯等。
Java 程 序 中, 对 于 数 据 的 输 入 / 输 出 操 作 以“ 流(stream)”的方式进行。
java.io 包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
输入 input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
输出 output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

2.2 流的分类

操作数据单位不同分为:字节流 (8 bit),字符流 (16 bit)
数据流的流向不同分为:输入流,输出流
流的角色的不同分为:节点流,处理流
Java 的 IO 流共涉及 40 多个类,实际上非常规则,都是从如下 4 个抽象基类派生的。
由这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

抽象基类字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

2.3 IO流体系结构

 2.3 节点流(或文件流)

2.3.1 FileReader读入数据的基本操作

抽象基类节点流(或文件流)
缓冲流(处理流的一种)
InputStreamFileInputStream (read(byte[] buffer))BufferedInputStream (read(byte[] buffer))
OutputStreamFileOutputStream (write(byte[] buffer,0,len)
BufferedOutputStream (write(byte[] buffer,0,len) / flush()
ReaderFileReader (read(char[] cbuf))BufferedReader (read(char[] cbuf) / readLine())
WriterFileWriter (write(char[] cbuf,0,len)BufferedWriter (write(char[] cbuf,0,len) / flush()

读取文件【四个步骤】

建立一个流对象,将已存在的一个文件加载进流。
        FileReaderfr= new FileReader(new File(“Test.txt”));
创建一个临时存放数据的数组。
        char[] ch= new char[1024];
调用流对象的读取方法将流中的数据读入到数组中。
        fr.read(ch);
关闭资源。
        fr.close();
将 hello.txt 文件内容读入程序中,并输出到控制台

说明点:
        1. read() 的理解:返回读入的一个字符。如果达到文件末尾,返回 -1。
        2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally 处理。
        3. 读入的文件一定要存在,否则就会报 FileNotFoundException。

案例:FileReader读入数据

    @Test
    public void test2()
        FileReader fileReader = null;

        try 
            // 实例化File对象,指明要操作的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello2.txt");
            // 提供具体的流
            fileReader = new FileReader(file);

            int data;
            while ((fileReader.read()) != -1)
                data = fileReader.read();
                System.out.println((char)data);
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            try 
                if (fileReader != null)
                    fileReader.close();
                
            catch (IOException e)
                e.printStackTrace();
            
        
    

2.3.2 FileReader中使用 read(char[] cbuf) 读取数据

    /**
     *   read(char[] cbuf):char[] 进行读取
     */
    @Test
    public void test3()
        FileReader fileReader = null;

        try 
            // 实例化File对象,指明要操作的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello2.txt");
            // 提供具体的流
            fileReader = new FileReader(file);

            char[] cbuf = new char[3];
            int len;
            while ((len = fileReader.read(cbuf)) != -1)
                String str = new String(cbuf,0,len);
                System.out.println(str);
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (fileReader != null)
                try 
                    fileReader.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        
    

2.3.3 FileWriter写出数据的操作

写入文件

创建流对象,建立数据存放文件。
        FileWriterfw= new FileWriter(new File(“Test.txt”));
调用流对象的写入方法,将数据写入流。
        fw.write(“atguigu-songhongkang”);
关闭流资源,并将流中的数据清空到文件中。
        fw.close();

说明:
(1) 输出操作,对应的 File 可以不存在的。并不会报异常。
(2)File 对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
        File 对应的硬盘中的文件如果存在:
                如果流使用的构造器是:FileWriter(file,false) / FileWriter(file): 对原有文件的覆盖。
                如果流使用的构造器是:FileWriter(file,true): 不会对原有文件覆盖,而是在原有文件基础上追加内容。

案例:

    /**
     *   fw.write(data):FileWriter 写出数据的操
     */
    @Test
    public void test4()
        FileWriter fw = null;

        try 
            // 实例化File对象,指明要写出到的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello.txt");
            // 提供FileWriter的对象,用于数据的写出
            fw = new FileWriter(file);
            // 写出的操作
            fw.write("io stream -- FileWriter");
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (fw != null)
                try 
                    fw.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        
    

2.3.4 使用 FileReader 和 FileWriter 实现文本文件的复制

    /**
     *   使用 FileReader 和 FileWriter 实现文本文件的复制
     */
    @Test
    public void test5()
        FileWriter fw = null;
        FileReader fr = null;
        try 
            // 实例化File对象,指明要写出到的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello2.txt");
            File file2 = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello3.txt");

            // 创建输入流和输出流的对象
            fr = new FileReader(file);
            fw = new FileWriter(file2);

            // 数据的读入和写出操作
            char[] chars = new char[3];
            int len;
            while ((len = fr.read(chars)) != -1)
                fw.write(chars,0,len);
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (fw != null)
                try 
                    fw.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
            if (fr != null)
                try 
                    fr.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        
    

2.3.5 不建议使用 FileInputStream 读取文本文件

原因:使用字节流 FileInputStream 处理文本文件,可能出现乱码。

测试 FileInputStream 和 FileOutputStream 的使用。
结论:

  1. 对于文本文件 (.txt,.java,.c,.cpp),使用字符流处理;
  2. 对于非文本文件 (.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理。
        FileInputStream fi = null;
        try 
            // 实例化File对象,指明要写出到的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello3.txt");

            // 创建输入流和输出流的对象
            fi = new FileInputStream(file);

            // 数据的读入和写出操作
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fi.read(bytes)) != -1)
                String str = new String(bytes,0,len);
                System.out.println(str);
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (fi != null)
                try 
                    fi.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        

2.3.6 使用 FileInputStream 和 FileOutputStream 读写非文本文件

实现对图片的复制操作

    /**
     *   使用 FileReader 和 FileWriter 实现文本文件的复制
     */
    @Test
    public void test7()
        FileInputStream fi = null;
        FileOutputStream fo = null;
        try 
            // 实例化File对象,指明要写出到的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hzw.webp");
            File file2 = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hzw2.webp");

            // 创建输入流和输出流的对象
            fi = new FileInputStream(file);
            fo = new FileOutputStream(file2);

            // 数据的读入和写出操作
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fi.read(buffer)) != -1)
                fo.write(buffer,0,len);
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (fi != null)
                try 
                    fi.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
            if (fo != null)
                try 
                    fo.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        
    

2.4 缓冲流

为了提高数据读写的速度,Java API 提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,缺省使用 8192 个字节 (8Kb) 的缓冲区。

         缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为:
                BufferedInputStream 和 BufferedOutputStream;
                BufferedReader 和 BufferedWriter;
        当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区。
        当使用 BufferedInputStream 读取字节文件时,BufferedInputStream 会一次性从文件中读取 8192 个 (8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个 8192 个字节数组。
        向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream 才会把缓冲区中的数据一次性写到文件里。使用方法 flush() 可以强制将缓冲区的内容全部写入输出流。
        关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。
        flush() 方法的使用:手动将 buffer 中内容写入文件。
        如果是带缓冲区的流对象的 close() 方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。

2.4.1 缓冲流(字节型)实现非文本文件的复制

处理流之一:缓冲流的使用
(1)缓冲流:
        BufferedInputStream
        BufferedOutputStream
        BufferedReader
        BufferedWriter
(2)作用:提供流的读取、写入的速度;
提高读写速度的原因:内部提供了一个缓冲区。
(3)处理流,就是“套接”在已有的流的基础上。

 案例:基于缓冲流实现读写

    @Test
    public void test1()
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try 
            // 实例化File对象,指明要写出到的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hzw2.webp");
            File file2 = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hzw3.webp");

            // 创建输入流和输出流的对象
            FileInputStream fi = new FileInputStream(file);
            FileOutputStream fo = new FileOutputStream(file2);

            // 创建缓冲流
            bis = new BufferedInputStream(fi);
            bos = new BufferedOutputStream(fo);

            // 数据的读入和写出操作
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1)
                bos.write(buffer,0,len);
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            // 要求:先关闭外层的流,再关闭内层的流
            if (bos != null)
                try 
                    bos.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
            if (bis != null)
                try 
                    bis.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
            // 说明:关闭外层流的同时,内层流也会自动的进行关闭。
            // 关于内层流的关闭,我们可以省略 .
        
    

缓冲流与节点流读写速度说明:基于缓冲流的读写速度要比节点流快很多。

2.4.2 缓冲流(字符型)实现文本文件的复制

    // 使用 BufferedReader 和 BufferedWriter 实现文本文件的复制
    @Test
    public void test1()
        BufferedReader br = null;
        BufferedWriter bw = null;
        try 
            // 实例化File对象,指明要写出到的文件
            File file = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello2.txt");
            File file2 = new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello4.txt");

            // 创建输入流和输出流的对象
            br = new BufferedReader(new FileReader(file));
            bw = new BufferedWriter(new FileWriter(file2));

            // 读写操作
            // 方式一:使用 char[] 数组
            // char[] cbuf = new char[1024];
            // int len;
            // while ((len = br.read(cbuf)) != -1) 
            // bw.write(cbuf, 0, len);
            // bw.flush();

            // 方式二:使用String
            // 数据的读入和写出操作
            String data;
            while ((data = br.readLine()) != null)
                // 方法一:
                // bw.write(data + "\\n");//data 中不包含换行符
                // 方法二:
                bw.write(data); // data 中不包含换行符
                bw.newLine(); // 提供换行的操作
            
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            // 要求:先关闭外层的流,再关闭内层的流
            if (br != null)
                try 
                    br.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
            if (bw != null)
                try 
                    bw.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
            // 说明:关闭外层流的同时,内层流也会自动的进行关闭。
            // 关于内层流的关闭,我们可以省略 .
        
    

2.5 转换流

2.5.1 转换流概述与 InputStreamReader 的使用。

转换流提供了在字节流和字符流之间的转换。

Java API 提供了两个转换流:
(1) InputStreamReader:将 InputStream 转换为 Reader
        实现将字节的输入流按指定字符集转换为字符的输入流。
        需要和 InputStream“套接”。
        构造器:
                public InputStreamReader(InputStreamin)
                public InputSreamReader(InputStreamin,StringcharsetName)
                如:Reader isr= new InputStreamReader(System.in,”gbk”);
(2)OutputStreamWriter:将 Writer 转换为 OutputStream
        实现将字符的输出流按指定字符集转换为字节的输出流。
        需要和 OutputStream“套接”。
        构造器:
                public OutputStreamWriter(OutputStreamout)
                public OutputSreamWriter(OutputStreamout,StringcharsetName)
字节流中的数据都是字符时,转成字符流操作更高效。
很多时候我们使用转换流来处理文件乱码问题,实现编码和解码的功能。

 2.5.2 转换流的使用

(1)转换流:属于字符流;
        InputStreamReader:将一个字节的输入流转换为字符的输入流。
        OutputStreamWriter:将一个字符的输出流转换为字节的输出流。
(2)作用:提供字节流与字符流之间的转换。
(3)解码:字节、字节数组 ---> 字符数组、字符串。
        编码:字符数组、字符串 ---> 字节、字节数组。
(4)字符集
ASCII:美国标准信息交换码。用一个字节的 7 位可以表示。
ISO8859-1:拉丁码表。欧洲码表。用一个字节的 8 位表示。
GB2312:中国的中文编码表。最多两个字节编码所有字符。
GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码。
Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符
                分配唯一的字符码。所有的文字都用两个字节来表示。
UTF-8:变长的编码方式,可用 1-4 个字节来表示一个字符。

Unicode 不完美,这里就有三个问题,一个是,我们已经知道,英文字母只用一个字节表示就够了,第二个问题是如何才能区别 Unicode 和ASCII ?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第三个,如果和 GBK 等双字节编码方式一样,用最高位是 1 或 0 表示两个字节和一个字节,就少了很多值无法用于表示字符,不够表示所有字符。
Unicode 在很长一段时间内无法推广,直到互联网的出现。

面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8 就是每次 8 个位传输数据,而 UTF-16 就是每次 16 个位。 这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

Unicode 只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储成什么样的字节流,取决于字符编码方案。
推荐的 Unicode 编码是 UTF-8 和 UTF-16。

    /**
     * 此时处理异常的话,仍然应该使用 try-catch-finally InputStreamReader
     * 的使用,实现字节的输入流到字符的输入流的转换
     * @throws FileNotFoundException
     */
    @Test
    public void test1() throws FileNotFoundException 
        FileInputStream fis = null;
        InputStreamReader isr = null;
        try 
            fis = new FileInputStream("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello3.txt");
            // 使用系统默认的字符集
            // 参数 2 指明了字符集,具体使用哪个字符集,取决于文件 hello3.txt 保存时使用的字符集
            isr = new InputStreamReader(fis,"UTF-8");
            char[] chars = new char[5];
            int len;
            while ((len = isr.read(chars)) != -1)
                String str = new String(chars,0,len);
                System.out.println(str);
            
         catch (UnsupportedEncodingException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (isr != null)
                try 
                    isr.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        

    

2.6 标准输入、输出流、打印流、数据流

2.6.1 标准输入、输出流

        System.in 和 System.out 分别代表了系统标准的输入和输出设备;
        默认输入设备是:键盘,输出设备是:显示器;
        System.in 的类型是 InputStream;
        System.out 的 类 型 是 PrintStream, 其 是 OutputStream的子类 FilterOutputStream 的子类;
        重定向:通过 System 类的 setIn,setOut 方法对默认设备进行改变。

public static void setIn(InputStreamin)
public static void setOut(PrintStreamout)

2.6.2 其他流的使用

  1. 标准的输入、输出流
  2. 打印流
  3. 数据流

(1)标准的输入、输出流

        System.in: 标准的输入流,默认从键盘输入;
        System.out: 标准的输出流,默认从控制台输出

        System 类的 setIn(InputStream is) / setOut(PrintStream ps) 方式重新指定输入和输出的流。

    @Test
    public void test2() throws FileNotFoundException 
       BufferedReader br = null;
       try 
           InputStreamReader isr = new InputStreamReader(System.in);
           br = new BufferedReader(isr);
           while (true)
               System.out.println("请输入字符串:");
               String data = br.readLine();
               if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data))
                   System.out.println("程序结束");
                   break;
               
               String upperCase = data.toUpperCase();
               System.out.println(upperCase);
           
       catch (IOException e)
           e.printStackTrace();
       finally 
           if (br != null)
               try 
                   br.close();
                catch (IOException e) 
                   e.printStackTrace();
               
           
       
    

2.6.3 打印流、数据流

打印流:实现将基本数据类型的数据格式转化为字符串输出;

打印流:PrintStream 和 PrintWriter;

  • 提供了一系列重载的 print() 和 println() 方法,用于多种数据类型的输出。
  • PrintStream 和 PrintWriter 的 输 出 不 会 抛 出IOException 异常。
  • PrintStream 和 PrintWriter 有自动 flush 功能
  • PrintStream 打印的所有字符都使用平台的默认

字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。

  • System.out 返回的是 PrintStream 的实例。

数据流:为了方便地操作 Java 语言的基本数据类型和 String 的数据,可以使用数据流。
数据流有两个类:( 用于读取和写出基本数据类型、String 类的数据)

  • DataInputStream 和 DataOutputStream
  • 分 别“ 套 接” 在 InputStream 和 OutputStream子类的流上。

DataInputStream 中的方法:

  • boolean readBoolean()
  • byte readByte()
  • char readChar()
  • float readFloat()
  • double readDouble()
  • short readShort()
  • long readLong()
  • int readInt()
  • String readUTF()
  • void readFully(byte[s] b

将上述的方法的 read 改为相应的 write 即可。

(1)打印流:PrintStream 和 PrintWriter。提供了一系列重载的 print() 和 println()

(2)数据流:

  • DataInputStream 和 DataOutputStream
  • 作用:用于读取或写出基本数据类型的变量或字符串。

案例:

(1)打印流

    @Test
    public void test()
        PrintStream ps = null;
        try 
            FileOutputStream fos = new FileOutputStream(new File("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello3.txt"));
            // 创建打印输出流,设置为自动刷新模式(写入换行符或字节‘\\n’时都会刷新输出缓冲区
            ps = new PrintStream(fos,true);
            if (ps != null)
                System.out.println(ps);
            

            for (int i = 0; i <= 255; i++) 
                System.out.println((char)i);
                if (i % 50 == 0)
                    System.out.println();
                
            
        catch (FileNotFoundException e)
            e.printStackTrace();
        finally 
            if (ps != null)
                ps.close();
            
        
    

(2)DataInputStream和DataOutputStream

    @Test
    public void test2()
        DataOutputStream dos = null;
        try 
            dos = new DataOutputStream(new FileOutputStream("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello3.txt"));
            dos.writeUTF("商大");
            dos.flush();// 刷新操作,将内存中的数据写入文件
            dos.writeInt(23);
            dos.writeBoolean(true);
            dos.flush();
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (dos != null)
                try 
                    dos.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

    /**
     * // 将文件中存储的基本数据类型变量和字符串读取到内存中,保存在
     * 变量中。
     * // 注意点:读取不同类型的数据的顺序要与当初写入文件时,保存的
     * 数据的顺序一致!
     */
    @Test
    public void test3()
        DataInputStream dis = null;
        try 
            dis = new DataInputStream(new FileInputStream("E:\\\\work_space\\\\IOStream\\\\fileFolder\\\\hello3.txt"));
            String name = dis.readUTF();
            int age = dis.readInt();
            boolean isMale = dis.readBoolean();
            System.out.println("name = " + name);
            System.out.println("age = " + age);
            System.out.println("isMale = " + isMale);
            dis.close();
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        

    

2.7 对象流

2.7.1 对象序列化机制的理解

ObjectInputStream OjbectOutputSteam。用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。

序列化:用 ObjectOutputStream 类保存基本类型数据或对象的机制。
反序列化:用 ObjectInputStream 类读取基本类型数据或对象的机制。

ObjectOutputStream ObjectInputStream 不能序列化 static 和 transient修饰的成员变量。
对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。

序列化的好处在于可将任何实现了 Serializable 接口的对象转化为字节数据,使其在保存和传输时可被还原。

序列化是 RMI(Remote Method Invoke - 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础。因此序列化机制是 JavaEE 平台的基础。

如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出 NotSerializableException 异常。

  • Serializable
  • Externalizable

2.7.2 对象流序列化与反序列化字符串

对象流的使用:

  1. ObjectInputStream 和 ObjectOutputStream
  2.  作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。
    /**
     * 序列化过程:将内存中的 java 对象保存到磁盘中或通过网络传输出去
     *              使用 ObjectOutputStream 实现
     */
    @Test
    public void test1()
        ObjectOutputStream oos = null;

        try 
            // 创建流
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            // 创建对象
            oos.writeObject(new String("五菱宏光"));
            // 刷新
            oos.flush();
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
        finally 
            if (oos != null)
                try 
                    oos.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

    /**
     * 反序列化:将磁盘文件中的对象还原为内存中的一个 java 对象 
     *           使用 ObjectInputStream 来实现
     */
    @Test
    public void test2()
        ObjectInputStream ois = null;
        try
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
            Object obj = ois.readObject();
            String str = (String)obj;
            System.out.println(str);
         catch (FileNotFoundException e) 
            e.printStackTrace();
         catch (IOException e) 
            e.printStackTrace();
         catch (ClassNotFoundException e) 
            e.printStackTrace();
        finally 
            if (ois != null)
                try 
                    ois.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

2.7.3 自定义类实现序列化与反序列化操作

若某个类实现了 Serializable 接口,该类的对象就是可序列化的:

  • 创建一个 ObjectOutputStream。
  • 调用 ObjectOutputStream 对象的 writeObject( 对象 )方法输出可序列化对象。
  • 注意写出一次,操作 flush() 一次。

反序列化

  • 创建一个 ObjectInputStream 对象调用 readObject()方法读取流中的对象。

强调:如果某个类的属性不是基本数据类型或 String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field 的类也不能序列化。

对象流的使用:
(1)ObjectInputStream 和 ObjectOutputStream。
(2)作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把 Java 中的对象写入到数据源中,也能把对象从数据源中还原回来。
(3)要想一个 java 对象是可序列化的,需要满足相应的要求。见 Person.java。
(4)序列化机制:
        对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流,从而允许把这种。
        二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
        当其它程序获取了这种二进制流,就可以恢复成原来的 Java 对象。

案例

/**
 * @author wushaopei
 * @create 2022-07-07 21:58
 */
public class Person implements Serializable 

    public static final long serialVersionUID = 475463534532L;
    private String name;
    private int age;
    public Person() 
    
    public Person(String name, int age) 
        this.name = name;
        this.age = age;
    
    @Override
    public String toString() 
        return "Person" + "name='" + name + '\\'' + ", age=" +
                age + '';
    
    public String getName() 
        return name;
    
    public void setName(String name) 
        this.name = name;
    
    public int getAge() 
        return age;
    
    public void setAge(int age) 
        this.age = age;
    

序列化与反序列化

package com.io.file;

import com.io.file.entity.Person;
import org.junit.Test;

import java.io.*;

/**
 * @author wushaopei
 * @create 2022-07-07 21:59
 */
public class ObjectTest 

    @Test
    public void serial()
        ObjectOutputStream oos = null;
        try 
            // 创建流
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            // 创建对象
            oos.writeObject(new String("创建新对象"));
            // 刷新
            oos.flush();
            // 继续写
            oos.writeObject(new Person("java",44));
            oos.flush();
        catch (IOException e)
            e.printStackTrace();
        finally 
            if (oos != null)
                try 
                    oos.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

    @Test
    public void deserial()
        ObjectInputStream ois = null;
        try 
            // 创建流
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
            Object o = ois.readObject();
            String s = (String)o;
            Person p = (Person) ois.readObject();
            System.out.println(s);
            System.out.println(p);
        catch (IOException e)
            e.printStackTrace();
         catch (ClassNotFoundException e) 
            e.printStackTrace();
         finally 
            if (ois != null)
                try 
                    ois.close();
                catch (IOException e)
                    e.printStackTrace();
                
            
        
    

2.7.4 serialVersionUID 的理解

凡是实现 Serializable 接口的类都有一个表示序列化版本标识符的静态变量:

  • private static final long serialVersionUID;
  • serialVersionUID 用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
  • 如果类没有显示定义这个静态常量,它的值是Java 运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议,显式声明。

        简单来说,Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列
化版本不一致的异常。(InvalidCastException)

Person 需要满足如下的要求,方可序列化:

  1.  需要实现接口:Serializable;
  2.  当前类提供一个全局常量:serialVersionUID;
  3.  除了当前 Person 类需要实现 Serializable 接口之外,还必须保证其内部所有属性。

        也必须是可序列化的。(默认情况下,基本数据类型可序列化)

public static final long serialVersionUID = 475463534532L;

2.7.5 随机存取文件流

RandomAccessFile 声明在 java.io 包下,但直接继承于 java.lang.Object 类。 并 且 它 实 现 了 DataInput、DataOutput 这两个接口,也就意味着这个类既可以读也可以写。

RandomAccessFile 类支持“随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件:
·支持只访问文件的部分内容·可以向已存在的文件后追加内容RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile 类对象可以自由移动记录指针:

  • long getFilePointer():获取文件记录指针的当前位置。
  • void seek(long pos):将文件记录指针定位到 pos 位置。

构造器:

  • public RandomAccessFile(Filefile, Stringmode)
  • public RandomAccessFile(Stringname, Stringmode)

创建 RandomAccessFile 类实例需要指定一个 mode 参数,
该参数指定 RandomAccessFile 的访问模式:

  • r : 以只读方式打开
  • rw:打开以便读取和写入
  • rwd: 打开以便读取和写入;同步文件内容的更新
  • rws: 打开以便读取和写入;同步文件内容和元数据

的更新。
如果模式为只读 r。则不会创建文件,而是会去读取一个
已经存在的文件,如果读取的文件不存在则会出现异常。
如果模式为 rw 读写。如果文件不存在则会去创建文件,
如果存在则不会创建。

2.8.1 RandomAccessFile 实现数据的读写操作

RandomAccessFile 的使用:

  1. RandomAccessFile 直 接 继 承 于 java.lang.Object 类, 实 现 了 DataInput和 DataOutput 接口。
  2. RandomAccessFile 既可以作为一个输入流,又可以作为一个输出流。
  3.  如果 RandomAccessFile 作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
  4. 可以通过相关的操作,实现 RandomAccessFile“插入”数据的效果。

案例:

demo1--读取图片

        RandomAccessFile raf1 = null;
        RandomAccessFile raf2 = null;
        try 
            raf1 = new RandomAccessFile(new File("xx.jpg"), "r");
                    raf2 = new RandomAccessFile(new File("xx.jpg"), "rw");
            byte[] buffer = new byte[1024];
            int len;
            while ((len = raf1.read(buffer)) != -1) 
                raf2.write(buffer, 0, len);
            
         catch (IOException e) 
            e.printStackTrace();
         finally 
            if (raf1 != null) 
                try
                    raf1.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
            if (raf2 != null) 
                try
                    raf2.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        

demo2-读取txt文件

RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");
raf1.write("xyz".getBytes());
raf1.close();

demo3-插入数据

    @Test
    public void test3() throws IOException 
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt", "rw");
        raf1.seek(3); // 将指针调到角标为 3 的位置
        // 保存指针 3 后面的所有数据到 StringBuilder 中
        StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
        byte[] buffer = new byte[20];
        int len;
        while ((len = raf1.read(buffer)) != -1) 
            builder.append(new String(buffer, 0, len));
        
        // 调回指针,写入“xyz”
        raf1.seek(3);
        raf1.write("xyz".getBytes());
        // 将 StringBuilder 中的数据写入到文件中
        raf1.write(builder.toString().getBytes());
        raf1.close();
        // 思考:将 StringBuilder 替换为 ByteArrayOutputStream
    

2.9.1 NIO.2中Path、Paths、Files类的使用

Java NIO (New IO,Non-Blocking IO) 是 从 Java 1.4版本开始引入的一套新的 IO API,可以替代标准的 JavaIO API。NIO 与原来的 IO 有同样的作用和目的,但是使用的方式完全不同,NIO 支持面向缓冲区的 (IO 是面向流的 )、基于通道的 IO 操作。NIO 将以更加高效的方式进行文件的读写操作。
Java API 中提供了两套 NIO,一套是针对标准输入输出 NIO,另一套就是网络编程 NIO。


随着 JDK 7 的发布,Java 对 NIO 进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO 已经成为文件处理中越来越重要的部分。
早期的 Java 只提供了一个 File 类来访问文件系统,但File 类的功能比较有限,所提供的方法性能也不高。而且,大多数方法在出错时仅返回失败,并不会提供异常信息。
NIO. 2 为了弥补这种不足,引入了 Path 接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置。Path 可以看成是 File 类的升级版本,实际引用的资源也可以不存在。

(1)Path接口

  • String toString():返回调用 Path 对象的字符串表示形式;
  • boolean startsWith(String path): 判断是否以 path 路径开始;
  • boolean endsWith(String path):判断是否以 path 路径结束;
  • boolean isAbsolute():判断是否是绝对路径;
  • Path getParent():返回 Path 对象包含整个路径,不包含 Path 对象指定的文件路径;
  • Path getRoot():返回调用 Path 对象的根路径;
  • Path getFileName():返回与调用 Path 对象关联的文件名;
  • intgetNameCount():返回 Path 根目录后面元素的数量;
  • Path getName(int idx):返回指定索引位置 idx 的路径名称;
  • Path toAbsolutePath():作为绝对路径返回调用 Path 对象;
  • Path resolve(Path p):合并两个路径, 返回台并后的路径对应的 Path 对象;
  • File toFile():将 Path 转化为 File 类的对象。

(2)Files类

 Files 常用方法:用于判断

Files 常用方法:用于判断

  • boolean exist(Path path, LinkOption .. opts):判断文件是否存在
  • boolean isDirectory(Path path, LinkOption .. opts):判断是否是目录
  • boolean isRegularFile(Path path, LinkOption .. opts):判断是否是文件
  • boolean isHidden(Path path):判断是否是隐藏文件
  • boolean isReadable(Path path):判断文件是否可读
  • boolean isWritable(Path path):判断文件是否可写
  • boolean notExists(Path path, LinkOption .. opts):判断文件是否不存在

Files 常用方法 : 用于操作内容

  • SeekableByteChannel newByteChannel(Path path, OpenOption..how):获取与指定文件的连接,how 指定打开方式。
  • DirectoryStream<Path> newDirectoryStream(Path path):打开 path 指定的目录。
  • InputStream newlnputStream(Path path, OpenOption..how):获取InputStream 对象。
  • OutputStream newOutputStream(Path path, OpenOptin...how):获取OutputStream 对象。

javano.file.Files 用于操作文件或目录的工具类
Files 常用方法:

  • Path copy(Path src, Path dest, CopyOption ... how):文件的复制;
  • Path createDirectory(Path path, FileAttribute<?> .... attr):创建一个目录;
  • Path createFile(Path path, FileAttribute<?> ... arr):创建一一个文件;
  • void delete(Path path):删除一个文件 / 目录,如果不存在,执行报错;
  • void deletelfExists(Path path):Path 对应的文件 / 月录如果存在,执行删除;
  • Path move(Path sre, Path dest, CopyOption.. .how):将 src 移动到 dest 位置;
  • long size(Path path):返回 path 指定文件的大小。

以上是关于Java - IO 流的主要内容,如果未能解决你的问题,请参考以下文章

JAVA基础-IO流

java 21 - 1 IO流中的字符流概述

Java中的IO流

IO流

JavaSE复习_11 IO流复习

基础知识之----------IO流