MENU

Java IO流笔记

June 14, 2020 • Read: 182 • Note,JavaCore

Java IO流

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

流的分类

操作数据单位:字节流、字符流

  • 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
  • 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理

数据的流向:输入流、输出流

  • 输入input 读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。
  • 输出output 将程序(内存)数据输出到磁盘、光盘等存储设备中。

流的角色:节点流、处理流

节点流:直接从数据源或目的地读写数据。

处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读x写功能。

常用的几个IO流结构

抽象基类节点流(或文件流)缓冲流(处理流的一种)
InputStreamFileInputStream (read(byte[] buffer))BufferedInputStream (read(byte[] buffer))
OutputSteamFileOutputStream (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()

对抽象基类的说明

抽象基类字节流字符流
输入流InputSteamReader
输出流OutputSteamWriter
  • 说明Java的lO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的。
  • 这四个类派生出来的子类名称都是以其父类名作为子类名后缀。

InputSteam&Reader

InputStream和Reader是所有输入流的基类。

  • InputStream(典型实现:FileInputStream
  • Reader(典型实现:FileReader

程序中打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源

FileInputStream从文件系统中的某个文件中获得输入字节。FileInputStream用于读取非文本数据之类的原始字节流。要读取字符流,需要使用 FileReader


InputSteam:

  • int read()

从输入流中读取数据的下一个字节。返回0到255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值-1。

  • int read(byte[] b)

从此输入流中将最多b.length个字节的数据读入一个byte数组中。如果因为已经到达流末尾而没有可用的字节,则返回值-1.否则以整数形式返回实际读取的字节数。

  • int read(byte[] b,int off,int len)

将输入流中最多len个数据字节读入byte数组。尝试读取len个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。如果因为流位于文件末尾而没有可用的字节,则返回值-1。

  • public void close throws IOException

关闭此输入流并释放与该流关联的所有系统资源。

Reader:

  • int read()

读取单个字符。作为整数读取的字符,范围在0到65535之间(0x00-0xffff)(2个字节的 Unicode码),如果已到达流的末尾,则返回-1。

  • int read(char[] cbuf)

将字符读入数组。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。

  • int read(char[] cbuf,int off,int len)

将字符读入数组的某一部分。存到数组cbuf中,从off处开始存储,最多读len个字符。如果已到达流的末尾,则返回-1。否则返回本次读取的字符数。

  • public void close throws IOException

关闭此输入流并释放与该流关联的所有系统资源

OutputSteam&Writer

Writer直接以字符作为操作单位,所以可以用字符串来替换字符数组,即以String对象作为参数。

FileOutputStream从文件系统中的某个文件中获得输出字节。FileOutputstream用于写出非文本数据之类的原始字节流。要写出字符流,需要使用 FileWriter

OutputStream:

  • void write(int b)

将指定的字节写入此输出流。 write的常规协定是:向输出流写入一个字节。要写入的字节是参数b的八个低位。b的24个高位将被忽略。即写入0~255范围的

  • void write(byte[] b)

将b.length个字节从指定的byte数组写入此输出流。write(b)的常规协定是:应该与调用wite(b,0,b.length)的效果完全相同。

  • void write(byte[] b,int off,int len)

将指定byte数组中从偏移量off开始的len个字节写入此输出流。

  • public void flush()throws IOException

刷新此输出流并强制写出所有缓冲的输出字节,调用此方法指示应将这些字节立即写入它们预期的目标。

  • public void close throws IOException

关闭此输岀流并释放与该流关联的所有系统资源。

Writer:

  • void write(int c)

写入单个字符。要写入的字符包含在给定整数值的16个低位中,16高位被忽略。即写入0到65535之间的 Unicode码。

  • void write(char[] cbuf)

写入字符数组

  • void write(char[] cbuf,int off,int len)

写入字符数组的某一部分。从off开始,写入len个字符

  • void write(String str)

写入字符串。

  • void write(String str,int off,int len)

写入字符串的某一部分。

  • void flush()

刷新该流的缓冲,则立即将它们写入预期目标。

  • public void close throws IOException

关闭此输出流并释放与该流关联的所有系统资源

输入、输出标准化过程

输入过程

① 创建File类的对象,指明读取的数据的来源。(要求此文件一定要存在)

② 创建相应的输入流,将File类的对象作为参数,传入流的构造器中

③ 具体的读入过程:创建相应的byte[] 或 char[]。

④ 关闭流资源

说明:程序中出现的异常需要使用try-catch-finally处理。

输出过程

① 创建File类的对象,指明写出的数据的位置。(不要求此文件一定要存在)

② 创建相应的输出流,将File类的对象作为参数,传入流的构造器中

③ 具体的写出过程:write(char[]/byte[] buffer,0,len)

④ 关闭流资源

说明:程序中出现的异常需要使用try-catch-finally处理。

节点流(文件流)

文件的输入 FileReader

从文件中读取到内存(程序)中

步骤:

  1. 建立一个流对象,将已存在的一个文件加载进流 FileReader fr = new FileReader(new File("Test. txt"));
  2. 创建一个临时存放数据的数组 char[] ch = new char[1024];
  3. 调用流对象的读取方法将流中的数据读入到数组中。 fr.read(ch);
  4. 关闭资源。 fr.close();
public void testFileReader()  {
    FileReader fr = null;
    try {
        //1.File类的实例化
        File file = new File("hello.txt");

        //2.FileReader流的实例化
        fr = new FileReader(file);

        //3.读入的操作
        //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
        char[] cbuf = new char[5];
        int len;
        while((len = fr.read(cbuf)) != -1){
            String str = new String(cbuf,0,len);
            System.out.print(str);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(fr != null){
            //4.资源的关闭
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

注意

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

文件的输出 FileWriter

从内存(程序)到硬盘文件中

步骤:

  1. 创建流对象,建立数据存放文件 FileWriter fw = new FileWriter(new File("Test.txt"))
  2. 调用流对象的写入方法,将数据写入流 fw.write("HelloWord")
  3. 关闭流资源,并将流中的数据清空到文件中。 fw.close()
public void testFileWriter() {
    FileWriter fw = null;
    try {
        //1.提供File类的对象,指明写出到的文件
        File file = new File("hello1.txt");

        //2.提供FileWriter的对象,用于数据的写出
        fw = new FileWriter(file,false);

        //3.写出的操作
        fw.write("I have a dream!\n");
        fw.write("you need to have a dream!");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4.流资源的关闭
        if(fw != null){

            try {
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

FileInputSteam

FileOutputSteam

与字符流操作几乎一样。

案例:实现图片文件复制操作

import java.io.*;

/**
 * @author 乐心湖
 * @date 2020/6/13 15:45
 **/
public class Test {

    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            fileInputStream = new FileInputStream(new File("test2.jpg"));
            fileOutputStream = new FileOutputStream(new File("F:\\VM","test2.png"));
            //复制图片的过程
            byte[] bytes = new byte[1024];
            while (fileInputStream.read(bytes)!=-1){
                fileOutputStream.write(bytes);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注意

  • 定义路径时,可以用“/”或“\”。
  • 输出操作,对应的File可以不存在的。并不会报异常。
  • File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
  • File对应的硬盘中的文件如果存在:

    • 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file) 对原有文件的覆盖。
    • 如果流使用的构造器是:FileWriter(file,true) 不会对原有文件覆盖,而是在原有文件基础上追加内容。
  • 读取文件时,必须保证文件存在,否则会报异常。
  • 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
  • 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理

缓冲流(主要)

  • BufferedInputStream
  • BufferedOutputStream
  • BufferedReader
  • BufferedWriter

作用:提高流的读取和写入的速度

提高读写速度的原因:内部提供了一个缓冲区。默认情况下是8kb

当读取数据时,数据按块读入缓冲区,后面的读操作则直接访问缓冲区。

当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组。

向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。

使用flush()可以强制将缓冲区的内容全部写入输出流。

关闭流的顺序和打开流的顺序相反。只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流。

flush()方法的使用:手动将buffer中内容写入文件。

如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出。


使用BufferInputStreamBufferOutputStream实现非文本文件的复制

import java.io.*;

/**
 * @author 乐心湖
 * @date 2020/6/13 15:45
 **/
public class Test {

    public static void main(String[] args) {
        BufferedInputStream bufferedInputStream = null;
        BufferedOutputStream bufferedOutputStream = null;
        try {
            //创造节点流
            FileInputStream fileInputStream = new FileInputStream(new File("test2.jpg"));
            FileOutputStream fileOutputStream = new FileOutputStream(new File("F:\\VM", "test8.png"));
            //创造缓冲流
            bufferedInputStream = new BufferedInputStream(fileInputStream);
            bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            //开始复制
            byte[] bytes = new byte[1024];
            while ((len = bufferedInputStream.read(bytes)) != -1) {
                bufferedOutputStream.write(bytes,0,len);
            }
        } catch (IOException e) {
            
            e.printStackTrace();
        } finally {
            if (bufferedInputStream != null) {
                try {
                    bufferedInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedOutputStream != null) {
                try {
                    bufferedOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

使用BufferedReaderBufferedWriter实现文本文件的复制

import java.io.*;

/**
 * @author 乐心湖
 * @date 2020/6/13 15:45
 **/
public class Test {

    public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        BufferedWriter bufferedWriter = null;
        try {
            //创造节点流
            FileReader fileReader = new FileReader(new File("test2.txt"));
            FileWriter fileWriter = new FileWriter(new File("F:\\VM", "test2.txt"));
            //创造缓冲流
            bufferedReader = new BufferedReader(fileReader);
            bufferedWriter = new BufferedWriter(fileWriter);
            //开始复制
            String data;
            while ((data = bufferedReader.readLine()) != null) {
                bufferedWriter.write(data);
                bufferedWriter.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedWriter != null) {
                try {
                    bufferedWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

转换流

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

Java API提供了两个转换流:

  • InputstreamReader:将 Inputstream转换为Reader
  • OutputStreamWriter:将 Writer转换为OutputStream

字节流中的数据都是字符时,转成字符流操作更高效。

很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能。

InputStreamReader

InputStreamReader将一个字节的输入流转换为字符的输入流。

解码:字节、字节数组 ---> 字符数组、字符串

构造器:

  • public InputStreamReader(InputStream in)
  • public InputStreamReader(Inputstream in,String charsetName)//可以指定编码集

OutputStreamWriter

OutputStreamWriter将一个字符的输出流转换为字节的输出流。

编码:字符数组、字符串 ---> 字节、字节数组

构造器:

  • public OutputStreamWriter(OutputStream out)
  • public OutputStreamWriter(Outputstream out,String charsetName)//可以指定编码集

说明:文件编码的方式(比如:GBK),决定了解析时使用的字符集(也只能是GBK)。

/**
综合使用InputStreamReader和OutputStreamWriter
     */
@Test
public void test1() {
    InputStreamReader isr = null;
    OutputStreamWriter osw = null;
    try {
        //1.造文件、造流
        File file1 = new File("dbcp.txt");
        File file2 = new File("dbcp_gbk.txt");

        FileInputStream fis = new FileInputStream(file1);
        FileOutputStream fos = new FileOutputStream(file2);

        isr = new InputStreamReader(fis, "utf-8");
        osw = new OutputStreamWriter(fos, "gbk");

        //2.读写过程
        char[] chars = new char[20];
        int len;
        while ((len = isr.read(chars)) != -1){
            osw.write(chars,0,len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //3.关流
        if (isr != null){

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

        }
    }
}

编码集

常见的编码表

  • ASCII:美国标准信息交换码。用一个字节的7位可以表示。
  • ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
  • GB2312:中国的中文编码表。最多两个字节编码所有字符
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
  • Unicode:国际标准码,融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
  • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。

  • 面向传输的众多UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
  • Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。

编码应用

  • 编码:字符串-->字节数组
  • 解码:字节数组-->字符串
  • 转换流的编码应用

    • 可以将字符按指定编码格式存储
    • 可以对文本数据按指定编码格式来解读
    • 指定编码表的动作由构造器完成

使用要求:

客户端/浏览器端 <----> 后台(java,GO,Python,Node.js,php) <----> 数据库

要求前前后后使用的字符集都要统一:UTF-8 。

标准输入&输出流

System.in:标准的输入流,默认从键盘输入

System.out:标准的输出流,默认从控制台输出

主要方法

System类的setIn(InputStream is) 方式重新指定输入的流

System类的setOut(PrintStream ps)方式重新指定输出的流。

练习

从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,

直至当输入“e”或者“exit”时,退出程序。

设计思路

方法一:使用Scanner实现,调用next()返回一个字符串

方法二:使用System.in实现。System.in ---> 转换流 ---> BufferedReader的readLine()

import java.io.*;
import java.util.Scanner;

/**
 * @author 乐心湖
 * @date 2020/6/13 15:45
 **/
public class Test {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while(true){
            String data = sc.nextLine();
            if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)){
                System.out.println("程序结束");
                break;
            }
            System.out.println(data.toUpperCase());
        }
    }
}

或者

public static void main(String[] args) {
    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();
            }

        }
    }
}

打印流

PrintStreamPrintWriter

说明:

  • 提供了一系列重载的print()和println()方法,用于多种数据类型的输出
  • System.out返回的是PrintStream的实例

对象流

ObjectInputStreamObjectOutputStream

  • ObjectOutputStream 内存中的对象--->存储中的文件、通过网络传输出去:序列化过程
  • ObjectInputStream 存储中的文件、通过网络接收过来 --->内存中的对象:反序列化过程

对象的序列化

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

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

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

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


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

  • private static final long serialVersionUID
  • serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容
  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议显式声明。
  • 简单来说,Java的序列化机制是通过在运行时判断类的serialversionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialversionUID与本地相应实体类的serialversionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)

实现序列化的对象所属的类需要满足

  1. 需要实现接口:Serializable(标识接口)
  2. 当前类提供一个全局常量:serialVersionUID(序列版本号)
  3. 除了当前Person类需要实现Serializable接口之外,还必须保证其内部所属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)

补充:ObjectOutputStreamObjectInputStream不能序列化statictransient修饰的成员变量

序列化代码实现

序列化:将对象写入磁盘或进行网络传输

要求被序列化对象必须实现序列化

@Test
public void testObjectOutputStream(){
    ObjectOutputStream oos = null;

    try {
        //1.创建对象,创建流
        oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
        //2.操作流
        oos.writeObject(new String("我爱北京天安门"));
        oos.flush();//刷新操作

        oos.writeObject(new Person("王铭",23));
        oos.flush();

        oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
        oos.flush();

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(oos != null){
            //3.关闭流
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }

}

反序列化代码实现

反序列化:将磁盘的对象数据源读出

@Test
public void testObjectInputStream(){
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream("object.dat"));

        Object obj = ois.readObject();
        String str = (String) obj;

        Person p = (Person) ois.readObject();
        Person p1 = (Person) ois.readObject();

        System.out.println(str);
        System.out.println(p);
        System.out.println(p1);

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

        }
    }
}

本站所有未注明转载的文章均为原创,并采用CC BY-NV-SA 4.0 授权协议,转载请注明来源。

Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment