目录
关于存储
一台计算机主要由这几个部分组成(冯诺依曼体系结构)
-
CPU
-
存储器
-
输入设备
-
输出设备
存储器是指能存储数据的器件,包括寄存器、缓冲、内存、硬盘,其对应CPU访问的速度有快到慢分别是:寄存器>缓冲>内存>硬盘。
- 寄存器:是中央处理器的注册部分,是一种直接整合到CPU中的有限的高速访问速度的存储器,它是由一些非门组合组成的,分为通用寄存器和特殊寄存器。其特点是容量小,主要存储指令和CPU频繁访问的数据,断电后数据丢失。
- 缓存:缓存其实是内存中的高速缓存(cache),它存在的目的是因为当CPU频繁访问内存中的一些数据时,如果每次都从内存中去读,花费的时间会更多,因此在寄存器和内存之间有了缓存,把CPU要频繁访问的一些数据存储在缓存中,这样效率就会提高。其特点是缓存的大小也是很小的,不能存放大量的数据,断电后数据丢失,并且缓存中存放的数据会因为CPU的访问而被替代。(比如说,某个数据开始被CPU频繁访问,然后将这个数据存在缓存中,但是后来不再频繁,那这个缓存里面存的这个数据的空间会被其他访问频繁的数据所占据)。缓存又可以分为一级缓存和二级缓存,一级的速度大于二级的速度。因此CPU在访问数据时,先到缓存中看有没有,没有的话再到内存中读取。
- 内存:内存分为只读存储器(ROM)和随机存储器(RAM)。其中RAM应用非常广,例如我们电脑上的内存条就是RAM。内存特点:存储空间比硬盘少了很多, 访问速度较快, 不能持续存储数据, 断电后数据就会丢失。
- 硬盘:储空间很大, 访问速度很慢, 速度比内存慢了3, 4个数量级。储存数据的时间很长, 断电后, 数据不会丢失。**硬盘,U盘等存储器都归入外存储器,它们的访问速度是最慢的。
关于文件
我们先来认识狭义上的文件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时, 往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概 念,就类似办公桌上的一份份真实的文件一般。
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据 而存在,我们把这部分信息可以视为文件的元信息。
同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然 的想法出现了,就是按照层级结构进行组织 —— 也就是我们数据结构中学习过的树形结构。这样,一 种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的 概念。
树形图大概长这样
文件路径
如何在文件系统中如何定位我们的一个唯一的文件就成为当前要解决的问题,这里就引入文件路径,包括绝对路径和相对路径。
绝对路径
树中的每个结点都可以被一条从根开始,一直到达的结点的路径所描 述,而这种描述方式就被称为文件的绝对路径(absolute path)
例如:下图的文件1的绝对路径就是 D:/照片/1/
相对路径
除了可以从根开始进行路径的描述,我们可以从任意结点出发,进行路径的描述,而这种描述方式就被 称为相对路径(relative path),相对于当前所在结点的一条路径。
比如上图从照片这个结点出发找到文件1,的路径就是 ./1
以.或者…开头的路径称为“相对路径”,先明确一个基准路径(工作目录),相对路径是从基准路径出发,找到目标的目录。
文件操作
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不 代表真实存在该文件。
构造方法
可以通过构造方法来基于一个路径构造一个文件
参数是绝对路径或者相对路径, 会根据路径创建一个 FIle 实例, 如果传入的字符串为空, 则抛出空指针异常。
关于 File 类的常用方法
文件读写
这里涉及到了一个概念 流
流, 数据在设备之间传输称为流
IO流, 描述数据流动的方向, 以内存为准, 流入内存为输入流, 流出内存则为输出流
IO 流又分为字节流和字符流, 字符流(Reader 和 Writer)适合用来操作文本文件, 字节流适合用来操作二进制文件
InputStream 输入流
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基 本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使 用 FileInputStream
FileInputStream 常用的有两个构造方法
而这两个构造方法其实都是一个意思, 如下图, 参数是 String 的构造方法会根据这个 String 先创建 File 对象, 再把 File 对象传入另一个构造方法, 本质上都是先创建一个File对象, 再进行其余的步骤。
文件内容的操作:
- 打开文件
- 关闭文件
- 读文件
- 写文件(3,4是关键操作)
在Java中关于文件读写,提供了一些类:
第一组:InputStream OutputStream 字节流:以字节为单位的流
第二组:Reader Writer 字符流:以字符流为单位的流
流在计算机中非常常见,形象的比喻
InputStream常用方法
需要注意的时候, 如果文件内容分多次读取数据, 每次读取任务完成后, 都会记录光标位置(也就是记录这次读到哪了), 下次读取数据的时候再从这个位置开始读取, 而不是每次都重新读
public class Demo6 {
public static void main(String[] args) throws IOException {
InputStream inputStream=null;
try (InputStream inputStream=new FileInputStream("./test2.txt")){
//1.打开文件
//2.读取文件
// while(true){
// int b=inputStream.read();//法一使用read()
// if(b==-1){
// //文件读完了
// break;
// }
// System.out.println(b);
// }
byte[] b=new byte[1024];
int len=inputStream.read(b);//法二使用read(byte[] b);
// System.out.println(len);
// for(int i=0;i<len;i++){
// System.out.println(b[i]);
// }
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面代码我使用了两种方法去读文件
- 法一:就是一个字节一个字节的读, 整形 b 则是字节对应的值
- 法二:将读到的数据存在 byte[] 中, 相比第一种方法, 第二种方法更加高效, 这个 byte[] 是起到了缓冲区的作用, 减少了很多"路程"上的资源消耗。
运行结果
第三种三个参数的版本一样把数据往byte[] b里塞,从b[off]位置开始塞,最多塞len个字节
利用 Scanner 进行字符读取
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我 们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。
构造方法 | 说明 |
---|---|
Scanner(InputStream is,String charset) | 使用charset字符集进行is的扫描读取 |
示例1
public class Demo8 {
// 对于文本文件, 还有更简单的写法~~
public static void main(String[] args) throws IOException {
InputStream inputStream=new FileInputStream("test2.txt");
Scanner scanner=new Scanner(inputStream);
String s=scanner.next();
System.out.println(s);
inputStream.close();
}
}
OutputStream 输出流
写文件:
字节流 OutputStream/FileOutSream
字符流 Writer/FileWriter
说明
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中, 所以使用 FileOutputStream
OutputStream 的构造方法和 InputStream 大同小异, 两个构造方法本质是一样的, 也是先创建 File 对象, 再以这个为对象为参数进入另一个构造方法。
示例1使用字节流写入,用write()方法
public class Demo9 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("test2.txt")) {
outputStream.write('h');
outputStream.write('e');
outputStream.write('l');
outputStream.write('l');
outputStream.write('o');
// 不要忘记 flush
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
第一种, 将参数写入, 且参数为 int,写入数据的时候, 都会先将文件的数据清空, 再进行写入,并且, 整形参数只有低8位会被写入, 高24位会被忽略。
如果打开文件时,不想清空旧的内容,可以再传入一个参数true,举个例子:
OutputStream outputStream = new FileOutputStream("test2.txt",true)
这样就开启续写,即在打开文件时,不会清空test2.txt的内容,而是继续写
示例二:用write(byte[] b)方法
public class Demo9 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("test2.txt")) {
String s="hello java";
outputStream.write(s.getBytes());
// 不要忘记 flush
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
对于输出流也是一样, 如果多次对同一个文件进行写入, 每次写入都会更新光标位置, 下一次写入就从光标位置开始继续写入, 当然光标的位置也可以进行手动调整。
第三种write三个参数的版本效果与read差不多,只是一个写一个读。
利用 PrintWriter 找到我们熟悉的方法
和Scanner相对的,还可以使用PrintWriter来简化针对字符流的写入操作
上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用 PrintWriter 类来完成输出,因为
PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
示例一
public class Demo11 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("test2.txt")) {
// 此处的 PrintWriter 的用法就和 System.out 是一样的了
PrintWriter printWriter=new PrintWriter(outputStream);
printWriter.println("aaa");
printWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}