这是我参与更文挑战的第1天,活动详情查看: 更文挑战。
学习java时,常常对各种io流搞得晕头转向,流的种类很多,如果不去分门别类的总结,很容易搞糊涂。本文就尝试去弄清楚io流的分类和使用,以后再见到流可以给别人讲个七七八八。
什么是流:
内存与存储设备之间的传输数据的通道
按照流向的不同,可以分为:
- 输入流:将存储设备中的内容读入到内存中
- 输出流:将内存中的内容写入到存储设备中
如上图所示:将文件读入内存需要用到输入流,将修改后的内容写会文件需要用到输出流
按照单位不同,可以分为
- 字节流:以字节为单位,可以读写所有数据
- 字符流:以字符为单位,只能读写文本数据
按照功能不同,可以分为:
- 节点流:具有实际传输数据的读写功能
- 过滤流:在节点流的基础之上增强功能
字节流的类结构
顶层两个抽象类
- InputStream:字节输入流 (read()、read(byte[] b)、read(byte[] b,int off,int len))
- OutputStream:字节输出流 (write()、write(byte[] b)、write(byte[] b,int off,int len))
文件字节流的使用
- FileInputStream:读取文件的字节流,将文件读入内存进行自定义的操作
- FileOutputStream:写入文件的字节流,将操作之后的文件重新写入文件,可以写入一个新的文件,或者覆盖原来的文件
FileInputStream的使用:
public static void main(String[] args) throws IOException {
//创建字节输入流
FileInputStream fileInputStream = new FileInputStream("d:\\aaa.txt");
//读取一个字节 如果文件读完了则返回-1
//fileInputStream.read();
int data = 0;
while((data = fileInputStream.read())!=-1){
System.out.println(data);
}
//关闭字节流
fileInputStream.close();
}
复制代码
FileOutputStream的使用,以及使用两者完成文件复制的功能
public static void main(String[] args) throws IOException {
//复制
//定义输入流
FileInputStream fis = new FileInputStream("d:\\1.jpg");
//定义输出流
FileOutputStream fos = new FileOutputStream("d:\\2.jpg");
//设置数组
byte[] buf = new byte[1024];
int c = 0;
//一边读 一边写 实现复制
while((c = fis.read(buf))!=-1){
fos.write(buf,0,c);
}
//关闭流
fis.close();
fos.close();
}
复制代码
字节缓冲流:
BufferedInputStream、BufferedOutputStream,会创建缓冲区,将文件先放到缓冲区中,再一次性读取或者写出,这样有两个好处
- 提高io效率,减少访问磁盘的次数,磁盘的访问是相当耗时和消耗资源的。
- 数据存储在缓冲区中,调用flush将缓冲区的内容一次性写入文件中,也可以直接close(内部会调用flush)。
查看源码,发现BufferedInputStream默认的缓冲区大小为8K,BufferedOutoutStream也是一样的
class BufferedInputStream extends FilterInputStream {
private static int DEFAULT_BUFFER_SIZE = 8192;
/**
* The internal buffer array where the data is stored. When necessary,
* it may be replaced by another array of
* a different size.
*/
protected volatile byte buf[];
}
复制代码
字节缓冲流的使用方法,跟普通的字节流使用方法很像,只不过创建的类是不一样的。同时创建的时候需要包裹一个基础的字节流,这也是装饰者模式的体现,对原始对象进行封装,添加一些其他的行为。
//字节输入流
FileInputStream fileInputStream1 = new FileInputStream("d:\\aaa.txt");
//创建缓冲流,将fileInputStream1当作参数,对其进行包裹并添加行为方法
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream1);
//通过read()方法一个字节一个字节读取
int data1 = 0;
while((data1 = bufferedInputStream.read())!=-1){
System.out.println((char)data1);
}
//***************** 多个字节一起读取 *************
//自定义缓冲区 一块一块读取
byte[] b1 = new byte[128];
int count1 = 0;
while((count1 = bufferedInputStream.read(b1))!=-1){
System.out.println(new String(b1,0,count1));
}
bufferedInputStream.close();
复制代码
BufferedOutputStream使用write方法写入缓冲区,flush方法写入磁盘。如果不调用flush方法,则只是写入到缓冲区中,没有写入磁盘文件中。close方法中会自己调用flush方法。
public static void main(String[] args) throws IOException {
//新建缓冲输出流
FileOutputStream fileOutputStream = new FileOutputStream("d:\\buffer.txt");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
for(int i=0;i<10;i++){
//写入缓冲区(默认8k)
bufferedOutputStream.write("abcdefg\r\n".getBytes());
//刷新到磁盘
bufferedOutputStream.flush();
}
//关闭(内部会调用flush方法)
bufferedOutputStream.close();
}
复制代码
对象流:
ObjectOutputStream、ObjectInputStream,使用流传输对象的过程称为序列化(将对象写入流中write的过程)和反序列化(读取并重构一个对象read的过程),所以在序列化和反序列化的过程中,会伴随有大量的io操作,使用对象流的几点好处:
- 增强缓冲区功能
- 增强了八种基本数据类型和字符串功能
- 增强了读写对象的功能(readObject()从流中读取一个对象、writeObject(Object obj)向流中写入一个对象)
序列化和反序列化的实现:
首先创建实例类,用于序列化和反序列化
//创建对象并实现Serializable接口
public class Student implements Serializable{
/**
* 序列化版本号id--保证序列化的类和反序列化的类是同一个类
*/
private static final long serialVersionUID = 1L;
int age;
String name;
....
}
复制代码
实现对象的序列化和反序列化操作:
public static void main(String[] args) throws IOException {
//序列化:
//表示一个二进制文件
FileOutputStream fileOutputStream = new FileOutputStream("d:\\stu.bin");
//创建ObjectOutputStream
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//新建对象(该实例需要实现Serializable表明该对象可以序列化)
Student stu = new Student(12,"zs");
//写入文件 实现序列化
objectOutputStream.writeObject(stu);
//关闭输出流(内部会调用flush方法)
objectOutputStream.close();
//反序列化:
//新建文件输入流和对象输入流
FileInputStream fileInputStream = new FileInputStream("d:\\stu.bin");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
//读取对象(反序列化)
Student student = (Student) objectInputStream.readObject();
//关闭流
objectInputStream.close();
System.out.println(student.toString());
}
复制代码
实现对象的序列化和反序列化时,需要注意的几点:
- 序列化类必须要实现Serializable接口
- 序列化类中的对象属性也需要实现Serializable接口
- 序列化版本号ID(serialVersionUID),保证序列化的类和反序列化的类是同一个类
- 使用transient(瞬时的)修饰属性,这个属性就不能序列化了
- 静态属性是不能序列化的,静态属性属于类不属于某个对象
- 序列化多个对象,可以借助List集合来实现
字符流
字符编码和字符集是两个基础性的概念,很多开发人员对其都并不陌生,但是很少有人能将其讲得很准确。
字符集(character set)是一个系统支持的所有抽象字符的集合。字符(character)就是各种文字和符号,包括国家文字、标点符号、图形符号、数字等。但是我们常说的字符集,其实是指编码字符集,编码字符集是指,这个字符集里的每一个字符,都对应到唯一的一个代码值。
字符编码的定义:是编码字符集的字符和实际的存储值之间的转换关系,当编码方式和解码方式不同,会出现乱码
常见的编码字符集有:
- ISO-8859-1:收录除了ASCII外,还包括西欧、希腊、泰语、阿拉伯语、希伯来语对应的文字符号
- UTF-8:针对Unicode码表的可变长度字符编码(一个汉字三个字节)
- GB2312:简体中文
- GBk:简体中文、扩充 GB2312的升级版
- BIG5:繁体中文
字符流的类结构
文件字符流:
FileReader:
- read():读取单个字符
- read(char[] c):从流中读取多个字符,将读到的内容存入c数组,返回实际读到的字符数;如果达到文件的尾部(读完了),则返回-1
使用文件字符流读取文件:
public static void main(String[] args) throws IOException {
//创建FileReader
FileReader fReader = new FileReader("d:\\h.txt");
//读取
//单个字符读取
int data = 0;
while((data = fReader.read())!=-1){
System.out.println((char)data);
}
//使用字符数组,多字符读取
char[] buf = new char[128];
int count = 0;
while((count = fReader.read(buf))!=-1){
System.out.println(new String(buf,0,count));
}
fReader.close();
}
复制代码
FileWriter:
- write(String str)一次性写入多个字符,将b数组中所有的字符,写入输出流
使用文件字符流写入文件:
public static void main(String[] args) throws IOException {
//创建FileWriter
FileWriter fWriter = new FileWriter("d:\\h.txt");
for(int i = 0;i<10;i++){
fWriter.write("abcdefg");
fWriter.flush();
}
fWriter.close();
}
复制代码
- 使用FileReader和FileWriter实现文件复制(只能复制文本文件,不能复制图片/音频等其他二进制文件):
public static void main(String[] args) throws IOException {
//创建FileReader
FileReader fReader = new FileReader("d:\\h1.txt");
//创建FileWriter
FileWriter fWriter = new FileWriter("d:\\h2.txt");
int data = 0;
//读写
while((data = fReader.read())!=-1){
fWriter.write(data);
}
fReader.close();
fWriter.close();
}
复制代码
字符缓冲流:
- BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现高效读取
- BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
- 使用缓冲流读文件:
public static void main(String[] args) throws IOException {
//创建缓冲流
FileReader fileReader = new FileReader("d:\\h.txt");
BufferedReader bReader = new BufferedReader(fileReader);
//方法1 使用字节数组
char[] buf = new char[1024];
int count = 0;
while((count = bReader.read(buf))!=-1){
System.out.println(new String(buf,0,count));
}
//方法二 使用readLine读取一行
String line = null;
while((line = bReader.readLine())!=null){
System.out.println(line);
}
bReader.close();
}
复制代码
- 使用缓冲流写入文件:
public static void main(String[] args) throws IOException {
//创建
FileWriter fileWriter = new FileWriter("d:\\w.txt");
BufferedWriter bWriter = new BufferedWriter(fileWriter);
//写入
for(int i = 0;i<10;i++){
bWriter.write("abcdefg");
//写入换行符
bWriter.newLine();
bWriter.flush();
}
bWriter.close();
}
复制代码
转换流:
InputStreamReader/OutputStreamWriter:
- 可以将字节流转换为字符流(InputStreamReader将字节输入流转换为字符输入流,OutputStreamWriter将字节输出流转换为字符输出流)
- 可以设置字符的编码方式
InputStreamReader是字节流通向字符流的桥梁,它使用指定的charset读取字节并将其解码为字符,它使用的字符集可以由名称指定或显式给定。
OutputStreamWriter:字符流通向字节流的桥梁
public static void main(String[] args) throws IOException {
//创建
FileInputStream fis = new FileInputStream("d:\\a.txt");
//设置字符的编码方式
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
//读取
int data = 0;
while((data = isr.read())!=-1){
System.out.println((char)data);
}
isr.close();
//创建
FileOutputStream fos = new FileOutputStream("d:\\b.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos,"utf-8");
//写入
for(int i = 0 ;i<10;i++){
outputStreamWriter.write("abcdefg");
}
//关闭
outputStreamWriter.close();
}
复制代码
File文件相关的api
代表物理盘符中的一个文件或者文件夹。常用的方法如下:
- createNewFile()—创建一个文件
- mkdir()—创建一个新目录
- delete()—删除文件或空目录
- exists()—判断file对象所代表的对象是否存在
- getAbsoulutePath()—获取文件的绝对路径
- getName()—获取名字
- getParent()—获取文件所在的目录
- isDirectory()—是否是目录
- isFile()—是否是文件
- length()—获取文件的长度
- listFiles()—获取目录中所有的内容
- renameTo()— 修改文件名为
结语
本文只是从基本的使用上展示了io框架是如何应用的,通过本文可以从宏观上了解io框架,同时对io框架的分类以及应用有一个更清楚的认识,当然更多的使用还是要看api的,在工作中灵活的使用才能有更深刻的认识。接下来还会从应用中更深入的剖析io。