Java笔记:IO流

IO概述

数据的传输,可以看作一种数据的流动,按照流动的放心,以内存为基准,分为输入input和输出output,即流向内存是输入流,流出内存是输出流

java中I/O操作,主要是指使用java.io包下的内容,进行输入输出操作,输入也被叫做读取数据,输出也被叫做写出数据

IO分类

根据数据的流向分为:输入流和输出流

  • 输入流:把数据从其他设备上读取到内存的流
  • 输出流:把数据从内存中写出到其他设备上的流

根据数据的类型分为:字节流和字符流

  • 字节流:以字节为单位,读写数据的流
  • 字符流:以字符为单位,读写数据的流

顶级父类

输入流 输出流
字节流 字节输入流InputStream 字节输出流OutputStream
字符流 字符输入流Reader 字符输出流Writer

分类概述

OutputStream:输出流的抽象类
FileOutputStream:字节输出流
ObjectOutputStream:序列化流
FilterOutputStream :自带过滤器的输出流
———- BufferedOutputStream:字节缓冲输出流
———- PrintStream:打印流
InputStream:输入流的抽象类
FileInputStream:字节输入流
ObjectInputStream:反序列化流
FilterInputStream:自带过滤器的输入流 —–> BufferedInputStream:字节缓冲输入流
Writer:字符输出流的抽象类
BufferedWriter:字符缓冲输出流
OutputStreamWriter:转换编码的字符输出流 —–> FileWriter :字符输出流
Reader:字符输入流的抽象类
BufferedReader:字符缓冲输入流
InputStreamReader:转换编码的字符输入流 —–> FileReader:字符输入流

字节流

一切文本数据在存储时,都是以二进制数字的形式保存,都是一个一个的字节,在传输时也一样如此,所以,字节流可以传输任意文件数据,在操作流的时候,要注意无论使用任何流对象,底层传输的始终为二进制数据

字节输出流【OutputStream】

java.io.OutputStream 抽象类是所有字节输出流的超类,将指定的字节信息写出到目的地,它定义了字节输出流的基本共性功能方法

  • public void close() :关闭此输出流并释放与此流相关联的所有系统资源
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
  • public void write(byte[] b) :将b.length字节从指定的字节数组写入此输出流
  • public void write(byte[] b,int off,int len) :从指定的字节数组写入len字节,从偏移量off开始输出到此输出流
  • public abstract void write(int b) :将指定的字节输出流

close方法,当完成流的操作时,必须调用此方法,释放系统资源

FileOutputStream类

java.io.FileOutputStream类是文件输出流,用于将数据写出到文件
构造方法

  • public FileOutputStream(File file) :创建文件输出流以写入由指定的File对象表示的文件
  • public FileOutputStream(String name) :创建文件输出流以指定的名称写入文件
    当创建一个流对象的时候,必须传入一个文件路径,若在该路径下不存在这个文件,便会创建该文件,如果有这个文件会清空这个文件的数据
    代码:
public class FileOutputStreamConstructor throws IOException{
    public static void main(String[] args){
        //使用File对象创建流对象
        File file = new File("a.txt");
        FileOutputStream fos = new FileOutputStream(file);
        //使用文件名称创建流对象
        FileOutputStream fos1 = new FileOutputStream("b.txt");
    }
}

写出字节数据

1.写出字节:write(int b)方法,每次可以写出一个字节数据:
public class FOSWrite{
    public static void main(String[] args) throws IOException{
        //使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //写出数据
        fos.write(97);
        fos.write(97);
        fos.write(97);
        //关闭资源
        fos.close();
        //输出abc
    }
}

虽然参数为int类型四个字节,但是只会保留一个字节的信息写出
流操作完毕后必须调用close方法释放系统资源
2.写出字节数组:write(byte[] b) 每次可以写出数组中的数据,代码:

public class FOSwrite{
    public static void main(Srting[] args) throws IOException{
        //使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //字符串转换为字节数组
        byte[] b = "张三丰".getBytes();
        //写出字节数组数据
        fos.write(b);
        //关闭资源
        fos.close();
        //输出张三丰
    }
}
3.写出指定长度字节数组:write(byte[] b,int off,int len),每次写出从off索引开始len个字节,代码:
public class FOSwrite{
    public static void main(Srting[] args) throws IOException{
        //使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt");
        //字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        //写出从索引2开始,2个字节长度
        fos.write(b,2,2);
        //关闭资源
        fos.close();
        //输出cd
    }
}

数据追加续写

以上代码每次程序运行都会创建输出流对象,清空目标文件中的数据,其实还有保留目标文件中的数据,添加新数据的方法
  • public FileOutputStream(File file,boolean append) :创建文件输出流以写入由指定的File对象表示的文件
  • public FileOutputStream(String name,boolean append) :创建文件输出流以以指定的名称写入文件

这两个构造方法参数中都需要传入一个布尔值,true表示追加数据,flase表示清空原有数据,这样创建的输出流对象就可以指定是否追加续写了,代码:

public class FOSwrite{
    public static void main(Srting[] args) throws IOException{
        //使用文件名称创建流对象
        FileOutputStream fos = new FileOutputStream("fos.txt"true);
        //字符串转换为字节数组
        byte[] b = "abcde".getBytes();
        fos.write(b);
        //关闭资源
        fos.close();
        //输出cdabcde
    }
}

写出换行

在windows系统中换行符号是\r\n,可以指定追加续写

  • 回车符\r和换行符\n
    回车符:回到一行的开头
    换行符:下一行
  • 系统中的换行
    windows系统里,每行结尾是回车加换行即\r\n
    Linux系统里每行结尾只有换行即\n
    Mac系统里,每行结尾是回车即\r,从Mac OS X开始于Linux统一

字节输入流【InputStream】

java.io.InputStream 抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中,它定义了字节输入流的基本共性功能方法

  • public void close() :关闭此输入流并释放与此流相关联的所有系统资源
  • public abstract int read() :从输入流读取数据的下一个字节
  • public int read(byte[] b) :从输入流中读取一些字节数,并将它们存储到字节数组b中
    close方法,当完成流的操作时,必须调用此方法,释放系统资源

FileInputStream类

java.io.FileInputStream 类是文件输入流类,从文件中读取字节 **构造方法** - `public FileInputStream(File file)` :通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的File对象file命名 - `public FileInputStream(String name)` :通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名 当创建一个流对象时,必须传入一个文件路径,此路径下没有存在该文件就会抛出FileNotFoundException 构造举例:
public class FileInputStreamConstructor{
    public static void main(String[] args)throws IOException{
        //使用File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
    }
}

读取字节数据

1.读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾时返回-1,代码使用:
public class FISRead{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream("read.txt");
        int read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        read = fis.read();
        System.out.println((char)read);
        //末尾处返回-1
        read = fis.read();
        System.out.println((char)read);
        fis.close();
    }
]
循环改进读取方式:
public class FISRead{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream("read.txt");
        int b;
        while((b = fis.read()) != -1){
            System.out.println((char)b);
        }
        fis.close();
    }
}

虽然读取了一个字节,但是会自动提升为int类型
流操作完毕之后必须执行close方法

2.使用字节数组读取:read(byte[] b),每次读取b的长度字节到数组中,返回读取到的有效字节个数,读取到末尾时返回-1,代码使用:
public class FISRead{
    public static void main(String[] args) throws IOException{
        FileInputStream fis = new FileInputStream("read.txt");
        int len;
        byte[] b = new byte[2];
        while((len = fis.read(b)) != -1){
            System.out.println(new String(b,0,len));
        }
        fis.close();
    }
}

使用数组读取,每次读取多个字节,减少了系统间IO操作次数,从而提高了读写效率,建议开发中使用
流的关闭原则,先开后关,后开先关

字符流

使用字节流读取文本文件的时候,可能会遇到一些问题,例如遇到中文字符时可能不会显示完整的字符,因为一个中文字符可能占用多个字节存储,所以java提供了一些字符流类,以字符为单位读写数据,专门用于处理文本文件

字符输入流【Reader】

java.io.Reader 抽象类时表示用于读取字符流的所有类的超类,可以读取字符信息到内存中,它定义了字符输入流的基本共性功能方法
  • public void close() :关闭此流以及相关联的任何系统资源
  • public int read() :从输入流读取一个字符
  • public int read(char[] cbuf) :从输入流中读取一些字符,并存储到字符数组cbuf中

FileReader类

java.io.FileReader 类就是读取字符文件的便利类,构造时使用系统默认的字符编码和默认字节缓冲区

字符编码:字节与字符的对应规则,windows系统的中文编码默认时GBK编码表,idea中为UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据

构造方法

  • FileReader(File file) :创建一个新的FileReader,给定要读取的File对象
  • FileReader(String fileName) :创建一个新的FileReader,给定要读取的文件的名称

当创建一个流对象的时候,必须传入一个文件路径
构造:

public class FileReaderConstructor{
    public static void main(String[] args) throws IOException{
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);

        FileReader fr1 = new FileReader("b.txt");
    }
}

读取字符数据
1.读取字符:使用read方法每次可以读取一个字符,并且提升为int型,读取到文件末尾时返回-1,可以利用循环来读取

public class FRRead{
    public static void main(String[] args) throws IOException{
        //创建对象
        FileReader fr = new FileReader("read.txt");
        //定义一个局部变量保存数据
        int b;
        //循环读取
        while((len = fr.read()) != -1){
            System.out.println((char)b);
        }
        fr.close();
    }
}

2.使用字符数组读取:read(char[] cbuf):每次读取多个字符到数组中,返回读取到的有效字符个数,读取到末尾时返回-1

public class FRRead{
    public static void main(String[] args) throws IOException{
        FileReader fr = new FileReader("read.txt");
        int len;
        char[] cbuf = new char[1024];
        while((len = fr.read(cbuf)) != -1){
            System.out.println(new String(cbuf,0,len))
        }
        fr.close();
    }
}

字符输出流【Writer】

java.io.Writer 抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地,它定义了字节输出流的基本共性功能方法

  • void write(int c) :写入单个字符
  • void writer(char[] cbuf) :写入字符数组
  • abstract void write(char[] cbuf,int off,int len) :写入字符数组的某一部分,off数组的开始索引,off数组的开始索引,len写的字符个数
  • void write(String str) 写入字符串
  • void write(String ,int off,int len) :写入字符串的某一部分,off字符串的开始索引,len写的字符个数
  • void flush() :刷新该流的缓冲
  • void close() :关闭此流,并先刷新

FileWriter类

java.io.FileWriter 类是写出字符到文件的便利类,构造时使用系统默认的字符编码和默认字节缓冲区
构造方法

  • FileWriter(File file) :创建一个新的FileWriter,给定要读取的File对象
  • FileWriter(String fileName) :创建一个新的FileWriter,给定要读取的文件的名称

当你创建一个流对象的时候,必须传入一个文件路径
举例

public class FileWriterConstructor{
    public static void main(String[] args){
        File file = new File("a.txt");
        FileWriter fw = new FileWriter(file);

        FileWriter fw1 = new FileWriter("b.txt"); 
    }
}

基本写出数据
使用write方法可以一次写出一个字节,字符数组或者字符串
关闭和刷新
因为内置缓冲区的原因,如果不关闭输出流,就无法写出字符到文件中,但是关闭流对象,就无法继续写出数据,那么就要使用flush方法

  • flush:刷新缓冲区,并可以继续使用流对象
  • close:先刷新缓冲区,然后通知系统释放资源,不可继续使用流对象
    即便调用了flush方法,在操作的最后还是要调用close方法释放资源
    字符流只能操作文本文件,不能操作图片,视频等非文本文件
    当只是单纯读写文本文件的时候使用字符流,其他情况使用字节流

IO异常的处理

**JDK7之前的处理方式** 实际开发中不能通过抛出异常的方式来处理异常,建议使用try..catch…finally代码
public class HandleExcepetion1{
    public static void main(String[] args){
        //声明变量
        FileWriter fw = null;
        try{
            //创建流对象
            fw = new FileWriter("fw.txt");
            //写出数据
            fw.write("123456");
        } catch (IOException e){
            e.printStackTrace();
        } finally{
            try{
                if(fw != null){
                    fw.close();
                }
            } catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}
**JDK7的处理** 可以使用JDK7的优化后的try-with-resource语句,该语句确保了每个资源在语句结束时关闭,所谓的资源是指程序完成后必须关闭的对象 格式:
try(创建流对象,多个对象之间使用;分隔){
    //读写数据
} catch (IOException e){
    e.printStackTrace();
}
演示
public class HandleException2{
    public static void main(String[] args){
        //创建流对象
        try(FileWriter fw= new FileWriter("fw.txt");){
            //写出数据
            fw.write("123456");
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

缓冲流

除了以上一些基本流以外,还有其他功能更强大的流,比如能够高效读写的缓冲流,转换编码的转换流,持久存储对象的序列化流等等,而这些流都是在基本的流对象基础之上创建而来的,相当于对基本流对象的一种增强。

概述

缓冲流,也叫高效流,是对基本的四个Filexxx流的增强,所以也是四个流,按照数据类型分类:
  • 字节缓冲流:BufferedInputStream,BufferedOutputStream
  • 字符缓冲流:BufferedReader,BufferedWriter

缓冲流的基本原理,是在创建流对象的时候,创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率

字节缓冲流

构造方法:
- public BufferedInputStream(InputStream in) :创建一个新的缓冲输入流
- public BufferedOutputStream(OutputStream out) :创建一个新的缓冲输出流
构造举例:

//创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
//创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

字符缓冲流

构造方法

  • public BufferedReader(Reader in) :创建一个新的缓冲输入流
  • public BufferedWriter(Writer out) :创建一个新的缓冲输出流

构造举例

//创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
//创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

特有方法
字符缓冲流的基本方法与普通字符流的调用方法一致,但是还有一些特有方法

  • BufferedReader:public String readLine() :读一行文字
  • BufferedWriter:public void newLine() :写一行行分隔符

转换流

编码引出的问题
在IDEA中使用FileReader读取文本文件时,默认使用的是UTF-8编码,而windows系统创建文件时,默认使用GBK编码,那么读取的时候会产生乱码

InputStreamReader类

转换流java.io.InputStream,作为Reader得子类是从字节流到字符流得桥梁,它能够读取字节并使用指定得字符集将其解码为字符,它得字符集可以由名称指定,也可以接收平台得默认字符集
构造方法

  • InputStreamReader(InputStream in) :创建一个使用默认字符集得字符流
  • InputStreamReader(InputStream in,String charsetName) :创建一个使用默认字符集的字符流

构造举例:

InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr1 = new InputStreamReader(new FileInputStream("in.txt"),"GBK");

OutputStreamWriter类

转换流java.io.OutputStreamWriter,作为Writer的子类,是字符流到字节流的桥梁,使用指定的字符集将字符编码为字节,它的字符集可以由名称指定,也可以接受平台的默认字符集
构造方法

  • OutputStreamWriter(OutputStream out) :创建一个使用默认字符集的字符流
  • OutputStreamWriter(OutputStream out,String charsetName) :创建一个使用指定字符集的字符流

构造举例

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter osw1 = new OutputStreamWriter(new FileOutputStream("out.txt"),"GBK");

序列化

java提供了一种对象序列化的机制,用一个字节序列可以表示一个对象,该字节序列包含该对象的数据,对象的类型和对象中存储的属性等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化,读取信息并且在内存中创建对象

ObjectOutputStream类

java.io.ObjectOutputStream类,将java对象的原始数据类型写出到文件,实现对象的持久存储
构造方法:

  • public ObjectOutputStream(OutputStream out) :创建一个指定OutputStream的ObjectOutputStream

构造举例

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));

序列化操作
一个对象如果想被序列化必须满足两个条件

  • 该类必须实现java.io.Serializable接口,该接口是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
  • 该类的属性必须是可序列化的,如果有一个属性不需要可序列化,则该属性必须注明是瞬态的,使用transient修饰
public class Employee implements Serializable{
    public String name;
    public transient int age;//瞬态修饰成员无法被序列化
    public void method(){
        System.out.println("===");
    }
}

写出对象方法

  • public final void writeObject(Obkect obj) :写出指定的对象
public class SerializeDemo{
    public static void main(String args){
        Employee e = new Employee();
        e.name = "11";
        e.age = 18;
        try{
            //创建序列化对象
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.txt"));
            //写出对象
            oos.writeObject(e);
            //释放资源
            oos.close();
        } catch(IOException i){
            i.printStackTrace();
        }
    }
}

ObjectInputStream类

java.io.ObjectInputStream 反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复成对象
构造方法

  • public ObjectInputStream(InputStream in) :创建一个指定InputStream的ObjectInputStream

反序列化操作1
如果能找到一个对象的class文件,就可以进行反序列化操作,调用ObjectInputStream读取对象的方法:

  • public final Object readObject() :读取一个对象
public class DeserializeDemo{
    public static void main(String[] args){
        Employee e = null;
        try{
            //创建反序列化流
            FileInputStream fileIn = new FileInputStream("employee.txt");
            ObjectInputStream ois = new ObjectInputStream(fileIn);
            //读取一个对象
            e = (Employee) ois.readObject();
            //释放资源
            in.close();
            fileIn.close();
        } catch(IOException i){
            i.printStackTrace();
            return;
        } catch(ClassNotFoundException c){
            c.printStackTrace();
            return;
        }
        System.out.println("Name:" + e.name);
        System.out.println("Name:" + e.age);
    }
}

对于JVM可以反序列化对象,它必须是能够找到class文件的类,如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常
反序列化操作2
另外,当JVM反序列对象时,能找到class文件,但是class文件在序列化对象之后发生了改变,那么反序列化操作也会失败,抛出InvalidClassException异常,原因如下

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型
  • 该类没有可访问的无参数构造方法

Serializable 接口给需要序列化的类,提供了一个序列版本号,serialVersionUID该版本号的目的在于验证序列化的对象和对应类是否版本匹配

public class Employee implements Serializable{
    //加入序列版本号
    private static final long serialVersionUID = 1L;
    public String name'
    public int age;
    //添加新的属性,重新编译,可以反序列化,该属性赋为默认值
    public int eid;
    public void method(){
        System.out.println("===");
    }
}

打印流

概述
print 和 println的方法都来自于java.io.PrintStream类

PrintStream类

构造方法

  • public PrintStream(String fileName) :使用指定的文件名创建一个新的打印流
    举例
PrintStream ps = new PrintStream("ps.txt");

改变打印流向
System.out 就是PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上,不过既然是流对象,就可以改变它的流向

public class PrintDemo{
    public static void main(String[] args){
        //调用系统打印流,控制台直接输出97
        System.out.println(97);

        //创建打印流,指定文件的名称
        PrintStream ps = new PrintStream("ps.txt");
        //设置系统的打印流流向,输出到ps.txt
        System.setOut(ps);
        //调用系统的打印流 ps.txt中输出97
        System.out.println(97);
    }
}

猜你喜欢

转载自blog.csdn.net/FallingSkies/article/details/82112910