分类 | 字节输入流 | 字节输出流 | 字符输入流 | 字符输出流 |
---|---|---|---|---|
抽象基类 | InputStream | OutputStream | Reader | Writer |
访问文件 | FileInputStream | FileOutputStream | FileReader | FileWriter |
访问数组 | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader | CharArrayWriter |
访问管道 | PipedInputStream | PipedOutputStream | PipedReader | PipedWriter |
访问字符串 | StringReader | StringWriter | ||
缓冲流 | BufferedInputStream | BufferedOutputStream | BufferedReader | BufferedWriter |
转换流 | InputStreamReader | OutputStreamWriter | ||
对象流 | ObjectInputStream | ObjectOutputStream | ||
抽象基类 | FilterInputStream | FilterOutputStream | FilterReader | FileWriter |
打印流 | PrintStream | |||
推回输入流 | PushbackInputStream | PushbackReader | ||
特殊流 | DataInputStream | DataOutputStream |
(1)输入流(InputStream):从文件或网络 资源中将数据输入到程序中的流
FileInputStream
:从文件读取数据,是最终数据源;
ServletInputStream
:从HTTP请求读取数据,是最终数据源;
Socket.getInputStream()
:从TCP连接读取数据,是最终数据源;
…
(2)输出流(OutStream):从程序中将数据输出到文件或网络的流
(1)字符流
(2)字节流
(1)节点流:直接与数据源相连,读入或读出
(2)处理流:处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流
处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接
常用的处理流:
缓冲流:BufferedInputStrean
、BufferedOutputStream
、 BufferedReader
、 BufferedWriter
:增加缓冲功能,避免频繁读写硬盘。
转换流:InputStreamReader
、OutputStreamReader
:实现字节流和字符流之间的转换。
数据流: DataInputStream
、DataOutputStream
:提供将基础数据类型写入到文件中,或者读取出来。
对象流:ObjectInputStream
和ObjectOutputStream
:提供将对象转换成二进制比特流写入到输出流中,也可以将对象从输入流中还原对象数据,构建新对象
读取单个字符
read()
方法返回范围为0x00-0xffff
正好为两个字节write(int c)
方法的参数值会取低16位
构建char类型字符public class WriterTest {
public static void main(String[] args) throws IOException {
try (Reader reader = new FileReader("src/test/resources/text.txt");
Writer writer = new FileWriter("src/test/resources/out.txt")) {
int len;
while ((len = reader.read()) != -1) {
writer.append((char) len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
读取多个字符存放到数组中,并使用read(char[] chars)
方法读取或者将该数组构造成CharSequence
对象,使用append(CharSqquence c,[int start,int end])
方法追加
int len;
char[] chars = new char[2048];
while ((len = reader.read(chars)) != -1) {
writer.append(new String(chars,0,len));
//reader.read(chars);//或者使用
}
BufferedWriter使用缓冲区将一串字符先存到缓冲区,当读或写的数据达到一定量, 再自动往文件里进行读写 ,效率相比FileWrter提高很多
由于设计时使用装饰者模式对原类的一种功能增强,所以使用方法与FileWriter/Reader相同
使用在finally
块调用close()
关闭流对象之前:
close()
方法不会出现问题 ;close()
方法 , 这个时候装在缓冲区的数据就不会自动的朝目标文件进行读或写 , 从而造成缓冲区中的这部分数据丢失 , 所以这个是时候就需要在close()
之前先调用flush()
方法 , 手动使缓冲区数据读写到目标文件public class WriterTest {
public static void main(String[] args) throws IOException {
try (Reader reader = new BufferedReader(new FileReader("src/test/resources/text.txt"));
Writer writer = new BufferedWriter(new FileWriter("src/test/resources/out.txt",true))) {
int len;
char[] chars = new char[2048];
while ((len = reader.read(chars)) != -1) {
writer.append(new String(chars,0,len));
//reader.read(chars);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
PiedWriter/Reader
是字符管道输出输入流,两者采用生产者消费者模式,当一方工作未完成时,另一方处于阻塞状态
PipedWriter
和PipedReader
的作用是可以通过管道进行线程间的通讯。在使用管道通信时,必须将PipedWriter
和PipedReader
配套使用
在使用时可以通过构造方法传入配套字符管道输出输入流来绑定传输方向;也可以通过connect(配套字符管道输出/输入流对象)
方法,连接配套字符管道输出/输入流
java.io.IOException
public boolean ready()
方法判断是否已经连接public class WriterTest {
public static void main(String[] args) throws IOException {
try(PipedReader reader = new PipedReader();
PipedWriter writer = new PipedWriter( reader)) {
//reader.connect(writer);
int len;
char[] chars = new char[2048];
writer.write("begin");
while ((len = reader.read(chars)) != -1) {
writer.append(new String(chars,0,len));
System.out.println((char)reader.read());
//reader.read(chars);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
该类通过传入一个字节流对象构造,是字节流与字符流之间的桥梁,能将字节流输出为字符流,并且能为字节流指定字符集,可输出一个个的字符
构造时可以传入编码方式
public class WriterTest {
public static void main(String[] args) throws IOException {
try(Reader reader = new InputStreamReader(new FileInputStream("src/test/resources/text.txt"),"UTF-8");
Writer writer = new BufferedWriter(new FileWriter("src/test/resources/out.txt"))) {
//reader.connect(writer);
int len;
char[] chars = new char[2048];
while ((len = reader.read(chars)) != -1) {
writer.append(new String(chars,0,len));
//writer.write(chars,0,len);
//System.out.println(new String(chars,0,len));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
PrintWriter
作为OutputStream
的装饰类,通过传入一个OutputStream对象来构造,转化成字符输出流,实现了将基本数据类型,字符串等数据输入到输出流,将数据进行格式化打印功能
public class WriterTest {
public static void main(String[] args) throws IOException {
try(PrintWriter writer = new PrintWriter(System.out) ){
writer.println("hello world");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Closeable
接口此类流为字节流的基本类OutputStream
的直接子类,可以将数据文件按字节或按存满字节数组大小时读取进输入流,并且通过输出流按字节或按字节数组进行输出
read()
方法返回范围为0x00-0xff
正好为1个字节
read(byte[] b,int off,int len)
与read(byte[] b)
两者作用将读取的字节为从off位开始存储,一直存储len位
很多情况下这两个方法并不能读取到期望的字节数,可以使用以下方法来确保该次读取的字节数目,除非中途遇到IO异常或者到了数据流的结尾(EOFException
)
byte[] b = new byte[count];
int readCount = 0; // 已经成功读取的字节的个数
while (readCount < count) {
readCount += in.read(bytes, readCount, count -readCount);
}
write(int c,[boolean append])
方法的参数值会取低8位
构建char类型字符
System.in
为一个静态InputStream
对象,可以通过System.setIn(InputStream in)
来设置
public class StreamTest {
public static void main(String[] args) {
try (FileInputStream in = new FileInputStream("src/test/resources/download.jpg");
FileOutputStream out = new FileOutputStream(("src/test/resources/download_copy.jpg"))) {
int len;
byte[] bytes = new byte[2048];
while((len = in.read(bytes))>0){
out.write(bytes, 0,len );
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
由于java中Char为2个字节,16个位,所以想要通过字节流来读取文本类文件,需要通过字节数组构造String对象来实现,如下:
read()
获取字节,那么对于超出Ascll
码的字符来说将会出现乱码现象try(FileInputStream in = new FileInputStream("src/test/resources/text.txt")){
int len;
byte[] bytes = new byte[1024];
while((len = in.read(bytes))>-1){
String input = new String(bytes,0,len);
System.out.println(input);
}
}catch (Exception e){
}
FilterInputStream
是所有处理流的父类,其方法签名与传入的原始流的方法一模一样,只是调用传入的原始流的方法,因此作用也相同BuffredStream 是常用的一种处理流
BufferedStream在基础流写入内存中能够提高读取与写入速度。但是缓冲区设置的大小对性能也有影响,默认值是4096
字节,并能够根据需求自动增长。并且很多属性都与基础流一致。
int size
,设置缓冲区大小缓冲数据能够减少对操作系统的调用次数,缓冲数据主要存储在缓冲区中,缓冲区是内存中的字节块。BufferedStream类提供从基础数据源或存储库读取字节以及将字节写入基础数据源或存储库的实现,在不需要缓冲区时可以防止缓冲区降级输入和输出速度。
缓冲类型下,会在后台自动下载定长的内容(read()
方法传入数组的大小),读的时候是从缓冲区中拿东西。
在程序逻辑速度大大慢于IO速度时,此方法效率明显。最好是在大文件的情况下,分块读,分块写
当使用BuffeInputStream进行文件写入时,最后一定要在close()
前使用flush()
方法将可能因缓存区中内容不够而没有写入到文件中的数据手动写入到文件中
public class StreamTest {
public static void main(String[] args) throws FileNotFoundException {
try (
BufferedInputStream in = new BufferedInputStream(new FileInputStream("src/test/resources/download.jpg"));
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(("src/test/resources/download_copy.jpg")));
){
int len;
byte[] bytes = new byte[2048];
while ((len = in.read(bytes)) > 0) {
out.write(bytes, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
数据输出流允许应用程序以与机器无关方式将Java基本数据类型写到底层输出流
public final void writeXX()throws IOException
这些方法将指定的基本数据类型以字节的方式写入到输出流
java.io.EOFException
writeInt(1)
写入1,后面用read()
读取,前面写入4个字节,后面一次只读1个字节,调用read()
4次才能全部读完,读取结果为0,0,0,1write(1)
写入1,后面用readInt()
读取,前面写入1个字节,后面一次读4个字节,会造成字节数量不够读的现象,抛出java.io.EOFExceptionpublic class StreamTest {
public static void main(String[] args) throws FileNotFoundException {
try (
DataInputStream in = new DataInputStream(new FileInputStream("src/test/resources/text.txt"));
DataOutputStream out = new DataOutputStream(new FileOutputStream("src/test/resources/text.txt"));
){
for (int i = 0; i < 99; i++) {
out.writeInt(i);
}
for (int i = 0; i < 99; i++) {
System.out.println(in.readInt());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
前提:需要被序列化和反序列化的类必须实现Serializable
接口
Serializable
接口java.io.NotSerializableException
异常transient关键字:
静态变量不能被序列化
该方法可以完成对象的深度拷贝
class Pojo implements Serializable{
private int ID;
private String name;
public Pojo(int ID, String name) {
this.ID = ID;
this.name = name;
}
@Override
public String toString() {
return "Pojo{" +
"ID=" + ID +
", name='" + name + '\'' +
'}';
}
}
public class StreamTest {
public static void main(String[] args) throws FileNotFoundException {
try (
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("src/test/resources/out.txt"));
ObjectInputStream in = new ObjectInputStream(new FileInputStream("src/test/resources/out.txt"));
){
List<Pojo> list = new LinkedList<Pojo>();
list.add(new Pojo('1',"Alice"));
list.add(new Pojo('2',"Boom"));
list.add(new Pojo('3',"Clear"));
out.writeObject(list);
List listCopy = (List)in.readObject();
System.out.println(list.hashCode()+" "+listCopy.hashCode());
System.out.println(list==listCopy);
System.out.println(listCopy);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
220810805 1932239246
false
[Pojo{ID=49, name='Alice'}, Pojo{ID=50, name='Boom'}, Pojo{ID=51, name='Clear'}]
PrintStream
流永远不会抛出异常.因为做了try{}catch(){}
会将异常捕获,出现异常情况会在内部设置标识,通过checkError()
获取此标识PrintStream
流有自动刷新机制,例如当向PrintStream
流中写入一个字节数组后自动调用flush()
方法PrintStream
流打印的字符通过平台默认的编码方式转换成字节,在写入的是字符,而不是字节的情况下,应该使用PrintWriter
print(Object obj)
重载方法和println(Object obj)
重载方法都是通过将对应数据先转换成字符串,然后调用write()
方法写到底层输出流中.
System.out
就被包装成PrintStream流,
System.setOut(PrintStream out)
设置System.err
也是PrintStream流
void System.setErr(PrintStream err)
设置(1)InputStream
静态常量
private static final int MAX_SKIP_BUFFER_SIZE = 2048;
MAX_SKIP_BUFFER_SIZE用于确定跳过时要使用的最大缓冲区大小
抽象方法int read()
public abstract int read() throws IOException;
此方法作用为读取单个字节,返回整数类型,范围为-1~255
int read(byte b[], int off, int len)
从输入流中读取len个字节,从off位开始,依次存储到数组中
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;
}
int c = read();
if (c == -1) {
return -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;
}
byte数组以及开始位置,长度
进行检查,不合理会抛出相应的异常read()
方法读取第一个字节,如果为没有可读字节直接返回-1,如果有则存入数组起始位offint read(byte b[])
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
0作为起始位,数组.length作为终止位,调用read(b, 0, b.length)
方法
long skip(long n)
public long skip(long n) throws IOException {
long remaining = n;
int nr;
if (n <= 0) {
return 0;
}
int size = (int)Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
byte[] skipBuffer = new byte[size];
while (remaining > 0) {
nr = read(skipBuffer, 0, (int)Math.min(size, remaining));
if (nr < 0) {
break;
}
remaining -= nr;
}
return n - remaining;
}
此方法作用为跳过并丢弃掉来自输入流的n字节数据,并返回实际上跳过的字节数量
size=(MAX_SKIP_BUFFER_SIZE, remaining)
的最小值作为要跳过的字节数
(size, remaining)
的较小值来作为读取长度,如果实际上读取的字节数已经小于0则直接退出循环,否则remaining -= nr
,记录剩余的还需跳过的字节数int available()
void close()
void mark(int readlimit)
该方法的作用为标记输入流的当前位置,对于rese()方法的后续调用会将该流重新定位在最后一个标记位,以便于重新读取相同的字节
public synchronized void mark(int readlimit) {}
void reset()
该方法将此流重新定位到上次调用mark()方法标记的位置
如果方法markSupported()
返回true
,则:
mark
由于上述流创建没有被调用,或从流中读取的字节数,比mark
最后调用的参数大,那么IOException
可能会被抛出。IOException
不抛出,那么流被重置为一个状态,使得所有字节读取自最近一次调用mark
(或自文件开始,如果mark
尚未调用)将被重新提供后续read
方法的呼叫者,之后是到重置到的调用mark标记位的下一个输入数据的字节。如果方法markSupported()
返回false
,那么:
reset
可能会抛出一个IOException
。IOException
,则流将重置为固定状态,这取决于输入流的特定类型及其创建方式。 将提供给read方法的后续调用者的read
取决于输入流的特定类型。在InpuStream中总是抛出异常IOException("mark/reset not supported")
,需要子类重写该方法
boolean markSupported()
(2)OutputStream
接口实现
抽象接口void write(int b)
void write(byte b[],int off,int len)
以off为起始位,将数组中len个字节写入到输出流
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]);
}
}
void write(byte b[])
write(byte b[],0,,b.length)
void flush()
Flushable
接口void close()
Closeable
接口,进而实现了AutoCloseable
接口,可以用try with resouces语法实现自动关闭(1)FilterInputStream
该类作为所有输入过滤流的父类,并没有开放构造方法,因此也不能通过new 来创建该类对象
源码:
public class FilterInputStream extends InputStream{
protected volatile InputStream in;
protected FilterInputStream(InputStream in) {
this.in = in;
}
...
}
该类中的所有方法均调用的属性InputStream in
的方法,没有做特别处理,该类也没有增加新的方法
(2)FilterOutputStream
该类作为所有输出过滤流的父类,开放构造方法,因此可以通过new 来创建该类对象
源码:这里只给出与out的方法内容不同的方法
public class FilterOutputStream extends OutputStream {
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
...
public void write(byte b[], int off, int len) throws IOException {
if ((off | len | (b.length - (len + off)) | (off + len)) < 0)
throw new IndexOutOfBoundsException();
for (int i = 0 ; i < len ; i++) {
write(b[off + i]);
}
}
public void close() throws IOException {
try (OutputStream ostream = out) {
flush();
}
}
}
close()
方法将在try()
中使用一个引用指向该对象的out
,然后进行flush()
方法输出所有缓存中的数据,当结束后会自动调用out.close()
方法(try with resource 语法)进行关闭
(1)DataInputStream
该类为FilterInputStream的直接子类
read整数
方法读取整数的实现方式,以readInt
为例
public final int readInt() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
}
read浮点数
方法读取浮点数的实现方式
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
readLine()
方法通过循环调用read()方法读入字节,遇到-1或者’\n’或者’\r\n’结束,然后将读取的字节构建String对象实现读取一行的作用,如果没有数据则返回null
readUTF()
实际上返回了return readUTF(this)
,readeUTF(DataInput in)较为复杂,有兴趣的可以自己看看源码,水平有限,先标记一下 :)//TODO
(2)DataOuputStream
该类为FilterOutputStream的直接子类
write整数
方法写入整数的实现方式,以writeInt
为例
public final void writeInt(int v) throws IOException {
out.write((v >>> 24) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
incCount(4);
}
write浮点数
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
writeBytes(String s)
与writeChars(String s)
public final void writeBytes(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
out.write((byte)s.charAt(i));
}
incCount(len);
}
public final void writeChars(String s) throws IOException {
int len = s.length();
for (int i = 0 ; i < len ; i++) {
int v = s.charAt(i);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 0) & 0xFF);
}
incCount(len * 2);
}
writeBytes
会将字符串的每个字符(2字节)强制转换为byte类型,也就是说超过单个字节的字符会出现乱码现象writeUTF(String s)
也是调用其静态方法static int writeUTF(String str, DataOutput out)
情况较为复杂,也不再此处赘述
使用任何流时都需要调用close()
方法,因为JVM垃圾回收机制不会去主动释放系统资源,应主动释放占有的文件资源
close方法调用应该在finally
块中,防止发生异常不会被调用
可以使用try(流对象) {...}
语句简写
try(声明并new IO流对象() ){
IO流对象的使用
}catch(异常){
...
}finally{
...
后续的处理
}