文章目录
关于存储
首先我们需要先了解一下电脑的存储
- 硬盘 | 存储空间很大, 访问速度很慢, 速度比内存慢了3, 4个数量级。储存数据的时间很长, 断电后, 数据不会丢失
- 内存 | 存储空间比硬盘少了很多, 访问速度较快, 不能持续存储数据, 断电后数据就会丢失
- 缓存 | CPU的速度比内存的速度还要快上100+ 倍, 如果CPU的指令需要一直到内存中才能获取, 那么CPU的速度再快, 也要被缓慢的内存访问速度所拖累, 所以在计算上增加了缓存这个器件。
实际上缓存就是一个临时储存区, 访问速度比内存快得多, 相反空间远小于内存, 断电后数据丢失, 可以存储CPU重复使用的数据或者可能会使用到的数据, 在缓存中找不到的数据再去内存中找, 这样就能进一步提升整体的运行速度。 - 寄存器 | 是CPU的组成部分, 同时访问速度也是最快的, 容量非常小, 断电后数据丢失, 用于储存计算机频繁使用的数据。
关于文件
文件 | 即计算机中储存数据的容器
而计算机中是以树形结构来组织文件的, 文件夹(目录)就是非叶子结点, 普通文件就是叶子结点
以这个为例, 这个树形图大概是:
文件路径
知道了文件是通过树形结构组织的, 那么我们怎么找到目标文件呢 ? 或者说怎么去描述一个文件的位置呢 ? 这里就引出了文件路径, 文件路径包括绝对路径和相对路径。
绝对路径 |
从数据结构的角度上来说, 就是从根节点开始到目标文件的结点描述, 即为绝对路径。
例如下图, “宝藏” 的绝对路径是 D:/可删/222/宝藏
相对路径 |
从数据结构上说, 就是从某个结点开始, 到达目标文件的结点描述, 即为相对路径。
如上图 ↑, 在"可删"结点出发, 宝藏的相对路径为 ./222/宝藏
(以 ./ 或者 . ./ 开头的路径即相对路径)
这就好比你想知道 广州塔在哪, 手机地图说 中国广东省广州市海珠区阅江西路222号, 根节点即出发点是中国, 这个路径就是绝对路径。而如果你已经在广州市珠海区了, 你问路人广州塔在哪里, 他说阅江西路222号, 这里的出发路径是珠海区, 这个路径即是相对路径。
文件操作
在 Java 中有文件类, 即 File 类, File 类中比较常用的构造方法有 ↓
参数是绝对路径或者相对路径, 会根据路径创建一个 FIle 实例, 如果传入的字符串为空, 则抛出空指针异常。
关于 File 类的常用方法
返回值 | 方法签名 | 备注 |
---|---|---|
String | getName() | 返回File对象的名称 |
String | getPath() | 返回File对象的路径 |
String | getAbsolutePath() | 返回File对象的绝对路径 |
boolean | exisits() | 判断File对象描述的文件是否真实存在 |
boolean | isDirectory() | 判断是否是文件夹 |
boolean | isFile() | 判断是不是普通文件(目录不算) |
boolean | delete() | 根据File对象来删除文件 |
String[] | list() | 返回File对象目录下所有文件名字 |
File[] | listFiles() | 返回File对象目录下所有文件 |
文件读写
这里涉及到了一个概念 流
流, 数据在设备之间传输称为流
IO流, 描述数据流动的方向, 以内存为准, 流入内存为输入流, 流出内存则为输出流
IO 流又分为字节流和字符流, 字符流(Reader 和 Writer)适合用来操作文本文件, 字节流适合用来操作二进制文件
InputStream | 输入流
InputStream 是一个抽象类, 无法实例化, 围绕今天的主题, 重点还是它的实现类 FileInputStream
FileInputStream 常用的有两个构造方法
构造方法 | 备注 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
而这两个构造方法其实都是一个意思, 如下图, 参数是 String 的构造方法会根据这个 String 先创建 File 对象, 再把 File 对象传入另一个构造方法, 本质上都是先创建一个File对象, 再进行其余的步骤
这是 InputStream 中的常用方法
返回值 | 方法签名 | 备注 |
---|---|---|
int | read() | 读取一个字节的数据,如果读完返回-1 |
int | read(byte[] b) | 将读取到的数据存入b中, 返回实际读到的数量;实际读取到的字节数不超过b.length, 如果读完返回-1 |
int | read(byte[] b,int off, int len) | b数组从 off 开始将读取的数据存入b中,返回实际读到的数量, b数组满了就不读了;返回 -1 代表读完了 |
void | close() | 关闭字节流 |
需要注意的时候, 如果文件内容分多次读取数据, 每次读取任务完成后, 都会记录光标位置(也就是记录这次读到哪了), 下次读取数据的时候再从这个位置开始读取, 而不是每次都重新读
例如, 如果某一次读取到如下 o 字符前面的 w 就结束了本次读取, 则会记录本次读取到的位置, 那么下次读取的时候再从 o 开始读取
InputStream 的 读取操作有三个read方法,
我在 D 盘中创建了 text.txt 文件, 里面有"hello world" , 以此举例子
第一种 | read()
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("D:/text.txt");
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.print(b + " ");
}
inputStream.close();
}
第一种就是一个字节一个字节的读, 整形 b 则是字节对应的值, 如下图, text.txt 的内容是"hello world", 转化成对应的整形就是以下输出。
第二种 | read(byte[] b)
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("D:/text.txt");
byte[] buf = new byte[500];
int ret = inputStream.read(buf); //接收实际读取到的字节数
for (int i = 0; i < ret; i++) {
System.out.print(buf[i] + " ");
}
inputStream.close();
}
第二种 : 将读到的数据存在 byte[] 中, 相比第一种方法, 第二种方法更加高效。假设我要买50个鸡蛋, 方法一就是跑到超市买一个鸡蛋, 然后回家, 买50次。方法二就是提一个大包去超时买50个, 然后回家。实际上, 这个 byte[] 也是起到了缓冲区的作用, 减少了很多"路程"上的资源消耗。
如下, 和上述方法一样的输出
第三种 read(byte[] b, int off, int end) 本质上和 第二种是差不多的, 这里不赘述了。
OutputStream | 输出流
OutputStream 和 InputStream 一样, 都是抽象类, 今天重点还是它的实现类 FileOutputStream
OutputStream 的构造方法和 InputStream 大同小异, 两个构造方法本质是一样的, 也是先创建 File 对象, 再以这个为对象为参数进入另一个构造方法
关于OutputStream 中常用的方法
返回值 | 方法签名 | 说明 |
---|---|---|
void | write(int b) | 写入参数数据 |
void | write(byte[]b) | 将 b 这个字符数组中的数据全部写入 |
int | write(byte[]b, int off,int len) | 将 b 这个字符数组中从 off 开始的数据写入,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 刷新此输出流并强制任何缓冲的输出字节被写出 |
这里说一下 flush 方法, 由于 IO 操作的速度慢, 大多数的 OutputStream 是有缓冲区的, 啥意思呢 ? 就是 大多 IO 操作的数据会先放在缓冲区中, 直到缓冲区满了, 或者达到某个条件之后, 才会将缓冲区的全部写入。所以有时候我们写入的数据还停留在缓冲区中, 就需要我们及时 flush , 将缓冲区的数据移道它该去的地方。
第一种 | write(int b)
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("D:/text.txt");
outputStream.write(97);
outputStream.close();
}
第一种, 将参数写入, 且参数为 int, 运行结果如下, 97对应的字符就是 ‘a’
这种情况在向文件写入数据的时候, 都会先将文件的数据清空, 再进行写入。
并且, 整形参数只有低8位会被写入, 高24位会被忽略
第二种 | write(byte[] b)
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("D:/text.txt");
String str = "hello";
byte[] buf = str.getBytes();
for (int i = 0; i < buf.length; i++) {
outputStream.write(buf[i]);
}
outputStream.close();
}
如上, 把 "hello"转化成对应的 byte[] 数组, 再进行写入
对于输出流也是一样, 如果多次对同一个文件进行写入, 每次写入都会更新光标位置, 下一次写入就从光标位置开始继续写入, 当然光标的位置也可以进行手动调整。
同样对于write(byte[] b, int off, int len) 也是差不多的, 这里不赘述