吃透Java基础十二:IO

一、什么是IO流

Java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流。

按数据来源(去向)分类:

  • 文件:FileInputStream、FileOutputStream、FileReader、FileWriter
  • 数组:字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream
    字符数组(char[]):CharArrayReader、CharArrayWriter
  • 管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
  • 基本数据类型:DataInputStream、DataOutputStream
  • 缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
  • 对象序列化反序列化:ObjectInputStream、ObjectOutputStream
  • 打印:PrintStream、PrintWriter
  • 转换:InputStreamReader、OutputStreWriter

根据处理数据类型不同分类

  • 字节流:数据流中最小的数据单元是字节。
  • 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。

字节流读取单个字节,字符流读取单个字符,字节流用来处理二进制文件如:图片、MP3、视频文件等等,字符流用来处理文本文件,可以看做是特殊的二进制文件,使用了某种编码,人可以阅读。
在这里插入图片描述
根据数据流向不同分类

  • 输入流: 程序从输入流读取数据源。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道
  • 输出流:程序向输出流写入数据。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。

二、源码分析

IO 类虽然很多,但最基本的是 4 个抽象类:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一个读 read() 方法、一个写 write() 方法。方法具体的实现还是要看继承这 4 个抽象类的子类,毕竟我们平时使用的也是子类对象。

字节输入流:InputStream


public abstract class InputStream implements Closeable {

    //读取一个字节数据,并返回读到的数据,如果返回-1,表示读到了输入流的末尾。
    public abstract int read() throws IOException;
    
    //将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }
    
    //将数据读入一个字节数组,同时返回实际读取的字节数。如果返回-1,表示读到了输入流的末尾。
    //off指定在数组b中存放数据的起始偏移位置;len指定读取的最大字节数。
    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
    
    //在输入流中跳过n个字节,并返回实际跳过的字节数。
    public long skip(long n) throws IOException {

        long remaining = n;
        int nr;

        if (n <= 0) {
            return 0;
        }

        int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
        byte[] skipBuffer = new byte[size];
        while (remaining > 0) {
            nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
            if (nr < 0) {
                break;
            }
            remaining -= nr;
        }

        return n - remaining;
    }
    
    //返回在不发生阻塞的情况下,可读取的字节数。
    public int available() throws IOException {
        return 0;
    }

    //关闭输入流,释放和这个流相关的系统资源。
    public void close() throws IOException {}

    //标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,
    //可以使用 markSupport() 方法判断
    public synchronized void mark(int readlimit) {}

    //返回到上一个标记。
    public synchronized void reset() throws IOException {
        throw new IOException("mark/reset not supported");
    }

    //测试当前流是否支持mark和reset方法。如果支持,返回true,否则返回false。
    public boolean markSupported() {
        return false;
    }

}

字节输出流:OutputStream

public abstract class OutputStream implements Closeable, Flushable {
    //往输出流中写入一个字节。
    public abstract void write(int b) throws IOException;

    //往输出流中写入数组b中的所有字节。
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
    }

    //往输出流中写入数组b中从偏移量off开始的len个字节的数据。
    public void write(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if ((off < 0) || (off > b.length) || (len < 0) ||
                   ((off + len) > b.length) || ((off + len) < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return;
        }
        for (int i = 0 ; i < len ; i++) {
            write(b[off + i]);
        }
    }

    //强制刷新,将缓冲中的数据写入
    public void flush() throws IOException {
    }

    //关闭输出流,释放和这个流相关的系统资源。
    public void close() throws IOException {
    }

}

字符输入流Reader

public abstract class Reader implements Readable, Closeable {

    //读取字符到字符缓存中
    public int read(java.nio.CharBuffer target) throws IOException {
        int len = target.remaining();
        char[] cbuf = new char[len];
        int n = read(cbuf, 0, len);
        if (n > 0)
            target.put(cbuf, 0, n);
        return n;
    }

    //读取一个字符,返回值为读取的字符
    public int read() throws IOException {
        char cb[] = new char[1];
        if (read(cb, 0, 1) == -1)
            return -1;
        else
            return cb[0];
    }

    //读取一系列字符到数组cbuf[]中,返回值为实际读取的字符的数量
    public int read(char cbuf[]) throws IOException {
        return read(cbuf, 0, cbuf.length);
    }

    //读取len个字符,从数组cbuf[]的下标off处开始存放,返回值为实际读取的字符数量,该方法必须由子类实现
    abstract public int read(char cbuf[], int off, int len) throws IOException;

    //跳过指定长度的字符数量
    public long skip(long n) throws IOException {
        if (n < 0L)
            throw new IllegalArgumentException("skip value is negative");
        int nn = (int) Math.min(n, maxSkipBufferSize);
        synchronized (lock) {
            if ((skipBuffer == null) || (skipBuffer.length < nn))
                skipBuffer = new char[nn];
            long r = n;
            while (r > 0) {
                int nc = read(skipBuffer, 0, (int)Math.min(r, nn));
                if (nc == -1)
                    break;
                r -= nc;
            }
            return n - r;
        }
    }

    //告诉此流是否已准备好被读取。
    public boolean ready() throws IOException {
        return false;
    }

    //判断当前流是否支持标记流
    public boolean markSupported() {
        return false;
    }

    //标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,
    //可以使用 markSupport() 方法判断
    public void mark(int readAheadLimit) throws IOException {
        throw new IOException("mark() not supported");
    }

    //返回到上一个标记。
    public void reset() throws IOException {
        throw new IOException("reset() not supported");
    }

     //关闭输入流,释放和这个流相关的系统资源。
     abstract public void close() throws IOException;

}

字符输出流Writer

public abstract class Writer implements Appendable, Closeable, Flushable {

    //将整型值c的低16位写入输出流 
    public void write(int c) throws IOException {
        synchronized (lock) {
            if (writeBuffer == null){
                writeBuffer = new char[WRITE_BUFFER_SIZE];
            }
            writeBuffer[0] = (char) c;
            write(writeBuffer, 0, 1);
        }
    }

    //将字符数组cbuf[]写入输出流 
    public void write(char cbuf[]) throws IOException {
        write(cbuf, 0, cbuf.length);
    }

    //将字符数组cbuf[]中的从索引为off的位置处开始的len个字符写入输出流
    abstract public void write(char cbuf[], int off, int len) throws IOException;

    //将字符串str中的字符写入输出流 
    public void write(String str) throws IOException {
        write(str, 0, str.length());
    }

    //将字符串str 中从索引off开始处的len个字符写入输出流 
    public void write(String str, int off, int len) throws IOException {
        synchronized (lock) {
            char cbuf[];
            if (len <= WRITE_BUFFER_SIZE) {
                if (writeBuffer == null) {
                    writeBuffer = new char[WRITE_BUFFER_SIZE];
                }
                cbuf = writeBuffer;
            } else {    // Don't permanently allocate very large buffers.
                cbuf = new char[len];
            }
            str.getChars(off, (off + len), cbuf, 0);
            write(cbuf, 0, len);
        }
    }

    //追加写入一个字符序列
    public Writer append(CharSequence csq) throws IOException {
        if (csq == null)
            write("null");
        else
            write(csq.toString());
        return this;
    }

    //追加写入一个字符序列的一部分,从 start 位置开始,end 位置结束
    public Writer append(CharSequence csq, int start, int end) throws IOException {
        CharSequence cs = (csq == null ? "null" : csq);
        write(cs.subSequence(start, end).toString());
        return this;
    }

    //追加写入一个 16 位的字符
    public Writer append(char c) throws IOException {
        write(c);
        return this;
    }

    //强制刷新,将缓冲中的数据写入
    abstract public void flush() throws IOException;

    //关闭输出流,流被关闭后就不能再输出数据了
    abstract public void close() throws IOException;

}

三、运用场景

1、字节流与字符流的转换

  • InputStreamReader:将一个字节流中的字节解码成字符。
  • OutputStreamWriter:将写入的字符编码成字节后写入一个字节流。

字节流和字符流转换看一下例子就行了:

扫描二维码关注公众号,回复: 8982003 查看本文章
  1. 创建一个文本文件:bobo.txt,然后写入内容:波波。
  2. 用FileInputStream读出文本内容然后以字节数组的形式缓存在fileInputStream里面。
  3. 用InputStreamReader 把fileInputStream缓存的字节数组读取到定义的charArray的字符数组里面,并且输出。
  4. 用OutputStreamWriter把字符数组charArray里面的字符写到InputStreamReader的字节数组缓存起来,然后输出。
  5. 输出结果如下图所示。
public class MyTest {
    public static void main(String[] args) throws IOException {
        //字节流读取文件内容
        FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Administrator\\Desktop\\bobo.txt");

        //转为字符流
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
        char[] charArray = new char[10];
        inputStreamReader.read(charArray);
        System.out.println(Arrays.toString(charArray));

        //转为字节流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(byteArrayOutputStream);
        outputStreamWriter.write(charArray);
        outputStreamWriter.close();
        System.out.println(Arrays.toString(byteArrayOutputStream.toByteArray()));
    }
}

运行输出:

[,,  ,  ,  ,  ,  ,  ,  ,  ]
[-26, -77, -94, -26, -77, -94, 0, 0, 0, 0, 0, 0, 0, 0]

2、字节数组的输入输出

ByteArrayOutputStream :字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中。
ByteArrayInputStream :字节数组输入流在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区中。

public class MyTest {
    public static void main(String[] args) throws IOException{
        byte[] byteArrAy = {1, 2, 3};
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byteArrayOutputStream.write(byteArrAy);

        byte[] outByteArray = new byte[3];
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        byteArrayInputStream.read(outByteArray);

        System.out.println(Arrays.toString(outByteArray));
    }
}

输出:

[1, 2, 3]

3、对象的输入输出

java.io.ObjectOutputStream是实现序列化的关键类,它可以将一个对象转换成二进制流通过write()方法写入到OutputStream输出流中。然后可以通过ObjectInputStream的read()方法将对应的InputStream二进制流还原成对象。

public class MyTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        List list = new ArrayList(Arrays.asList(1, 2, 3));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(list);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        List list1 = (ArrayList) objectInputStream.readObject();
        System.out.println(list == list1);
        System.out.println("list:" + list + " list1:" + list1);
    }
}

输出

false
list:[1, 2, 3] list1:[1, 2, 3]

4、控制台的输入输出

  • System.out:标准的输出流,out是System类里面的静态变量,其类型是PrintStream,PrintStream继承于FilterOutputStream,也是输出类OutputStream的子类。
  • System.in:标准的输入流,in是System类里面的静态变量,其类型是
    InputStream。

我们常用的System.out和System.in作为控制台的输出与输入。

单个字符输入

public class MyTest {
    public static void main(String[] args) throws IOException {
        //把控制台输入的字节流转为字符流
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        System.out.println("请输入一个字符:按q键结束");
        char c;
        do {
            c = (char) bufferedReader.read();
            System.out.println("输入的字符为:" + c);
        } while (c != 'q');
    }
}

整行字符输入:

public class MyTest {
    public static void main(String[] args) throws IOException {
        //把控制台输入的字节流转为字符流
        InputStreamReader inputStreamReader = new InputStreamReader(System.in);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        System.out.println("请输入一行字符:");
        String line = bufferedReader.readLine();
        System.out.println("输入的内容为:" + line);
    }
}

运行输出:

请输入一行字符:
bobo
输入的内容为:bobo

5、二进制文件的输入输出

public class MyTest {
    public static void main(String[] args) throws IOException {
        String fileDirectly = "C:\\Users\\Administrator\\Desktop\\bobo.txt";
        byte[] byteArray = {1, 2, 3, 4, 5};
        FileOutputStream fileOutputStream = new FileOutputStream(fileDirectly);
        fileOutputStream.write(byteArray);
        fileOutputStream.close();

        FileInputStream fileInputStream = new FileInputStream(fileDirectly);
        int c;
        while ((c = fileInputStream.read()) != -1) {
            System.out.println(c);
        }
    }
}

输出:

1
2
3
4
5

6、文本文件的输入输出

用FileWriter和FileReader读取文本文件:

public class MyTest {
    public static void main(String[] args) throws IOException {
        String fileDirectly = "C:\\Users\\Administrator\\Desktop\\bobo.txt";
        FileWriter fileWriter = new FileWriter(fileDirectly);
        fileWriter.write("波波");
        fileWriter.write("测试");
        fileWriter.append("文件的读写");
        System.out.println("默认编码:" + fileWriter.getEncoding());
        fileWriter.flush();
        fileWriter.close();

        //一个字符一个字符的读取
        System.out.println("---------------------------");
        System.out.println("一个字符一个字符的读取");
        FileReader fileReader = new FileReader(fileDirectly);
        int c;
        while ((c = fileReader.read()) != -1) {
            System.out.println((char) c);
        }
        fileReader.close();

        //一行一行的读取
        System.out.println("---------------------------");
        System.out.println("一行一行的读取");
        FileReader fileReaderLine = new FileReader(fileDirectly);
        BufferedReader bufferedReader = new BufferedReader(fileReaderLine);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
    }
}

输出:

一个字符一个字符的读取
波
波
测
试
文
件
的
读
写
---------------------------
一行一行的读取
波波测试文件的读写

使用字节流和字符流的转换类 InputStreamReader 和 OutputStreamWriter 可以指定文件的编码,使用 Buffer 相关的类来读取文件的每一行。

public class MyTest {
    public static void main(String[] args) throws IOException {
        String fileDirectly = "C:\\Users\\Administrator\\Desktop\\bobo.txt";
        //将字符流以GBK编码的方式转化成字节流写入文件
        FileOutputStream fileOutputStream = new FileOutputStream(fileDirectly);
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "GBK");
        outputStreamWriter.write("波波");
        outputStreamWriter.write("测试");
        outputStreamWriter.append("文件的读写");
        outputStreamWriter.flush();
        System.out.println("编码方式为:" + outputStreamWriter.getEncoding());
        outputStreamWriter.close();
        fileOutputStream.close();

        //将读取的字节流以GBK编码的方式转化为字符流
        FileInputStream fileInputStream = new FileInputStream(fileDirectly);
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK");
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
        fileInputStream.close();
    }
}

运行输出:

编码方式为:GBK
波波测试文件的读写
发布了66 篇原创文章 · 获赞 138 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/u013277209/article/details/102962002
今日推荐