Java—I/O

Java—IO

1. IO: java的IO是实现输入和输出的基础,可以方便地实现数据的输入和输出操作。在java中把不同的输入/输出源(键盘,文件,网络连接等)抽象的表述为 “流”(Stream)。通过流的形式允许java程序使用相同的方式来访问不同的输入/输出源。

2.IO分类:

1> 按流向分:输入流,输出流

  • 输入流:只能从中读取数据,不能写入数据
  • 输出流,只能向其写入数据,不能读取数据

    这里的输入输出是相对而言的,在本地用程序读写文件时,我们始终以程序为中心,即程序从文件中读取时就是硬盘向内存中输入,程序向文件中写入就是内存向硬盘输出

但对于服务器和客户端来说,服务器将数据输出到网络中,这是Sever端程序的输出流。客户端从网络中读取数据,这是Client端的输入流

Java的输入流主要是InputStream和Reader作为基类,而输出流则主要是由OutputStream和Writer作为基类,他们都是一些抽象基类,无法直接创建实例


2> 按操作单元分:字节流和字符流

  • 字节流:以InputStream和OutputStream作为基类
  • 字符流:以Reader和Writer作为基类

  • 其实现子类:FileInputStream和FileOutputStream可以对任意类型的文件进行读写操作,但 FileReader和FileWriter只能对纯文本文件进行操作


3> 按照流的角色可以分为节点流和处理流。

  • 可以从/向一个特定的IO设备中读/写数据的流,称之为节点流。
  • 处理流则对一个已存在的流进行连接和封装,通过封装后的流来实现数据的读/写功能。

image.png

  • 从图中可以看出当使用处理流进行输入/输出时,程序不会直接连接到实际的数据源,没有和实际的输入和输出节点连接。实际上,处理流时 “连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更强大的读写功能
常用的处理流有:
  • 缓冲流:在读入或写出时,对数据进行缓存,以减少I/O的次数:BufferedReader与BufferedWriter,BufferedInputStream和BufferedOutputStream
  • 对象流:可以对实例对象进行读写操作。ObjectInputStream和ObjectOutputStream
  • 数据流:按基本类型和String类型读写。DataInputStream和DataOutputStream

IO应用

1> IO体系的基类(InputStream/Reader,OutputStream/Writer)

  • InputStream和Reader是所有输入流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以他们的方法是所有输入流都可使用的方法。

    InputStream:

  • int read():从输入流中读取单个字节,返回所读取的字节数据(字节数据可直接转为int型)

  • int read(byte []):从输入流中最多读取b.length个字节的数据,并将其存储在字节数组b中,返回实际读取的字节数。
  • int read(byte[], int off, int len): 从输入流中最多读取len个字节的数据,并将其存储在数组b中,放入b数组时,并不是从数组起点开始,而是从off位置开始,返回实际读取的字节数

Reader:

  • int read():从输入流中读取单个字符,返回所读取的字符数据(可直接转为int类型)
  • int read(char[]):从输入流中最多读取b.length个字符的数据,并将其存储在数组b中,返回实际读取的字符数。
  • int read(char[] b, int off, int len):从输入流中最多读取len个字符的数据,并将其存储在数组b中,放入数组b时,并不是从数组七点开始,而是从off位置开始,返回实际读取的字符数。
对比InputStream和Reader所提供的方法,就不难发现这两个基类的功能基本是一样的。如何判断输入流的终点呢?当read()方法返回-1,就表明到了输入流的结束点

另外InputStream和Reader中还有一些移动指针的方法:

  • void mark(int readAheadLimit); 在记录指针当前位置记录一个标记(mark)。
  • boolean markSupported();
  • 判断此输入流是否支持mark()操作,即是否支持记录标记。
  • void reset(); 将此流的记录指针重新定位到上一次记录标记(mark)的位置。
  • long skip(long n); 记录指针向前移动n个字节/字符。

OutputStream:

  • void write(int c):将指定的字节/字符输出到输出流中,c可以代表字节,也可以代表字符
  • void write(byte[]/byte[] buf):将字节数组,字符数组的数据输出到指定输出流中
  • void write(byte[]/byte[] buf, int off, int len):将字节数组/字符数组从off位置开始,长度为len的字节/字符数组输出到输出流中

因为字符流直接以字符为操作单位,所以Writer可以用字符串代替字符数组,即以String对象为参数,Writer中还包括这两个方法:

  • void write(String str):将str字符串里包含的字符输出到指定输出流中
  • void write(String str, int off, int len):将str字符串里从off位置开始,长度为len的字符输出到指定输出流中

2> IO体系的基类文件流的使用(FileInputStream/FileReader,FileOutputStream/FileWriter)

  • 前面说到的InputStream和Reader都是抽象类,本身不能创建实例,但他们分别有一个用于读取文件的输入流:FileInputStream和FileReader,他们都是节点流——会直接和指定文件关联,下面是二者使用的例子
  • FileInputStream:
public class FileInputDemo {
    public static void main(String[] args) throws IOException {
        InputStream is = new FileInputStream(new File("d:\\test.txt"));
        byte[] flush = new byte[1024];
        int len;
        while(-1 != (len = is.read(flush))){
            System.out.println(new String(flush));
        }
        is.close();
    }
}
  • Reader
public class ReaderTest {
    public static void main(String[] args) throws IOException {
        Reader r = new FileReader(new File("d:\\test.txt"));
        char[] flush = new char[1024];
        int len;
        while (-1 != (len = r.read(flush))){
            System.out.println(new String(flush));
        }
        r.close();
    }
}

上面程序最后用close()方法关闭该文件的输入流,与JDBC编程一样,程序里面打开的文件IO资源不属于内存的资源,垃圾回收机制无法回收该资源,所以应该显示地关闭打开的IO资源。JAVA 7 改写了所有的IO资源类,他们都实现了AutoCloseable接口,因此都可以通过自动关闭资源的try语句来关闭这些IO流

  • FileOutputStream
public class FileOutputDemo {
    public static void main(String[] args) throws IOException {
        OutputStream os = new FileOutputStream(new File("d:\\test.txt"));

        Scanner scan = new Scanner(System.in);
        String str = scan.nextLine();

        byte[] flush = str.getBytes();

        os.write(flush);

        os.close();
    }
}
  • Writer
public class WriterDemo {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter(new File("d:\\test.txt"));

        Scanner scan = new Scanner(System.in);
        String str = scan.nextLine();

        writer.write(str);
        //char[] chars = str.toCharArray();
        //writer.write(chars);

        writer.close();
    }
}

3> 转换流的使用

转换流作用:
  • 1 . 是字符流和字节流之间的桥梁
  • 2 . 可对读取到的字节数据经过指定编码换成字符
  • 3 . 可对读取到的字符数据经过指定编码转成字节

示例:1.读取键盘中的每一行内容,并写到文本中

public class InputDemo {
    public static void main(String[] args) throws IOException {
        String file = "d:\\test.txt";

       OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(new File(file)),"utf-8");

        osw.write("这是要保存的字符");
        osw.close();

        InputStreamReader isr = new InputStreamReader(new FileInputStream(new File(file)),"utf-8");
        char[] buf = new char[1024];
        isr.read(buf);
        System.out.println(buf);
    }
}

4> 对象流(ObjectInputStream/ObjectOutputStream)

  • 思考一个问题,如果我们要写入文件内的数据不是基本数据类型(使用DataInputStream),也不是字符型或字节型数据,而是一个对象,应该怎么写?这个时候就用到了处理流中的对象流。下面是一个对象的写入文件和从文件中读取
  • 反序列化 输入流:ObjectInputStream
  • 序列化 输出流 ObjectOutputStream
注意:
  • 先序列化后反序列化; 反序列化顺序必须与序列化一致
  • 不是所有对象都可以序列化 必须实现 java.io.Serializable
  • 不是所有属性都需要序列化 不需要序列化的属性 要加 transient
public class Person implements Serializable {
    private String name;
    private int age;
    private transient String intro;   //不需要序列化

    public Person(String name, int age, String intro) {
        this.name = name;
        this.age = age;
        this.intro = intro;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getIntro() {
        return intro;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setIntro(String intro) {
        this.intro = intro;
    }

    public String toString(){
        return name + " " + age + " " + intro;
    }
}
进行序列化和反序列化后:
public class WriteObject {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(
                new File("d:\\test.txt"))));

        oos.writeObject(new Person("shi",13,"A"));
        oos.writeObject(new Person("zhang",15,"B"));
        oos.writeObject(new Person("hao",18,"C"));

        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(
                new FileInputStream(new File("d:\\test.txt"))));
        Person p1 = (Person) ois.readObject();
        Person p2 = (Person) ois.readObject();
        Person p3 = (Person) ois.readObject();
        ois.close();
        System.out.println(p1);
        System.out.println(p2);
        System.out.println(p3);
    }
}
  • 序列化后文件内容:

image.png

反序列化后读到的对象:

image.png

我们可以发现,自定义类中被transient 标记的数据将不被序列化和反序列化,而且自定义类也必须要实现Serializable接口。另外要注意,序列化时的参数类型和反序列化时的返回类型都是Object类型,所以在反序列化接收类对象数据时要用强制类型转换

5> 缓冲流(BufferedInputStream/BufferedReader,BufferedOutputStream/BufferedWriter)

  • 在原有的节点流对象外部包装缓冲流,为IO流增加了内存缓冲区,增加缓冲区的两个目的:

    • 1 .允许IO一次不止操作一个字符,这样提高整个系统的性能
    • 2 .由于有缓冲区,使得在流上执行skip,mark和reset方法都成为可能
  • 缓冲流要套接在节点流之上,对读写的数据提供了缓冲功能,增加了读写的效率,同时增加了一些新的方法。例如:BufferedReader中的readLine方法,BufferedWriter中的newLine方法

  • 四种换缓存流,常用的构造方法:
//字符输入流
BufferedReader(Reader in)//创建一个32字节的缓冲区
BufferedReader(Reader in, int size)//size为自定义缓存区的大小

//字符输出流
BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

//字节输入流
BufferedInputStream(InputStream in)
BufferedInputStream(InputStream in, int size)

//字节输出流
BufferedOutputStream(OutputStream in)
BufferedOutputStream(OutputStream in, int size)

对于输出的缓冲流,BufferedWriter和BufferedOutputStream会先在内存中缓存,使用flush方法会使内存中的数据立刻写出

  • 示例:
public class BufferedDemo {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(
                new FileInputStream(new File("d:\\test.txt")));

        BufferedOutputStream bos = new BufferedOutputStream(
                new FileOutputStream(new File("d:\\test_01.txt")));

        byte[] buf = new byte[64];
        int len = 0;
        while (-1 != (len = bis.read(buf))){
            bos.write(buf, 0, len);
            bos.flush();
        }
        bis.close();
        bos.close();
    }

}

猜你喜欢

转载自blog.csdn.net/wintershii/article/details/81281710