一、入门
1、数据源
(1)数据源分为:
- 源设备:为程序提供数据,一般对应 输入流
- 目标设备:程序数据的目的地,一般对应 输出流
2、数据流
3、典型的IO流代码
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("d:/a.txt"); // 内容是:abc
StringBuilder sb = new StringBuilder();
int temp = 0;
//当temp等于-1时,表示已经到了文件结尾,停止读取
while ((temp = fis.read()) != -1) {
sb.append((char) temp);
}
System.out.println(sb);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
//这种写法,保证了即使遇到异常情况,也会关闭流对象。
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
4、流概念细分
(1)按流的方向分类:
- 输入流:数据源 到 程序(以InputStream、Reader结尾的流)。
- 输出流:程序 到 目的地(以OutPutStream、Writer结尾的流)。
(2)按处理的数据单元分类:
- 字节流:以字节为单位读写数据,如FileInputStream、FileOutputStream。
- 字符流:以字符为单位读写数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWriter。
(3)按处理对象不同分类:
- 节点流:可以直接从数据源或目的地读写数据,如FileInputStream、FileReader、DataInputStream等。
- 处理流:不直接连接到数据源或目的地,通过对其他流的处理提高程序的性能,如BufferedInputStream、BufferedReader等。
5、IO流类体系
(1)四大IO抽象类
1、InputStream:
- int read():读取一个字节的数据,并将字节的值作为int类型返回(0-255之间的一个值),返回值为-1表示读取结束。
- void close():
2、OutputStream:
- void write(int n):向目的地中写入一个字节。
- void close():关闭输出流对象,释放相关系统资源。
3、Reader:
- int read(): 读取一个字符的数据,并将字符的值作为int类型返回(Unicode值),返回值为-1表示读取结束。
- void close() : 关闭流对象,释放相关系统资源。
4、Writer:
- void write(int n): 向输出流中写入一个字符。
- void close() :
(2)各种流总结
- FileInputStream/FileOutputStream
节点流:以字节为单位直接操作“文件”。
- ByteArrayInputStream/ByteArrayOutputStream
节点流:以字节为单位直接操作“字节数组对象”。
- ObjectInputStream/ObjectOutputStream
处理流:以字节为单位直接操作“对象”。
- DataInputStream/DataOutputStream
处理流:以字节为单位直接操作“基本数据类型与字符串类型”。
- FileReader/FileWriter
节点流:以字符为单位直接操作“文本文件”(注意:只能读写文本文件)。
- BufferedReader/BufferedWriter
处理流:将Reader/Writer对象进行包装,增加缓存功能,提高读写效率。
- BufferedInputStream/BufferedOutputStream
处理流:将InputStream/OutputStream对象进行包装,增加缓存功能,提高 读写效率。
- InputStreamReader/OutputStreamWriter
处理流:将字节流对象转化成字符流对象。
- PrintStream
处理流:将OutputStream进行包装,可以方便地输出字符,更加灵活。
二、IO流应用
1、文件字节流(FileInputStream/ FileOutputStream)
(1)文件复制的实现
static void copyFile(String src, String dec) {
FileInputStream fis = null;
FileOutputStream fos = null;
//为了提高效率,设置缓存数组!(读取的字节数据会暂存放到该字节数组中)
byte[] buffer = new byte[1024];
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//边读边写
//temp指的是本次读取的真实长度,temp等于-1时表示读取结束
while ((temp = fis.read(buffer)) != -1) {
/*将缓存数组中的数据写入文件中,注意:写入的是读取的真实长度;
*如果使用fos.write(buffer)方法,那么写入的长度将会是1024,即缓存
*数组的长度*/
fos.write(buffer, 0, temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//两个流需要分别关闭
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(2)注意
- 通常设置缓存数组来提高效率,读取时:read(byte[] b);写入时:write(byte[ ] b, int off, int length)
- 每个流都要单独关闭
2、文件字符流(FileReader/FileWriter)
(1)文件复制的实现
public static void main(String[] args) {
// 写法和使用Stream基本一样。只不过,读取时是读取的字符。
FileReader fr = null;
FileWriter fw = null;
int len = 0;
try {
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//为了提高效率,创建缓冲用的字符数组
char[] buffer = new char[1024];
//边读边写
while ((len = fr.read(buffer)) != -1) {
fw.write(buffer, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、 缓冲字节流( BufferedInputStream/BufferedOutputStream)
(1)原理
- 缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地
(2)文件复制
/**缓冲字节流实现的文件复制的方法*/
static void copyFile1(String src, String dec) {
FileInputStream fis = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
int temp = 0;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
//缓存区的大小(缓存数组的长度)默认是8192,也可以自己指定大小
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
while ((temp = bis.read()) != -1) {
bos.write(temp);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//注意:增加处理流后,注意流的关闭顺序!“后开的先关闭!”
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (bis != null) {
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3) 注意
- 在关闭流时,后开的先关闭。
- 缓存区的大小默认是8192字节,也可以使用其它的构造方法自己指定大小。
4、缓冲字符流(BufferedReader/BufferedWriter)
public static void main(String[] args) {
// 注:处理文本文件时,实际开发中可以用如下写法,简单高效!!
FileReader fr = null;
FileWriter fw = null;
BufferedReader br = null;
BufferedWriter bw = null;
String tempString = "";
try {
fr = new FileReader("d:/a.txt");
fw = new FileWriter("d:/d.txt");
//使用缓冲字符流进行包装
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
//BufferedReader提供了更方便的readLine()方法,直接按行读取文本
//br.readLine()方法的返回值是一个字符串对象,即文本中的一行内容
while ((tempString = br.readLine()) != null) {
//将读取的一行字符串写入文件中
bw.write(tempString);
//下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (br != null) {
br.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fr != null) {
fr.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(1) 注意
- readLine()方法是BufferedReader特有的方法
- 写入一行后要记得使用newLine()方法换行
5、字节数组流(ByteArrayInputStream|ByteArrayOutputStream)
public static void test(byte[] b) {
ByteArrayInputStream bais = null;
StringBuilder sb = new StringBuilder();
int temp = 0;
//用于保存读取的字节数
int num = 0;
try {
//该构造方法的参数是一个字节数组,这个字节数组就是数据源
bais = new ByteArrayInputStream(b);
while ((temp = bais.read()) != -1) {
sb.append((char) temp);
num++;
}
System.out.println(sb);
System.out.println("读取的字节数:" + num);
} finally {
try {
if (bais != null) {
bais.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(1)注意
- ByteArrayInputStream是把 字节数组 当做数据源
- 用在需要 流 和 数组 之间转化的情况
6、数据流(DataInputStream和DataOutputStream)
(1)实现对基本数据类型和字符串类型的读写
public static void main(String[] args) {
DataOutputStream dos = null;
DataInputStream dis = null;
FileOutputStream fos = null;
FileInputStream fis = null;
try {
fos = new FileOutputStream("D:/data.txt");
fis = new FileInputStream("D:/data.txt");
//使用数据流对缓冲流进行包装,新增缓冲功能
dos = new DataOutputStream(new BufferedOutputStream(fos));
dis = new DataInputStream(new BufferedInputStream(fis));
//将如下数据写入到文件中
dos.writeChar('a');
dos.writeInt(10);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("北京尚学堂");
//手动刷新缓冲区:将流中数据写入到文件中
dos.flush();
//直接读取数据:读取的顺序要与写入的顺序一致,否则不能正确读取数据。
System.out.println("char: " + dis.readChar());
System.out.println("int: " + dis.readInt());
System.out.println("double: " + dis.readDouble());
System.out.println("boolean: " + dis.readBoolean());
System.out.println("String: " + dis.readUTF());
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
7、 对象流(ObjectInputStream/ObjectOutputStream)
/**使用对象输出流将数据写入文件*/
public static void write(){
// 创建Object输出流,并包装缓冲流,增加缓冲功能
OutputStream os = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try {
os = new FileOutputStream(new File("d:/bjsxt.txt"));
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
// 使用Object输出流
oos.writeInt(12);
oos.writeDouble(3.14);
oos.writeChar('A');
oos.writeBoolean(true);
oos.writeUTF("北京");
oos.writeObject(new Date());
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭输出流
if(oos != null){
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos != null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**使用对象输入流将数据读入程序*/
public static void read() {
// 创建Object输入流
InputStream is = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
try {
is = new FileInputStream(new File("d:/bjsxt.txt"));
bis = new BufferedInputStream(is);
ois = new ObjectInputStream(bis);
// 使用Object输入流按照写入顺序读取
System.out.println(ois.readInt());
System.out.println(ois.readDouble());
System.out.println(ois.readChar());
System.out.println(ois.readBoolean());
System.out.println(ois.readUTF());
System.out.println(ois.readObject().toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭Object输入流
}
}
(1)注意
- 对象流不仅可以读写对象,还可以读写基本数据类型。
- 使用对象流读写对象时,该对象必须序列化与反序列化。
- 系统提供的类(如Date等)已经实现了序列化接口,自定义类必须手动实现序列化接口。
- 示例代码中在 read 之前必须先关闭输出流,不然会报 EOF 异常。
8、转换流(InputStreamReader/OutputStreamWriter)
(1)用来实现将字节流转化成字符流
public static void main(String[] args) {
// 创建字符输入和输出流:使用转换流将字节流转换成字符流
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 使用字符输入和输出流
String str = br.readLine();
// 一直读取,直到用户输入了exit为止
while (!"exit".equals(str)) {
// 写到控制台
bw.write(str);
bw.newLine();// 写一行后换行
bw.flush();// 手动刷新
// 再读一行
str = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭字符输入和输出流
}
}
三、序列化
1、概述
(1)序列化和反序列化
- Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。
- 把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。
(2)序列化的前提
- java类必须实现 java.io.Serializable接口
(3) 序列化的作用
- 持久化: 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中
- 网络通信:在网络上传送对象的字节序列
2、序列化对象
(1)ObjectOutputStream 类用来序列化一个对象,方法void writeObject(Object x),示例如下:
import java.io.*;
public class SerializeDemo
{
public static void main(String [] args)
{
Employee e = new Employee();
e.name = "Reyan Ali";
e.address = "Phokka Kuan, Ambehta Peer";
e.SSN = 11122333;
e.number = 101;
try
{
FileOutputStream fileOut =
new FileOutputStream("/tmp/employee.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in /tmp/employee.ser");
}catch(IOException i)
{
i.printStackTrace();
}
}
}
(2)注意:
- 序列化到文件扩展名一般为.ser
- static属性不参与序列化
- 不想被序列化的属性,不能使用static,而是使用transient修饰(private transient String attr5;)
- 为了防止读和写的序列化ID不一致,一般指定一个固定的序列化ID
3、反序列化对象
(1)示例:
import java.io.*;
public class DeserializeDemo
{
public static void main(String [] args)
{
Employee e = null;
try
{
FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
e = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("Name: " + e.name);
System.out.println("Address: " + e.address);
System.out.println("SSN: " + e.SSN);
System.out.println("Number: " + e.number);
}
}
(2)注意事项:
- JVM反序列化对象的前提是必须是能够找到该类的class文件对象,找不到的话,readObject() 方法会抛出一个 ClassNotFoundException 异常
- 有属性被标记为transient,反序列化后,该属性为默认值
- 反序列化后得到的是Object对象,一般需要强转为相应的类型
四、工具类( Apache IOUtils和FileUtils)
1、装饰器模式
(1)概念:实现对原有类的包装和装饰,使新的类具有更强的功能
(2)代码演示:
class Iphone {
private String name;
public Iphone(String name) {
this.name = name;
}
public void show() {
System.out.println("我是" + name + ",可以在屏幕上显示");
}
}
class TouyingPhone {
public Iphone phone;
public TouyingPhone(Iphone p) {
this.phone = p;
}
// 功能更强的方法
public void show() {
phone.show();
System.out.println("还可以投影,在墙壁上显示");
}
}
public class TestDecoration {
public static void main(String[] args) {
Iphone phone = new Iphone("iphone30");
phone.show();
System.out.println("===============装饰后");
TouyingPhone typhone = new TouyingPhone(phone);
typhone.show();
}
}
(3)IO流中的装饰器模式:处理流使用了装饰器模式,进行了功能的增强
2、FileUtils的使用
public static void main(String[] args) throws Exception {
FileUtils.copyDirectory(new File("d:/aaa"), new File("d:/bbb"), new FileFilter() {
@Override
public boolean accept(File pathname) {
// 使用FileFilter过滤目录和以html结尾的文件
if (pathname.isDirectory() || pathname.getName().endsWith("html")) {
return true;
} else {
return false;
}
}
});
}