【字节流、字符流】
- IO流
- 字节流
- 异常处理
- 字符流
- JDK7中关于流异常处理的新特性
IO概述
Java中I/O操作主要是指使用java.io
包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
IO的分类
根据数据的流向分为:输入流和输出流。
- 输入流 :把数据从
其他设备
上读取到内存
中的流。 - 输出流 :把数据从
内存
中写出到其他设备
上的流。
格局数据的类型分为:字节流和字符流。
- 字节流 :以字节为单位,读写数据的流。
- 字符流 :以字符为单位,读写数据的流。
IO的流向说明图解
1.4 顶级父类们
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
字节流
一切皆为字节
一切文件数据(文本、图片、视频等)在存储时,都是以二进制数字的形式保存,都一个一个的字节,那么传输时一样如此。所以,字节流可以传输任意文件数据。在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
字节输出流【OutputStream】
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
/*
* 字节输出流
* java.io.OutputStream 所有字节输入流的超类
* 作用:从java程序,写出文件
* 字节:这样的流每次只操作文件中的1个字节
* 写任意文件
*
* 方法都是写入文件的方法
* write(int b) 写入1个字节
* write(byte[] b) 写入字节数组
* write(byte[] b,int off,int len)写入字节数组,int 开始写入的索引,int写几个
* close()方法,关闭流对象,释放与流相关的资源
* 流对象,操作文件的时候,自己不做,依赖操作系统
*/
close方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileOutputStream类
OutputStream
是一个抽象类,并不能直接使用,因此我们需要他的子类。
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
构造方法
public FileOutputStream(File file)
:创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流以指定的名称写入文件。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据。
- 构造举例,代码如下:
import java.io.FileOutputStream;
import java.io.IOException;
/*
java.io.FileOutputStream extends OutputStream
FileOutputStream:文件字节输出流
作用:把内存中的数据写入到硬盘的文件中
构造方法:
FileOutputStream(String name)创建一个向具有指定名称的文件中写入数据的输出文件流。
FileOutputStream(File file) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
参数:写入数据的目的
String name:目的地是一个文件的路径
File file:目的地是一个文件
构造方法的作用:
1.创建一个FileOutputStream对象
2.会根据构造方法中传递的文件/文件路径,创建一个空的文件
3.会把FileOutputStream对象指向创建好的文件
写入数据的原理(内存-->硬盘)
java程序-->JVM(java虚拟机)-->OS(操作系统)-->OS调用写数据的方法-->把数据写入到文件中
字节输出流的使用步骤(重点):
1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
2.调用FileOutputStream对象中的方法write,把数据写入到文件中
3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
*/
public class Demo01OutputStream {
public static void main(String[] args) throws IOException {
//1.创建一个FileOutputStream对象,构造方法中传递写入数据的目的地
FileOutputStream fos = new FileOutputStream("d:\\a.txt");
//2.调用FileOutputStream对象中的方法write,把数据写入到文件中
//public abstract void write(int b) :将指定的字节输出流。
fos.write(97);
//3.释放资源(流使用会占用一定的内存,使用完毕要把内存清空,提供程序的效率)
//fos.close();
}
}
写出字节数据
- 写出字节:
write(int b)
方法,每次可以写出一个字节数据,代码使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("d:\\a.txt");
// 写出数据
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();
}
}
输出结果:
abc
小贴士:
- 虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。
- 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
- 写出字节数组:
write(byte[] b)
,每次可以写出数组中的数据,代码使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("d:\\a.txt");
// 字符串转换为字节数组
byte[] b = "hello".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();
}
}
输出结果:
hello
- 写出指定长度字节数组:
write(byte[] b, int off, int len)
,每次写出从off索引开始,len个字节,代码使用演示:
public class FOSWrite {
public static void main(String[] args) throws IOException {
// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("d:\\a.txt");
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b,2,2);
// 关闭资源
fos.close();
}
}
输出结果:
cd
数据追加续写和换行
经过以上的演示,每次程序运行,创建输出流对象,都会清空目标文件中的数据。如何保留目标文件中数据,还能继续添加新数据呢?
public FileOutputStream(File file, boolean append)
: 创建文件输出流以写入由指定的 File对象表示的文件。public FileOutputStream(String name, boolean append)
: 创建文件输出流以指定的名称写入文件。
这两个构造方法,参数中都需要传入一个boolean类型的值,true
表示追加数据,false
表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写了,代码使用演示:
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* FileOutputStream 文件的续写和换行问题
* 续写:FileOutputStream构造方法,的第二参数中,加入true
* 在文件中写入换行,符号换行 \r\n
*/
public class FileOutputStreamDemo1 {
public static void main(String[] args) throws IOException {
File file = new File("d:\\b.txt");
FileOutputStream fos = new FileOutputStream(file,true);
fos.write("hello\r\n".getBytes());
fos.write("hello\r\n".getBytes());
fos.close();
}
}
- 回车符
\r
和换行符\n
:
- 回车符:回到一行的开头(return)。
- 换行符:下一行(newline)。
- 系统中的换行:
- Windows系统里,每行结尾是
回车+换行
,即\r\n
;- Unix系统里,每行结尾只有
换行
,即\n
;- Mac系统里,每行结尾是
回车
,即\r
。从 Mac OS X开始与Linux统一。
字节输入流【InputStream】
java.io.InputStream
抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。
/*
java.io.InputStream:字节输入流
此抽象类是表示字节输入流的所有类的超类。
定义了所有子类共性的方法:
int read()从输入流中读取数据的下一个字节。
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
void close() 关闭此输入流并释放与该流关联的所有系统资源。
*/
FileInputStream类
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
构造方法
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
当你创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件,会抛出FileNotFoundException
。
- 构造举例,代码如下:
public class FileInputStreamConstructor throws IOException{
public static void main(String[] args) {
// 使用File对象创建流对象
File file = new File("a.txt");
FileInputStream fos = new FileInputStream(file);
// 使用文件名称创建流对象
FileInputStream fos = new FileInputStream("b.txt");
}
}
读取字节数据
- 读取字节:
read
方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
,代码使用演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("09_IOAndProperties\b.txt");
// 读取数据,返回一个字节
int read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
read = fis.read();
System.out.println((char) read);
// 读取到末尾,返回-1
read = fis.read();
System.out.println( read);
// 关闭资源
fis.close();
}
}
循环改进读取方式,代码使用演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象
FileInputStream fis = new FileInputStream("09_IOAndProperties\b.txt");
// 循环读取
/*
发现以上代码是一个重复的过程,所以可以使用循环优化
不知道文件中有多少字节,使用while循环
while循环结束条件,读取到-1的时候结束
布尔表达式(len = fis.read())!=-1
1.fis.read():读取一个字节
2.len = fis.read():把读取到的字节赋值给变量len
3.(len = fis.read())!=-1:判断变量len是否不等于-1
*/
int len = 0; //记录读取到的字节
while((len = fis.read())!=-1){
System.out.print(len);
}
// 关闭资源
fis.close();
}
}
小贴士:
- 虽然读取了一个字节,但是会自动提升为int类型。
- 流操作完毕后,必须释放系统资源,调用close方法,千万记得。
- 使用字节数组读取:
read(byte[] b)
,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
,代码使用演示:
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("09_IOAndProperties\b.txt"); // 文件中为ABCDE
// 定义变量,作为有效个数
int len ;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b))!=-1) {
// 每次读取后,把数组变成字符串打印
System.out.println(new String(b));
}
// 关闭资源
fis.close();
}
}
输出结果:
AB
CD
ED
public class FISRead {
public static void main(String[] args) throws IOException{
// 使用文件名称创建流对象.
FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
// 定义变量,作为有效个数
int len ;
// 定义字节数组,作为装字节数据的容器
byte[] b = new byte[2];
// 循环读取
while (( len= fis.read(b))!=-1) {
// 每次读取后,把数组的有效字节部分,变成字符串打印
System.out.println(new String(b,0,len));// len 每次读取的有效字节个数
}
// 关闭资源
fis.close();
}
}
输出结果:
AB
CD
E
IO 流中异常的处理
/*
* IO流的异常处理
* try catch finally
*
* 细节:
* 1.保证流对象变量,作用域足够
* 2. catch里面怎么处理异常
* 输入异常的信息,目的看到哪里出现了问题
* 停下程序,重新尝试
* 3.如果流对象建立失败了,需要关闭资源吗
* new 对象的时候,失败了,没有占用系统资源
* 释放资源的时候 需要对流对象判断null
* 变量不是null,对象建立成功,需要关闭资源
*/
public class FileOutputStreamDemo2 {
public static void main(String[] args) {
//try 外面声明对象,try 里面建立对象
FileOutputStream fos = null;
try {
fos = new FileOutputStream("d:\\z.txt");
fos.write("hello\r\n".getBytes());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("文件写入失败,请重试!");
}finally {
try {
if(fos!=null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("关闭资源失败");
}
}
}
}
文件复制
案例实现(并伴有异常的处理)
每次读写一个字节的形式,实现文件复制
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* 将数据源 c:\\a.txt
* 复制到 d:\\a.txt 数据目的
* 字节输入流,绑定数据源
* 字节输出流,绑定数据目的
*
* 输入,读取1个字节
* 输出,写1个字节
*/
public class Copy {
public static void main(String[] args) {
//定义两个流的对象变量
FileInputStream fis = null;
FileOutputStream fos = null;
try{
//建立两个流的对象,绑定数据源和数据目的
fis = new FileInputStream("c:\\a.txt");
fos = new FileOutputStream("d:\\b.txt");
//字节输入流,读取1个字节,输出流写1个字节
int len = 0 ;
while((len = fis.read())!=-1){
fos.write(len);
}
}catch(IOException ex){
System.out.println(ex);
throw new RuntimeException("文件复制失败");
}finally{
try{
if(fos!=null)
fos.close();
}catch(IOException ex){
throw new RuntimeException("释放资源失败");
}finally{
try{
if(fis!=null)
fis.close();
}catch(IOException ex){
throw new RuntimeException("释放资源失败");
}
}
}
}
}
流的关闭原则:先开后关,后开先关。
每次读写一个字节,我们发现速度是特别的慢,慢到了我们用户无法忍受。
接下来我们采用字节数组来作为缓冲区来复制文件。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/*
* 字节流复制文件
* 采用数组缓冲提高效率
* 字节数组
* FileInputStream 读取字节数组
* FileOutputStream 写字节数组
*/
public class Copy1 {
public static void main(String[] args) {
long s = System.currentTimeMillis();
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("c:\\office2016 软件安装包.rar");//2.36 GB (2,538,018,536 字节)
fos = new FileOutputStream("d:\\office2016 软件安装包.rar");
//定义字节数组,缓冲
byte[] bytes = new byte[1024*10];
//读取数组,写入数组
int len = 0;
while((len = fis.read(bytes))!=-1){
fos.write(bytes, 0, len);
}
}catch(IOException ex){
System.out.println(ex);
throw new RuntimeException("文件复制失败");
}finally{
try{
if(fos!=null)
fos.close();
}catch(IOException ex){
throw new RuntimeException("释放资源失败");
}finally{
try{
if(fis!=null)
fis.close();
}catch(IOException ex){
throw new RuntimeException("释放资源失败");
}
}
}
long e = System.currentTimeMillis();
System.out.println(e-s);//23145 共23秒
}
}
通过以上代码,我们发现2G多的文件,23秒复制完成,速度达到我们的预期,但是以上代码,还有一个弊端,那就是我们在以字节流复制中文文件时容易出现乱码。
演示代码:
/*
使用字节流读取中文文件
1个中文
GBK:占用两个字节
UTF-8:占用3个字节
*/
public class Demo01InputStream {
public static void main(String[] args) throws IOException {
//文件内容:你好
FileInputStream fis = new FileInputStream("D:\\a.txt");
int len = 0;
while((len = fis.read())!=-1){
System.out.println((char)len);
}
fis.close();
}
}
字符流
当使用字节流读取文本文件时,可能会有一个小问题。就是遇到中文字符时,可能不会显示完整的字符,那是因为一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。
字符输入流【Reader】
java.io.Reader
抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public void close()
:关闭此流并释放与此流相关联的任何系统资源。public int read()
: 从输入流读取一个字符。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
/*
* 字符输入流读取文本文件,所有字符输入流
* java.io.Reader
* 专门读取文本文件
*
* 读取的方法 read()
*
* int read() 读取一个字符
* int read(char[] c) 读取字符数组
*
* Reader 类的抽象类,找到子类对象
*
* 构造方法:绑定数据源
* 参数:
* File类行对象
* String 文件名
*
*/
FileReader类
java.io.FileReader
类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
小贴士:
字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
idea中UTF-8
字节缓冲区:一个字节数组,用来临时存储字节数据。
public interface ReaderDemo {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("d:\\1.txt");
/*int len = 0;
while((len = fr.read()) != -1) {
System.out.println((char)len);
}*/
// 定义字符数组,作为装字符数据的容器
char[] ch = new char[1024];
// 定义变量,保存有效字符个数
int len = 0;
// 循环读取
while((len = fr.read(ch)) != -1) {
System.out.println(new String(ch,0,len));
}
// 关闭资源
fr.close();
}
}
字符输出流【Writer】
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
void write(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)
写入字符串的某一部分,off字符串的开始索引,len写的字符个数。void flush()
刷新该流的缓冲。void close()
关闭此流,但要先刷新它。
/*
* 字符输出流
* java.io.Writer 所有字符输出流的超类
* 写文件,写文本文件
*
* 写的方法write
* write(int c) 写1个字符
* write(char[] c)写字符数组
* write(char[] c,int off,int len) 写字符数组的一部分,开始索引,写几个
* write(String s)写入字符串
* 字符输出写数据的时候,必须要运行一个功能,刷新功能
* flush()
* /
FileWriter类
java.io.FileWriter
类是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。
构造方法
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。
当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。
public class WriterDemo {
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("d:\\1.txt");
//写一个字符
fw.write(100);
fw.flush();
//写一个字符数组
char[] c = {'a','b','c','d','e'};
fw.write(c);
fw.flush();
//写入部分字符数组
fw.write(c, 0, 2);
fw.flush();
//写入字符串
fw.write("hello");
fw.flush();
fw.close();
}
}
flush和close方法的区别
因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush
方法了。
flush
:刷新缓冲区,流对象可以继续使用。close
:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
小贴士:字符流,只能操作文本文件,不能操作图片,视频等非文本文件。
当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
JDK7中关于流异常处理的新特性(扩展知识点了解内容)
还可以使用JDK7优化后的try-with-resource
语句,该语句确保了每个资源在语句结束时关闭。所谓的资源(resource)是指在程序完成后,必须关闭的对象。
格式:
try (创建流对象语句,如果多个,使用';'隔开) {
// 读写数据
} catch (IOException e) {
e.printStackTrace();
}
我们先来看一下,正常情况下,用字符流来复制文本文件的代码:
/*
* 字符流复制文本文件,必须是文本文件
* 字符流查询本机默认的编码表,简体中文GBK
* FileReader 读取数据源
* FileWriter 写入数据目地
*/
public class Copy2 {
public static void main(String[] args) {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("d:\\1.txt");
fw = new FileWriter("d:\\2.txt");
int len = 0;
char[] cbuf = new char[1024];
while ((len = fr.read(cbuf)) != -1) {
fw.write(cbuf, 0, len);
fw.flush();
}
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("复制文件失败");
} finally {
try {
if (fw != null)
fw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException("释放资源失败");
} finally {
try {
if (fr != null)
fr.close();
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("释放资源失败");
}
}
}
}
}
我们发现finally代码块内的代码太多,而采用新特性下的代码:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Copy {
public static void main(String[] args) {
try (FileReader fr = new FileReader("d:\\a.txt");
FileWriter fw = new FileWriter("d:\\b.txt")) {
int len = 0;
// 创建char类型的数组缓冲区
char[] cbuf = new char[1024];
while ((len = fr.read(cbuf)) != -1) {
fw.write(cbuf, 0, len);
}
} catch (IOException e) {
}
}
}