按照不同的分类方式,可以把流分为不同的类型。常用的分类有三种。
1、按照流的流向分,可以分为输入流和输出流。(相对于程序而言的输入输出)
2、按照操作单元划分,可以划分为字节流和字符流。
InputStream
和OutputStream
作为基类,而字符流则主要由Reade
r和Writer
作为基类。从这里我们就可以发现,字符流的效率一定是比字节低的!因为计算机的磁盘的存储本质上就是字节存储,如果按照字符去读取和写入,那么我们必须先按照字节读取,然后再进行指定编码读取,再写入到目的文件。这个效率比字节读取和写入要低下。并且字符流实际上只能处理文本文件,当时字节流可以处理一切文件。但是!既然如此为什么还会存在字符流?大家可以去试试读取中文文本,每当按照字节读取时,每读取一次就输出一次,我们会发现输出的是乱码,所以这就是问题(中文字符占1-5个字节),但是当我们使用字节流时就不需要担心了,我们按照了指定的字符编码去读取和写入,所以每一次读取的都是一个字符并且经过Unicode编码,所以就不会出现乱码了。
java.io.InputStream
类是所有Java IO输入流的基类,它是以字节为单位的输入流。
public abstract class InputStream extends Object implements Closeable
InputStream
子类的应用程序必须总是提供返回下一个输入字节的方法。BufferedInputStream
: 提供了缓冲功能。DataInputStream
: 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。PipedInputStream
: 允许以管道的方式来处理流。当连接到一个PipedOutputStream
后,它会读取后者输出到管道的数据。PushbackInputStream
: 允许放回已经读取的数据。SequenceInputStream
: 能对多个inputstream
进行顺序处理。方法名 | 用法说明 |
---|---|
int read() |
读取一个字节并以整数的形式返回(0~255),如果返回-1已到输入流的末尾。 |
int read(byte[] buffer) |
读取一系列字节并存储到一个数组buffer,返回实际读取的字节数,如果读取前已到输入流的末尾返回-1。 |
int read(byte[] buffer, int off, int len) |
读取length个字节并存储到一个字节数组buffer,从off位置开始存,最多len, 返回实际读取的字节数,如果读取前以到输入流的末尾返回-1。 |
int available() |
返回输入流中可以读取的字节数。注意:若输入阻塞,当前线程将被挂起,如果InputStream对象调用这个方法的话,它只会返回0,这个方法必须由继承InputStream类的子类对象调用才有用。 |
long skip(long n) |
此方法是从输入流的当前位置往后跳过n个字符,这样这n个字符就丢失了。当然如果你要找回,可以直接mark,并且reset当前的流位置,重新获取流就可以了。 |
void close() |
使用完后,必须对我们打开的流进行关闭。 |
void mark(int readlimit) |
设置输入流的当前位置,InputStream的mark方法不执行任何操作,只是标记输入流的位置,如果要重新定位到这个位置,需要调用reset()方法才行。 |
boolean markSupported() |
获取输入流是否支持mark和reset操作,InputStream.markSupported只有返回true,则流将以某种方式记住在调用mark之后读取的所有字节,这个时候调用reset()方法才行。 |
void reset() |
重新定位输入流的位置,InputStream.reset()必须先调用mark()方法标识了流的位置,并且调用markSupported返回true,这个时候才能生效。 |
public class File_StreamTest {
public static void main(String[] args) {
FileInputStream in = null;
try {
in = new FileInputStream(new File("D:\\Java文件IO\\Test\\e.txt"));
//TODO:read()方法
// int read = -1; //如果读取不到数据返回-1
int read = in.read();//以字节为单位读取数据,单个数据读取
// while ((read = in.read()) != -1) {
// System.out.print((char) read);
// }
// System.out.println();
//TODO:read(bytes)方法
// int len = in.available(); //获取文件内容的数据大小
// byte[] bytes = new byte[len];
// in.read(bytes);
// System.out.println(new String(bytes));
//TODO:5个字节5个字节读取数据
// byte[] bytes = new byte[5]; //5个字节5个字节读取数据
// int read = -1; //如果读取不到数据返回-1
// while ((read = in.read(bytes)) != -1) {
// System.out.println(new String(bytes));
// }
//TODO:指定每次读数据的数量
byte[] bytes = new byte[1024]; //5个字节5个字节读取数据
int read = -1; //每次读取的数据量
int readLen = 0; //每次往数组中存储数据的数据下标
int len = 3; //指定每次读数据的数量
while ((read = in.read(bytes,readLen,len)) != -1) {
readLen += read;
}
System.out.println(new String(bytes));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
in = null; //释放强引用
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1、为什么read()在读取的时候是按照字节读取的,但是方法返回的却是int类型??
InputStream
替代任何它的子类(比如FileInputStream
)进行开发。这么做能够让你的代码兼容任何类型而非某种确定类型的输入流。Inputstream
是不能重复读取的,它的读取是单向的,因为读取的时候,会有一个pos
指针,他指示每次读取之后下一次要读取的起始位置,当读到最后一个字符的时候,pos
指针不会重置。InputStream
对象,可以先把InputStream
转化成ByteArrayOutputStream
,后面要使用InputStream
对象时,再从ByteArrayOutputStream
转化回来就好了。java.io.OutputStream
类是所有Java IO输出流的基类,它是以字节为单位的输出流。
public abstract class OutputStream implements Closeable, Flushable
OutputStream
子类的应用程序必须总是提供返回下一个输出字节的方法。OutputStream
是以字节为单位的输出流的超类,提供了write()
函数从输出流中读取字节数据。ByteArrayOutputStream
是字节数组输出流,写入ByteArrayOutputStream
的数据被写入到一个byte
数组,缓冲区会随着数据的不断写入而自动增长,可使用toByteArray()
和toString()
获取数据。PipedOutputStream
是管道输出流,和PipedInputStream
一起使用,能实现多线程间的管道通信。FilterOutputStream
是过滤输出流,是DataOutputStream
,BufferedOutputStream
和PrintStream
的超类。DataOutputStream
是数据输出流,用来装饰其他的输出流,允许应用程序以与机器无关方式向底层写入基本Java数据类型。BufferedOutputStream
是缓冲输出流,它的作用是为另一个输出流添加缓冲功能。PrintStream
是打印输出流,用来装饰其他输出流,为其他输出流添加功能,方便的打印各种数据值。FileOutputStream
是文件输出流,通常用于向文件进行写入操作。ObjectOutputStream
是对象输出流,它和ObjectInputStream
一起对基本数据或者对象的持久存储。方法名 | 用法说明 |
---|---|
void write(int b) |
向输出流中写入一个字节数据,该字节数据为参数b的低8位。 |
void write(byte[] b) |
将一个字节类型的数组中的数据写入输出流。 |
void write(byte[] b, int off, int len) |
将一个字节类型的数组中的从指定位置(off)开始的,len个字节写入到输出流。 |
void close() |
关闭并释放输出流资源。 |
void flush() |
将输出流中缓冲的数据全部写出到目的地。 |
class OutputStreamTest {
public static void main(String[] args) {
FileOutputStream out = null;
try {
//FileOutputStream默认会覆盖原文件数据,如果要在文件后面添加数据,append一定要设置成true。
out = new FileOutputStream(
new File("D:\\Java文件IO\\Test\\e.txt"),true);
String str = "Hello World!";
byte[] bytes = str.getBytes();
// for (int i = 0; i < bytes.length; i++) {
// out.write(bytes[i]);
// }
out.write(bytes);
out.flush(); //将输出流中缓冲的数据全部写出到目的地
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
if(out != null) {
out.close(); // 关闭输出流
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1、什么时候需要用 flush 方法?
OutputStream
类是Java IO API中所有输出流的基类。子类包括BufferedOutputStream
,FileOutputStream
等等。OutputStream
是一个典型的装饰者模式,使用的时候直接new子类。OutputStream
可以输出到console
,文件,磁盘等目标媒介中。OutputStream
也属于资源,处理完了以后务必要close()
关闭并释放此流有关的所有系统资源,不然会大量占用系统内存资源,大量不释放资源会导致内存溢出。OutputStream.flush()
方法将所有写入到OutputStream
的数据冲刷到相应的目标媒介中。比如,如果输出流是FileOutputStream
,那么写入到其中的数据可能并没有真正写入到磁盘中。即使所有数据都写入到了FileOutputStream
,这些数据还是有可能保留在内存的缓冲区中。通过调用flush()
方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。Reader是一个抽象类,它是以字符为单位的输入流的公共父类。
public abstract class Reader implements Readable, Closeable
BufferedReader
:从流里面读取文本,通过缓存的方式提高效率,读取的内容包括字符、数组和行。缓存的大小可以指定,也可以用默认的大小,大部分情况下,默认大小就够了。InputStreamReader
:把字节翻译成字符的,可以处理乱码问题。FileReader
:方便读取字符文件的。方法名 | 用法说明 |
---|---|
int read() |
读取一个字符并以整数的形式返回(0~2^16),如果返回-1已到输入流的末尾。 |
int read(char[] cbuf) |
读取一系列字符并存储到一个数组buffer,返回实际读取的字符数,如果读取前已到输入流的末尾返回-1。 |
int read(char[] cbuf, int off, int len) |
读取length个字符,并存储到一个数组buffer,从off位置开始存,最多读取len,返回实际读取的字符数,如果读取前以到输入流的末尾返回-1。 |
void mark(int readlimit) |
在此输入流中标记当前的位置,readlimit - 在标记位置失效前可以读取字节的最大限制。 |
boolean markSupported() |
测试此输入流是否支持 mark 方法。 |
long skip(long n) |
跳过和丢弃此输入流中数据的 n 个字节/字符。 |
void reset() |
将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。 |
boolean ready() |
判断是否准备读取此流。 |
public class File_Test {
public static void main(String[] args) {
//编码对应的问题
Reader reader = null;
try {
reader = new InputStreamReader(
new FileInputStream("D:\\Java文件IO\\Test\\b.txt"), "gbk");
//read();
// int read1 = reader.read();//以两个字节(一个字符)为单位读取数据
//char : -2^15 ~ 2^15 -1
// read1 取值范围:0 ~ 65535 数量: 65536
int read = -1; //.read()读取不到数据的话就返回负一
while ((read = reader.read()) != -1) {
System.out.print((char) read);
}
//read(char[])
// char[] chars = new char[5];
// int read = -1; //.read(bytes) 读取不到数据的话就返回负一
// while ((read = reader.read(chars)) != -1) {
// System.out.print(new String(chars));
// }
//read(char[],int off,int len)
// char[] chars = new char[1024]; //1000000 3
// int read = -1; //每次读到的数据数量
// int readLen = 0; //每次往数组中存储数据的数据的下标
// int len = 3; //指定每次读数据的数量
// while ((read = reader.read(chars, readLen, len)) != -1) {
// readLen += read;
// }
// System.out.println(new String(chars));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
reader = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1、为什么已经有了字符流Reader / Writer,要这个处理流 InputStreamReader / OutputStreamReader 干嘛?就为了给定一个字符编码?
FileReader
用于以字符为单位读取文本文件,能够以字符流的形式读取文件内容。除了读取的单位不同之外,FileReader
与FileInputStream
并无太大差异,也就是说,FileReader
用于读取文本。根据不同的编码方案,一个字符可能会相当于一个或者多个字节。
FileReader
对象进行文件输入操作的时,JVM
先读取本地文本文,然后将其格式转化为Unicode
编码格式进行操作。再用FileWriter
进行文本文件输出时则把Unicode
编码格式再转换成本地(本地主机上)的编码格式(如ASCII
或者GBK
等)。FileReader
与FileWriter
两个类和FileInputStream
和FileOutputStream
两个类的操作方法基本相同,只不过前者基于字符,后者基于字节(byte
),若操作的文件不是文本文件,则建议使用FileInputStream
和FileOutputStream
进行文件的输入输出。Writer类是Java IO中所有Writer的基类。子类包括BufferedWriter和PrintWriter等等。
public abstract class Writer implements Appendable, Closeable, Flushable
BufferedWriter
:字符缓冲输出流。FileWriter
:用来写入字符串到文件。OutputStreamWriter
:写入字符,同时可以设置编码集。方法名 | 用法说明 |
---|---|
void write(int c) |
向输出流中写入一个字符数据,该字节数据为参数b的低16位。 |
void write(char[] cbuf) |
将一个字符类型的数组中的数据写入输出流。 |
void write(char[] cbuf, int offset, int length) |
将一个字符类型的数组中的从指定位置(offset)开始的,length个字符写入到输出流。 |
void write(String string) |
将一个字符串中的字符写入到输出流。 |
void write(String string, int offset, int length) |
将一个字符串从offset开始的length个字符写入到输出流。 |
void flush() |
将输出流中缓冲的数据全部写出到目的地。 |
Writer append(char c) |
将指定字符添加到此 writer。 |
Writer append(CharSequence csq) |
将指定字符序列添加到此 writer。 |
Writer append(CharSequence csq, int start, int end) |
将指定字符序列的子序列添加到此 writer.Appendable。 |
void close() |
关闭此流,但要先刷新它。 |
class IODemoTest {
public static void main(String[] args) {
Writer writer = null; //字符输出流
try {
writer = new OutputStreamWriter(new FileOutputStream //真正写文件的流
("D:\\Java文件IO\\Test\\b.txt", true), "gbk");
// writer.write(8); 每个数字对应一个字符
writer.write("倒萨的机会");
// char[] array = {'a','b','c','a','b','c','a','b','c'};
// writer.write(array);
// writer.write(array, 3, 2);
//业务 1 。2。。。
//业务 3 又需要之前存储的数据了 另外一个线程
writer.flush();//在close之前我们向刷新缓冲区的话。
} catch (IOException e) {
e.printStackTrace();
} finally {
//方法最后执行
try {
if (writer != null) {
writer.close(); //将缓冲区中的数据刷新到文件里
writer = null;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
FileWriter
是文件字符输出流,主要用于将字符写入到指定的打开的文件中,其本质是通过传入的文件名、文件、或者文件描述符来创建FileOutputStream
,然后使用OutputStreamWriter
使用默认编码将FileOutputStream
转换成Writer
(这个Writer
就是FileWriter
)。
FileWriter
写数据的时候,FileWrite
r内部是维护了一个1024个字符数组的,写数据的时候会先写入到它内部维护的字符数组中,如果需要把数据真正写到硬盘上,需要调用flush
或者是close
方法或者是填满了内部的字符数组。FileWriter
的时候,如果目标文件不存在,那么会自动创建目标文件。FileWriter
的时候, 如果目标文件已经存在了,那么默认情况会先情况文件中的数据,然后再写入数据 , 如果需要在原来的基础上追加数据,需要使用“new FileWriter(File , boolean)”
的构造方法,第二参数为true
。