一、块读写
解决拷贝速度,提高每次读写量,减少读取次数,就可以提高读写效率。
块读写是每次读取一组字节的形式,每次多读一些字节内容,减少读取次数。
无法确定一个文件有多少个字节,所以无法确定设定的块包含多少字节能够刚好读完。
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 拷贝demo02 -------:使用块读写的方式提高读写效率
*/
public class CopyDemo02 {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("./神里.jpeg");//建立输入流管道链接源文件
FileOutputStream fos= new FileOutputStream("./神里3.jpeg");//建立输出流体管道 链接目标写出的文件
块读写的思路:
* java.io.inputStream 超类 中定义 read
* int read(byte[] data); 因为有方法重载
所以读取时也可以传入字节数组,一次读取的字节数,取决于给定的字节数组的容量
* 假设源文件 有 7个 字节
* 神里.jpeg ---- 11100010 00110101 11001100 01010101 01010101 01110101 01011111
* byte[] data = new byte[4]; ^^^^^^^^ 第二次只能读取3个字节
* int length = fis.read(data) length代表实际读取到的字节数。
* data : [11100010 00110101 11001100 01010101 ]
* length 此时为 4
* length = fis.read(date);
* data : [01010101 01110101 01011111 01010101 ]
* |--------新读取的3个字节-------| |--旧数据--|
* length 此时为 3
int length;
/**
* 1b ---- 8位
* b ---字节
* 1024b ---- 1kb
* 1024kb ---- 1Mb
* 1024MB ---- 1GB
* 1024GB ---- 1TB
* */
byte[] data = new byte[1024*10];//10kb
long start = System.currentTimeMillis();
while ((length =fis.read(data))!= -1){//读取后看下 如果不等于-1 那么就不是文件末尾
fos.write(data);//将读取到的data数组中的字节 一次行写入到文件中。
}
long end = System.currentTimeMillis();
System.out.println("拷贝完毕,总共耗时:"+(end-start)+"ms");//1ms
fis.close();//释放输入流体
fos.close();//释放输出流体
}
}
注意,fis.read()和fis.read(data)是不一样的概念。fis.read()输出的是下一个字节的int值,但fis.read(data)是指fis在data数组里实际读取到的字节数量,这就是为什么上面的length = 4。
复制之后,目标文件的大小可能会比原有的文件大小大一些,因为多读了一部分倒数第二个字节的数据,但是不影响使用。具体看下面的过程就明白了:
倒数第二次写入时:
最后一次写入时:
所以目前的问题:通过快读写的方式,效率提高了,但是写出时,会把多余的旧数据一并写出到目标文件中,导致目标文件与源文件大小不一样。
write有三个重载的方法:
write(int b )写入传入的字节
write(byte[] byte)写入传入的字节数组
write(byte[] byte, int off, int len)这个就是解决此问题的方法
第一个参数:需要写入的字节数组
第二个参数:从字节数组的哪个地方开始写入(从头开始就写0)
第三个参数:实际要写入文件的字节数量
所以这里直接length = fis.read(data),然后fos.write(data,0,length)就能解决问题了。
这里就相当于,每次按实际读取到的length长度写入data数组的字节数
byte[] data = new byte[1024*10];//10kb
long start = System.currentTimeMillis();
while ((length =fis.read(data))!= -1){//读取后看下 如果不等于-1 那么就不是文件末尾
// fos.write(data);//将读取到的data数组中的字节 一次行写入到文件中。
/**
* write(int b) 代表传入字节 来写出
* write(byte[] byte) 代表传入一个字节数组 来写出
* write(byte[] byte,int off ,int len) 1.需要写出的字节数组 2. 0 3.length 传入实际读取的字节长度
*/
fos.write(data,0,length); //使用含有三个参数的write方法来实现 copy 解决:目标文件多出字节的问题。
}
long end = System.currentTimeMillis();
System.out.println("拷贝完毕,总共耗时:"+(end-start)+"ms");
fis.close();//释放输入流体
fos.close();//释放输出流体
}
}
如何在当前项目路径下在文件中写入字符串的内容?(很常用)
先读取demo.txt文件作为输出文件,写出要写入的字符串,因为要把它转成字节数组才能传入,所以我们要先进行转换。
注意:每个汉字在内存中都是占2个字节的,我国用的叫GBK编码表
为防止编码格式不同,我们使用国际统一标准Unicode(万国码),基本上用的都是utf-8
所以在写入时,要使用
byte[] data = str.getBytes(standardCharsets.UTF-8)
f.write(data)
这样就写入成功了,具体代码如下:
接下来,把这个文件的内容再读取到程序中:
fis.avaiable()方法可以获取当前文本内容的大致长度
package io;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 读取项目路径下demo.txt文件的内容到程序中
*/
public class ReadStringDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("./demo.txt");
int len = fis.available();
//available方法,可以获取到当前文本内容的长度 (可以估算长度)
byte[] data = new byte[len];//开辟估算长度的空间
fis.read(data);//一次读取到data数组中。
String line = new String(data, StandardCharsets.UTF_8);
//将data数组中读到的字节转换为字符串 ,转换的编码格式UTF8
System.out.println(line);//打印转换后的内容
fis.close();//释放流体
}
}
FileOutStream类还提供的追加的构造方法,在操作现文件基础上追加内容。
FileOutputStream fos = new FileOutputStream("./demo.txt",true);
equalsIgnoreCase可以在判断时,忽略输入的内容的大小写。
二、Java流分类: 节点流和处理流
除了文件流之外,还有其他的流。我们学的文件流就是节点流里的一种。
Java里的流分为节点流和处理流
(1)节点流是低级流:
定义:用来真实连接程序与另一端(可以理解为文件)的管道。读写一定是建立在节点流的基础上进行的,例如:文件流就是节点流.
生动形象理解一下低级流(就是至少要有个管道),但是自来水我们不能直接喝,所以需要在这个管道上加一些东西,比如净水器来实现我们的需求,那这就是高级流。
低级流(比较基础):
处理流是高级流,且是不能够独立存在的,必须链接在其它流(不一定是低级流,但肯定得有流)基础上,
目的:当数据流经过它时,可以对数据做一些加工处理,简化我们的同等操作。
(2)高级流:
实际在开发过程,我们经常会串联一组高级流最终连接到低级流上,让读写的数据以流水线的方式加工处理,这个操作称之为流的链接。
处理流和高级流的关系可以用以下方式描述:
- 处理流是对基本流进行包装或装饰,为基本流添加了额外的功能,提供了更高级别的操作接口。
- 高级流是基于处理流构建的,通过串联多个处理流的方式,将多个功能组合在一起,形成更高级别的流操作接口。
1. 缓冲流:
缓冲流有两种:
缓冲输入流 -->java.io.BufferedInputStream
缓冲输出流--->java.io.BufferedOutputStream
就是把原先创建的输入流作为参数,放入到缓冲输入流中作为参数,创建高级缓冲输入流
输出流也是一样的。
然后,原先后面写的fis和fos都可以换成bis和bos了。
最后,直接释放高级流就好了,内部也会把对应的低级流自动释放掉。
package io;
import java.io.*;
/**
* 缓冲流的使用演示类:
* 缓冲流是高级流,功能:加快读写效率
*/
public class BOSDemo {
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("./神里.jpeg");//建立输入流管道链接源文件
BufferedInputStream bis = new BufferedInputStream(fis);//创建高级缓冲输入流
FileOutputStream fos= new FileOutputStream("./神里6.jpeg");//建立输出流体管道 链接目标写出的文件
BufferedOutputStream bos = new BufferedOutputStream(fos);//创建高级缓冲写出流
int length;
long start = System.currentTimeMillis();
while ((length =bis.read())!= -1){//读取后看下 如果不等于-1 那么就不是文件末尾
bos.write(length);//将读到的字节length内容 写出到目标文件的环节
}
long end = System.currentTimeMillis();
System.out.println("拷贝完毕,总共耗时:"+(end-start)+"ms");
bis.close();//释放高级流 内部也会把对应低级流释放
bos.close();//释放高级流 内部也会把对应低级流释放
}
}
生动形象理解一下bis(缓冲输入流)和bos (缓冲输出流):
缓冲字节输出流,缓冲区会存在一些问题。
缓冲流本质上是块读写, 内部做的块容量8kb, 如果内容较少,而且没有刷新的话,系统是不会立刻写出到文件的,导致有可能不能及时存入数据,可以通过手动打点调用 flush刷新。close()方法里本身就带有这个刷新方法。
package io;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 缓冲输出流:
* 在当前项目路径下写出一个内容到文件中。
*/
public class BOSDemo02 {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("./bos.txt",true);
BufferedOutputStream bos = new BufferedOutputStream(fos);
String line = "同学们";
bos.write(line.getBytes(StandardCharsets.UTF_8));
System.out.println("写出完毕!");
//缓冲流是块读块写的方式,如果写入的内容较少(不满足内部声明的数组容量),则有可能不会立刻写出
bos.flush();//手动刷新。
bos.close();// 关闭缓冲流时,内部会执行以flush刷新操作。
}
}
2. 对象流(也是一种高级流)
对象的反序列化:将一组字节(必须是序列化的一组字节)还原为一个对象的过程。
好处:有了对象流,我们可以很方便区读写任何Java对象
对象输入流:java.io.ObjectInputStream
对象输出流:java.io.ObjectOutputStream
对象如果需要序列化是需要为当前对象的模板类添加实现Serializable接口。
用一个例子来说明(待续):
人的模板类:
package io;
import java.io.Serializable;
import java.util.Arrays;
/**
* 人类模板
*/
public class Person implements Serializable {
private String name;
private int age;
private String gender;
private String[] otherInfo;//其它信息
//1.在当前空白区域 alt + insert 或 右键 找到 Generate 选中
//选择 get and set 方法 全选生成
//重复操作1. 选择 toString
//重复操作1. 选择 第一个选择 生成构造方法 全选 生成。
public Person(String name, int age, String gender, String[] otherInfo) {
this.name = name;
this.age = age;
this.gender = gender;
this.otherInfo = otherInfo;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", otherInfo=" + Arrays.toString(otherInfo) +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String[] getOtherInfo() {
return otherInfo;
}
public void setOtherInfo(String[] otherInfo) {
this.otherInfo = otherInfo;
}
}