I/O流

缓冲区

  遇到与底层相关的概念,往往都很模糊,所以才想要把这个问题解决清楚;

  那么什么是缓冲区呢,我为什么会有这样的疑问呢;接触到Java的I/O流,其中底层是怎么实现的,其中BufferedReader中的Buffered是什么意思,C语言中的输入、输出底层是什么实现的,它们跟缓冲有什么关系;

  缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对I/O的数据做临时存储,这部分预留的内存空间叫缓冲区。

  对于scanf()来说,它是从缓冲区里读取数据到对应的变量里,如果缓冲区没有数据,这时候执行scanf(),它会因读取不到数据而一直等待,从而发生死锁;

Java File类

  在java中,File对象是一个对文件或者目录名的抽象,在windows中即是文件夹,它不是真实硬盘里的文件或者文件夹,File实例是在内存中new出来的一个对象,实际上它只是 与硬盘上的文件或者文件夹进行了映射,它的构造函数参见File类构造函数,他不含有无参的构造函数,即它这个new出来的对象,根据构造函数参数代表的文件进行映射,如果文件不存在,那么对于这个file实例来说,它不会发生错误,它的属性依然拥有默认值,不过当如果通过流去读取时,会发生错误;构造函数参数文件名,不区分大小写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.io.*;  
import java.lang.reflect.Array;
import java.util.*;
import java.lang.Integer;

/**
* Created by Messick on 2019/10/26. */
public class Main {
public static void main(String[] args) {
File file = new File("D:\\1.txt");
System.out.println(file.canExecute());
System.out.println(file.canRead());
System.out.println(file.canWrite());
System.out.println(file.isHidden());
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.length());
System.out.println(file.lastModified());
File file1 = new File("D:\\2.txt");
try {
boolean v1 = file1.createNewFile();
System.out.println(v1);
}catch (IOException e) {
e.printStackTrace();
}
File file2 = new File("D:\\3");
File file3 = new File("D:\\l1\\l2");
System.out.println(file2.mkdir());
//递归创建文件夹
System.out.println(file3.mkdirs());
System.out.println(file1.delete());
System.out.println(file2.delete());
//删除文件或空文件夹
System.out.println(file3.delete());
}
}

Java 文件流

  分为字节型文件流和字符型文件流;

  文件存在的原因,是因为文件存储在硬盘上,是永久保存的,而类似变量、集合,都是暂时性地存储,并且文件可以存储很多份地信息。

  对于文件的读取,实际上是将文件里的信息读取,存到内存中的集合等容器中,然后对此容器进行操作,此容器相当于缓存。而文件流即是对于文件的读取或者写入。

字节型文件流

  FileInputStream/FileOutputStream

  1. FileInputStream是InputStream的子类,字节型输入流都是InputStream的子类。它的构造函数有三个,分别是字符串、file对象,第三个不做了解;
  2. FileInputStream.read(),java对该方法进行了重载,对于不带参数的方法,为读取一个字节,返回值为读取字节对应的Unicode;实际上都是当作字符来读取,如果读取到汉字,而汉字编码又不是一个字节的情况下,那么仅读取该汉字的一字节的数据,如果输出这个单字节数据的Unicode码对应的char,可能会是乱码;对于带参数的情况,参数可以是一个byte数组,即一次性读取byte数组大小的数据,并存到该byte数组中;

  如图,使用一个3字节大小的byte数组去成功读取一个汉字,所以,可以想到的是,如果我们去读取1字节,那么仅仅是该汉字的一部分;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.io.*;  
import java.lang.reflect.Array;
import java.util.*;
import java.lang.Integer;

/**
* Created by Messick on 2019/10/26. */
public class Main {
public static void main(String[] args) {
File file = new File("D:\\1.txt");
try {
FileInputStream fis = new FileInputStream(file);
//读取一个字节 返回为读取字节int的Unicode码
int t = fis.read();
//byte [] a = new byte[10003];
////文件读取到byte数组里,cnt为读取到的有效字节个数,包括\r\n
//int cnt = fis.read(a);
//for(byte x:a) {
// System.out.println(x);
// 流管道中剩余的缓存的字节数
// System.out.println(fis.available());
//}
while (t != -1) {
//字节对应的Unicode码
System.out.println(t);
t = fis.read();
//这个过程中,流管道中缓存的字节数逐渐减少至0
System.out.println(fis.available());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

/* 需要注意的是,在文件中,每一行的结尾一定是\r\n,分别代表换行符和回车,对应的Unicode码为13、10 */

  同FileInputStream,FileOutputStream是OutputStream的子类,字节型输出流都是OutputStream的子类;在这里,如果创建文件输出流,文件路径不存在,则创建该文件;对于FileInputStream则会抛出异常;并且,FileOutputStream实例含有一个两个参数的构造函数,第一个参数为文件路径,第二个参数为一个布尔型,为true代表可追加,否则每次新建一个文件输出流,文件内容都会被清空。

  1. FileOutputStream,该类构造函数第一种,参数为File类实例,还有一个boolean参数 默认false,第二种构造函数和第一种类似,不过把File换成了String;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.io.*;  
import java.lang.reflect.Array;
import java.util.*;
import java.lang.Integer;

/**
* Created by Messick on 2019/10/26. */
public class Main {
public static void main(String[] args) {
File file = new File("D:\\1.txt");
try {
// 流管道
FileOutputStream fos = new FileOutputStream(file,true);
byte [] b = new byte[]{97, 98, 99};
String str = "1 + 1 = 2";
fos.write(b);
fos.write(str.getBytes());
fos.write(102);
fos.write(103);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}
}

字符型文件流

  FileReader/FileWriter;

  1. FileReader是字符型文件输入流,是InputStreamReader的子类,而InputStreamReader是Reader的子类,它含有两个构造函数,第一种,含一个参数是String,即文件路径,第二种参数是一个File实例;

  2. FileReader.read(),对于它不含参数的方法,读取一个字符,返回值为该字符的Unicode码;对于含参数的方法,它的参数应该是一个空的char数组,即将通过流将文件中字符读到char数组中,返回值为读取的有效字符数;

  3. FileWriter,常用方法,FileWriter.wirte(),参数可以是你想要写到文件里的字符对应的Unicode码,或者是一个字符数组,或者是一个String,不能是字符;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.io.*;  
import java.lang.reflect.Array;
import java.util.*;
import java.lang.Integer;

/**
* Created by Messick on 2019/10/26. *///你哈珀
public class Main {
public static void main(String[] args) {
try {
File file = new File("D:\\1.txt");
FileReader fr = new FileReader(file);
FileInputStream fis = new FileInputStream(file);
FileWriter fw = new FileWriter(file, true);
int code = fr.read();
System.out.println((char)code);
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
}
}

Java 缓冲流

  同样的,也分为字节型缓冲流和字符型缓冲流,BufferedInputStream/BufferedOutputStream,BufferedReader/BufferedWriter;

  使用缓冲的目的即是为了提高速度,比如,一次读取很多字节,但不写入磁盘,先放到内存中,等凑够了缓冲区的大小一次性写入磁盘,这样减少了磁盘操作次数,会提高速度;

  所以,缓冲流就是实现了缓冲功能的输入流和输出流,使用带缓冲的输入输出流,效率更高速度更快。

  BufferedInputStream/BufferedOutputStream本质上是通过一个内部缓冲字节数组实现的,那么BufferedReader/BufferedWriter本质上是通过一个内部缓冲字符数组实现的。具体参见java API;

  需要注意,缓冲流的构造函数的参数需要是一个文件流的实例;

Java序列化与反序列化

  之前学过了也讲过了\(\dots\)

待续