传统IO(阻塞IO)

目录

一、IO说明

二、输入流 与 输出流:

2.1 输入/输出流的主要方法

2.2 输入/输出流构造方法

三、转换流:

四、推回输入流:

五、重定向标准输入/输出流:

六、RandomAccessFile

传统IO流总结:


一、IO说明

按照POSIX的标准划分IO, 可以把IO分成两类:同步IO 和 异步IO

按照线程对IO业务的处理是否被阻塞的角度,可以将IO分成两类:阻塞IO  非阻塞IO

(POSIX的标准 : UNIX系统的一个设计标准)

 

对于IO的操作步骤可以分成两步:

1、程序发出IO请求

2、完成实际的IO操作

 

  • 阻塞IO 和 非阻塞IO 的划分标准是针对第一步来划分的(程序发出IO请求),如果发出IO请求会阻塞线程,就是阻塞IO;如果发出的IO请求没有阻塞IO,就是非阻塞IO

     举例:传统IO 是阻塞IO;   基于Channel的非阻塞IO是非阻塞IO

  • 同步IO 和 异步IO的区别在第二步,如果实际的IO操作由操作系统完成,在将结果返回给应用程序,就是异步IO; 如果实际的IO需要应用程序本身执行,会阻塞线程,那就是同步IO.

       举例:传统IO、基于Channel的非阻塞IO, 都是异步IO

 

二、输入流  输出流:

按照流的流向可以分为输入流和输出流。

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

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

注意:这里的输入输出是从程序运行所在的内存的角度来划分的,比如,从内存到硬盘的称为输出流。

InputStream / Reader : 所有输入流的基类,前者是字节流, 后者是字符流

OutputStream / Writer : 所有输出流的基类,前者是字节流, 后者是字符流

(说明:字节流处理的数据单元是8位的字节, 字符流处理的数据单元是16位的字符

 

输入流 使用隐式的指针来标识应该从当前哪个位置读取数据。每当从输入流中读取一个数据后,这个指针自动向后移动

输出流 使用隐式的指针来标识        数据即将要放入的位置。   每当向输出流里输出一个数据后,这个指针自动向后移动

(可以看出字符流或字节流处理的数据单元 是以本身能处理的单位为单位(8位字节或16位字符))

 

2.1 输入/输出流的主要方法

InputStream里的主要方法:(读取数据的基本单位是字节)

1. public abstract int read(): 从输入流中读取单个字节,返回读取到的字节数据(字节数据可以直接转换成int类型)

2.public int read(byte b[]) : 从输入流中最多读取b.length个字节数据,并将其存储再数组b中,返回实际读取的字节数

3.public int read(byte b[], int off, int len):从输入流中最多读取len个字节数据,并将其存储再数组b中,且是从数组的off位置开始放数据,返回实际读取的字节数

Reader里的主要方法:

1. public int read():从输入流中读取单个字符,返回读取的字符数据(字符数据可以直接转换成int类型)

2.public int read(char cbuf[]):从输入流中最多读取cbuf.length个字符数据,返回实际读取的字符数

3.public int read(char cbuf[], int off, int len):从输入流中最多读取len个字符数据,并将其存储再数组cbuf中,且是从数组的off位置开始放数据,返回实际读取的字符数

两者相同方法:

1.  public long skip(long n) : 将指针向前移动n个字节/字符, 返回实际skip的数据单元

2. public synchronized void mark(int readlimit) :将记录指针当前位置记录一个标记(mark)

3. public boolean markSupported():判断输入流是否支持mark操作,即是否支持记录标记

4. public synchronized void reset():将此流的记录指针重新定位到上次记录标记(mark)的位置

OutputStream 与 Writer 同理

1.  void write(int c) : 将指定的字节/字符输出到输出流中,其中c即可以代表字节,也可以代表字符

2. void write(byte[]/char[]  buf) :  将字节/字符数组输出到指定的输出流中

3. void write(byte[]/char[]  buf, int off, int len) :  将字节/字符数组从off位置开始的len个字节/字符输出到指定的输出流中

Writer还包括的两个方法:

字符流直接以字符位操作单位,所以Writer可以使用字符串来代理字符数组,即以String对象作为参数

1. void write(String str) : 将str字符串包含的字符输出到指定的输出流中

2. void write(String str, int off, int len) : 将str字符串从off位置开始的len个字符输出到指定的输出流中

2.2 输入/输出流构造方法

输入、输出流体系中表示的类众多,不过可以分为两大类:节点流 和 处理流

节点流:直接以物理节点创建的流,直接和底层的I/O设备、文件交互

处理流:以节点流为参数来创建,用于对节点流的包装

 

以物理节点作为构造器的参数创建流的方式(物理节点可选的方式)

1、字节流 / 字符流  以文件作为物理节点,示例:

 FileOutputStream fileOutputStream2 = new FileOutputStream("test2.txt");
 FileReader fileReader = new FileReader("test2.txt");

2、字节流 / 字符流 以字节数组 / 字符数组 为物理节点, 示例:

 

3、字符流 以字符串作为物理节点,示例:

String ss = "hello world";

// 读取

StringReader sr = new StringReader(ss);

// 写入

StringWriter sw = new StringWriter();

sw.write(ss);

 

输入/输出流体系:

 

通常来说,字节流的功能比字符流更强大,因为计算机里所有的数据都是二进制的,而字节流可以处理所有的二进制文件。But, 如果使用字节流处理文本文件,则需要把这些字节转换成字符,这就增加了编码的复杂度。

所以一个准则是:如果进行输入/输出的内容是文本内容,   应该考虑用字符流;

                             如果进行输入/输出的内容是二进制内容,应该考虑用字节流。

思考:如何区分文本内容和二进制内容?

计算机文本通常分为文本文件和二进制文件。所有可以使用记事本打开的文件称为文本文件,反之则是二进制文件。实际上文本文件是一种特殊的二进制文件,只是恰巧该二进制文件的内容能够使用合适的字符集(utf-8、 gbk)解析成字符。

但是更多的情况下我们操作的都是文本文件,所以字符流还是很重要的。

一个简单的示例:

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.PrintStream;



/**

 * 存在问题:

 * 1、一个中文字符使用两个字节,如果read()方法读取时只读取到了半个中文字符,就会导致乱码

 * 2、程序里打开的IO资源不属于内存资源,垃圾回收机制无法回收该资源,所以应该显示的关闭IO资源。

 * Java7改写了所有的IO资源,他们本身都实现了AutoClosable接口,因此可以通过自动关闭资源的try语句(try-with-resources)来关闭这些IO流

 * 3、直接以物理节点作为构造器的参数创建的流称为字节流,相反,以原有流作为构造器的参数创建的流称为处理流(高级流)

 * 在使用处理流包装了底层节点流后,关闭输入/输出资源时,只要关闭最上层的处理流即可,系统会自动关闭 其包装的底层的节点流

 */

public class FileInputStreamTest {

    public static void main(String[] args) {

        try (FileInputStream fileInputStream = new FileInputStream("/Users/qinwenjing/Documents/Projects/Test/src/main"

                + "/java/com/es/MyRestClient.java");

             FileOutputStream fileOutputStream = new FileOutputStream("test.txt");



             // 使用处理流直接往输出流中写入一段文字

             FileOutputStream fileOutputStream2 = new FileOutputStream("test2.txt");

             PrintStream printStream = new PrintStream(fileOutputStream2)) {



            byte[] buff = new byte[1024];

            int hasRead = 0;

            while ((hasRead = fileInputStream.read(buff)) > 0) {

                fileOutputStream.write(buff, 0, hasRead);

                //System.out.println(new String(buff, 0, hasRead));

            }



            printStream.println("hello world, 大家好");

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

三、转换流:

用于实现 字节流 → 字符流,其中

InputStreamReader将  字节输入流 转换成 字符输入流

OutputStreamWriter将 字节输出流 转换成 字符输出流

示例:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
/**
 * BufferedReader:具有缓冲的功能,可以一次读取一行数据,以换行作为标志,如果没有读取到换行,readLine()方法就会被阻塞。
 * BufferedReader具有readLine()方法,可以方便的一次读入一行数据,所以疆场把读取文本内容的输入流包装成BufferedReader,用来方便的读取输入流的文本内容
 */
public class KeyInTest {
    public static void main(String[] args) {
 
        String line = null;
        try ( // 字节流(System.in)转换成字符流(InputStreamReader)
              InputStreamReader inputStreamReader = new InputStreamReader(System.in);
              // 将普通的Reader包装成BufferedReader
              BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
 
            while ((line = bufferedReader.readLine()) != null) {
                if (line.endsWith("exit")) {
                    System.exit(1);
                }
                System.out.println("输出内容:" + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
 
    }
}

四、推回输入流:

PushbackInputStream  和 PushbackReader (是处理流)

1. void unread(int b) : 将一个字节/字符推回到缓冲区中,从而允许重复读取刚刚读取的内容

2. void unread(byte[]/char[]  buf) : 将一个字节/字符数组内容推回到缓冲区中,从而允许重复读取刚刚读取的内容

3. void unread(byte[]/char[]  buf, int off, int len) : 将一个字节/字符数组从off位置开始的len长度内容推回到缓冲区中,从而允许重复读取刚刚读取的内容

而推回输入流每次调用read()方法时,先从推回缓冲区读取数据,只有完全读取了推回缓冲区的内容后,才会从原输入流中读取数据

 

五、重定向标准输入/输出流:

java的标准 输入/输出流 分别通过System.in和System.out表示,默认情况下他们分别代表键盘和屏幕。

但是,如果还想使用System.out输出数据,但是不输出到屏幕上呢?或者说还想使用System.in输入数据,但是不想通过键盘输入呢?这时候可以对标注输入/输出进行重定向。

示例:

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.PrintStream;

import java.util.Scanner;



public class RedirectTest {

    public static void main(String[] args) {

        try (PrintStream printStream = new PrintStream(new FileOutputStream("test.txt"));

             FileInputStream fileInputStream = new FileInputStream("/Users/qinwenjing/Documents/Projects/Test/src"

                     + "/main/java/IO/RedirectTest.java");

        ) {

            // 重定向输出流到printStream

            System.setOut(printStream);

         /*   System.out.println("hello world"); // 发现结果输出到了文件test.txt中

            System.out.println(new RedirectTest());*/



            // 重定向输入流到printStream

            System.setIn(fileInputStream);

            Scanner scanner = new Scanner(System.in);

            scanner.useDelimiter("\n");

            while (scanner.hasNextLine()) {

                System.out.println("输出内容是:" + scanner.nextLine());

            }



        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

结果会看到没有通过键盘输入任何数据,也没有再屏幕上输出任何东西

其中test.txt文件写入了要写的数据:

 

 

六、RandomAccessFile

RandomAccessFile是Java输入输出体系中内容丰富的文件内容访问类。RandomAccessFile对象包含一个记录指针,用以标识当前读写处的位置,当程序创建一个该对象时,该对象的文件指针位于文件头(也就是0),当读/写了n个字节后,文件记录指针向后移动n个字节,另外,RandomAccessFile可以自由的移动记录指针。

优点:

可以定位到文件中的某个位置进行操作,

同时提供的读和写的方法

应用场景:需要读取从某个位置开始的文件; 需要在文件的末尾追加内容

局限:

只能读写文件,不能读写其他IO节点

主要方法:

1. long getFilePointer() : 返回文件记录指针的当前位置

2. void seek(long pos) : 将文件指针定位到pos位置(第pos个字节处)

3. 类似与InputStream的三个read()方法

4. 类似OutputStream的三个write() 方法

5. ​​getChannel() : 返回的FileChannel是只读的还是读写的,取决去RandomAccessFile打开文件的模式。

示例:

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.RandomAccessFile;



public class RandomAccessFileTest {

    public static void main(String[] args) {

        try(RandomAccessFile ref = new RandomAccessFile

                ("test.txt", "rw")) {

            // 获取RandomAccessFile对象文件指针的位置,初始位置应该为0

            System.out.println("RandomAccessFile的文件的初始位置:"+ref.getFilePointer());

            // 移动ref的文件记录指针的位置,也就是说从300字节的位置开始读写

            ref.seek(300);



            byte[] bbuf = new byte[1024];

            int hasread = 0;

            while((hasread = ref.read(bbuf)) > 0){

                System.out.println(new String(bbuf, 0, hasread));

            }

            ref.seek(ref.length());

            ref.write("我是追加的字符哈哈哈".getBytes());


        } catch (FileNotFoundException e) {

            e.printStackTrace();

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

RandomAccessFile可以直接以物理节点创建,是个节点流。

RandomAccessFile和普通输入输出流比对:

1、读写的任意性

普通的输入流读取必须要顺序读取,比如:

hasRead = fileInputStream.read(buff)

line = bufferedReader.readLine()

而RandomAccessFile可以定位一个具体的文件位置进行读写,这是RandomAccessFile的优势

2、内容的追加

普通的输出流写文件时,会覆盖掉原有的文件内容,而RandomAccessFile可以在文件的末尾进行文件的追加

 

传统IO流总结:

1、读写是以字节为单位(即使不直接处理字节流,但底层的实现还是依赖于字节处理),也就是输入/输出系统一次只能处理一个字节,因此效率注定不高

2、传统的输入/输出流都是阻塞的输入输出,即,如果读写不能拿到数据,现成将会被阻塞。

由于这些原因,才有了NIO

3、到此处,还是没有看出传统IO和NIO的阻塞体现在哪里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值