文件系统:
文件系统是操作系统操作设备上文件和数据结构的方法。操作系统中负责管理文件信息的软件单元叫文件管理系统,简称文件系统,文件系统由三部分组成:文件系统的接口,对象操作和管理的软件集合和对象及属性。文件系统主要解决信息的长期存储。
文件:
文件是信息存储的形式,一个文件是一个命名的,存储在设备上的信息的线性字节流。文件在需要的时候可以读取这些信息或者写入新的信息。存储在文件中的信息是永久的。文件系统中最重要的就是如何呈现一个文件信息:文件属性----文件由什么组成,如何命名,如何保护文件;文件操作---增删改查及如何组织存储。
从用户的角度,主要关注文件怎么使用和特征。
文件特征:
命名:
文件最大的特征就是命名,文件的操作都是基于命名。
数据:
数据是文件信息的存储。
还有一些属性:创建日期,文件长度,创建信息,文件权限等
文件操作:
创建文件: 创建没有任何信息的文件,知识神明文件存在,并附加一些属性。
删除文件: 释放文件存储空间。
打开文件: 读取文件之前需要先打开文件,文件系统内存中保留打开文件的一些信息,又是会创建对文件存取的相关机制
关闭文件: 关闭文件可以将内存中的一些信息写回文件,并释放打开文件占用资源
读文件: 一般是从当前位置读取数据到一个缓存区
写文件: 向文件中写入信息数据
追加数据: 在文件末尾添加信息数据
读记录: 在结构化文件中读取记录
写记录: 在结构化文件中写入记录
删除记录: 在结构化文件中删除记录
移动当前位置: 将文件操作文位置移动到特定位置
获取文件属性:
设置文件属性:
文件名修改:
目录:
目录可以理解为文件的集合或者容器。多个文件可以存储在一个目录下。
所有的文件操作都是文件系统提供的功能,在使用文件操作功能前,需要加载文件系统。
java I/O
Java IO 体系看起来类很多,感觉很复杂,但其实是 IO 涉及的因素太多了。在设计 IO 相关的类时,编写者也不是从同一个方面考虑的,所以会给人一种很乱的感觉,并且还有设计模式的使用,更加难以使用这些 IO 类,所以特地对 Java 的 IO 做一个总结。
IO 类设计出来,肯定是为了解决 IO 相关的操作的,想一想哪里会有 IO 操作?网络、磁盘。网络操作相关的类是在 java.net 包下,不在本文的总结范围内。提到磁盘,你可能会想到文件,文件操作在 IO 中是比较典型的操作.
在 Java 中引入了 “流” 的概念,它表示任何有能力产生数据源或有能力接收数据源的对象,数据传输以不同的运输方式对应不同的运输特性。
数据源I/O:
- 1、文件(file):FileInputStream、FileOutputStream、FileReader、FileWriter
- 2、数组([]):
- 2.1、字节数组(byte[]):ByteArrayInputStream、ByteArrayOutputStream
- 2.2、字符数组(char[]):CharArrayReader、CharArrayWriter
- 3、管道操作:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter
- 4、基本数据类型:DataInputStream、DataOutputStream
- 5、缓冲操作:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
- 6、打印:PrintStream、PrintWriter
- 7、对象序列化反序列化:ObjectInputStream、ObjectOutputStream
- 8、转换:InputStreamReader、OutputStreWriter
数据传输方式:
- 1、字节流
- 2、字符流
IO 类虽然很多,但最基本的是 4 个抽象类:InputStream、OutputStream、Reader、Writer。最基本的方法也就是一个读 read() 方法、一个写 write() 方法。方法具体的实现还是要看继承这 4 个抽象类的子类,毕竟我们平时使用的也是子类对象
InputStream 类
方法 | 方法介绍 |
---|---|
public abstract int read() | 读取数据 |
public int read(byte b[]) | 将读取到的数据放在 byte 数组中,该方法实际上是根据下面的方法实现的,off 为 0,len 为数组的长度 |
public int read(byte b[], int off, int len) | 从第 off 位置读取 len 长度字节的数据放到 byte 数组中,流是以 -1 来判断是否读取结束的(注意这里读取的虽然是一个字节,但是返回的却是 int 类型 4 个字节,这里当然是有原因,这里就不再细说了,推荐这篇文章,链接) |
public long skip(long n) | 跳过指定个数的字节不读取,想想看电影跳过片头片尾 |
public int available() | 返回可读的字节数量 |
public void close() | 读取完,关闭流,释放资源 |
public synchronized void mark(int readlimit) | 标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断 |
public synchronized void reset() | 重置读取位置为上次 mark 标记的位置 |
public boolean markSupported() | 判断当前流是否支持标记流,和上面两个方法配套使用 |
OutputStream 类
方法 | 方法介绍 |
---|---|
public abstract void write(int b) | 写入一个字节,可以看到这里的参数是一个 int 类型,对应上面的读方法,int 类型的 32 位,只有低 8 位才写入,高 24 位将舍弃。 |
public void write(byte b[]) | 将数组中的所有字节写入,和上面对应的 read() 方法类似,实际调用的也是下面的方法。 |
public void write(byte b[], int off, int len) | 将 byte 数组从 off 位置开始,len 长度的字节写入 |
public void flush() | 强制刷新,将缓冲中的数据写入 |
public void close() | 关闭输出流,流被关闭后就不能再输出数据了 |
再来看 Reader 和 Writer 类中的方法,你会发现和上面两个抽象基类中的方法很像。
Reader 类
方法 | 方法介绍 |
---|---|
public int read(java.nio.CharBuffer target) | 读取字节到字符缓存中 |
public int read() | 读取单个字符 |
public int read(char cbuf[]) | 读取字符到指定的 char 数组中 |
abstract public int read(char cbuf[], int off, int len) | 从 off 位置读取 len 长度的字符到 char 数组中 |
public long skip(long n) | 跳过指定长度的字符数量 |
public boolean ready() | 和上面的 available() 方法类似 |
public boolean markSupported() | 判断当前流是否支持标记流 |
public void mark(int readAheadLimit) | 标记读取位置,下次还可以从这里开始读取,使用前要看当前流是否支持,可以使用 markSupport() 方法判断 |
public void reset() | 重置读取位置为上次 mark 标记的位置 |
abstract public void close() | 关闭流释放相关资源 |
Writer 类
方法 | 方法介绍 |
---|---|
public void write(int c) | 写入一个字符 |
public void write(char cbuf[]) | 写入一个字符数组 |
abstract public void write(char cbuf[], int off, int len) | 从字符数组的 off 位置写入 len 数量的字符 |
public void write(String str) | 写入一个字符串 |
public void write(String str, int off, int len) | 从字符串的 off 位置写入 len 数量的字符 |
public Writer append(CharSequence csq) | 追加吸入一个字符序列 |
public Writer append(CharSequence csq, int start, int end) | 追加写入一个字符序列的一部分,从 start 位置开始,end 位置结束 |
public Writer append(char c) | 追加写入一个 16 位的字符 |
abstract public void flush() | 强制刷新,将缓冲中的数据写入 |
abstract public void close() | 关闭输出流,流被关闭后就不能再输出数据了 |
下面我们就直接使用他们的子类,在使用中再介绍下面没有的新方法。
1、读取控制台中的输入
import java.io.*;
public class IOTest {
public static void main(String[] args) throws IOException {
// 三个测试方法
// test01();
// test02();
test03();
}
public static void test01() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一个字符");
char c;
c = (char) bufferedReader.read();
System.out.println("你输入的字符为"+c);
}
public static void test02() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一个字符,按 q 键结束");
char c;
do {
c = (char) bufferedReader.read();
System.out.println("你输入的字符为"+c);
} while (c != 'q');
}
public static void test03() throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一行字符");
String str = bufferedReader.readLine();
System.out.println("你输入的字符为" + str);
}
}
至于控制台的输出,我们其实一直都在使用呢,System.out.println()
,out 其实是 PrintStream 类对象的引用,PrintStream 类中当然也有 write() 方法,但是我们更常用 print() 方法和 println() 方法,因为这两个方法可以输出的内容种类更多,比如一个打印一个对象,实际调用的对象的 toString() 方法。
2、二进制文件的写入和读取
注意这里文件的路径,可以根据自己情况改一下,虽然这里的文件后缀是txt,但该文件却是一个二进制文件,并不能直接查看。
@Test
public void test04() throws IOException {
byte[] bytes = {12,21,34,11,21};
FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test.txt");
// 写入二进制文件,直接打开会出现乱码
fileOutputStream.write(bytes);
fileOutputStream.close();
}
@Test
public void test05() throws IOException {
FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test.txt");
int c;
// 读取写入的二进制文件,输出字节数组
while ((c = fileInputStream.read()) != -1) {
System.out.print(c);
}
}
3、文本文件的写入和读取
write() 方法和 append() 方法并不是像方法名那样,一个是覆盖内容,一个是追加内容,append() 内部也是 write() 方法实现的,也非说区别,也就是 append() 方法可以直接写 null,而 write() 方法需要把 null 当成一个字符串写入,所以两者并无本质的区别。需要注意的是这里并没有指定文件编码,可能会出现乱码的问题。
@Test
public void test06() throws IOException {
FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt");
fileWriter.write("Hello,world!\n欢迎来到 java 世界\n");
fileWriter.write("不会覆盖文件原本的内容\n");
// fileWriter.write(null); 不能直接写入 null
fileWriter.append("并不是追加一行内容,不要被方法名迷惑\n");
fileWriter.append(null);
fileWriter.flush();
System.out.println("文件的默认编码为" + fileWriter.getEncoding());
fileWriter.close();
}
@Test
public void test07() throws IOException {
FileWriter fileWriter = new FileWriter(new File("").getAbsolutePath()+"/io/test.txt", false); // 关闭追加模式,变为覆盖模式
fileWriter.write("Hello,world!欢迎来到 java 世界\n");
fileWriter.write("我来覆盖文件原本的内容");
fileWriter.append("我是下一行");
fileWriter.flush();
System.out.println("文件的默认编码为" + fileWriter.getEncoding());
fileWriter.close();
}
@Test
public void test08() throws IOException {
FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
BufferedReader bufferedReader = new BufferedReader(fileReader);
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}
fileReader.close();
bufferedReader.close();
}
@Test
public void test09() throws IOException {
FileReader fileReader = new FileReader(new File("").getAbsolutePath()+"/io/test.txt");
int c;
while ((c = fileReader.read()) != -1) {
System.out.print((char) c);
}
}
使用字节流和字符流的转换类 InputStreamReader 和 OutputStreamWriter 可以指定文件的编码,使用 Buffer 相关的类来读取文件的每一行。
@Test
public void test10() throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(new File("").getAbsolutePath()+"/io/test2.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "GBK"); // 使用 GBK 编码文件
outputStreamWriter.write("Hello,world!\n欢迎来到 java 世界\n");
outputStreamWriter.append("另外一行内容");
outputStreamWriter.flush();
System.out.println("文件的编码为" + outputStreamWriter.getEncoding());
outputStreamWriter.close();
fileOutputStream.close();
}
@Test
public void test11() throws IOException {
FileInputStream fileInputStream = new FileInputStream(new File("").getAbsolutePath()+"/io/test2.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "GBK"); // 使用 GBK 解码文件
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str;
while ((str = bufferedReader.readLine()) != null) {
System.out.println(str);
}
bufferedReader.close();
inputStreamReader.close();
}
4、复制文件
这里笔者做了一些测试,不使用缓冲对文件复制时间的影响,文件的复制实质还是文件的读写。缓冲流是处理流,是对节点流的装饰。
注:这里的时间是在我这台华硕笔记本上测试得到的,只是为了说明使用缓冲对文件的读写有好处。
@Test
public void test12() throws IOException {
// 输入和输出都使用缓冲流
FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
BufferedInputStream inBuffer = new BufferedInputStream(in);
FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
BufferedOutputStream outBuffer = new BufferedOutputStream(out);
int len = 0;
byte[] bs = new byte[1024];
long begin = System.currentTimeMillis();
while ((len = inBuffer.read(bs)) != -1) {
outBuffer.write(bs, 0, len);
}
System.out.println("复制文件所需的时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 200 多毫秒
inBuffer.close();
in.close();
outBuffer.close();
out.close();
}
@Test
public void test13() throws IOException {
// 只有输入使用缓冲流
FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
BufferedInputStream inBuffer = new BufferedInputStream(in);
FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
int len = 0;
byte[] bs = new byte[1024];
long begin = System.currentTimeMillis();
while ((len = inBuffer.read(bs)) != -1) {
out.write(bs, 0, len);
}
System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 500 多毫秒
inBuffer.close();
in.close();
out.close();
}
@Test
public void test14() throws IOException {
// 输入和输出都不使用缓冲流
FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
int len = 0;
byte[] bs = new byte[1024];
long begin = System.currentTimeMillis();
while ((len = in.read(bs)) != -1) {
out.write(bs, 0, len);
}
System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间 700 多毫秒
in.close();
out.close();
}
@Test
public void test15() throws IOException {
// 不使用缓冲
FileInputStream in = new FileInputStream("E:\\视频资料\\大数据原理与应用\\1.1大数据时代.mp4");
FileOutputStream out = new FileOutputStream("1.1大数据时代.mp4");
int len = 0;
long begin = System.currentTimeMillis();
while ((len = in.read()) != -1) {
out.write(len);
}
System.out.println("复制文件所需时间:" + (System.currentTimeMillis() - begin)); // 平均时间约 160000 毫秒,约 2 分多钟
in.close();
out.close();
}