IO流
1、定义
IO流流动的是数据。是用于传输数据的API。Input/Output Stream输入输出流。
输入流:数据从程序外部流向程序。例如:利用程序读取文件。
输出流:数据从程序流向外部。例如:利用程序向文件中写入数据。
流具有排外性:当一个流在操作一个数据的时候,不允许其他操作产生。
2、分类
根据流的传输方向:输入流、输出流。
根据流的传输形式:字节流、字符流。
根据四种类型交叉形成来下面的四种基本流:
输入流 输出流
字节流 InputStream OutputStream
字符流 Reader Writer
流中的四个基本流都是抽象类,所以在使用过程中用的是对应的子类。
数据的来源(目的地):硬盘,网络,输入设备,内存。
3、流的异常处理
1.需要将流对象在try之外定义并且赋值为null,将流的对象放到try中初始化。
2.在关流之前需要判断流对象是否初始化成功。
3.需要将流对象置为null,以回收内存并且也是为来流关闭失败的时候释放文件。
4.需要在关流之前手动的冲刷缓冲区,以防关流失败造成数据丢失。
4、字符流
Java中一切字符流只能读取字符类文件。例如:txt、java、html、js、css。
Java的原生的字符流不能读取office组件。很好用的一个组件→POI
字符流包含:Reader abstract class 和Writer abstract class
1.FileWriter class(写入文件)
FileWriter class 继承了OutputStreamWriter class。
流操作的过程:
创建流 -> 流出数据 -> 冲刷缓冲区 -> 关闭流。
最后将流对象赋值为null标记为一个垃圾对象。
构造函数
FileWriter(File file);
无论文件是否存在,都会创建一个新的文件覆盖原来的文件。
FileWriter(File file,boolean append);
是否追加内容。如果存在目标文件,不会删除原文件。
FileWriter(String fileName);
根据给定的文件名fileName构造一个对象。
FileWriter(String fileName,boolean append);
判断是否追加数据。
重要方法
write(String s);
写入数据。
数据不是直接写到文件中个,而是先写到缓冲区,缓冲区满了之后,才会将数据挪到文件中。如果缓冲区没有满而程序已经结束了,那么数据就死在来缓冲区。
flush();
冲刷缓冲区。
close();
关流。为了释放文件。流在关闭之前会自动的冲刷一次缓冲区,以防有数据死在缓冲区中。流关闭了,流对象依然存在。
2.FileReader class(读取文件)
FileReader class继承了InputStreamReader class。是缓冲流的一个子类。
流操作的过程
创建流 -> 读取数据 -> 关闭流。
在输入流中没有缓冲区,可以自己指定缓冲区。
构造函数
FileReader(File file);
读取给定的文件。
FileReader(String fileName);
读取给定的文件名称的文件。
重要方法
read();
每次只读取一个字符。返回值是一个int类型,表示的是这个字符对应的编码。
read(char cs);
返回值表示读取到字符的个数,或者读入数组的字符个数。
练习
复制文件
import java.io.FileReader;
import java.io.FileWriter;
public class CopyFileDemo {
public static void main(String[] args) {
// 创建一个输入流指向要复制的文件
FileReader reader = null;
// 创建一个输出流指向要存储的文件
FileWriter writer = null;
try {
reader = new FileReader("E:\\a.txt");
writer = new FileWriter("F:\\a.txt");
// 定义一个变量来记录读取的字符个数
int i = -1;
// 定义一个数组作为缓冲区使用
char[] cs = new char[4];
// 读取文件
while ((i = reader.read(cs)) != -1) {
// 将读取的数据写出
writer.write(cs, 0, i);
}
// 需要手动冲刷缓冲区
writer.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
reader = null;
}
}
if (writer != null) {
try {
writer.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
writer = null;
}
}
}
}
}
5、缓冲流
1.BufferedReader class
继承了Reader class
在读取文件的过程中,考虑到FileReader没有缓冲区,读取效率比较低,就利用bufferedReader包装上一层缓冲区。关闭的时候只关闭外层流即可,因为关闭外层流的时候,会自动关闭里层的流。也可以手动关闭,关闭顺序要从里往外关闭。
构造函数
BufferedReader(Reader in);
使用默认大小输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in ,int sz)
创建一个使用指定大小输入缓冲区的缓冲字符输入流。
重要方法
readLine();
读取一行(以回车换行作为一行的结束),返回的是String类型。
2.BufferedWriter class
用其他字符输出流来写出数据,提供来一个更大的缓冲区,同时提供来newLine();能够屏蔽不同系统之间换行的差异性。
构造函数
BufferedWriter(Writer out);
BufferedWriter(Writer out , int sz);
重要方法
newLine();
换行。在不同的系统中,转换为对应的换行。
练习
统计Java代码的行数。
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class GetRowCountDemo {
// 记录行数
static int count = 0;
public static void main(String[] args) {
// 创建一个File对象指向工作空间
File file = new File("E:\\workspace");
count(file);
System.out.println(count);
}
private static void count(File file) {
// 判断file是否为空
if (file == null) {
throw new NullPointerException("亲,文件不能为空哦~~~");
}
// 判断是否是一个目录
if (file.isDirectory()) {
// 获取子文件和子目录
File[] fs = file.listFiles();
for (File f : fs) {
count(f);
}
} else if (file.getName().endsWith(".java")) {
// 统计代码行数---按行读取
BufferedReader br = null;
try {
// 创建一个缓冲流指向要统计的文件
br = new BufferedReader(new FileReader(file));
// 按行读取
while (br.readLine() != null) {
count++;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
br = null;
}
}
}
}
}
}
6、字节流
以字节形式来读写文件。
1.FileOutputStream class
FileOutputStream class 继承了OutputStream class。
创建一个字节流 -> 写入数据 -> 关流。
构造函数
FileOutputStream(File file);
FileOutputStream(File file);
FileOutputStream(String name);
FileOutputStream(String name,boolean append)
重要方法
write(int b);
write(byte[] b);
write(byte[] b,int off,int len);
2.FileInputStream class
FileInputStream class继承了InputStream class。
创建一个字节流 -> 创建缓冲区 -> 读出数据 -> 关流。
构造函数
FileInputStream(File file);
FileInputStream(String name);
重要方法
read();
read(byte[] b);
read(byte[] b ,int off,int len);
练习
复制文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileExer {
public static void main(String[] args) throws IOException {
long begin = System.currentTimeMillis();
// 创建一个字节流来读取指定的文件
FileInputStream in = new FileInputStream("E:\\aaa.zip");
// 创建有一个字节流来指向存储的文件
FileOutputStream out = new FileOutputStream("F:\\b.zip");
// 构建一个字节数组作为缓冲区
byte[] bs = new byte[1024 * 1024 * 15];
// 定义一个变量来记录读取到的字节个数
int len = -1;
// 读取数据
while ((len = in.read(bs)) != -1) {
// 将读取到的数据写出
out.write(bs, 0, len);
}
// 关流
out.close();
in.close();
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
7、转换流
转换流都是字符流。将字节流转化为字符流。
在转换的时候如果没有指定编码,采用的是系统平台码,即当前系统使用的默认编码。
InputStreamReader(转换输入流)
InputStreamReader继承了Reader class。
将字节流转化为字符流。以字节形式读取数据,但是以字符形式展现数据。
构造函数
InputStreamReader(InputStream in);
创建一个使用默认编码参数为字节流的对象。
InputStreamReader(InputStream in,Charset cs);
创建一个使用指定编码参数为字节流的对象。
InputStreamReader(InputStream in,String charsetName);
InputStreamReader(InputStream in,CharsetDecoder dec);
重要方法
read();
读取单个字符。
read(char[] cbuf,int offset,int length);
读入字符数组中的某一部分。
OutputStreamWriter(转换输出流)
OutputStreamWriter class 继承了Writer class。
将字符转化为字节流。以字符形式将数据传入,但是以字节形式来传输的。
构造函数
OutputStreamWriter(OutputStream out);
创建一个使用默认编码参数为字节流的对象。
OutputStreamWriter(OutputStream out,Charset cs);
OutputStreamWriter(OutputStream out,CharsetEncoder enc);
OutputStreamWriter(OutputStream out,String charsetName);
练习
转换文件的编码。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class ChangeEncodeExer {
public static void main(String[] args) throws Exception {
// 创建一个File对象指向源文件
File old = new File("F:\\java基础增强.txt");
// 创建一个File对象作为临时文件
File temp = new File("F:\\temp");
// 准备缓冲流对象以实现按行读取
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(old), "utf-8"));
// 创建一个缓冲流对象将读取的数据写到临时文件中
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(temp), "gbk"));
// 定义一个字符串记录每行的数据
String line = null;
// 读取数据
while ((line = br.readLine()) != null) {
// 将读取的数据写出
bw.write(line);
bw.newLine();
}
// 关流
br.close();
bw.close();
// 删除源文件
old.delete();
// 将临时文件命名为原文件的名字
temp.renameTo(old);
}
}
8、系统流/标准流
系统流都是字节流。系统一旦关闭就无法二次使用。
System.in
标准输入流。
System.out
标准输出流。是线程不安全的。
System.err
标准错误流。是线程不安全的。
练习
从控制台获取一行数据。
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = br.readLine();
System.out.println(str);
br.close;
9、打印流
打印流只有输出流,没有输入流。
PrintStream class
提供来一系列格式化打印一个对象的方法。是一个字节流。
构造方法
PrintStream(File file);
PrintStream(OutputStream out);
重要方法
print(int/long/float/double/char/char[]/boolean/String/Object s);
打印。
println(/int/long/float/double/char/char[]/boolean/String/Object s);
打印换行。
PrintWriter
是一个字符流。
构造方法
PrintWriter(File file);
PrintWriter(OutputStream out);
PrintWriter(String fileName);
PrintWriter(Writer out);
重要方法
print();
println();
write();
代码示例:
public static void main(String[] args) {
// 表示将数据打印到控制台上
PrintStream ps = new PrintStream(System.out);
// ps.print(new Object());
ps.print(new char[] { 'a', 'b', 'c' });
// BufferedWriter---newLine()
ps.println("abc");
ps.close();
}
10、合并流
合并流只有输入,没有输出。
SequenceInputStream class
如果我们需要合并多个流的话,我们需要将这多个流放入一个Vector集合中,然后利用Vector中的elements方法来产生Enumeration对象,然后利用Enumeration对象来构建合并流对象。
构造函数
SequenceInputStream(Enumeration
重要方法
read();
read(byte[] b,int off,int len);
代码示例
public static void main(String[] args) throws Exception {
// 创建一个输出流,将读取的数据写出
FileOutputStream out = new FileOutputStream("E:\\test.txt");
// 创建一个输入流指向a.txt
FileInputStream in1 = new FileInputStream("E:\\a.txt");
FileInputStream in2 = new FileInputStream("E:\\b.txt");
FileInputStream in3 = new FileInputStream("E:\\c.txt");
// 创建一个Vector对象来存储这三个输入流
Vector<InputStream> v = new Vector<>();
v.add(in1);
v.add(in2);
v.add(in3);
// 获取Enumeration对象
Enumeration<InputStream> e = v.elements();
// 创建合并流对象
SequenceInputStream sis = new SequenceInputStream(e);
// 利用合并流来读取数据
byte[] bs = new byte[4];
int len = -1;
while ((len = sis.read(bs)) != -1) {
out.write(bs, 0, len);
}
// 关流
out.close();
sis.close();
}
11、序列化/反序列化流
定义
将对象保存到其他位置的操作叫序列化。对应的类:ObjectOutputStream class。
将对象保存到硬盘上的操作叫持久化。
对象还原回来的过程叫反序列化。对应的类:ObjectInputStream class。
注意
1.想要将一个对象序列化,那么这个对象对应的类要实现Serializable Interface,这个接口中没有任何的方法和属性,这个接口仅仅用于标记类的对象可以被序列化。
2.被static/transient修饰的数据不会被序列化。如果一个属性没有被序列化出去,那么在反序列化的时候会给一个默认值。
3.在类实现Serializable接口之后,这个类中就会默认添加一个版本号。如果没有手动指定版本号,那么JVM在编译的时候会根据当前类中的属性和方法来自动的计算一个版本号。在反序列化的时候,会自动的比较当前类的版本号是否一致,如果一致才可以反序列化回来。所以在开发过程中,需要给类手动指定一个版本号,以防类发生微小变动的时候,已经序列化出去的对象反序列化不回来。
private static final long serialVersionUID = 3258469L;
//类的版本号中,权限修饰符和值以外,其他的都不能改。
4.集合以及Map不允许序列化。
5.一个文件中只能序列化一个对象。
ObjectOutputStream
构造函数
ObjectOutputStream();
ObjectOutputStream(OutputStream out);
重要方法
writeObject();
代码示例:
// 准备一个对象
Person p = new Person();
p.setName("雪花");
p.setAge(15);
p.setHeight(150);
p.setGender('女');
// 静态属性没有被序列化出去,为什么?
// p.hobby = "睡觉~~~";
// 创建一个序列化流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\p.data"));
// 将对象序列化出去
oos.writeObject(p);
// 关流
oos.close();
ObjectInputStream
构造函数
ObjectInputStream();
ObjectInputStream(InputStream in);
重要方法
readObject();
代码示例
// 创建反序列化流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\p.data"));
// 将对象反序列化回来
Person p = (Person) ois.readObject();
// 关流
ois.close();
System.out.println(p.getName());
System.out.println(p.getAge());
// 如果一个属性没有被序列化出去,那么在反序列化的时候会给一个默认值
System.out.println(p.getHeight());
加密序列化/反序列化
仅作了解
KeyGenerator
加密算法:对称加密、非对称加密。
Key
Cipher
SealedObject
import java.io.FileInputStream; import java.io.FileOutputStream;
import java.io.ObjectInputStream; import java.io.ObjectOutputStream;
import java.security.Key; import javax.crypto.Cipher;
import javax.crypto.KeyGenerator; import javax.crypto.SealedObject;
public class SerialDemo {
Key key;
public void serial(Person p) throws Exception {
// 找制作者制作钥匙
// 加密算法---对称加密,非对称加密---MD5---8 明码 暗码
KeyGenerator generator = KeyGenerator.getInstance("DESede");
// 获取钥匙
key = generator.generateKey();
// 获取锁
Cipher cipher = Cipher.getInstance("DESede");
// 将钥匙和锁进行配对
cipher.init(Cipher.ENCRYPT_MODE, key);
// 获取盒子
SealedObject so = new SealedObject(p, cipher);
// 序列化盒子
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("p.data"));
oos.writeObject(so);
oos.close();
}
public Person deSerial() throws Exception {
// 将盒子反序列化回来
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("p.data"));
SealedObject so = (SealedObject) ois.readObject();
ois.close();
// 利用钥匙打开盒子,将对象取出
return (Person) so.getObject(key);
}
public static void main(String[] args) throws Exception {
Person p = new Person();
p.setName("柱子");
p.setAge(80);
SerialDemo sd = new SerialDemo();
sd.serial(p);
Person p2 = sd.deSerial();
System.out.println(p2.getName());
System.out.println(p2.getAge());
}
}