字节流与字符流
字节流与字符流
流的概念
在Java.io包中,File类是唯一一个与文件本身有关的程序处理类,但是File只能操作文件的本身,而不能操作文件的内容。在实际开发中,IO操作的核心意义在于:输入与输出操作。
对于流的处理形式,在java.io包中提供有两类支持:
字节处理流:OutputStream(输出字节流)、InputStream(输入字节流);
字符处理流:Writer(输出字符流)、Reader(输入字符流);
所有的流操作都应该采用如下统一的步骤进行(下面以文件处理的流程为例):
1、如果现在进行的是文件的读写操作,则一定要通过File类找到一个文件路径;
2、通过字节流或字符流的子类为父类对象实例化(都是抽象类,所以要用子类);
3、利用字节流或字符流中的方法实现数据的输入与输出操作;
4、流的操作属于资源操作,资源操作必须进行关闭处理。
OutputStream字节输出流
首先看一下OutputStream的定义
public abstract class OutputStream extends Object implements Closeable,Flushable
不难发现,OutputStream实现了Closeable和Flushable两个接口
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
public interface Flushable {
public void flush() throws IOException;
}
OutputStream类定义的是一个公共的输出操作标准,而在这个操作标准中定义了三个内容输出的方法:
方法 | 定义 |
---|---|
输出单个字节数据: | public abstract void write(int b) throws IOException; |
输出一组字节数据: | public void write(byte[] b) throws IOException; |
输出部分字节数据: | public void write(byte[] b,int off,int len) throws IOException; |
需要注意的是:OutputStream类是一个抽象类,抽象类要想获得实例化对象,应该通过子类实例的向上转型完成。
如果现在要进行文件处理操作,则可以使用FileOutputStream子类:
- 因为最终需要发生向上转型的处理关系,所以对于此时的FileOutputStream子类核心的关注点应该是其构造方法:
方法 定义 【覆盖】构造方法: public FileOutputStream(File file) throws FileNotFoundException; 【追加】构造方法: public FileOutputStream(File file,boolean append) throws FileNotFoundException; - 追加中的换行:\r\n
InputStream字节输入流
与OutputStream类对应的是字节输入流,InputStream类主要实现的就是字节数据读取,定义如下:
public abstract class InputStream extends Object implements Closable
在InputStream类中定义有如下的核心方法:
方法 | 定义 |
---|---|
读取单个字节数据(如果读取到底,返回-1): | public abstract int read() throws IOException; |
读取一组字节数据(返回的是读取的个数,没有数据返回-1): | public int read(byte[] b) throws IOException; |
读取一组字节数据的部分: | public int read(byte[] b,int off,int len) throws IOException; |
InputStream同样属于抽象类,依靠子类类实例化对象,如果要从文件读取一定要使用FileInputStream子类,其构造方法为:public FileInputStream(File file) throws FileNotFoundException;
对于字节输入流最麻烦的问题在于,使用read()方法读取的时候,只能够以字节数组为主进行接收。
从JDK1.9开始,在InputStream类中增加了一个新的方法:public byte[] readAllBytes() throws IOException;(读取全部数据,该方法不建议使用)
Writer字符输出流
使用OutputStream字节流输出进行数据输出的时候使用的都是字节类型的数据,而很多情况下,字符串的输出比较方便,所以java.io包于JDK1.1时提供了字符输出流:Writer。
public abstract class Writer extends Object implements Appendable,Closeable,Flushable
Writer类中提供的一些输出方法:
方法 | 定义 |
---|---|
输出字符数组: | public void write(char[] cbuf) throws IOException; |
输出字符串: | public void write(String str) throws IOException; |
使用Writer输出的最大优势是在于可以直接利用字符串完成,而作为字符处理的优势在于中文数据上。
Reader字符输入流
Reader是实现字符输入流的一种类型,其本身属于一个抽象类,定义如下:
public abstract class Reader extends Object implements Readable,Closeable
Reader类中并没有像Writer类一样提供有整个字符串的输入的处操作,只能使用字符数组实现接收。
方法 | 定义 |
---|---|
接收数据: | public int read(char[] cbuf) throws IOException; |
字符流读取的时候只能按照数组的形式来实现操作。
字节流与字符流的区别
在使用OutputStream和Writer输出的时候,都调用了close()方法进行了关闭处理。
但如果在使用OutputStream输出的时候没有使用close()方法关闭输出流,会发现内容依然可以实现正常的输出,但是如果在使用Writer的时候,没有使用close()方法关闭输出流,则内容将无法进行输出,因为Writer使用了缓冲区,当使用close()方法的时候,实际上会出现强制刷新缓冲区的情况,所以这个时候,会将内容进行输出,如果没有关闭,则无法进行输出操作,所以在不关闭的情况下,想要全部输出,可以使用flush()方法强制清空。
字节流在进行处理的时候并不会使用到缓冲区,而字符流会使用到缓冲区。另外使用缓存区的字符流更加适合于进行中文数据的处理。
6、转换流
转换流
转换流可以实现字节流和字符流操作的功能转换,为此,在java.io包中提供有两个类:OutputsTreamWriter、InputStreamReader。
类 | 定义 | 构造 |
---|---|---|
OutputsTreamWriter: | public class OutputsTreamWriter extends Writer; | public OutputStreamWriter(OutputStream out); |
InputStreamReader: | public class InputStreamReader extends Reader; | public InputStreamReader(InputStream in); |
通过类的继承结构于构造方法可以发现,所谓的转换处理,就是将接收到的字节流对象通过向上转型变成字符流对象。
对于OutputStream类有FileOutputStream直接子类,InputStream类有FileInputStream直接子类,但是对于FileWriter、FileReader类的继承关系。
类 | 定义 |
---|---|
FileWriter: | public class FileWrtier extends OutputStreamWriter |
FileReader: | public class FileReader extends InputStreamReader |
实际上的缓存区可以理解为程序中间的一道缓冲区。
文件拷贝,从JDK1.9开始InputStream和Reader类都追加又数据转存的方法:
类 | 方法 |
---|---|
InputStream: | public long transferTo(OutputStream out) throws IOException; |
Reader: | public long transferTo(Writer out) throws IOException; |
IO流操作深入
字符编码
在实际开发中常用的编码:
GBK/GB2312:国标编码,可以描述中文信息,其中GB2312只描述简体中文,而GBK包含有简体中文与繁体中文;
ISO8859-1:国际通用编码,可以用于描述所有字母信息,如果是象形文字则需要进行编码处理;
UNICODE编码:采用十六进制的方式存储,可以描述所有的文字信息;
UTF编码:象形文字部分使用十六进制编码,而普通的字母采用的是ISO8859-1编码,其优势在于适合快速传输,节约带宽,首选编码,主要使用“UTF-8”编码。
方法 | 定义 |
---|---|
列出本机属性: | System.getProperties().list(System.out); |
内存操作流
文件操作流的特点,程序利用InputStream读取文件内容,再利用OutputStream向文件输出内容。所有操作都是以文件为终端的。
如果此时需要实现IO操作,但是又不希望产生文件,则就可以以内存为终端进行处理。
在Java中提供有两类内存操作流:
字节内存操作流:ByteArrayOutputStream、ByteArrayInputStream;
字符内存操作流:charArrayWriter、charArrayReader;
类 | 构造方法 |
---|---|
ByteArrayInputStream: | public ByteArrayInputStream(byte[] buf); |
ByteArrayOutputStream: | public ByteArrayOutputStream(); |
在ByteArrayOutputStream类中提供了一个可以获取全部保存在内存流中的数据信息的方法:
方法 | 定义 |
---|---|
获取数据: | public byte[] toByteArray(); |
使用字符串的形式来获取: | public String toString(); |
管道流
管道流主要的功能是实现两个线程之间的IO处理操作。
对于管道流也是分为两类:
字节管道流:PipedOutputStream、PipedInputStream;
方法 | 定义 |
---|---|
连接处理: | public void connect(PipedInputStream snk) throws IOException; |
字符管道流:PipedWriter、PipedReader;
方法 | 定义 |
---|---|
连接处理: | public void connect(PipedReader snk) throws IOException; |
(管道流是OutputStream等的子类,即继承结构图同上,与FileInputStream、ByteArrayInputStream等同级,图略)
RandomAccessFile
对于文件内容的操作主要是通过InputStream(Reader)、OutputStream(Writer)来实现,但是利用这些类实现的内容读取只能将数据部分部分读取进来。当文件过大时,如果仍然采用传统的IO操作进行读取和分析根本不可能完成。所以在java.io包中提供了RandomAccessFile类,该类可以实现文件的跳跃式读取,可以只读取文件的部分内容(前提:需要有一个完善的保存形式,即数据保存的位数要确定好)。
RandomAccessFile类中定义有如下操作:
方法 | 定义 |
---|---|
构造方法: | public RandomAccessFile(File file,String mode) throws FileNotFoundException; |
- 文件处理模式:r、rw;
RandomAccessFile最大的特点在于数据的读取处理上,因为所有的数据是按照固定的长度进行保存,所以读取的时候进行跳字节读取:
方法 | 定义 |
---|---|
跳字节读取(向下跳): | public int skipBytes(int n) throws IOException; |
跳字节读取(向上跳): | public void seek(long pos) throws IOException; |
整体的使用之中,由用户自行定义要读取的位置,而后按照指定的结构进行数据的读取。
输入与输出支持
打印流
程序实现内容的输出,核心本质一定要依靠OutputStream类完成,但是OutputStream最大的缺点在于这个类的数据输出操作功能有限:public void write(byte[] b) throws IOException,所有的数据一定要转为字节数组后才可以输出,如果要输出long、double、Date等,必须将数据转为字节的形式才能处理,过于麻烦。
所以在java.io包中提供有了打印流:PrintStream、PrintWriter。
类 | 定义 | 构造方法 |
---|---|---|
PrintStream: | public class PrintStream extends FilterOutputStream implements Appendable,Closeable | public PrintStream(OutputStream out); |
PrintWriter: | public class PrintWriter extends Writer | public PrintWriter(Writer out);public PrintWriter(OutputStream out); |
比起直接使用OutputStream类,使用PrintStream、PrintWriter类处理的操作会更加简单,内容输出首先考虑打印流。
System类对IO的支持
System类是一个从头到尾一直在使用的系统类,该类中提供有三个与输入输出有关的常量:
方法 | 定义 |
---|---|
标准输出(显示器): | public static final PrintStream out; |
错误输出: | public static final PrintStream err; |
标准输入(键盘): | public static final InputStream in; |
System.out和System.err都是同一类型的,但是使用不同的编译软件,软件会对两种进行颜色区分(System.err在Eclipse中输出时颜色为红色)。
最初的设计目的:System.out是输出希望用户可以看到的,而System.err是输出用户不可见的。如果需要也可以修改输出的位置:
方法 | 定义 |
---|---|
修改out的输出位置: | public static void setOut(PrintStream out); |
修改err的输出位置: | public static void setErr(PrintStream err); |
在System类中还提供有in的常量,该常量对应的是标准输入设备键盘的输入处理,可以实现键盘的数据输入。但是这样的输入本身是有缺陷的:如果长度不足,就只能接收部分数据,输入可能需要进行重复的输入流数据接收,而且接收的时候还可能牵扯到中文输入,可能出现乱码问题。
BufferedReader缓冲输入流
BufferedReader类提供的是一个缓冲字符输入流的概念,利用该类可以很好解决输入流数据读取的问题。该类是在最初的时候提供的最完善的数据输入处理(JDK1.5之前),该类提供有一个重要方法:
方法 | 定义 |
---|---|
读取一行数据: | public String readLine() throws IOException; |
Scanner扫描流
java.uitl.Scanner是从JDK1.5之后追加的一个程序类,其主要目的是为了解决输入流的访问问题,可以理解为BufferedReader的替代功能类。在Scanner类中有以下几种方法:
方法 | 定义 |
---|---|
构造方法: | public Scanner(InputStream source); |
判断是否有数据: | public boolean hasNext(); |
取出数据: | public String next(); |
设置分隔符: | public Scanner useDelimiter(String pattern); |
使用Scanner输入数据还有一个最大的特点是可以直接利用正则进行验证判断。
- hasNext()中可以直接添加正则表达式进行判断,next()中同样可以。