本文转载:https://blog.csdn.net/weixin_45676630/article/details/105691569
1.什么是IO流以及IO的作用
1.1 什么是IO
I/O实际上是input和output,也就是输入和输出。而流其实是一种抽象的概念,它表示的是数据的无结构化传递。
1.2 IO的作用
1.3 应用场景
比如,我们想要把本地磁盘的文件上传到一个FTP服务器或者远程的云服务器上,那么我们会涉及到以下操作,首先,使用imputstream把本地的磁盘的文件读取到内存中,然后我们会拿到这个文件的所有二进制流数据,之后再使用outputsteam把文件输出到FTP服务器上。这就是IO流的一个基本的概念。
2.Java中的IO流以及IO流的分类
2.1Java中的IO体系
在Java中I/O流操作的类很多,但是核心体系实际上就只有File、InputStream、OutputStream、Reader、Writer。
字节流:操作的数据单元是8位的字节。InputStream、OutputStream作为抽象基类
字符流:操作的数据单元是字符。以Writer、Reader作为抽象基类
2.2 应用示例
案例:使用IO流读取本地磁盘指定文件进行输出。
首先,在G盘根目录下创建了test.txt文件,并输入了“Hello world !”
public class FileInputStreamDemo {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("G:/test.txt");
int i = 0;
while ((i = fileInputStream.read()) != -1){
System.out.print((char)i);
}
System.out.println((char)i);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出:
3. 深入分析Java中的IO流
3.1 IO流的数据来源与API
IO流的数据来源有硬盘、内存、键盘、网络。
代码示例:
// 磁盘IO
try {
FileInputStream inputStream = new FileInputStream("G:/test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// 内存IO
String str = "Hello World !";
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(str.getBytes());
// 键盘
// Scanner
InputStream inputStream = System.in;
// 网络IO
// 后面详细讲解
Socket socket ;
socket.getInputStream();
socket.getOutputStream();
3.2 File类简介
File类是Java中为文件进行创建、删除、重命名、移动等操作而设计的一个类,它属于Java.io包下。提供了四种构造方法:
- File(File parent,String child): 根据parent抽象路径名和child路径名字符串创建一个新的File实例
- File(String pathname): 将指定路径名转化为抽象路径名创建一个新的File实例
- File(String parent,String child):根据parent路径名和child路径名创建一个File实例
- File(URI uri): 指定URI转化为抽象路径名
通过结合前面的部分内容,来整合一个案例,根据用户端输入的路径进行目录的遍历
代码示例:
public class FileDemo {
public static void main(String[] args) {
InputStreamReader reader = new InputStreamReader(System.in);
BufferedReader bufferedReader = new BufferedReader(reader);
try {
String path = bufferedReader.readLine();// 读取用户输入的路径
File file = new File(path);
if (file.isDirectory() && file.exists()) {
// 遍历这个目录下的所有子目录
fileList(file);
} else {
System.out.println("文件路径输入错误");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedReader.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void fileList(File filePath) {
File[] files = filePath.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
System.out.println(files[i].getName()); // 输出具体的文件名
} else {
fileList(files[i]);
}
}
}
}
}
3.3.基于文件的字节输入输出流实战
3.3.1文件的拷贝案例
public class InputStreamDemo {
public static void main(String[] args) throws IOException {
File file = new File("G:/io.jpg");
FileInputStream fileInputStream = new FileInputStream(file);
FileOutputStream fileOutputStream = new FileOutputStream("G:/io-1.jpg");
int len = 0;
byte[] buffer = new byte[1024];
while ((len = fileInputStream.read(buffer)) != -1) {
// 读取的数据可以保存在内存中
//也可以写出到磁盘
fileOutputStream.write(buffer,0,len); // 把InputStream的输入字节写出到指定的目录下
}
fileInputStream.close();
fileOutputStream.close();
}
}
执行结果:
3.3.2 深入浅出read方法
我们首先看下上面这个图,会发现最后一次输出中并不是“ld”,而是“ldr”,这是为什么呢???
原因是这样的,我们定义了byte数组的长度为3,可以理解为先创建了一个长度为3的数组,每次读取是将读取的内存替换掉原来数组的元素。最后一次读取时只读取到了“ld”,上一次的“r”还没被替换掉,所以这里输出了“ldr”。 所以,为了防止出现这种情况,我们经常是这样写的System.out.println(new String(buffer,0,i));
并且从上面例子中,可以看到,如果不设置byte数组,那么需要和磁盘进行11次IO操作,增加之后只需要4次IO,那如果把数组的长度设置成读取的字节长度,就只会进行一次IO操作。那么是不是越大越好呢?显然,肯定不是的。我们来以下两点来分析:
- 设置数组的长度,这里是会 占用内存空间的,如果设置的值太大,全部都写入到内存中的话是可能会导致内存溢出的。
- 数据读取越大,虽然IO操作次数少了,但是写入的速度也慢了
3.4 基于内存的字节输入输出流实战
3.4.1 字符串转大写案例
public class MemoryDemo {
static String str = "Hello world";
public static void main(String[] args) {
// 从内存中读取数据
ByteArrayInputStream inputStream = new ByteArrayInputStream(str.getBytes());
// 写出到内存中
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int i=0;
while((i=inputStream.read()) != -1){
char c = (char)i;
outputStream.write(Character.toUpperCase(c));
}
System.out.println(outputStream.toString());
}
}
输出结果:
3.5 基于缓冲流的输入输出实战
3.5.1 缓冲流与普通流复制文件性能对比
这里为了更好的体验出效果,我准备了一个400M左右的文件。
public class BufferedCopyDemo {
private static File fileSource = new File("G:/mysql-installer-community-5.7.24.0.msi");
private static File fileTarget = new File("G:/mysql-installer-community-5.7.24.0_copy.msi");
public void copyWithNormal(){
try(FileInputStream inputStream = new FileInputStream(fileSource);
FileOutputStream outputStream = new FileOutputStream(fileTarget)){
byte[] buf = new byte[1024];
int len = 0;
while ((len=inputStream.read(buf)) != -1){
outputStream.write(buf,0,len);
}
}catch (Exception e){
}
}
public void copyWithBuffered(){
try(FileInputStream inputStream = new FileInputStream(fileSource);
FileOutputStream outputStream = new FileOutputStream(fileTarget);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)){
byte[] buf = new byte[1024];
int len = 0;
while ((len=bufferedInputStream.read(buf)) != -1){
bufferedOutputStream.write(buf,0,len);
}
}catch (Exception e){
}
}
public static void main(String[] args) {
BufferedCopyDemo copyDemo = new BufferedCopyDemo();
long star = System.currentTimeMillis();
copyDemo.copyWithNormal();
System.out.println("普通复制耗时:"+(System.currentTimeMillis()-star));
star = System.currentTimeMillis();
copyDemo.copyWithBuffered();
System.out.println("缓冲流赋值耗时:"+(System.currentTimeMillis()-star));
}
}
运行结果:
可以看出,缓冲流的性能明显更好。当然,我们通过调整普通流的byte数组的大小,也有可能比缓冲流效率更高,使用缓冲流的好处在于我们不需要去考虑这个过程了。
3.6 详解Flush方法
在使用缓冲流BufferedOutputStream时,我们常用到flush这个方法,这个方法是用来做什么的呢?
这里通过几个案例来了解这个方法。
3.6.1 demo1
public static void main(String[] args) throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("G:/test-2.txt"));
outputStream.write("Hello World".getBytes());
}
3.6.2 demo2
这里加入了flush
public static void main(String[] args) throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("G:/test-3.txt"));
outputStream.write("Hello World".getBytes());
outputStream.flush();
}
3.6.3 demo3
public static void main(String[] args) throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("G:/test-4.txt"));
outputStream.write("Hello World".getBytes());
outputStream.close();
}
3.6.4 最终结果
可以看到,最终生成了三个文件,但是第一个文件是0kb,里面的内容为空。其实close方法里面也调用flush方法,源码如下:
那么,为什么为什么没调用flush方法的时候生成的文件为空呢?其实,我们通过write方法去写入的时候,会首先写到缓冲区里面,缓冲区的默认大小为8kb,当8kb满了之后才会触发写入到磁盘的动作,显然,我们的测试案例里面远远没有达到8kb。通过flush方法就可以触发写入到磁盘的动作。
3.7 基于文件的字符输入输出流实战
3.7.1
我在G盘 的test.txt中写入了“Hello World 晚上好”这句话,通过FileInputStream去读取的时候,发现后面的中文变成了乱码,这是为什么呢? 我们知道,一个中文是占了三个字节的,这里每次读取一个字节,那么肯定不能正常显示中文了,可以通过设置byte[] 来解决这个问题,但是难免会遇到临界的时候还是会有个别字符无法正确读取,所以引入了字符流。
3.7.2 FileReader&FileWriter Demo
通过FileReader&FileWriter实现txt的拷贝案例
public static void main(String[] args) {
try (FileReader reader = new FileReader("G:/test.txt");
FileWriter writer = new FileWriter("G:/test-8.txt")) {
int len = 0;
char[] by = new char[1024];
while ((len = reader.read(by)) != -1) {
writer.write(by,0,len);
}
} catch (Exception e) {
}
}
虽然这样可以保证每个中文都被读取,但是这里存在编码格式的问题,txt的默认编码格式是GBK,但是一般我们的编程环境都是UTF-8,这样就导致新生成的文件产生乱码的问题。如我们刚生成的文件。
3.7.3 BufferedReader&BufferedWriter
通过BufferedReader&BufferedWriter可以解决上面的乱码问题,对于读写都可以指定字符集编码
public static void main(String[] args) {
try(BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("G:/test.txt"));
InputStreamReader reader = new InputStreamReader(inputStream,"GBK");
BufferedReader bf = new BufferedReader(reader);
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("G:/test-9.txt"),"GBK");
BufferedWriter bw = new BufferedWriter(writer)){
bw.write(bf.readLine());
bw.flush();
}catch (Exception e){
}
}
4.序列化和反序列化
4.1什么是序列化和反序列化
- 序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化。
- 反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化。
user类:
public class User implements Serializable {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
User user = new User("DBL",18);
try(ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("G:/user"));){
objectOutputStream.writeObject(user);
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try(ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("G:/user"))){
User user = (User) objectInputStream.readObject();
System.out.println(user.toString());
}catch (Exception e){
}
}