文章目录
- 1. IO流
- 2. 字节流
- 2.1 OutputStream
- 2.2 InputStream
- 3. 字符流
- 3.1 Reader
- 3.2 Writer
- 4. IO流中的异常处理
1. IO流
Java中不同的输入和输入源都抽象表述为流(stream),通过流的方法允许程序可以使用相同的方法访问不同的输入和输出源中的数据。Java中流可以分为:
抽象基类 | 输入流 | 输出流 |
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
2. 字节流
在具体学习字节流中的输入流和输出流之前,我们需要明白一个本质概念:一切皆为字节!根据计算机组成原理中的知识可知,任何类型的数据在计算机中只能以二进制的形式存储,表示为一个一个字节的形式。因此,字节流就可以实现任何形式文件的传输,只需要关注所作的操作类型,而不必在意文件的类型。
2.1 OutputStream
java.io.OutputStream表示字节输出流,它是输出字节流的所有相关类的超类。共性的成员方法:
public void close()
: 关闭此输出流并释放与此相关的任何系统资源关闭一个输出流的同时还会冲刷用于该输出流的缓冲区,所有被临时置于缓冲区中,以便用更大的包的形式传递的字节在关闭输出流时都会被送出。
public void flush()
: 刷新此输出流并强制任何缓冲的输出字节被写入public void write(byte[] b)
: 将b.length
的字节从指定的字符数组写入此输出流
- 如果写的第一个字节是正数(0-127),那么显式的时候会查询ASCII表
- 如果写的第一个字节是负数,那么第一个字节和第二个字节共同组成一个中文显式,查询默认码表(GBK)
public void write(byte[] b, int off, int len)
: 从指定的字节数组写入len
长度的字节,从偏移量off
开始输出到此输出流中public abstract void write(int b
): 将指定的字节输出流
下面我们看一个它的子类FileOutputStream,它表示文件字节输出流,作用是把内存中的数据写入到硬盘的文件中。FileOutputStream中的构造方法有:
-
FileOutputStream(String name)
:创建一个向指定名称的文件中写入数据的输出文件流 -
FileOutputStream(File file)
:创建一个向指定的File对象表示的文件中写入数据的文件输出流 -
FileOutputStream(string name, boolean append)
:append为追加写开关 -
FileOutputStream(File file, boolean append)
:append为追加写开关
通过构造方法会创建一个FileOutputStream对象,并根据传递的参数创建一个空文件,最后将FileOutputStream对象指向创建好的文件。
public class OutputStreamTest {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("D:\\data\\Code\\Java_code\\src\\test.txt");
// 一次写一个字节
// fos.write(68);
// 一次写多个字节
byte[] b = {65, 66, 67, 68, 69}; // ABCDE
// fos.write(b, 0, 2);
fos.write(b);
fos.close();
}
}
2.2 InputStream
java.io.InputStreams是字节输入流,它是所有表示字节输入流类的超类。共性的成员方法有:
int read()
: 从输入流中读取数据的下一个字节int read(byte[] b)
::从输入流中读取一定数量的字节,并将其存储在缓冲区数组b中void close()
:关闭此输入流并释放与该留相关的所有系统资源
同样看其中的一个子类FileInputStream,它的作用是将把硬盘文件中的数据读取到内存中使用。构造方法有:
FileInputStream(String name)
FileInputStream(File file)
通过构造方法创建FileInputStream对象,并把FileInputStream对象指定构造方法中读取的文件。字节输入流的使用步骤:
- 创建FileInputStream对象,构造方法中指定要读取的数据源
- 使用FileInputStream对象的
read()
读取文件 - 释放资源
根据InputStreams中读取数据的方法,我们可以使用两种方法读取:
- 一次读取一个字节
public class InputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\data\\Code\\Java_code\\src\\test.txt");
int len = 0;
// -1表示文件的结束符
while ((len = fis.read()) != -1){
System.out.println(len); // 65 66 67 68 69
}
fis.close();
}
}
- 一次读取多个字节
public class InputStreamTest {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\data\\Code\\Java_code\\src\\test.txt");
byte[] b = new byte[2];
int len = 0;
while((len = fis.read(b)) != -1){
System.out.println(len); // 2 2 1
System.out.println(new String(b)); // AB CD ED
}
fis.close();
}
}
String构造方法:
String(byte[] bytes)
:把字节数组转换为字符串String(byte[] bytes, int offset, int length)
:把字节数组的一部分转换为字符串
那么FileInputStream是如何读取多个字节的数据呢?下面通过图来直观的感受一下:
我们使用Java程序读取硬盘中的数据,中间需要JVM和操作系统的参与,这里为了简便只关注两端的情况。当使用read(byte[] b)
方法读取多个字节时,首先要新建一个byte类型数组的缓冲区,每一次读取数据都更新数组中的值,并移动读取硬盘数据的指针,直到遇到文件的结束符-1。
特别是读取完CD之后,后续有效数据只有E一个,因此第四次读取中只更新数组的第一个元素,数组的输出为E D。
- 一次读取全部字节或一部分
-
byte[] readAllBytes()
:读取流中所有的字节 -
int readNBytes(byte[] b, int off, int len)
:读取流中指定数量的字节
private static void read3() throws IOException {
FileInputStream fis = new FileInputStream("src\\test.txt");
byte[] bytes = fis.readAllBytes();
System.out.println(Arrays.toString(bytes)); // [98, 57, 55, 13, 10]
}
除了读取流中字节数据外,InputStream中的long transferTo(OutputStream out)
可以实现将当前输入流中的所有字节传送到指定的输出流。
private static void read4() throws IOException{
FileInputStream fis = new FileInputStream("test.txt");
FileOutputStream fos = new FileOutputStream("test1.txt");
long l = fis.transferTo(fos);
System.out.println(l);
fis.close();
fos.close();
}
其他的一些方法:
int available()
:返回在不阻塞的情况下可获取的字节数long skip(long n)
:在输入流中跳过n个字节,返回实际跳过的字节数void mark(int readlimit)
:在输入流的当前位置打标记boolean markSupported()
: 判断当前输入流是否支持打标记
3. 字符流
前面使用字节流读取和保存英文数据时不会有什么问题,但是中文数据每个字通过都是用多个字节保存,因此如果使用字节流会出现读取和保存不完整字符的问题。因此,更好的方法就是使用字符流。
3.1 Reader
Java.io.Reader是一个抽象类,它是用于读取字符流的所有类的超类,作用是读取信息到内存中。实现类共性的成员方法有:
public void close()
: 关闭并释放与流相关联的任何系统资源public int read()
: 从输入流中读取一个字符public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组中
这里同样先看和文件相关的实现类FileReader ,它负责把硬盘文件中的数据以字符的形式读取到内存中。构造方法:
FileReader(String filename)
FileReader(File file)
public class ReaderTest {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D:\\data\\Code\\Java_code\\src\\test.txt");
// int len = 0;
// while((len = fr.read()) != -1){
// System.out.println((char)len);
// }
char[] c = new char[10];
int len = 0;
while((len = fr.read(c)) != -1){
System.out.println(new String(c, 0, len));
}
fr.close();
}
}
它使用上和字节流并没有太大的区别,所以对比字节流很容易明白Reader中方法的使用。
3.2 Writer
java.io.Writer表示字符输出流,它是所有字符输出流的最顶层的父类,同样它也是一个抽象类。实现类共性的成员方法:
void writer(int c)
: 写入单个字符void write(char[] cbuf)
: 写入字符数组abstract void write(char[] cbuf, int off, int len)
:写入字符数组的某一部分,off为开始索引,len为写入长度void write(String str)
: 写入字符串void write(String str, int off, int len)
: 写入字符串的某一部分void flush()
:刷新该流中的缓冲void close()
:关闭流对象
flush()
和close()
的区别:
- flush() : 刷新缓冲区,流对象可以继续使用
- close() : 先刷新缓冲区,然后系统释放资源,流对象不可以再被使用了
FileWriter表示文件字符输出流,作用是把内存中字符数据写入到文件中。构造方法:
FileWriter(File file)
FileWriter(File, boolean append)
FileWriter(String filename)
FileWriter(String filename, boolean append)
字符输出流的使用步骤:
- 创建FileWriter对象,构造方法中绑定要写入数据的目的地
- 使用FileWriter中的
write()
,把数据写入到内存缓冲区中(字符转换为字节的过程) - 使用FileWriter中
flush()
把内存中缓冲区中的数据刷新到文件中 - 释放资源(会先把内存缓冲区中的数据刷新到文件中)
public class WriterTest{
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("D:\\data\\Code\\Java_code\\src\\test.txt", true);
fw.write(97);
char[] c = {'1', 'b', 'c'};
fw.write(c);
fw.write(c, 0, 2);
fw.write("AAAAA");
fw.write("AAAAA", 0, 2);
fw.close();
}
}
4. IO流中的异常处理
不管是使用字节流还是字符流,当出现异常时程序都会抛出IOException
。我们上面只是简单的使用throws关键字将其抛出,将异常交给JVM处理,同样我们也可以使用try-catch语句块进行自行处理。
public class WriterTest {
public static void main(String[] args){
FileWriter fw = null;
try{
fw = new FileWriter("D:\\data\\Code\\Java_code\\src\\test.txt", true);
for (int i = 0; i < 10; i++) {
fw.write(97 + "\r\n");
}
}catch(IOException e){
e.printStackTrace();
}finally {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
但是上面的写法看起来有点复杂,我们可以使用JDK7之后出现的新特性简化书写。我们在try的后便可以增加一个(),在括号中可以定义流对象,因此,这个流对象只在try中有效。当try中的代码执行完毕,会自动的把该流对象释放,不用写finally手动释放。使用格式为:
try(定义流对象...){
可以会抛出异常的代码
} catch (){
异常处理逻辑
}
public class WriterTest {
public static void main(String[] args){
// JDK7新特性使用
try(FileWriter fw = new FileWriter("D:\\data\\Code\\Java_code\\src\\test.txt", true)){
for (int i = 0; i < 10; i++) {
fw.write(97 + "\r\n");
}
} catch (IOException e){
e.printStackTrace();
}
}
}