Java IO数据流介绍
在程序开发中,将不同输入/输出设备(例如文件、网络、压缩包等)之间的数据传输抽象为流。可以将流分为字节流(以Stream结尾的流)和字符流(以Reader和Writer结尾的流)两种
try-with-resources语句
Java 7提供了一个非常强悍的功能来自动关闭资源:try-with-resources语句(try( resource )),非所有的类都能用try-with-resources处理。实现AutoCloseable接口是使用try-with-resources语句的前提。在try-with-resources语句中,只有实现了AutoCloseable接口的类才会被视为资源进行相关处理,否则会出现编译错误
实现AutoCloseable接口时,AutoCloseable.close()不宜抛出InterrupedException被中断异常,因为程序运行时一旦该异常被抑制,将会引发指针处理方面的问题
按照规范,这里的close方法不能是幂等的,但最佳做法仍然是将其实现为幂等的。也就是说,即便close方法被多次调用,其结果都是一致的
close()是AutoCloseable接口的唯一方法,该方法在程序运行时会被自动调用
public interface AutoCloseable {
void close() throws Exception;
}
(1)try中的多个资源在初始化过程中,一旦发生异常,那些已经完成初始化的资源,将按照与其创建时相反的顺序依次关闭
(2)如果在try-with-resources语句中遇到了异常,close关闭语句会先于catch语句执行
Closeable接口继承了AutoCloseable接口,其close()是幂等的
try中声明的资源,会在try代码块开始执行之前完成资源的实例化
try-with-resources中的资源会被隐式地声明为final
File file = new File("D:/one.txt");
try (OutputStream out = new FileOutputStream(file);
BufferedOutputStream buffer = new BufferedOutputStream(out);) {
} catch (Exception e) {
e.printStackTrace();
}
File类
在计算机系统中,文件是非常重要的存储方式。Java的标准库java.io提供了File对象来操作文件和目录
File类可以对文件操作,也可以对文件夹操作
(1)创建File对象本身不涉及IO操作
(2)可以获取路径getPath()/绝对路径getAbsolutePath()/规范路径getCanonicalPath()
(3)可以获取目录的文件(list())和子目录(listFiles())
(4)可以创建或删除文件和目录
构造方法
构造方法 | 描述 |
---|---|
File(URI uri) | 通过将给定的file: URI转换为抽象路径名来创建新的Fil实例 |
File(String pathname) | 通过将给定的路径名字符串转换为抽象路径名来创建新的File实例 |
File(String parent, String child) | 从父路径名字符串和子路径名字符串创建新的 File实例 |
File(File parent, String child) | 从父抽象路径名和子路径名字符串创建新的File实例 |
常用方法
方法 | 描述 |
---|---|
boolean createNewFile() | 当且仅当不存在具有指定名称的文件时,创建一个新的空文件 |
String getParent() | 获取文件的父路径 |
boolean isHidden() | 判断文件是否是隐藏文件 |
boolean exists() | 判断文件或目录是否存在 |
boolean isFile() | 判断是否是文件 |
boolean isDirectory() | 判断是否是目录 |
String getPath() | 返回文件相对路径名 |
String getAbsolutePath() | 返回文件绝对路径名 |
String getName() | 返回文件或目录名称 |
boolean delete() | 删除此对象指定的文件或目录 |
boolean canRead() | 判断文件是否是可读的 |
boolean canWrite() | 判断文件是否可被写入 |
long length() | 返回文件长度。单位:字节;不存在返回0L |
File createTempFile(String prefix, String suffix) | 在默认临时文件夹中创建一个新的空文件,使用给定前缀和后缀生成其名称 |
long getFreeSpace() | 返回此抽象路径名指定的分区中未匹配的字节数 |
long getTotalSpace() | 返回此抽象路径名指定的分区大小 |
long lastModified() | 获取文件最后修改时间 |
boolean renameTo(File dest) | 重命名文件 |
boolean setLastModified(long time) | 设置文件或文件夹的最后一次修改时间 |
boolean setReadOnly() | 将文件或文件夹设置为只读 |
URI toURI() | 构造一个表示此抽象路径名的file:URI |
文件的常用方法
File类中对文件夹进行操作的常用方法
方法 | 描述 |
---|---|
boolean isDirectory() | 判断是不是文件夹 |
boolean mkdir() | 创建此抽象路径名指定的目录(一个) |
boolean mkdirs() | 创建此抽象路径名指定的目录,包括所有必须但不存在的父目录 |
String[] list() | 返回字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录 |
String[] list(FilenameFilter filter) | 这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录 |
File[] listFile() | 返回抽象路径名数组,这些路径名表示此抽象路径名表示目录中的文件 |
File[] listFiles(FileFilter filter) | 返回抽象路径名数组,这些路径名表示此抽象路径名表示的目录中满足指定过滤器的文件和目录 |
字节流(InputStream/OutputStream)
FileInputStream和FileOutputStream类都用来操作磁盘文件。如果用户的文件读写需求比较简单,则可以使用FileInputStream类和FileOutputStream类,提供了基本的文件读写能力
InputStream抽象类(输入流)
public abstract class InputStream
implements Closeable {}
表示输入字节流的所有类的超类,只有有一个无参构造,Java标准库提供的最基本的输入流,位于java.io包里。java.io包提供了所有同步IO的功能。需要定义InputStream子类的应用InputStream必须始终提供一种返回输入的下一个字节的方法
注意:InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类。这个抽象类定义的一个最重要的抽象方法就是int read();read()方法会读取输入流的下一个字节,并返回字节表示的int值(0~255)。如果已读到末尾,返回-1表示不能继续读取了。
方法
方法 | 描述 |
---|---|
abstract int read() | 从输入流读取数据的下一个字节 |
int read(byte[] b) | 从输入流读取一些字节数,并将它们存储到缓冲区b |
int read(byte[] b, int off, int len) | 从输入流读取最多len字节的数据到一个字节数组 |
long skip(long n) | 跳过并丢弃来自此输入流的n字节数据 |
boolean markSupported() | 测试这个输入流是否支持mark()和reset() |
void reset() | 将此流重新定位到上次在此输入流上调用mark()时的位置 |
void mark(int readlimit) | 标记此输入流中的当前位置 |
int available() | 返回从该输入流中可以读取(或跳过)的字节数的估计值,而不会被下一次调用此输入流的方法阻塞 |
void close() | 关闭此输入流并释放与流相关联的任何系统资源 |
FileInputStream(子类)
public class FileInputStream
extends InputStream{}
FileInputStream是InputStream的一个子类,是InputStream常用的一个实现类,可以从文件获取输入流(文件流中读取数据)。此外,ByteArrayInputStream可以在内存中模拟一个InputStream
ByteArrayInputStream实际上是把一个byte[]数组在内存中变成一个InputStream,虽然实际应用不多,但测试的时候,可以用它来构造一个InputStream
在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的。应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时地关闭,以便让操作系统把资源释放掉,否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行。
InputStream和OutputStream都是通过close()方法来关闭流。关闭流就会释放对应的底层资源。
还要注意到在读取或写入IO流的过程中,可能会发生错误,例如,文件不存在导致无法读取,没有写权限导致写入失败,等等,这些底层错误由Java虚拟机自动封装成IOException异常并抛出。因此,所有与IO操作相关的代码都必须正确处理IOException
(1)Java标准库的java.io.InputStream定义了所有输入流的超类
FileInputStream:实现了文件流输入
ByteArrayInputStream:在内存中模拟一个字节流输入
(2)总是使用try(resource)来保证InputStream正确关闭
构造方法
构造方法 | 描述 |
---|---|
FileInputStream(String name) | 通过打开与实际文件的连接来创建一个FileInputStream ,该文件由文件系统中的路径名name命名 |
FileInputStream(File file) | 通过打开与实际文件的连接创建一个FileInputStream,该文件由文件系统中的File对象file命名 |
FileInputStream(FileDescriptor fdObj) | 创建FileInputStream通过使用文件描述符fdObj ,其表示在文件系统中的现有连接到一个实际的文件 |
子类新增方法
方法 | 描述 |
---|---|
protected void finalize() | 确保当这个文件输入流的close方法没有更多的引用时被调用 |
FileChannel getChannel() | 返回与此文件输入流相关联的唯一的FileChannel对象 |
FileDescriptor getFD() | 返回表示与此FileInputStream正在使用的文件系统中实际文件的连接的FileDescriptor对象 |
基本使用
File file = new File("D:\\one.txt");
InputStream in = new FileInputStream(file);
BufferedInputStream buffer = new BufferedInputStream(in);//缓存
byte[] b = new byte[1024]; // 缓存区
int len = 0;
while ((len = buffer.read(b)) != -1) {// 将数据缓冲到byte数组
// 将byte数组转为字符串输出
System.out.println("文件数据:" + new String(b, 0, len));
}
in.close();
OutputStream抽象类(输出流)
public abstract class OutputStream
implements Closeable, Flushable {}
OutputStream也是抽象类,是所有输出流的超类,输出流接收输出字节并将其发送到某个接收器。这个抽象类定义的一个最重要的抽象方法就是void write(int b);write(int b)方法是写入一个字节到输出流。要注意的是,虽然传入的是int参数,但只会写入一个字节,即只写入int最低8位表示字节的部分
OutputStream也提供了close()方法关闭输出流,以便释放系统资源
注意:OutputStream还提供了一个flush()方法,目的是将缓冲区的内容真正输出到目的地
flush()作用?
因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream有个flush()方法,能强制把缓冲区内容输出
方法
方法 | 描述 |
---|---|
void write(byte[] b) | 将 b.length字节从指定的字节数组写入此输出流 |
void write(byte[] b, int off, int len) | 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流 |
void flush() | 刷新此输出流并强制任何缓冲的输出字节被写出 |
void close() | 关闭此输出流并释放与此流相关联的任何系统资源 |
FileOutputStream(子类)
public class FileOutputStream
extends OutputStream{}
用FileOutputStream可以从文件获取输出流,这是OutputStream常用的一个实现类。此外,ByteArrayOutputStream可以在内存中模拟一个OutputStream
ByteArrayOutputStream实际上是把一个byte[]数组在内存中变成一个OutputStream,虽然实际应用不多,但测试的时候,可以用它来构造一个OutputStream
(1)Java标准库的java.io.OutputStream定义了所有输出流的超类
FileOutputStream 实现了文件流输出
ByteArrayOutputStream 在内存中模拟一个字节流输出
(2)某些情况下需要手动调用OutputStream的flush()方法来强制输出缓冲区
(3)总是使用try(resource)来保证OutputStream正确关闭
构造方法
构造方法 | 描述 |
---|---|
FileOutputStream(String name) | 创建文件输出流以指定的名称写入文件 |
FileOutputStream(File file) | 创建文件输出流以写入由指定的 File对象表示的文件 |
FileOutputStream(File file,boolean append) | 一个具有指定名称的文件中写入数据的输出文件流。如果第二个参数为true,则将文件写入文件末尾处 |
FileOutputStream(FileDescriptor fdObj) | 创建文件输出流以写入指定的文件描述符,表示与文件系统中实际文件的现有连接 |
FileOutputStream(String name, boolean append) | 创建文件输出流以指定的名称写入文件 |
子类新增方法
方法 | 描述 |
---|---|
protected void finalize() | 清理与文件的连接,并确保当没有更多的引用此流时,将调用此文件输出流的 close方法 |
FileChannel getChannel() | 返回与此文件输出流相关联的唯一的FileChannel对象 |
FileDescriptor getFD() | 返回与此流相关联的文件描述符 |
基本使用
File file = new File("D:/one.txt");
OutputStream out = new FileOutputStream(file);
BufferedOutputStream buffer = new BufferedOutputStream(out);//缓存
String str = "OutputStream写入数据";
byte[] bytes = str.getBytes(); // 字符串转为字节数据
buffer.write(bytes);
buffer.close();
字符流(Reader/Writer)
使用FileOutputStream/FileInputStream类向文件中写入/读取数据,都存在不足,即这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件占用两个字节,因此使用字节流,读取不当可能会出现乱码现象,采用字符流Reader或Writer可以避免这种现象
Reader(抽象类)
public abstract class Reader
implements Readable, Closeable {
abstract public int read(char cbuf[], int off, int len) throws IOException;
abstract public void close() throws IOException;
}
Reader是Java的IO库提供的另一个输入流接口。和InputStream的区别是,InputStream是一个字节流,即以byte为单位读取,而Reader是一个字符流,即以char为单位读取
java.io.Reader是所有字符输入流的超类,用于读取字符流的抽象类。子类必须实现的唯一方法是read(char [],int,int)和close()。然而,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率,附加的功能或两者
Reader基于InputStream
Reader本质上是一个基于InputStream的byte到char的转换器,带编码的转换器
InputStream | Reader |
---|---|
字节流,以byte为单位 | 字符流,以char为单位 |
读取字节(-1,0~255):int read() | 读取字符(-1,0~65535):int read() |
读到字节数组:int read(byte[] b) | 读到字符数组:int read(char[] c) |
Fields(字段)
字段 | 描述 |
---|---|
protected Object lock | 用于同步此流上的操作的对象 |
方法
方法 | 描述 |
---|---|
int read() | 读一个字符 |
int read(char[] cbuf) | 将字符读入数组 |
int read(CharBuffer target) | 尝试将字符读入指定的字符缓冲区 |
abstract int read(char[] cbuf, int off, int len) | 将字符读入数组的一部分 |
void mark(int readAheadLimit) | 标记流中的当前位置 |
boolean markSupported() | 告诉这个流是否支持mark()操作 |
boolean ready() | 告诉这个流是否准备好被读取 |
void reset() | 重置流 |
long skip(long n) | 跳过字符 |
abstract void close() | 关闭流并释放与之相关联的任何系统资源 |
(1)Reader定义了所有字符输入流的超类
FileReader:实现了文件字符流输入,使用时需要指定编码
CharArrayReader和StringReader:可以在内存中模拟一个字符流输入
(2)Reader是基于InputStream构造的:可以通过InputStreamReader在指定编码的同时将任何InputStream转换为Reader
(3)总是使用try (resource)保证Reader正确关闭
FileReader
public class FileReader
extends InputStreamReader {}
FileReader是InputStreamReader的一个子类,InputStreamReader是Reader的一个子类,它可以打开文件并获取Reader
读取一个纯ASCII编码的文本文件是没有问题的。但如果文件中包含中文,就会出现乱码,因为FileReader默认的编码与系统相关,例如,Windows系统的默认编码可能是GBK,打开一个UTF-8编码的文本文件就会出现乱码
FileReader是用于读取字符流,该类的构造函数假定默认字符编码和默认字节缓冲区大小是适当的。要自己指定这些值
要避免乱码问题,需要在创建FileReader时指定编码:
Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8);
和InputStream类似,Reader也是一种资源,需要保证出错的时候也能正确关闭,所以我们需要用try (resource)来保证Reader在无论有没有IO错误的时候都能够正确地关闭:
try (Reader reader = new FileReader("src/readme.txt", StandardCharsets.UTF_8) {
// TODO
}
构造方法
构造方法 | 描述 |
---|---|
FileReader(String fileName) | 创建一个新的FileReader,给定要读取的文件的名称 |
FileReader(File file) | 创建一个新的FileReader,给出File读取 |
FileReader(FileDescriptor fd) | 创建一个新的FileReader,给定FileDescriptor读取 |
方法:拥有InputStreamReader、Reader、Object所继承的方法
CharArrayReader(了解)
实现了一个字符缓冲区,可以用作字符输入流(Reader子类)
CharArrayReader可以在内存中模拟一个Reader,它的作用实际上是把一个char[]数组变成一个Reader,这和ByteArrayInputStream非常类似
Fields字段
Fields字段 | 描述 |
---|---|
protected char[] buf | 字符缓冲区 |
protected int count | 这个缓冲区结束的索引 |
protected int markedPos | 标记在缓冲区中的位置 |
protected int pos | 当前的缓冲位置 |
构造方法
构造方法 | 描述 |
---|---|
CharArrayReader(char[] buf) | 从指定的字符数组中创建CharArrayReader |
CharArrayReader(char[] buf, int offset, int length) | 从指定的字符数组中创CharArrayReader |
StringReader(了解)
一个字符流,其源是一个字符串
可以直接把String作为数据源,它和CharArrayReader几乎一样
try (Reader reader = new StringReader("Hello")) {}
基本使用
File file = new File("D:\\one.txt");
Reader red = new FileReader(file);
char a []= new char[1024];
red.read(a);
red.close();
Writer(抽象类)
public abstract class Writer
implements Appendable, Closeable, Flushable {}
Writer是所有字符输出流的超类
Writer基于OutputStream
Writer就是带编码转换器的OutputStream,它把char转换为byte并输出
用于写入字符流的抽象类。子类必须实现的唯一方法是write(char [],int,int)、flush()、close()。然而,大多数子类将覆盖这里定义的一些方法,以便提供更高的效率,附加的功能或两者
OutputStream | Writer |
---|---|
字节流,以byte为单位 | 字符流,以char为单位 |
写入字节(0~255):void write(int b) | 写入字符(0~65535):void write(int c) |
写入字节数组:void write(byte[] b) | 写入字符数组:void write(char[] c) |
无对应方法 | 写入String:void write(String s) |
方法
方法 | 描述 |
---|---|
Writer append(char c) | 将指定的字符附加到此Writer |
Writer append(CharSequence csq) | 将指定的字符序列附加到此Writer |
Writer append(CharSequence csq, int start, int end) | 将指定字符序列的子序列附加到此Writer |
abstract void close() | 关闭流,先刷新 |
abstract void flush() | 刷新流 |
void write(char[] cbuf) | 写入一个字符数组 |
abstract void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(int c) | 写一个字符 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
(1)Writer定义了所有字符输出流的超类
FileWriter:实现了文件字符流输出
CharArrayWriter和StringWriter:在内存中模拟一个字符流输出
(2)使用try (resource)保证Writer正确关闭
(3)Writer是基于OutputStream构造的,可以通过OutputStreamWriter将OutputStream转换为Writer,转换时需要指定编码
FileWriter
public class FileWriter
extends OutputStreamWriter {}
OutputStreamWriter的子类,FileWriter就是向文件中写入字符流的Writer。它的使用方法和FileReader类似
构造方法
构造方法 | 描述 |
---|---|
FileWriter(String fileName) | 构造一个给定文件名的FileWriter对象 |
FileWriter(File file) | 给一个File对象构造一个FileWriter对象 |
FileWriter(String fileName, boolean append) | 构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据 |
FileWriter(File file, boolean append) | 给一个File对象构造一个FileWriter对象 |
FileWriter(FileDescriptor fd) | 构造与文件描述符关联的FileWriter对象 |
CharArrayWriter(了解)
CharArrayWriter可以在内存中创建一个Writer,它的作用实际上是构造一个缓冲区,可以写入char,最后得到写入的char[]数组,这和ByteArrayOutputStream非常类似:
try (CharArrayWriter writer = new CharArrayWriter()) {
writer.write(65);
writer.write(66);
writer.write(67);
char[] data = writer.toCharArray(); 值为{ 'A', 'B', 'C' }
}
StringWriter(了解)
StringWriter也是一个基于内存的Writer,它和CharArrayWriter类似。实际上,StringWriter在内部维护了一个StringBuffer,并对外提供了Writer接口
基本使用
File file = new File("D:\\one.txt");
Writer wr = new FileWriter(file);
String str = "君子以自强不息,厚德载物";
wr.write(str);
wr.close();
字节流、字符流相互转换
InputStreamReader(字节 --> 字符)
字节流到字符流的桥梁:它读取字节,并使用指定的charset将其解码为字符。它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集(Reader子类)
每个调用InputStreamReader的read()方法之一可能会导致从底层字节输入流读取一个或多个字节。为了使字节有效地转换为字符,可以从底层流读取比满足当前读取操作所需的更多字节。
为了最大的效率,请考虑在BufferedReader中包装一个InputStreamReader
Reader in = new BufferedReader(new InputStreamReader(""));
构造方法
构造方法 | 描述 |
---|---|
InputStreamReader(InputStream in) | 使用默认字符集 |
InputStreamReader(InputStream in, Charset cs) | 使用给定字符集 |
InputStreamReader(InputStream in, CharsetDecoder dec) | 使用给定字符集解码器 |
InputStreamReader(InputStream in, String charsetName) | 使用命名字符集 |
方法
方法 | 描述 |
---|---|
int read() | 读一个字符 |
int read(char[] cbuf, int offset, int length) | 将字符读入数组的一部分 |
boolean ready() | 告诉这个流是否准备好被读取 |
String getEncoding() | 返回此流使用的字符编码的名称 |
void close() | 关闭流并释放与之相关联的任何系统资源 |
基本使用
InputStream in = new FileInputStream("");
InputStream bf = new BufferedInputStream(in);
Reader ir = new InputStreamReader(bf);
OutputStreamWriter(字符 --> 字节)
字符流到字节流的桥梁:向其写入的字符编码成使用指定的字节charset。它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
每次调用write()方法都会使编码转换器在给定字符上被调用。所得到的字节在写入底层输出流之前累积在缓冲区中。可以指定此缓冲区的大小,但是默认情况下它大部分用于大多数目的。请注意,传递给write()方法的字符不会缓冲
为了最大的效率,请考虑在BufferedWriter中包装一个OutputStreamWriter,以避免频繁的转换器调用
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
构造方法
构造方法 | 描述 |
---|---|
OutputStreamWriter(OutputStream out) | 创建一个使用默认字符编码OutputStreamWriter |
OutputStreamWriter(OutputStream out, String charsetName) | 创建一个使用命名字符集的OutputStreamWriter |
OutputStreamWriter(OutputStream out, Charset cs) | 创建一个使用给定字符集的OutputStreamWriter |
OutputStreamWriter(OutputStream out, CharsetEncoder enc) | 创建一个使用给定字符集编码器的OutputStreamWriter |
常用方法
方法 | 描述 |
---|---|
String getEncoding() | 返回此流使用的字符编码的名称 |
void flushBuffer() | 将输出缓冲区刷新到底层的字节流,而不进行刷新 |
void write(int c) | 写一个字符 |
void write(char cbuf[], int off, int len) | 写入字符数组的一部分,off开始,到len结束 |
void write(String str, int off, int len) | 写入字符串的一部分,off开始,到len结束 |
void flush() | 刷新流至文件 |
void close() | 关闭流 |
基本使用
OutputStream out = new FileOutputStream("");
OutputStream bf = new BufferedOutputStream(out);
Writer wr =new OutputStreamWriter(bf);
数据流
数据输入/输出流(DataInputStream类与DataOutputStream类)允许应用程序从输入流中读取Java基本数据类型的数据,也就是说,当读取一个数据时,不必再关心这个数值应当是哪种类型。
DataInputStream
public class DataInputStream
extends FilterInputStream
implements DataInput {}
数据输入流允许应用程序以独立于机器的方式从底层输入流读取原始Java数据类型。应用程序使用数据输出流来写入稍后可以被数据输入流读取的数据
DataInputStream对于多线程访问来说不一定是安全的。线程安全是可选的
构造方法
构造方法 | 描述 |
---|---|
DataInputStream(InputStream in) | 创建使用指定的底层的InputStreamDataInputStream |
常用方法
方法 | 描述 |
---|---|
int read(byte[] b) | 从包含的输入流中读取一些字节数,并将它们存储到缓冲区数组b |
int read(byte[] b, int off, int len) | 从包含的输入流读取最多 len个字节的数据为字节数组 |
byte readByte() | 该输入流的下一个字节作为带符号的8位 byte |
int readUnsignedByte() | 该输入流的下一个字节被解释为无符号的8位数字 |
short readShort() | 该输入流的下两个字节,被解释为一个有符号的16位数字 |
int readUnsignedShort() | 该输入流的下两个字节被解释为无符号16位整数 |
int readInt() | 该输入流的接下来的四个字节被解释为 int |
long readLong() | 该输入流的接下来的八个字节,解释为 long |
float readFloat() | 该输入流的接下来的四个字节被解释为 float |
double readDouble() | 这个输入流的接下来的八个字节被解释为 double |
char readChar() | 该输入流的下两个字节,解释为 char |
boolean readBoolean() | boolean值读取 |
void readFully(byte[] b) | 从所包含的输入流中读取此操作的字节数,读取数据的缓冲区 |
void readFully(byte[] b, int off, int len) | b:读取数据的缓冲区,off :数据的起始偏移量,len :要读取的字节数 |
String readUTF() | 一个Unicode字符串 |
static String readUTF(DataInput in) | 一个Unicode字符串,in - 数据输入流 |
int skipBytes(int n) | 要跳过的字节数 |
上述方法都抛出了IOException异常 |
DataOutputStream
public class DataOutputStream
extends FilterOutputStream
implements DataOutput {}
数据输出流使应用程序以便携式方式将原始Java数据类型写入输出流。 然后应用程序可以使用数据输入流来读取数据
Fields 字段
Fields | 描述 |
---|---|
protected int written | 到目前为止写入数据输出流的字节数 |
构造方法
构造方法 | 描述 |
---|---|
DataOutputStream(OutputStream out) | 创建一个新的数据输出流,以将数据写入指定的底层输出流 |
常用方法
方法 | 描述 |
---|---|
void flush() | 刷新此数据输出流 |
int size() | 返回计数器的当前值written,到目前为止写入此数据输出流的字节数 |
void write(int b) | 将指定的字节(参数b的低8位)写入底层输出流 |
void write(byte[] b, int off, int len) | 写入len从指定的字节数组起始于偏移off基础输出流 |
void writeByte(int v) | 将byte作为1字节值写入底层输出流 |
void writeBytes(String s) | 将字符串作为字节序列写入基础输出流 |
void writeShort(int v) | 将short写入底层输出流作为两个字节,高字节优先 |
void writeInt(int v) | 将底层输出流写入 int作为四字节,高位字节 |
void writeLong(long v) | 将long写入底层输出流,为8字节,高字节为首 |
void writeFloat(float v) | 浮子参数的转换 int使用floatToIntBits方法在类 Float ,然后写入该int值基础输出流作为一个4字节的数量,高字节 |
void writeDouble(double v) | 双参数传递给转换long使用doubleToLongBits方法在类Double,然后写入该long值基础输出流作为8字节的数量,高字节 |
void writeChar(int v) | 将char写入底层输出流作为2字节值,高字节优先 |
void writeChars(String s) | 将字符串写入底层输出流作为一系列字符 |
void writeBoolean(boolean v) | 将boolean写入底层输出流作为1字节值 |
void writeUTF(String str) | 使用modified UTF-8编码以机器无关的方式将字符串写入基础输出流 |
PrintStream和PrintWriter(文本输入输出流)
PrintStream
将对象的格式表示打印到文本输出流。这个类实现了全部在发现print种方法PrintStream。它不包含用于编写原始字节的方法,程序应使用未编码的字节流
不像PrintStream类,如果启用自动刷新,它只会在调用的println,print,或format方法来完成,而不是当一个换行符恰好是输出。这些方法使用平台自己的行分隔符而不是换行符
这个类中的方法不会抛出I / O异常,尽管它的一些构造函数可能。 客户可以通过调用checkError()查询是否发生错误
PrintStream是一种FilterOutputStream,它在OutputStream的接口上,额外提供了一些写入各种数据类型的方法
方法 | 描述 |
---|---|
print(int) | 写入int |
print(boolean) | 写入boolean |
print(String) | 写入String |
print(Object) | 写入Object,实际上相当于print(object.toString()) |
println() | 会自动加上换行符 |
经常使用的System.out.println()实际上就是使用PrintStream打印各种数据。其中,System.out是系统默认提供的PrintStream,表示标准输出:
System.out.print(12345); // 输出12345
System.out.print(new Object()); // 输出类似java.lang.Object@3c7a835a
System.out.println("Hello"); // 输出Hello并换行
System.err是系统默认提供的标准错误输出
PrintStream和OutputStream相比,除了添加了一组print()/println()方法,可以打印各种数据类型,比较方便外,它还有一个额外的优点,就是不会抛出IOException,这样在编写代码的时候,就不必捕获IOException
PrintWriter
PrintStream最终输出的总是byte数据,而PrintWriter则是扩展了Writer接口,它的print()/println()方法最终输出的是char数据。两者的使用方法几乎是一模一样的
StringWriter buffer = new StringWriter();
try (PrintWriter pw = new PrintWriter(buffer)) {
pw.println("Hello");
pw.println(12345);
pw.println(true);
}
System.out.println(buffer.toString());
(1)PrintStream是一种能接收各种数据类型的输出,打印数据时比较方便:
System.out是标准输出
System.err是标准错误输出
(2)PrintWriter是基于Writer的输出
Filter模式(装饰者模式,增加功能)
Java的IO标准库使用Filter模式为InputStream和OutputStream增加功能
(1)可以把一个InputStream和任意个FilterInputStream组合
(2)可以把一个OutputStream和任意个FilterOutputStream组合
Filter模式可以在运行期动态增加功能(又称Decorator模式)
Java的IO标准库提供的InputStream根据来源可以包括:
(1)FileInputStream:从文件读取数据,是最终数据源
(2)ServletInputStream:从HTTP请求读取数据,是最终数据源
(3)Socket.getInputStream():从TCP连接读取数据,是最终数据源
(4)…
当需要给一个“基础”InputStream附加各种功能时,先确定这个能提供数据源的InputStream,因为需要的数据总得来自某个地方
Filter例子
1.获取数据源
File配置FileInputStream获取数据来源自文件
File file = new File("");
InputStream in = new FileInputStream(file);
2.使用BufferedInputStream包装
紧接着,希望FileInputStream能提供缓冲的功能来提高读取的效率,因此用BufferedInputStream包装这个InputStream,得到的包装类型是BufferedInputStream,但它仍然被视为一个InputStream
InputStream buffered = new BufferedInputStream(file);
3.使用GZIPInputStream包装
最后,假设该文件已经用gzip压缩,希望直接读取解压缩的内容,就再包装一个GZIPInputStream,但它仍然被视为一个InputStream
InputStream gizp = new GZIPInputStream(buffered);
无论包装多少次,得到对象始终是InputStream,直接用InputStream来引用它,就可以正常读取
InputStream file = new FileInputStream("test.gz");
InputStream buffered = new BufferedInputStream(file);
InputStream gizp = new GZIPInputStream(buffered);
通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式(装饰器模式:Decorator)。它可以让通过少量的类来实现各种功能的组合
字节流
FilterInputStream直接子类
FilterInputStream是所有装饰器类的基类,用于提供特殊的输入流控制
类 | 描述 |
---|---|
BufferedInputStream | 可以防止每次读取时都得进行实际的操作,代表使用缓冲区 |
DataInputStream | 与DataOutputStream搭配使用,可按照可移植方式从流中读取、写入基本数据类型(int、char、long等) |
LineNumberInputStream | 跟踪输入流中的行号,可以调用getLineNumber()和setlineNumber(int) |
PushbackInputStream | 具有“能弹出一个字节的缓冲区”因此可以将读到的最后一个字符回退 |
BufferedInputStream、DataInputStream、LineNumberInputStream、PushbackInputStream、CheckedInputStream、CipherInputStream、DeflaterInputStream、DigestInputStream、InflaterInputStream、ProgressMonitorInputStream |
FilterOutputStream直接子类
FilterInputStream是所有装饰器类的基类,用于提供特殊的输出流控制
类 | 描述 |
---|---|
BufferedOutputStream | 可以防止每次写入时都得进行实际的操作,代表使用缓冲区 |
DataOutputStream | 与DataInputStream搭配使用,可按照可移植方式从流中读取、写入基本数据类型(int、char、long等) |
BufferedOutputStream、DataOutputStream、CheckedOutputStream、CipherOutputStream、DeflaterOutputStream、DigestOutputStream、InflaterOutputStream、PrintStream |
字符流
Reader直接子类
BufferedReader、CharArrayReader、FilterReader、InputStreamReader、PipedReader、StringReader
Writer直接子类
已知直接子类:
BufferedWriter、CharArrayWriter、FilterWriter、OutputStreamWriter、PipedWriter、PrintWriter、StringWriter
字节流具体例子
缓冲流
缓冲是I/O的一种性能优化。缓冲流为I/O流增加了内存缓冲区。有了缓冲区,使得在流上执行skip()、mark()和reset()方法都能成为可能
字节缓冲流
BufferedInputStream
FilterInputStream的子类,BufferedInputStream类可以对所有InputStream类进行带缓冲区的包装以达到性能的优化
为一个输入流添加了缓冲输入和支持mark和reset方法的功能。当创建BufferedInputStream时,将创建一个内部缓冲区数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节。mark操作会记住输入流中的一点,并且reset操作会导致从最近的mark操作之后读取的所有字节在从包含的输入流中取出新的字节之前重新读取。
类 | 描述 |
---|---|
BufferedInputStream(InputStream in) | 创建一个BufferedInputStream并保存其参数 |
BufferedInputStream(InputStream in, int size) | 创建 BufferedInputStream具有指定缓冲区大小,并保存其参数 |
BufferedOutputStream
public class BufferedOutputStream extends FilterOutputStream {}
该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
使用BufferedOutputStream输出信息和用OutputStream输出信息完全一样,只不过有一个flush()方法将缓冲区的数据强制输出完
类 | 描述 |
---|---|
BufferedOutputStream(OutputStream out) | 创建一个有32个字节的缓冲输出流,以将数据写入指定的底层输出流 |
BufferedOutputStream(OutputStream out, int size) | 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流 |
字符缓冲流
BufferedReader类与BufferedWriter类分别继承Reader类和Writer类,这两个类同样具有内部缓冲机制,并可以以行为单位进行输入/输出
BufferedReader:Reader的子类
从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途。
通常,由读取器做出的每个读取请求将引起对底层字符或字节流的相应读取请求。建议将BufferedReader包装在其read()操作可能昂贵的读取器上
BufferedReader in = new BufferedReader(new FileReader(“foo.in”));
将缓冲指定文件的输入。如果没有缓冲,每次调用read()或readLine()可能会导致从文件中读取字节,转换成字符,然后返回,这可能非常低效
构造方法
构造方法 | 描述 |
---|---|
BufferedReader(Reader in) | 创建使用默认大小的输入缓冲区的缓冲字符输入流 |
BufferedReader(Reader in, int sz) | 创建使用指定大小的输入缓冲区的缓冲字符输入流 |
方法
方法 | 描述 |
---|---|
Stream lines() | 返回一个Stream,其元素是从这个BufferedReader读取的行 |
String readLine() | 读取一个文本行,并将其返回为字符串。若无数据可读,则返回null |
BufferedWriter:Writer的子类
将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
提供了一个newLine()方法,它使用平台自己的系统属性line.separator定义的行分隔符概念。 并非所有平台都使用换行符(‘\ n’)来终止行。 因此,调用此方法来终止每个输出行,因此优选直接写入换行符。
一般来说,Writer将其输出立即发送到底层字符或字节流。 除非需要提示输出,否则建议将BufferedWriter包装在其write()操作可能很昂贵的Writer上
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));
将缓冲PrintWriter的输出到文件。如果没有缓冲,每次调用print()方法都会使字符转换为字节,然后立即写入文件,这可能非常低效
构造方法
构造方法 | 描述 |
---|---|
BufferedWriter(Writer out) | 创建使用默认大小的输出缓冲区的缓冲字符输出流 |
BufferedWriter(Writer out, int sz) | 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区 |
方法
方法 | 描述 |
---|---|
void write(String s,int off,int len) | 写入字符串的某一部分 |
void flush() | 刷新该流的缓冲 |
void newLine() | 写入一个行分隔符,也就是换行 |
操作ZIP
ZipInputStream是一种FilterInputStream,它可以直接读取zip包的内容
ZipInputStream可以读取zip格式的流,ZipOutputStream可以把多份数据写入zip包;配合FileInputStream和FileOutputStream就可以读写zip文件
JarInputStream是从ZipInputStream派生,它增加的主要功能是直接读取jar文件里面的MANIFEST.MF文件。因为本质上jar包就是zip包,只是额外附加了一些固定的描述文件
读取zip包(ZipInputStream)
ZipInputStream的基本用法,创建一个ZipInputStream,通常是传入一个FileInputStream作为数据源,然后,循环调用getNextEntry(),直到返回null,表示zip流结束
一个ZipEntry表示一个压缩文件或目录,如果是压缩文件,就用read()方法不断读取,直到返回-1
try (ZipInputStream zip = new ZipInputStream(new FileInputStream)) {
ZipEntry entry = null;
while ((entry = zip.getNextEntry()) != null) {
String name = entry.getName();
if (!entry.isDirectory()) {
int n;
while ((n = zip.read()) != -1) {
// ..
}
}
}
}
写入zip包(ZipOutputStream)
ZipOutputStream是一种FilterOutputStream,它可以直接写入内容到zip包。要先创建一个ZipOutputStream,通常是包装一个FileOutputStream,然后,每写入一个文件前,先调用putNextEntry(),然后用write()写入byte[]数据,写入完毕后调用closeEntry()结束这个文件的打包
File file = new File("");
try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(file))) {
String str = "自强不息、厚德载物!";
byte[] bytes = str.getBytes();
zip.putNextEntry(new ZipEntry(file.getName()));
zip.write(bytes);
zip.closeEntry();
}
上面的代码没有考虑文件的目录结构。如果要实现目录层次结构,new ZipEntry(name)传入的name要用相对路径
序列化(ObjectOutputStream)
序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组
为什么要把Java对象序列化呢?
因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了
一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口
Serializable接口:没有定义任何方法,它是一个空接口。把这样的空接口称为“标记接口”(Marker Interface),实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法
使用序列化
把一个Java对象变为byte[]数组,需要使用ObjectOutputStream。它负责把一个Java对象写入一个字节流。
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
// 写入int:
output.writeInt(12345);
// 写入String:
output.writeUTF("Hello");
// 写入Object:
output.writeObject(Double.valueOf(123.456));
}
System.out.println(Arrays.toString(buffer.toByteArray()));
ObjectOutputStream既可以写入基本类型,如int、boolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object。因为写入Object时需要大量的类型信息,所以写入的内容很大
反序列化(ObjectInputStream)
把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象
使用反序列化
ObjectInputStream负责从一个字节流读取Java对象
try (ObjectInputStream input = new ObjectInputStream(new FileInputStream(""))) {
int n = input.readInt();
String s = input.readUTF();
Double d = (Double) input.readObject();
}
除了能读取基本类型和String类型外,调用readObject()可以直接返回一个Object对象。要把它变成一个特定类型,必须强制转型
readObject()可能抛出的异常有
异常 | 描述 |
---|---|
ClassNotFoundException | 没有找到对应的Class |
InvalidClassException | Class不匹配 |
对于ClassNotFoundException,这种情况常见于一台电脑上的Java程序把一个Java对象
例如:Person对象序列化以后,通过网络传给另一台电脑上的另一个Java程序,但是这台电脑的Java程序并没有定义Person类,所以无法反序列化
对于InvalidClassException,这种情况常见于序列化的Person对象定义了一个int类型的age字段,但是反序列化时,Person类定义的age字段被改成了long类型,所以导致class不兼容
为了避免这种class定义变动导致的不兼容,Java的序列化允许class定义一个特殊的serialVersionUID静态变量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的class版本
public class Person implements Serializable {
private static final long serialVersionUID = 2709425275741743919L;
}
要特别注意反序列化的几个重要特点:
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行
安全性
因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞
实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息
(1)可序列化的Java对象必须实现java.io.Serializable接口,类似Serializable这样的空接口被称为“标记接口”(Marker Interface)
(2)反序列化时不调用构造方法,可设置serialVersionUID作为版本号(非必需)
(3)Java的序列化机制仅适用于Java,如果需要与其它语言交换数据,必须使用通用的序列化方法,例如JSON
流向区分
流 | 描述 |
---|---|
输出流(写文件) | OutputStream和Writer作为基类 |
输入流(读取文件) | InputStream和Reader作为基类 |
按处理数据单元区分
流 | 描述 |
---|---|
字节流 | 字节输入流InputStream基类、字节输出流OutputStream基类 |
字符流 | 字符输入流Reader基类、字符输出流Writer基类 |
字节流是8位通用字节流,字符流是16位Unicode字符流 |
注意:并不是所有的InputStream类的子类都支持InputStream中定义的方法,如skip()、mark()、reset()等,只对某些有用。
①File file=new File(“D:\\ONE.txt”); //指定文件数据源
InputStream fileObject=new FileInputStream(file);
②nputStream fileObject=new FileInputStream(“D:\\ONE.txt”); //name指定文件数据源,包含路径信息
fileObject.available(); //可读取字节数
fileObject.read(); //读取文件的数据,可循环输出
fileObject.close(); //关闭流对象
使用FileReader和BufferedReader读取文本文件
Reader fr=new FileReader(“C:\\myTest.txt”);
BufferedReader br=new BufferedReader(fr);
br.readLine();
br.close();
fr.close();
使用字符流写文本文件
Writer fr=new FileWerter(“C:\\my.txt”);
bw.write(“Holllo”);
bw.flush();
fw.close();
Writer fw=new FileWriter(“C:\\my.txt”);
BufferedWriter bw=new BufferedWriter(fw);
bw.write(“hello”);
bw.flush();
fw.close();
二进制文件的读写
使用字节流类DataInputStream读二进制文件
FileInputStream fis=new FileInputStream(“C:my.txt”);
DataInputStream dis=new DataInputStrean(fis); //数据输入流类的方法读取二进制文件的数据
dis.read();
dis.close();
二进制字节流写文件
FileOutputStream outfile=new FileOutputStream(“C:mt.txt”);
DataOutputStream out=new DataOutputStream(outfile); //数据输出流类的方法写二进制文件的数据
out.write(1);
out.close();