一、IO流概念
1. 什么是IO流:
input output stream:输入输出流
计算数据是在cpu,有cpu两大功能:运算器和控制器。但是cpu不能存储数据,数据存在存储设备,如磁盘、硬盘、U盘、光盘,才能永久存储数据,需要将数据计算之后由cpu运算到磁盘上,会用到IO流;同样的,要计算某个数据,需要将数据先传到cpu中,也要用到IO流
1)、从狭义上来说:数据在内存中输入和输出
将数据提前缓存,加快程序的运行
代码包括操作系统,普通应用层软件,jvm,都是在在内存中运行
2)、从广义来说,不同电脑之间的数据流动,(借助网络设备)也是一种IO流
狭义上的IO流:本地进程间的数据流动 (同一个台电脑,不同元器件之间的流动)
广义上的IO流:远程进程间的数据流动 (不同电脑)
2. IO流的分类:
1)、流的流动方法(数据的传输方向)站在内存的角度:
|-- 输入流 如将数据从磁盘读到内存
|-- 输出流 如将计算好的数据存到磁盘上(内存读到磁盘上)
2)、流的数据格式
|-- 字节流 (二进制形式) 所有都能用
|-- 字符流 (二进制效率差,比如一句话,按照字节去翻译就很长)注意:不是所有的 流都能用字符流,如一张图片,字符串适用于肉眼能看到的符号
3)、流的主要作用
|-- 节点流
|-- 装饰流(过滤流)装饰节点流
4)、转换流
转换字节和字符流的特殊流
二、字节流
字节流:
|-- InputStream 输入流
|-- OutputStream 输出流
字符流:
|-- Reader 输入流
|-- Writer 输出流
字节流
装饰流
数据流
字符流
对象流
对象序列化
转换流
字节流:
输入流:
输出流:
示例:
存在bug,读整个字符数组不合适,应该给多少位读多少位。不能保证刚好就是1024,会出现多读的情况
对比原代码,程序本来到红色的部分已经结束了,但是控制台却多输出了很多其他的代码(绿色部分及之后未展示出来的结果)显然不对.读满了会重复去读
@Test
void testIStream01() {
InputStream is = null;
try {
is = new FileInputStream(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java"));
//read()方法是一次读尽数据,若文件很大,内存是有限的,所以不建议
//所以做一个字节数组
//也可以是1024*4,文件存在磁盘上,是以页存储,一个页是1024*4
//返回int值,-1表示读到末尾
byte[] buf = new byte[1024];
while(is.read(buf) != -1) {
System.out.write(buf);
//println是输出字符串,不能用,操作不了,这里是字节数组
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们将读取的字节数记录下来,只要没读到末尾,一直循环,从0读到记录的位置,这样就可以避免上述情况的发生
@Test
void testIStream02() {
InputStream is = null;
try {
is = new FileInputStream(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java"));
byte[] buf = new byte[1024];
int len = 0;//记录读的字节数
while((len = is.read(buf)) != -1) {
System.out.write(buf, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1. InputStream OutputStream的子类
1)、FileInputStream 字节输入流
@Test
void test01() {
InputStream is = null;
try {
// 1、创建一个输入流对象(XXX数据流)
is = new FileInputStream("a.txt");
// 创建字节数组,通过字节数组读取数据
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) != -1) {
System.out.write(buf, 0, len);//从buf中读,从第0个读len长度
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 一定要注意:流必须关闭
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
若是带一次的代码,不用len记录,直接读。会导致字节数组读不满,打出空格(byte[] 默认初始化为0)
@Test
void test01() {
InputStream is = null;
try {
// 1、创建一个输入流对象(XXX数据流)
is = new FileInputStream("a.txt");
// 创建字节数组,通过字节数组读取数据
byte[] buf = new byte[1024];
while ((len = is.read(buf)) != -1) {
System.out.write(buf);//从buf中读,从第0个读len长度
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 一定要注意:流必须关闭
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
读字符串时,可以用System.out.println()。(借助字符串对象,将字节数组构造为字符串对象)
@Test
void test02() {
InputStream is = null;
try {
// 1、创建一个输入流对象(XXX数据流)
is = new FileInputStream("a.txt");
// 创建字节数组,通过字节数组读取数据
byte[] buf = new byte[1024];
int len = 0;
while ((len = is.read(buf)) != -1) {
// 借助字符串对象,将字节数组构造为字符串对象
String str = new String(buf, 0, len);
System.out.println(str);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 一定要注意:流必须关闭
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2)、FileOutputStream 字节输出流
@Test
void test03() {
String msg = "你好啊";
FileOutputStream fos = null;
try {
fos = new FileOutputStream(new File("b.txt"));
// 直接将数据一次写入进去(只适用于比较少,下面的例子时拷贝文件,则必须定义字节数组了)
fos.write(msg.getBytes());
System.out.println("写入数据成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3)综合
字节输入流,字节输出流(以文件输入流,文件输出流为例)来拷贝文件
@Test
void test04() {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 使用输入流读取数据
fis = new FileInputStream("D:\\javaSE\\note\\第23天笔记.txt");
fos = new FileOutputStream(new File("c:\\a.txt"));
// 一定要通过字节数组进行读取和写入
// 防止内存过大,导致出现OOM
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
System.out.println("写入数据成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2. 装饰流:
又被称为过滤流,这种流不能直接使用,主要的作用就是用来装饰节点流(各种可以直接使用流)。如果节点流读取数据比较慢,使用装饰流做缓冲
FilterInputStream是个普通类,但是一般使用它的子类,BufferedInputStream
装饰流,本质就是一种装饰者设计模式的体现。对原有对象的功能进一步的装饰增强。
GOF 23中设计模式:
|-- 单例设计模式(对象不需要多个)节约内存,避免频繁创建对象。加快效率
|-- 装饰者设计
1)、BufferedInputStream
案列:
@Test
void test05() {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//装饰流装饰节点流,所以需要传一个节点流
bis = new BufferedInputStream(new FileInputStream("G:\\windows 系统\\CentOS-7-x86_64-DVD-1810.iso"));
bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\a.iso")));
byte[] buf = new byte[1024*8];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
System.out.println("文件拷贝成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果不关闭流:
文件少了,且拷贝的图片下面部分是灰色的
满了才会将缓冲区的数据刷进去,但是最后一次不一定满,可能把之前的数据拿过来,数据出问题
不满数字是0,0在颜色里面表示为黑色
@Test
void test06() {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("c:\\a.jpg"));
bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\b.jpg")));
byte[] buf = new byte[1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
System.out.println("文件拷贝成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
如果不关闭,我们可以利用flush()方法,强制刷新,将缓冲池的东西清到目的地
但是不建议直接手动刷新,建议关闭流。
缓存流,必须要关闭,因为JVM在缓存流关闭时,将最后数据完成自动刷新
节点流也需要关闭,因为操作的是一个具体的文件,你一旦占用不关闭,别人就操作不了。比如打开一个文件,此时删除这个文件是删除不掉的
@Test
void test06() {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream("c:\\a.jpg"));
bos = new BufferedOutputStream(new FileOutputStream(new File("c:\\b.jpg")));
byte[] buf = new byte[1024];
int len = 0;
while ((len = bis.read(buf)) != -1) {
bos.write(buf, 0, len);
}
// 当代码执行到这儿的时候,读取完成
// 如果最后一次数据没有读取完成,则可以手动刷新缓冲区
// bos.flush();
System.out.println("文件拷贝成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 缓存流,必须要关闭,因为JVM在缓存流关闭时,将最后数据完成自动刷新
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2)、DataOutputStream DataInputStream
数据流
特殊的装饰流
引入:存入数字
@Test
void test07() throws Exception {
FileOutputStream fis = new FileOutputStream("a.dat");
int msg = 100;
//fis.write(int b),这个方法是记录读了多少次,并不能存int数据
// String str = Integer.valueOf(msg).toString();先转成包装类,再转成字符串
// fis.write(str.getBytes());利用之前示例过的方法,转成字节数组
// 也可以这样完成转换
fis.write((msg + "").getBytes());
fis.close();
}
但是这样存储也有问题,比如将msg的值放大
我们知道int只占4个字节,但是转成字符串就是一个符号占一个字节,所以就是7个字节了
我们可以用DataOutputStream来解决这个问题
数据流的使用:
我们之前讲解的流,如果要保存数字,只要将数字转换为字符串,也就是说
以字符流的形式保存数据,这样有时候并不是我们需要的,我们有时候就是
需要以字节保存数据,因此就可以使用数据流来包装文件流完成。
int msg = 1000000;
@Test
void test08() throws Exception {
int msg = 1000000;
DataOutputStream dos = new DataOutputStream(new FileOutputStream("a.dat"));
dos.writeInt(msg);
dos.writeInt(100);
dos.writeInt(10000);
dos.writeInt(123456);
dos.close();
}
DataInputStream :
读int值:
@Test
void test09() throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("a.dat"));
System.out.println(dis.readInt());
System.out.println(dis.readInt());
System.out.println(dis.readInt());
System.out.println(dis.readInt());
dis.close();
}
如果再读会抛错
读long值:
@Test
void test10() throws Exception {
long msg = 1000000000L;
DataOutputStream dos = new DataOutputStream(new FileOutputStream("b.dat"));
dos.writeLong(msg);
dos.close();
}
用int读long 数据会返回0
@Test
void test11() throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("b.dat"));
System.out.println(dis.readInt());
dis.close();
}
用long读long 结果正常
@Test
void test11() throws Exception {
DataInputStream dis = new DataInputStream(new FileInputStream("b.dat"));
System.out.println(dis.readLong());
dis.close();
}
三、字符流
字符流:
计算机底层使用的二进制数据(字节数据)。所以计算机中都可以用字节去操作,字节流是计 算机最核心的流,但是速度慢。在开发过程中,若是字符串,就非常慢
字符流是为了加快流的操作,而设计的专门用来操作字符串的一种流。
注意:字符流 字符串存在编码问题,需要保证在读取和写入时,保持一致!!!(在Ascll码中 的不影响)
字节流无编码问题(二进制)
字符输入流:
Reader(抽象类),使用的时候应该使用它的子类
字符串输出流:
Writer(抽象类)
字符装饰流:
BufferedReader 是Reader的子类
BufferedWriter 是Writer的子类
PrintWriter 更推荐使用这个打印输出流
字符输入流例:
@Test
void test01() {
Reader reader = null;
try {
reader = new FileReader(new File("D:\javaSE\eclipse\code\classDemo\src\com\openlab\demo02\TestFile.java"));
char[] buf = new char[1024];
int len = 0;
while((len = reader.read(buf)) != -1) {
System.out.println(String.valueOf(buf, 0, len));
//valueOf支持字符数组,不支持字节数组
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
字符输出流例:
@Test
void test02() {
Reader reader = null;
Writer writer = null;
try {
reader = new FileReader(new File("D:\javaSE\eclipse\code\classDemo\src\com\openlab\demo02\TestFile.java"));
writer = new FileWriter("c:\\a.java");
char[] buf = new char[1024];
int len = 0;
while((len = reader.read(buf)) != -1) {
writer.write(buf, 0, len);
}
System.out.println("拷贝文件成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedReader :
@Test
void test03() {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
String msg = null;
// 字符输入流,可以使用装饰流,按行读取
while ((msg = br.readLine()) != null) {
System.out.println(msg);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
BufferedWriter:
@Test
void test04() {
BufferedReader br = null;
BufferedWriter bw = null;
try {
br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
bw = new BufferedWriter(new FileWriter("d:\\a.java"));
String msg = null;
// 字符输入流,可以使用装饰流,按行读取
while ((msg = br.readLine()) != null) {
// bw.write(msg);
// 我们可以手动添加换行符
bw.write(msg + "\n");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw != null) {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
PrintWriter:
@Test
void test05() {
BufferedReader br = null;
PrintWriter out = null;
try {
br = new BufferedReader(new FileReader(new File("D:\\javaSE\\eclipse\\code\\classDemo\\src\\com\\openlab\\demo02\\TestFile.java")));
out = new PrintWriter("d:\\a.java");
String msg = null;
// 字符输入流,可以使用装饰流,按行读取
while ((msg = br.readLine()) != null) {
out.println(msg);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
}
}
总结:
|-- 字符缓存流完成字符的操作
通过行来读取和写入字符串,所以字符流建议带上装饰流(能按行读取)
|-- 字符串输入缓存流
BufferedReader
|-- 字符串输出缓存流
BufferedWriter
不太推荐大家使用BufferedWriter,因为需要手动添加换行符(按行读取,readLine找到\n 就解析了,能把数据写进去,但是最终结果是不会换行)
推荐使用打印输出流(节点流)
PrintWriter
|-- print()
|-- println()
四、转换流:
将字节流转换为字符流操作
读取的是字节数据,一般是文本数据(字符串数据)为了加快效率,可以转成字符流
如控制台的标准输入流是字节流,直接转流类型不匹配。BufferedReader只能接受Reader
InputStreamReader Reader的子类
OuputStreamWriter Writer的子类
InputStreamReader 可以做一个简单人工智能
@Test
void test01() throws IOException {
BufferedReader br = null;
// InputStreamReader 转换流
// 可以将字节输入流转换为字符输入流
br = new BufferedReader(new InputStreamReader(System.in));
String str = null;
while ((str = br.readLine()) != null) {
if (str.equalsIgnoreCase("exit"))
break;
System.out.println(str.replace("吗?", ""));
}
br.close();
}
@Test
void test02() throws IOException {
BufferedReader br = null;
PrintWriter out = null;
br = new BufferedReader(new InputStreamReader(System.in));
out = new PrintWriter(new OutputStreamWriter(new FileOutputStream("d:\\a.html")));
String str = null;
while ((str = br.readLine()) != null) {
if (str.equalsIgnoreCase("exit"))
break;
out.println(str);
}
br.close();
out.close();
}
五、对象流
对象流:
对象本质:它是一个抽象概念,是JVM中的一种虚拟出来的抽象概念
在jvm中,通过new 关键字,配合class类,就可以构造一个java对象
对象流就是java提供一种,可以将java对象这种虚拟的概念转换为
一种物理可以存储或者传输的真实数据。
将虚拟的JVM中的对象转换为字节数据
String实现了Serializable接口,可以直接用
@Test
void test01() {
String msg = "你好";
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("c:\\info.dat"));
oos.writeObject(msg);
System.out.println("对象保存成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
void test02() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("c:\\info.dat"));
// Object msg = ois.readObject();不强转
String msg = (String) ois.readObject();//刚刚存的字符串,知道数据类型,所以直接强转了
System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象序列化:
@Test
void test03() {
Person p1 = new Person(1, "张三", "法外狂徒", "男", 30);
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("c:\\info.dat"));
oos.writeObject(p1);
System.out.println("对象保存成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
对象反序列化:
@Test
void test04() {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("c:\\info.dat"));
Person p1 = (Person) ois.readObject();
System.out.println(p1);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
1. 对象序列化:
Serialize:将虚拟对象转换为一种可以直接传输或者保存到数据(字节、字符)过程
2. 对象反序列化:
将序列化后的字节或者字符数据重新转换为对象,对象反序列化
3. 对象持久化:
将数据永久保存(可以通过IO、数据库)
java官方提供的序列化,是将java对象转换为字节数据。
注意:java的对象要实现序列化和反序列化,必须实现Serializable(这个标记接口)。
4. transient关键字
transient:被这个关键字修饰的属性,无法被持久化。
运算过程中临时使用的,不需要记录值
例:
将Person的gender用transient关键字修饰,之后重新将数据序列号、持久化(Test03)。
之后反持久化,反序列化(Test04)
private transient String gender;
在持久化的时候没有将gender持久化,所以读不到,赋了默认值(String是对象,默认值为null)