概述
1、数据流
在电脑上的数据有三种存储方式:外存、内存和缓存。数据流是一组有序、有起点和终点的字节的数据序列,包括输入流和输出流。Java 数据流分为两种:字节流
(Byte)和字符流
(Character)。采用数据流的目的就是使得输出输入独立于设备。
(1)数据流分类
① 字节流
以8位为单位对二进制数据进行操作
。这些类都是InputStream和OutputStream的子类。
字节流中最小的数据单元是字节(8位)。
② 字符流
以字符为单位对数据进行操作,在读的时候,将二进制数据转换为字符,在写时,将字符转换为二进制数据
。这些类都是Reader和Writer的子类。
字符流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节(16位)。
两者区别:
①
字节流可用于任何类型的对象
,包括二进制对象,处理单元为1个字节,操作字节和字节数组;字符流只能处理字符或者字符串
,处理单元为2个字节的Unicode字符,操作字符、字符数组或字符串。
② 如果是处理音频文件、图片、歌曲等,建议使用字节流;如果处理纯文本数据,建议使用字符流。
③所有文件的储存是都是字节(byte)的储存
。字符流在写入文本数据时,先把字符编码成字节,再储存这些字节到磁盘;在读取文本文件时,先将字节序列转换成成字节序列再处理。
(2)输入流与输出流
一组有序,有起点和终点的字节的数据序列。包括输入流和输出流。
① 输入流(Input Stream)
程序从输入流读取数据源(数据:外界——>程序)
。数据源包括外界(键盘、文件、网络…),即是将数据源读入到程序的通信通道。
Input Stream 不关心数据源来自何种设备(键盘,文件,网络)。
② 输出流(out Stream)
程序向输出流写入数据(数据:程序——>外界)
。将程序中的数据输出到外界(显示器、打印机、文件、网络…)的通信通道。
Output Stream 不关心数据的目的是何种设备(键盘,文件,网络)。
2、Java 的 I/O 体系
在整个Java.io包中最重要的就是5个类和一个接口:5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable。
Java I/O主要包括如下几个层次,包含三个部分:
(1)
流式部分
――IO的主体部分
如:InputStream类(二进制格式操作)、OutputStream(二进制格式操作)、Reader类(文件格式操作)和Writer类(文件格式操作)。
(2)非流式部分
――主要包含一些辅助流式部分的类
如:File类(文件特征与管理)、RandomAccessFile类(随机文件操作)和FileDescriptor等类;(3)
其他类
–文件读取部分的与安全相关的类
如:SerializablePermission类,以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。
Java 中的 I/O 操作主要是基于数据流进行操作的,输入流用于从源读取数据,输出流用于向目标写数据。
一、字符数据流(Writer/Reader)
字符流就是
在字节流的基础上,加上编码形成的数据流
。在读的时候将二进制数据转换为字符,在写时将字符转换为二进制数据。
,字符流适用于处理字符数据(诸如文本文件)
。
字符流出现的原因:因为字节流在操作字符时,可能会有中文导致的乱码,所以由字节流引申出了字符流
。
1、Reader抽象类
用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
主要成员方法:
测试流是否可以读取
boolean ready() throws IOException;
读取一个字符,返回值为读取的字符,若已达流末尾,返 -1
public int read() throws IOException;
读取 cbuf.length 长度的数据放到 cbuf 数组中,返回值为实际读取的字符的数量
public int read(char cbuf[]) throws IOException;
读取 len 个字符,存放到偏移量为off的 cbuf 数组中,返回值为实际读取的字符数量,该方法必须由子类实现
public abstract int read(char cbuf[],int off,int len) throws IOException;
关闭数据流
public void close() throws IOException;
(1)FileReader(外存文件读入数据)
文件字符输入流。
创建一个 FileReader 对象指向要读取的文件,用于把硬盘文件中的数据以字符的方式读取到内存中
。
构造函数:
// 1、以文件路径的字符串作为参数
FileReader f = new FileReader(“c:/temp.txt”);
// 2、将File对象作为其参数
File f = new file(“c:/temp.txt”);
FileReader f1 = new FileReader(f);
(2)InputStreamReader
InputStreamReader
将字节流转换为字符流
。是字节流通向字符流的桥梁,能将字节流输出转换为字符流输出
,并且能为字节流指定编码字符集
,可输出一个个的字符。如果不指定字符集编码,该解码过程将使用平台默认的字符编码,如:GBK。
构造函数
InputStreamReader(InputStream in) //创建一个使用默认字符集的 InputStreamReader。
InputStreamReader(InputStream in, Charset cs) //创建使用给定字符集的 InputStreamReader。
InputStreamReader(InputStream in, CharsetDecoder dec) //创建使用给定字符集解码器的 InputStreamReader。
InputStreamReader(InputStream in, String charsetName) //创建使用指定字符集的 InputStreamReader。
例子
class InputStreamReaderDemo {
public static void transReadNoBuf() throws IOException {
/**
* 没有缓冲区,只能使用read()方法。
*/
//读取字节流
//InputStream in = System.in;//读取键盘的输入。
InputStream in = new FileInputStream("D:\\demo.txt");//读取文件的数据。
//将字节流向字符流的转换。要启用从字节到字符的有效转换,
//可以提前从底层流读取更多的字节.
InputStreamReader isr = new InputStreamReader(in);//读取
//综合到一句。
//InputStreamReader isr = new InputStreamReader(
//new FileInputStream("D:\\demo.txt"));
char []cha = new char[1024];
int len = isr.read(cha);
System.out.println(new String(cha,0,len));
isr.close();
}
public static void transReadByBuf() throws IOException {
/**
* 使用缓冲区 可以使用缓冲区对象的 read() 和 readLine()方法。
*/
//读取字节流
//InputStream in = System.in;//读取键盘上的数据
InputStream in = new FileInputStream("D:\\demo.txt");//读取文件上的数据。
//将字节流向字符流的转换。
InputStreamReader isr = new InputStreamReader(in);//读取
//创建字符流缓冲区
BufferedReader bufr = new BufferedReader(isr);//缓冲
//BufferedReader bufr = new BufferedReader(
//new InputStreamReader(new FileInputStream("D:\\demo.txt")));可以综合到一句。
/*int ch =0;
ch = bufr.read();
System.out.println((char)ch);
*/
String line;
while((line = bufr.readLine())!=null){
System.out.println(line);
}
isr.close();
}
}
(3)CharArrayReader(从内存字符数组读入数据)
CharArrayReader 是一个把字符数组作为源的输入流的实现
。该类实现了一个将内存中的字符数组用作字符输入流的字符缓冲区(数据源),即该类可利用字符缓冲区当做字符输入流进行读取工作。
构造函数:
CharArrayReader(char array[ ])
CharArrayReader(char array[ ], int start, int numChars)
示例:
public class Main {
public static void main(String[] args) throws Exception {
char[] ch = { 'H', 'E', 'L', 'L', 'O' }; // 定义一个字符数组作为数据源
CharArrayReader car = new CharArrayReader(ch); // 以数据源作为参数创建字符缓冲区对象
int value = 0;
while ((value = car.read()) != -1) {
char c = (char) value;
System.out.print(c + " : ");
System.out.println(value);
}
}
}
输出结果:
(4)StringReader(从内存字符串读入)
StringReader 是一个把字符串作为源的输入流的实现
。该类实现了一个将内存中的字符串用作字符输入流的字符缓冲区(数据源),即该类可利用字符缓冲区当做字符输入流进行读取工作。
构造函数:
StringReader(String str);
(5)BufferedReader(提供缓冲功能)
BufferedReader 是缓冲字符输入流,作用是
为其他字符输入流提供缓冲功能(拥有 8192 字符的缓冲区)
,而且提供了很实用的 readLine,读取一个文本行(reader.readLine()方法返回的一行字符中不包含换行符,所以输出的时候要自己加上换行符),从字符输入流中读取文本
,缓冲各个字符,从而提供字符、数组和行的高效读取。
创建BufferReader时,我们会通过它的构造函数指定某个Reader为参数。BufferReader会将该Reader中的数据分批读取,每次读取一部分到缓冲中;操作完缓冲中的这部分数据之后,再从Reader中读取下一部分的数据。
为什么需要缓冲呢?缓冲中的数据实际上是保存在内存中,而原始数据可能是保存在硬盘或NandFlash中;而我们知道,从内存中读取数据的速度比从硬盘读取数据的速度至少快10倍以上。
源码关键字段
// 字符输入流
private Reader in;
// 字符缓冲区
private char cb[];
//读取字符存储的最末下标+1
private int nChars;
//读取字符存储的起始下标
private int nextChar;
private static final int INVALIDATED = -2;
private static final int UNMARKED = -1;
private int markedChar = UNMARKED;
// 仅在markedChar为0时有效
private int readAheadLimit = 0;
// 如果下个字符是换行符,则跳过--专用于readLine()方法里面控制
private boolean skipLF = false;
// 设置标志时的markedSkipLF--用于mark()方法的变量
private boolean markedSkipLF = false;
// 默认的字符缓冲大小
private static int defaultCharBufferSize =8192;
//用于readLine()方法时初始化StringBuffer的初始容量
private static int defaultExpectedLineLength = 80;
构造函数:
public BufferedReader(Reader in); //创建缓冲区字符输入流
public BufferedReader(Reader in,int size); //创建输入流并设置缓冲区大小
该类有个重要方法:读取文本行
public String readLine() throws IOException
// 读取一个文本行
// 下列字符之一即可认为某行已终止:换行 (’\n’)、回车 (’\r’) 或回车后直接跟着换行。
// 若已达流末尾,返 null
示例:
FileReader fd = new FileReader("java.txt"); // 创建一个 FileReader 对象
BufferReader mbr = new BufferedReader(fd); // 以 FileReader 对象为参数,创建 BufferReader 对象
String line = null;
while((line = mbr.readLine())!= null) { //读取文本行
System.out.println(line)
}
(6)PipedReader
管道字符输入流,用于
读取对应绑定的管道字符输出流写入其内置字符缓存数组buffer中的字符
,借此来实现线程之间的通信
、pr中专门有两个方法供pw调用、receive(char c)、receive(char[] b, int off, intlen)、使得pw可以将字符或者字符数组写入pr的buffer中。
构造方法
PipedReader(PipedWriter src) // 使用默认的buf的大小和传入的pw构造pr
PipedReader(PipedWriter src, int pipeSize) // 使用指定的buf的大小和传入的pw构造pr
PipedReader() // 使用默认大小构造pr
PipedReader(int pipeSize) // 使用指定大小构造pr
源码关键字段:
public class PipedReader extends Reader {
// 标记PipedWriter是否关闭
boolean closedByWriter = false;
// 标记PipedReader是否关闭
boolean closedByReader = false;
// 标记PipedWriter与标记PipedReader是否关闭的连接是否关闭
boolean connected = false;
// 拥有PipedReader的线程
Thread readSide;
// 拥有PipedWriter的线程
Thread writeSide;
//用于循环存放PipedWriter写入的字符数组的默认大小
private static final int DEFAULT_PIPE_SIZE = 1024;
//用于循环存放PipedWriter写入的字符数组
char buffer[];
// buf中下一个存放PipedWriter调用此PipedReader的receive(int c)时、c在buf中存放的位置的下标。
// in为-1时、说明buf中没有可读取字符、in=out时已经存满了。
int in = -1;
// buf中下一个被读取的字符的下标
int out = 0;
}
2、Writer 抽象类
写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
主要成员方法:
将指定字符添加到此 writer,
返回流对象本身
(也就是可以写成这种格式: append().append())public Writer append(char c) ;
将指定字符序列添加到此 writer,返回流对象本身(也就是可以写成这种格式: append().append())
public Writer append(CharSequence csq) ;
将指定字符序列的子序列添加到此 writer.Appendable,返回流对象本身(也就是可以写成这种格式: append().append())
public Writer append(CharSequence csq, int start, int end) ;
将整型值c的低16位写入输出流
public void write(int c) throws IOException;
将字符数组 cbuf[] 写入输出流 ,从而将其输出到 Writer 对象指定的位置
public void write(char cbuf[]) throws IOException;
将字符数组 cbuf[] 中的从索引为off的位置处开始的len个字符写入输出流 ,从而将其输出到 Writer 对象指定的位置
public abstract void write(char cbuf[],int off,int len) throws IOException;
将字符串str中的字符写入输出流 ,从而将其输出到 Writer 对象指定的位置
public void write(String str) throws IOException;
将字符串str 中从索引off开始处的len个字符写入输出流 ,从而将其输出到 Writer 对象指定的位置
public void write(String str,int off,int len) throws IOException;
刷空输出流,并输出所有被缓存的字节。
flush( )
关闭流 ,释放系统资源,关闭前会刷新缓冲区。
close()
(1)FileWrite(将数据写入外存文件)
FileWriter 类对象,用于将字符类型数据写入文件。构造函数
第一个参数表明创建对象时要指定输出的地址
,第二个参数表明我在输出的时候是否要覆盖源内容(默认为false覆盖)
,可以加一个参数true表示追加不覆盖。
构造函数:
FileWriter(File file) // 给一个File对象构造一个 FileWriter 对象。
FileWriter(File file, boolean append) // 给一个File对象构造一个 FileWriter 对象。
FileWriter(FileDescriptor fd) // 构造与文件描述符关联的 FileWriter 对象。
FileWriter(String fileName) // 根据一个给定文件的路径创建 FileWriter 对象。
FileWriter(String fileName, boolean append) // 根据一个给定文件的路径构造一个FileWriter对象,给出一个带有布尔值的文件名,表示是否附加写入的数据
示例:
File file = new File("D:\\www\\love.txt"); // 定义一个File类的对象
Writer out = new FileWriter(file); // 多态的写法
out.write(97); // 97-->对应的字符a 输出a
out.write("\r\n"); // 换行
out.write('a'); // a
out.write('我'); // 我
out.write("我爱中国"); // 我爱中国
out.write("我爱中国",2,2); // 中国
//定义一个字符数组
char[] ch = {'我','爱','世','界'};
out.write(ch); // 我爱世界
out.write(ch,2,2); // 世界
out.flush(); // 刷新输出流
out.write("可以继续输出"); //可以继续输出
out.close(); // 关闭流
out.write(97); // 会报错,因为此时的流已经关闭。java.io.IOException: Stream closed
(2)CharArrayWrite(向内存字符数组写入数据)
字符数组输出流,用于
将字符写入到内存中的字符缓存数组 char[] buf 中
,当此数组存满时会自动扩容
。
在此类上调用 close() 无效,即 close 方法对CharArrayWriter没有影响,可以在流关闭后继续调用此类的方法而不生成IOException
。
源码关键字段:
public class CharArrayWriter extends Writer {
// 字符数组缓冲
protected char buf[];
// 字符数组缓冲大小
protected int count;
}
构造函数:
CharArrayWriter caw = new CharArrayWriter(); // 创建使用默认大小的CharArrayWriter对象
CharArrayWriter caw = new CharArrayWriter(int initialSize); // 创建缓冲区大小为 initialSize 的CharArrayWriter对象
示例:
char[] ArrayLetters = new char[] {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
// 创建CharArrayWriter字符输出流
CharArrayWriter caw = new CharArrayWriter();
// 写入“A”个字符
caw.write('A');
// 写入字符串“BC”个字符
caw.write("BC");
//System.out.printf("caw=%s\n", caw);
// 将ArrayLetters数组中从“3”开始的后5个字符(defgh)写入到caw中。
caw.write(ArrayLetters, 3, 5);
//System.out.printf("caw=%s\n", caw);
// (01) 写入字符0
// (02) 然后接着写入“123456789”
// (03) 再接着写入ArrayLetters中第8-12个字符(ijkl)
caw.append('0').append("123456789").append(String.valueOf(ArrayLetters), 8, 12);
System.out.printf("caw=%s\n", caw);
输出:
(3)StringWriter
StringWriter是
将一个 StringBuffer 对象作为输出目的地
,并且可以在构造函数指定 buffer 的大小。
StringWriter只是将数据写入到一个 buffer 对象中,而不是实质的物理介质,这个对象最后又可以转换成字符串(toString)
。
在字符串缓冲区中收集输出的字符流,可用于构造字符串,关闭流无效,关闭后调用其他方法不会报异常
。
源码关键字段:
public class StringWriter extends Writer {
// StringBuffer 字符串缓冲
private StringBuffer buf;
}
构造函数:
StringWriter = new StringWriter(); // 创建使用默认大小的 StringWriter 对象
StringWriter = new StringWriter(int initialSize); // 创建大小为 initialSize 的 StringWriter 对象
示例:
StringWriter stringWriter = new StringWriter();
stringWriter.write("中国");
stringWriter.append("人民");
String str = stringWriter.toString(); // 将 stringWriter 对象最后又可以转换成字符串
System.out.println(str);
输出:
(4)BufferedWriter
BufferedWriter 是缓冲字符输出流,作用是
为其他字符输出流提供缓冲功能(拥有 8192 字符的缓冲区)
。
使用 BufferedWriter 时,写入的数据并不会先输出到目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出
。
关闭此流时,要先刷新它,强行将缓冲区中的数据写出。否则可能无法写出数据。
。
源码关键字段:
public class BufferedWriter extends Writer {
//字符输出流
private Writer out;
//字符缓冲区
private char cb[];
//设置的字符缓冲区大小变量
private int nChars;
//字符缓冲区中的已存储元素的位置
private int nextChar;
//默认字符缓冲区大小
private static int defaultCharBufferSize = 8192;
/**
* 行分割字符串-
* property at the moment that the stream was created.
*/
private String lineSeparator;
}
构造函数:
BufferedWriter(Writer out); // 默认缓冲区大小构造字符缓冲输出流对象
BufferedWriter(Writer out,int size); // 指定缓冲区大小
(5)OutputStreamWriter
OutputStreamWriter 是 Writer 的子类,将
输出的字符流变为字节流
,即将一个字符流的输出对象变为字节流的输出对象
。它是字符流通向字节流的桥梁,可使用指定的 charset 将要写入流中的字符编码成字节
。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
构造函数
OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, Charset cs) 创建使用给定字符集的 OutputStreamWriter。
OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc) 创建使用给定字符集编码器的 OutputStreamWriter。
源码分析
/**
* 字符输出流、是使用指定的编码或者时系统默认的编码将字节转换成字符。
* 是OutputStream转换成Writer的桥梁、也是PrintStream、能够在构造的时候指定编码的关键。
* 实际上是将OutputStream传递给了StreamEncoder来操作out
*/
public class OutputStreamWriter extends Writer {
//通过StreamEncoder se进行字节与字符的转码。
private final StreamEncoder se;
/**
* 用指定的编码将OutputStream out转换成OutputStreamWriter。
*/
public OutputStreamWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException
{
// 调用父类Writer的构造方法创建一个新的字符流 writer,其关键部分将同步给自身。
super(out);
if (charsetName == null)
throw new NullPointerException("charsetName");
//初始化StreamEncoder se.
se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
}
/**
* 使用默认编码创建osw。
*/
public OutputStreamWriter(OutputStream out) {
super(out);
try {
se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
/**
* 使用指定的字符集创建osw
*/
public OutputStreamWriter(OutputStream out, Charset cs) {
super(out);
if (cs == null)
throw new NullPointerException("charset");
se = StreamEncoder.forOutputStreamWriter(out, this, cs);
}
/**
* 使用指定的字符编码创建osw
*/
public OutputStreamWriter(OutputStream out, CharsetEncoder enc) {
super(out);
if (enc == null)
throw new NullPointerException("charset encoder");
se = StreamEncoder.forOutputStreamWriter(out, this, enc);
}
/**
* 获取字符流使用的字符集、或者编码
*/
public String getEncoding() {
return se.getEncoding();
}
/**
* 将osw中的字符flush到底层OutputStream中、此方法不包含flush底层out中的字节
*/
void flushBuffer() throws IOException {
se.flushBuffer();
}
/**
* 将字节转换成字符后、向out中写入一个字符(字符的编码在构造OutputStreamWriter时指定或者使用默认编码
*/
public void write(int c) throws IOException {
se.write(c);
}
/**
* 将cbuf中的一部分写入到out中
*/
public void write(char cbuf[], int off, int len) throws IOException {
se.write(cbuf, off, len);
}
/**
* 将str一部分写入到out中
*/
public void write(String str, int off, int len) throws IOException {
se.write(str, off, len);
}
/**
* flush此流、实际上是调用StreamEncoder的flush方法、
*/
public void flush() throws IOException {
se.flush();
}
/**
* 关闭StreamEncoder、即关闭OutputSteramWriter.
*/
public void close() throws IOException {
se.close();
}
}
例子
/**
* OutputStreamWriter 演示函数
*
*/
private static void testWrite() {
try {
// 创建文件“file.txt”对应File对象
File file = new File(FileName);
// 创建FileOutputStream对应OutputStreamWriter:将字节流转换为字符流,即写入out1的数据会自动由字节转换为字符。
OutputStreamWriter out1 = new OutputStreamWriter(new FileOutputStream(file), CharsetName);
// 写入10个汉字
out1.write("字节流转为字符流示例");
// 向“文件中”写入"0123456789"+换行符
out1.write("0123456789\n");
out1.close();
} catch(IOException e) {
e.printStackTrace();
}
}
(6)PipedWriter
管道字符输出流,用于
将当前线程的指定字符写入到与此线程对应的管道字符输入流中去
、所以PipedReader(pr)、PipedWriter(pw)必须配套使用、缺一不可
。管道字符输出流的本质就是调用pr中的方法将字符或者字符数组写入到pr中、这一点是与众不同的地方。所以pw中的方法很少也很简单、主要就是负责将传入的pr与本身绑定、配对使用、然后就是调用绑定的pr的写入方法、将字符或者字符数组写入到pr的缓存字符数组中
。
源码关键字段:
public class PipedWriter extends Writer {
//与此PipedWriter绑定的PipedReader
private PipedReader sink;
//标示此流是否关闭。
private boolean closed = false;
}
构造方法
PipedReader(PipedWriter src) // 使用默认的buf的大小和传入的pw构造pr
PipedReader(PipedWriter src, int pipeSize) // 使用指定的buf的大小和传入的pw构造pr
PipedReader() // 使用默认大小构造pr
PipedReader(int pipeSize) // 使用指定大小构造pr
二、字节数据流(InputStream/OutputStream)
在Java中,字节流一般适用于
处理字节数据(诸如图片、视频)
,InputStream 和 OutputStream
分别是字节输入流与字节输出流的基类,它们的子类都是字节流,主要用在按字节来处理二进制数据。
1、InputStream 类
InputStream 为字节输入流,它本身为一个抽象类,必须依靠其子类实现各种功能,此抽象类是表示字节输入流的所有类的超类。 继承自InputStream 的流都是向程序中输入数据的,且数据单位为字节(8bit)。
主要方法
返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
int available()
关闭此输入流并释放与该流关联的所有系统资源。
void close()
在此输入流中标记当前的位置。
void mark(int readlimit)
测试此输入流是否支持 mark 和 reset 方法。
boolean markSupported()
从输入流中读取数据的下一个字节,若已达流末尾,返 -1
abstract int read()
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[] b)
将输入流中最多 len 个数据字节读入 byte 数组。
int read(byte[] b, int off, int len)
将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
void reset()
跳过和丢弃此输入流中数据的 n 个字节。
long skip(long n)
(1)FileInputStream
从文件系统中的某个文件中获得输入字节,用于读取诸如
图像
数据之类的原始字节流。
构造函数
FileInputStream(File file) // 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的 File 对象 file 指定。
FileInputStream(FileDescriptor fdObj) // 通过使用文件描述符 fdObj 创建一个 FileInputStream,该文件描述符表示到文件系统中某个实际文件的现有连接。
FileInputStream(String name) // 通过打开一个到实际文件的连接来创建一个 FileInputStream,该文件通过文件系统中的路径名 name 指定。
(2)ObjectInputStream
对象输入流,
对使用 ObjectOutputStream 写入的对象进行反序列化
。
ObjectOutputStream 和 ObjectInputStream 分别与 FileOutputStream 和 FileInputStream 一起使用时,可以为应用程序提供对对象图形的持久存储。
ObjectInputStream 用于恢复那些以前序列化的对象。用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取
。
对象要能从流中读取,它必须是可序列化的;读取的顺序应该与写入时的顺序是一致的
。
构造方法
ObjectInputStream(InputStream in); // 创建从指定 InputStream 读取的 ObjectInputStream。
示例
private static void read() throws FileNotFoundException, IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("t.tmp");
ObjectInputStream ois = new ObjectInputStream(fis);
// 顺序读取
int readInt = ois.readInt();
String string = (String) ois.readObject();
Date date=(Date) ois.readObject();
System.out.println("readInt="+readInt);
System.out.println("string="+string);
System.out.println("date="+date);
ois.close();
}
(3)ByteArrayInputStream
ByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。
关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可继续读取,而不会产生任何 IOException
。
构造方法
ByteArrayInputStream(byte[] buf) // 创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
ByteArrayInputStream(byte[] buf, int offset, int length) // 创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组。
示例
private static void test1() {
byte[] buf=new byte[]{'h','e','l','l','o'};
// 用一个字节数组来构造一个ByteArrayInputStream
ByteArrayInputStream bais=new ByteArrayInputStream(buf,0,3);
byte[] buff=new byte[bais.available()-1];
try {
bais.read(buff);
System.out.println(new String(buff));
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
bais.close(); // 关闭 ByteArrayInputStream 无效,是一个空实现
} catch (IOException e) {
e.printStackTrace();
}
}
// 仍然可以继续读
int read = bais.read();
System.out.println((char)read);
}
(4)BufferInputStream
BufferedInputStream 是 FilterInputStream 的子类,作为过滤器子类,使用它们可以防止每次读取/发送数据时进行实际的写操作,代表着使用缓冲区。并且
支持 mark() 标记和 reset() 重置方法
,mark 操作记录输入流中的某个点,reset 操作使得在从包含的输入流中获取新字节之前,再次读取自最后一次 mark 操作后读取的所有字节。
BufferedInputStream 本质上是通过一个内部缓冲区数组实现的。例如,在新建某输入流对应的BufferedInputStream后,当我们通过read()读取输入流的数据时,BufferedInputStream会将该输入流的数据分批的填入到缓冲区中。每当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区;如此反复,直到我们读完输入流数据位置
。
不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!
源码关键字段
//内置缓存字节数组的大小 8KB
private static int defaultBufferSize = 8192;
//内置缓存字节数组
protected volatile byte buf[];
//当前buf中的字节总数、注意不是底层字节输入流的源中字节总数
protected int count;
//当前buf中下一个被读取的字节下标
protected int pos;
//最后一次调用mark(int readLimit)方法记录的buf中下一个被读取的字节的位置
protected int markpos = -1;
//调用mark后、在后续调用reset()方法失败之前云寻的从in中读取的最大数据量、用于限制被标记后buffer的最大值
protected int marklimit;
//返回底层流对应的源中有效可供读取的字节数
int available();
//关闭此流、释放与此流有关的所有资源
void close();
//查看此流是否支持mark
boolean markSupport();
//标记当前buf中读取下一个字节的下标
void mark(int readLimit);
//读取buf中下一个字节
int read();
//读取buf中下一个字节
int read(byte[] b, int off, int len);
//重置最后一次调用mark标记的buf中的位子
void reset();
//跳过n个字节、 不仅仅是buf中的有效字节、也包括in的源中的字节
long skip(long n);
构造函数
BufferedInputStream(InputStream in) // 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
BufferedInputStream(InputStream in, int size) // 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
(5)PipedInputStream
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。 通常,
数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream
。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程
。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开
。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
管道连接的方法
①在构造时以管道输出流对象做参数来构造,即使用PipedInputStream(PipedOutputStream src) 构造;
②使用connect(PipedOutputStream src) 方法来建立连接;
构造函数
PipedInputStream() // 创建尚未连接的 PipedInputStream。
PipedInputStream(int pipeSize) // 创建一个尚未连接的 PipedInputStream,并对管道缓冲区使用指定的管道大小。
PipedInputStream(PipedOutputStream src) // 创建 PipedInputStream,使其连接到管道输出流 src。
PipedInputStream(PipedOutputStream src, int pipeSize) // 创建一个 PipedInputStream,使其连接到管道输出流 src,并对管道缓冲区使用指定的管道大小。
示例
public class PipedStream {
public static void main(String[] args) throws IOException {
PipedInputStream input = new PipedInputStream();
PipedOutputStream output = new PipedOutputStream();
// 使输入管道流与输出管道流联通
input.connect(output);
// 将其放在两个线程里,避免发生死锁
new Thread(new Input(input)).start();
new Thread(new Output(output)).start();
}
}
class Input implements Runnable {
private PipedInputStream in;
Input(PipedInputStream in) {
this.in = in;
}
public void run() {
try {
byte[] buf = new byte[1024];
int len = -1;
while ((len = in.read(buf)) != -1) { //read 读取从管道输出流写入的数据,没有读到数据则阻塞
String s = new String(buf, 0, len);
System.out.println("s=" + s);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Output implements Runnable {
private PipedOutputStream out;
Output(PipedOutputStream out) {
this.out = out;
}
public void run() {
try {
while (true) { // 模拟数据写入
Thread.sleep(3000);
out.write("Output管道写入的数据!".getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、OutputStream 类
OutputStream 是以字节为单位的输出流的超类,输出流接受输出字节并将这些字节发送到某个接收器。
主要方法
关闭此输出流并释放与此流有关的所有系统资源。
void close() ;
刷新此输出流并强制写出所有缓冲的输出字节。
void flush()
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
void write(byte[] b, int off, int len)
将指定的字节写入此输出流。
abstract void write(int b)
(1)FileOutputStream
FileOutputStream 是文件字节输出流,它把内存中的数据以字节形式写入到硬盘的文件中,一般用于向文件进行写入操作。
构造函数第一个参数表明创建对象时要指定输出的地址
,第二个参数表明我在输出的时候是要覆盖原内容还是在原内容后追加(默认为false覆盖)
,可以加一个参数true表示追加不覆盖。
使用完流之后,要进行关闭,否侧会占用JVM的一定资源。
构造函数
FileOutputStream(String name) // 创建一个向具有指定名称的文件中写入数据的输出文件流。
FileOutputStream(File file) // 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(File file, boolean append) // –追加 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
例子
public static void main(String[] args)
{
OutputStream fs = null;
try
{
StringBuffer buf = new StringBuffer();
buf.append("Hello JAVASCHOOL!").append("\r\n");
buf.append("Hello www.51gjie.com").append("\r\n");
fs = new FileOutputStream(new File("c:\\51gjie.txt"), true)
fs.write(buf.toString().getBytes()) //S tring 的 getBytes() 方法是得到字符串对应的的字节数组
}
catch(Exception e)
{}
finally
{
fs.close();
}
}
(2)BufferedOutputStream
为输出流提供缓冲作用。
当创建 BufferedOutputStream 时,会创建一个内部缓冲区数组,应用程序可以向底层输出流写入字节数据,当写入数据时,可以不用每次都去调用底层方法,而是直接从缓存区获取数据。
BufferedOutputStream 缓冲输出流在输出的时候,不是直接一个字节一个字节的操作,而是先写入内存的缓冲区内。直到缓冲区满了或者我们调用 close 方法或 flush 方法,该缓冲区的内容才会写入目标
。才会从内存中转移到磁盘上,因此效率是非常高的。
构造方法
public BufferedOutputStream(OutputStream out);
public BufferedOutputStream(OutputStream out,int size);
例子
public static void main(String[] args) throws IOException
{
OutputStream os = new FileOutputStream("c:/51gjie.txt");
OutputStream bs = new BufferedOutputStream(os);
byte[] buffer = "欢迎来到www.51gjie.com".getBytes();
bs.write(buffer);
bs.close();//写入文件
os.close();
}
(3)ByteArrayOutputStream
ByteArrayOutputStream
在内存中创建了一个字节数组,所有发送到输出流的数据都会保存到该字节数组的缓冲区中。数据写入缓冲区以后,然后使用 toByteArray() 和 toString() 输出
。
构造函数
public ByteArrayOutputStream() // 创建使用默认大小的 ByteArrayOutputStream 对象
public ByteArrayOutputStream(int size) // 创建缓冲区大小为 siae 的 ByteArrayOutputStream 对象
例子
public static void main(String[] args)
{
ByteArrayOutputStream bos = null;
FileInputStream fs = null;
try
{
bos = new ByteArrayOutputStream();
fs = new FileInputStream("c:\\51gjie.txt");
int len;
while((len = fs.read()) != -1)
{
// 把读取到的数据逐个写到ByteArrayOutputStream中
bos.write(len);
}
byte[] array = bos.toByteArray();
// 指定utf-8解码的字符集
System.out.println(new String(array, "utf-8"));
fs.close();
}
catch(IOException e)
{}
finally
{
fs.close();
}
}
(4)PipedOutputStream
一个 PipedInputStream 实例对象必须和一个 PipedOutputStream 实例对象进行连接而产生一个通信管道。PipedOutputStream 可以向管道中写入数据,PipedIntputStream 可以读取 PipedOutputStream 向管道中写入的数据。
这两个类主要用来完成线程之间的通信,且必须成对存在
。
构造函数
public PipedOutputStream() // 创建尚未连接的 PipedInputStream
public PipedOutputStream(PipedInputStream snk) // 创建 PipedOutputStream,使其连接到管道输出流 snk。
例子
class Write implements Runnable
{
private PipedOutputStream out;
Write(PipedOutputStream out)
{
this.out = out;
}
public void run()
{
try
{
Thread.sleep(1000);
out.write("hello www.51gjie.com".getBytes());
out.close();
}
catch(Exception e)
{}
}
}
class Read implements Runnable
{
private PipedInputStream in ;
Read(PipedInputStream in )
{
this.in = in ;
}
public void run()
{
try
{
byte[] buf = new byte[1024];
int len = in .read(buf);
String s = new String(buf, 0, len);
System.out.println("读取到的数据:" + s); in .close();
}
catch(IOException e)
{}
}
}
public static void main(String[] args)
{
PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream();
in.connect(out); // 管道连接
Read r = new Read(in);
Write w = new Write(out); // in 是输入的位置,out是已经读出去的位置值,读取和写入是多个线程处理的。如果in<out:代表输入值从out到尾端的值还没读出,0到in的位置也还没读出;如果in>out:代表还有out-in的值没有读取如果in==out :代表没有空间,所有空间都写完了。
// 开启线程
new Thread(r).start();
new Thread(w).start();
}
(5)PrintStream
PrintStream 是打印输出流,是用来装饰其它输出流,它能为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
PrintStream 流打印的字符通过指定的编码方式转换成字节
。
PrintStream 流不是直接将数据写到文件的流,需要传入底层输出流out,而且要实现指定编码方式,需要中间流 OutputStreamWriter,OutputStreamWriter 流实现了字符流以指定编码方式转换成字节流。
PrintStream 流有自动刷新机制
,当向 PrintStream 流中写入一个字节数组后自动调用 flush() 方法。
PrintStream 流永远不会抛出异常
:因为做了 try{}catch(){} 会将异常捕获,出现异常情况会在内部设置标识,通过checkError()获取此标识。
PrintStream 可以通过 setOut() 改变 System.out 不用打印到控制台,而且转到其他媒介。
构造函数
public PrintStream(OutputStream out)
public PrintStream(OutputStream out,boolean autoFlush)
public PrintStream(OutputStream out,boolean autoFlush,String encoding)
public PrintStream(String fileName)
public PrintStream(String fileName,String csn)
public PrintStream(File file)
public PrintStream(File file,String csn)
例子
public static void main(String[] args)
{
PrintStream ps = null;
try
{
FileOutputStream fos = new FileOutputStream("G:/51gjie.txt");
ps = new PrintStream(fos);
}
catch(Exception e)
{
}
if(ps != null)
{
//将输出定向到文件
System.setOut(ps);
//系统打印用PS输出
System.out.println("hello www.51gjie.com");
}
}