面试|详细了解Java中的字节流

在Java中有一个名为java.io的包定义Java世界数据的输入(input)和输出(output);IO流根据读取的形式和结果的不同分为:字节流和字符流;根据数据流向不同分为:输入流和输出流

1.字节流和字符流的区别

字节流,顾名思义就是以字节的形式读取外部内容到内存中;而字符流则是以字符的形式读取外部内容到内存中;字符流是把读取到的字节流按照某种编码的形式转换成字符的形式,所以字符流是对字节流进行加工后的一种流方式它更加适用于有编码形式的文本。按道理来讲,字节流应该能读取计算机中存储的任何格式的内容,包括文本、音频或者图片,因为计算机存储的格式就是二进制的字节码,所以字节流的使用更加广泛。
PS:字节流和字符流还说明其在JVM内存中的存储形式,按照字节流形式读取的内容以字节的形式存储在JVM内存中,而以字符流形式读取的内容以字符的形式存储在JVM内存中

2.输出入流和输出流的区别

从外部设备读取内容到计算机内存是为输入流,因此输入流的关键方法是读方法,可以以字节流或者字符流的形式读取外部内容到计算机;从计算机内存写数据到外部设备(如磁盘)是为输出流,因此输出流的关键方法是写方法,可以把内存中的内容以字节流或者字符流的形式写入外部设备中;至于何时使用字节流进行输入还是输出,何时使用字符流进行输入还是输出,取决于我们操作的内容。

3.Java中的字节流
3.1 输入字节流

输入字节流的父类是抽象类InputStream,类名本意也是输入流的意思;

public abstract class InputStream implements Closeable {
	// 抽象的read(),子类需要实现read
	public abstract int read() throws IOException;
	public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
	}
	public void close() throws IOException {}
	public synchronized void mark(int readlimit) {}
}

父类InputStream中有一个重要的抽象方法read(),需要在子类中实现,方法的本意是:

Reads the next byte of data from the input stream. The value byte is returned as an int in the range 0 to 255. If no byte is available because the end of the stream has been reached, the value -1 is returned. This method blocks until input data is available, the end of the stream is detected,or an exception is thrown.
从输入流的数据中读取下一个字节,方法返回的byte值是以0-255的int类型表现出来,如果读取到流的末尾而没有返回则返回-1,同时该read()方法也是阻塞的。

另外一个重要的方法是read(byte b[]),方法的意思是:

Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes actually read is returned as an integer. This method blocks until input data is available, end of file is detected, or an exception is thrown.
从输入流中读取byte值,并把它存储在byte类型数组b中,数组b实际存储的是int类型的值;也是一个阻塞方法。

public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
}

很明显,如果要调用该方法,参数必须是自己定义的数组,也就是说一次读取的字节个数由我们定义的,后面有实例会讲到。

它调用下面这个方法:

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;
        }
   
        // 通过read()方法读取单个字符
        int c = read();
        if (c == -1) {
            return -1;  // 如果输入流为空,则返回-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;
    }

方法中有三个点需要注意:

  • 读取单个字节的方式是read(),也就是上面说的抽象方法,所以字节流和字符流的区别就在于读取字节流的不同;
  • 该方法除了抛出异常外,返回的是一个int类型的值:若参数长度为0则返回0,即读取0个字节;若输入流为空则返回-1;若读取到输入流则返回1;
  • 读取的字节保存在b[]数组内,即JVM内存中,供程序使用;
    inputStream
    从上面的类图可以看出,输入字节流的种类有很多:
  • SequenceInputStream:该输入字节流最大的特点是可接受多个相同或者不同的输入字节流,读取方式决定于列表中输入字节流的类别;
  • FileInputStream:文件输入字节流;这个使用的较多,后面重点来说;
  • PipedInputStream:管道输入字节流;主要用于从网络某管道读取字节流,暂时还没有遇见过;
  • ByteArrayInputStream:字节数组输入字节流;这种方式比较简单,因为输入类别与读取之后的类别一致,都是字节数组;
  • StringBufferInputStream:字符输入字节流;该类已经不推荐使用,就不介绍了;
  • FilterInputStream:过滤输入字节流;读取方式决定于需要过滤的输入字节流;
  • DataInputStream:FilterInputStream的子类,可以按需读取各种类别的字节,感兴趣的可以看看源码;
  • BufferedInputStream:FilterInputStream的子类,默认从输入流中读取8192个字节;
  • LineNumberInputStream:FilterInputStream的子类,是一个废弃的类;

下面以我们常见的FileInputStream为例,用代码写个示例熟悉如何读取的。

static void fileInputStreamTest() throws IOException {
	FileInputStream fis = new FileInputStream("file/fis.txt");
	byte[] fisByte = new byte[100];
	int readNum = 0;
	
	while ((readNum = fis.read(fisByte)) > 0) {
		System.out.println(new String(fisByte, 0, readNum));
			
	}
	fis.close();
}

有几个点可以知道:

  • FileInputStream是文件输入字节流,输入的是文件,因此构造函数中给出了文件的路径;构造函数也可以是File类的对象;
  • fisByte数组存储每次读取的字节流,这里规定每次读取100个字节,然后为了直观就打印出来了;
  • 如果分析FileInputStream类的源码,会发现读取的方法基本都是本地方法,也就是说调用的基本是c语言的方法;大概是在IO中,从文件中获取内容的运用比较广泛,所以采用本地方法便于提高效率。

综上,输入字节流的关键是不同形式的来源导致读取的方式不同,但最后都是以字节的形式存储在JVM内存中。不同的来源包括文件形式,网络管道形式,字节数组形式等等,正是因为多种来源才导致我们需要定义每种来源的read()方法。

3.2 输出字节流

输出字节流的父类是一个抽象类OutputStream,它除了实现Closeable接口,还实现Flushable接口

public abstract class OutputStream implements Closeable, Flushable {
    public abstract void write(int b) throws IOException;
    public void write(byte b[]) throws IOException {
        write(b, 0, b.length);
	}
    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 {}
}

输出字节流的主要作用是把内存中的内容以字节流的形式输出到某个地方,这个地方可以是某磁盘上的文件,也可以是其他设备。所以,它的基本方法是抽象方法write(int b),根据输出地方的不同采取不同的write(int b)方法,方法本意是:

Writes the specified byte to this output stream. The general contract for write is that one byte is written to the output stream. The byte to be written is the eight low-order bits of the argument b. The 24 high-order bits of b are ignored.
把指定的字节写入此输出流,一般约定以单个字节写入输出流;要写的字节是参数b的8个低阶位,忽略参数b的24个高阶位。这是因为byte类型是1个字节,也就是8位,而24个高阶位都是补位的0.

输出字节流中关键方法write(byte[],int,int)

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]);   // 把字节数组内容调用write()方法输出
    }
}

这个方法的意思很明显,把指定字节数组中的字节通过write(int b)方法放入输出流中,输出方式由各子类提供

输出字节流还实现Flushable接口,该接口只有一个flush()方法,flush方法的本意是:

Flushes this output stream and forces any buffered output bytes to be written out. The general contract of flush is that calling it is an indication that, if any bytes previously written have been buffered by the implementation of the output stream, such bytes should immediately be written to their intended destination.
If the intended destination of this stream is an abstraction provided by the underlying operating system, for example a file, then flushing the stream guarantees only that bytes previously written to the stream are passed to the operating system for writing; it does not guarantee that they are actually written to a physical device such as a disk drive.

简单来说,就是调用flush()方法会强制把缓冲区的字节写入底层流,底层流就是我们的写入目标文件或者设备

下面是父类OutputStream及其子类的类图:
在这里插入图片描述
基本跟输入字节流对应:

  • ByteArrayOutputStream:字节数组输出字节流,把JVM内存中的字节数组输出到某个字节数组中;
  • FileOutputStream:文件输出字节流,把JVM内存中的字节数组输出到某个文件中,后面会用实例来说明;
  • FilterOutputStream:与FilterInputStream对应,有相应的输入就有对应的输出;
  • ObjectOutputStream:对象输出字节流,可以把JVM内存中的字节数组按照原生类型或者对象的形式输出;
  • PipedOutputStream:管道输出字节流,可以把JVM内存中的字节数组输出到某网络管道中;

下面以我们最熟悉的FileOutputStream为例了解下字节数组如何输出到文件中:

static void fileOutputStreamTest() throws IOException {
	try {
		FileInputStream fis = new FileInputStream("file/fis.txt");
		FileOutputStream fos = new FileOutputStream("file/fos.txt");
		byte[] fisByte = new byte[100];
		int readNum = 0;
		
		while((readNum = fis.read(fisByte)) > 0) {
			fos.write(fisByte, 0, readNum);
		}
		System.out.println("write success!");
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	}
}

本实例的意思是从文件”file/fis.txt”获取内容(通过read(byte[])方法),获取的内容放入fisByte字节数组中并通过write()方法写入到文件”file/fos.txt”;这里重点是write(byte[],int,int)方法,查看底层源码,其实调用的是writeByte(byte[],int,int,boolean),该方法是本地方法,其他重载的write()方法,如write(int)和write(byte[])都是调用的本地方法。

综上,输出字节流的关键是输出目的地,根据目的地的不同write()及其重载的方法也不同。

4 简单总结

这里主要介绍了两个点:

  • 总体说明Java语言中io流的分类及其区别;
  • 介绍分类中的字节流,包括输入字节流和输出字节流;

后面会接着分析输入字符流和输出字符流。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值