1. 认识文件
1)文件概念
“文件”是一个广义的概念,可以代表很多东西
操作系统里,会把很多的硬件设备和软件资源抽象成“文件”,统一管理
但是大部分情况下的文件,都是指硬盘的文件(文件相当于是对“硬盘”数据的一种抽象)
机械硬盘(HDD)适合顺序读取(磁头移动需要时间)
固态硬盘(SSD)内部是集成程度很高的芯片
2)目录
一台计算机上有很多文件,这些文件是通过“文件系统”(操作系统提供的模块)来进行组织的
操作系统,使用“目录”(文件夹)这样的结构来组织文件
目录内部可能还包含其他的文件/目录
3)文件路径
可以通过文件路径,来确定当前文件具体所在的位置
1. 绝对路径:以 C: D: 盘开头
2. 相对路径:先指定一个目录作为基准目录,从基准目录出发,看沿着怎样的路径能找到指定的文件。一般以 . 或者 .. 开头( . 的情况可省略)
. 当前目录
.. 当前目录的上一级目录
如果是命令行进行操作,基准目录,就是当前所处的目录
如果是图形化界面的程序,基准目录就不好判断了
对于IDEA来说,基准目录,就是项目目录
4)文件类型
从编程的角度看:
1. 文本——文件中保存的数据,都是字符串,保存的内容,都是合法字符
2. 二进制——文件中保存的数据,仅仅是二进制数据,包要求保存的内容是合法字符
合法字符涉及字符集/字符编码
如 utf8:
有一个大的表格(码表),列出什么字符,对应到什么编码
如果文件是 utf8 编写的,此时文件的每个数据都是合法的 utf8 编码的字符 ——文本文件
如果存在一些不是 utf8 合法字符的情况——二进制
判断文件的类型——>
直接使用记事本打开这个文件,如果是乱码,文件就是二进制,否则就是文本
记事本就是尝试按照字符的方式来展示内容,这个过程会自动查码表
写代码时,文本文件和二进制文件的编码方式不同
2. Java 中操作文件
1. 文件系统的操作: File
创建文件,删除文件,判断文件是否存在,判断文件类型,重命名
2. 文件内容的操作: 流对象
读文件/写文件
1)File 概述(文件系统操作)
属性
修饰符积累性 | 属性 | 说明 |
static String | pathSeparator | 依赖于系统的路径分隔符, String 类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符, char 类型的表示 |
pathSeparator 是一个路径中用来分割目录的符号
Windows => \ 和 /
Linux => /
一般还是使用 / ,使用 \ 在代码中要搭配转义字符使用
构造方法
签名 | 说明 |
File(File parent,String child) | 根据父目录+孩子文件, 创建一个新的对象 |
File(String pathname) | 根据文件路径创建一个新的实例, 路径可以是绝对路径或相对路径 |
File(String parent,Strinf child) | 根据父目录+孩子文件路径创建实例, 父目录用路径表达式 |
1)一个File对象,就表示一个硬盘上的文件
在构造对象的时候,就需要把这个文件的路径指定进来(绝对路径/相对路径都可以)
2)文件名 = 前缀 + 扩展名
使用路径构造对象,一定要把前缀和扩展名都带上
方法
修饰符及返回值类型 | 方法签名 | 说明 |
String | getParent() | 返回File对象的父目录 文件路径 |
String | getName() | 返回File对象的纯文件名称 |
String | getPath() | 返回File对象的文件路径 |
String | getAbsolutePath() | 返回File对象绝对路径的 |
String | getCanonicalPath() | 返回File对象的修饰过的 绝对路径 |
boolean | exist() | 返回File对象描述的文件是否 真实存在 |
boolean | isDirectory() | 返回File对象代表的文件是否是 一个目录 |
boolean | isFile() | 返回File对象代表的文件是否是 一个普通文件 |
boolean | createNewFile() | 根据File对象,自动创建一个 空文件,创建成功后返回true |
boolean | delete() | 根据File对象,删除该文件, 成功删除后返回true |
void | deleteOnExit() | 根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行 |
String[] | list() | 返回File对象代表的目录下的 所有文件名 |
File[] | listFile() | 返回File对象代表的目录下的所有文件,以File对象表示 |
boolean | mkdir() | 创建File对象代表的目录 |
boolean | mkdirs() | 创建File对象代表的目录,如果必要,会创建中间目录 |
boolean | renameTo(File dest) | 进行文件(剪切,粘贴操作) |
boolean | canRead() | 判断用户是否对文件有可读的权限 |
boolean | canWrite() | 判断用户是否对文件有可写的权限 |
Windows上的盘符不分大小写
getCanonicalPath() 针对绝对路径进行简化后得到的路径
对IDEA来说,基准目录就是项目所在的目录
绝对路径就是把当前的相对路径拼接到基准目录上
System.out.println(files);打不出数组内容,是数组的哈希值
在JVM上层,Java代码中,没有任何办法获取到“内存地址”
要想拿到内存地址,只能靠native方法进入到JVM内部,通过C++代码获取到
System.out.println(Arrays.toString(files));会打印出系统自带的特殊目录,不让用户感知到,防止被随意修改
2)文件内容的读写 —— 数据流
流对象(文件内容操作)
在标准库中,提供的读写文件的流对象(stream)有很多类,可以归结到两个大的类别中:
1. 字节流(对应二进制文件)
每次读/写的最小单位,是“字节”(8bit)
InputStream OutputStream
2. 字符流 (对应文本文件)
每次读/写的最小单位,是“字符”(一个字符可能对应多个字节)
本质上是对字节流的又一层封装,把文件中几个相邻的字节,转换成一个字符(自动查字符集表)
Reader Writer
GBK,一个中文字符集 => 两个字节
utf8,一个中文字符集 => 三个字节
输入/输出是站在CPU的角度上: 输出——>读 输入——>写
1. Reader 类
Reader 是抽象类,不能new实例,只能new子类 标准库已经提供了现成的类
创建Reader对象的过程,就是”打开文件“的过程 文件不存在就就会打开失败
1. 无参数 read:一次读取一个字符
2. 一个参数 read:一次读取若干个字符,会把参数指定的 cbuf 数组给填充满
4. 三个参数 read :一次读取若干个字符,会把参数中的 cbuf 数组,从off位置开始,到len的范围尽量填满
1)把这个 cbuf 空数组(不是null,没有实际意义的数组)尽量填满
2)使用 close 方法,是为了释放文件描述符(PCB)——>
顺序表(数组)
进程每次打开文件,都需要在这个表里分配一个元素,这个数组的长度是存在上限的
当数组占满后再尝试打开文件,会触发文件资源泄露(类似内存泄漏)
3)read 返回值是 int 表示读取的字符数,若文件读完了,返回 -1
reader.close();该方法可能会执行不到,如果程序出现逻辑错误,会报异常终止程序,调用不到 close
可以使用
try{}finally{reader.close(); } //但过于繁琐,不够优美try{
} // try with resources
() 中定义的变量,会在 try 代码结束的时候(正常结束,还是抛出异常),自动调用其中的 close 方法
要求写到 () 里的对象必须实现 Closeable 借口 ——流对象都可以这么写
在Java标准库内部,对于字符编码做了很多的处理:
1)只使用char,此时使用的字符集固定是 unicode
若使用String,此时会自动把每个字符的 Unicode 转换成 utf8
2)char[] c 包含的每个字符都是 unicode 编码的
String s = new String(c); 一旦使用这个字符数组构成String,就会在内部把每个字符都转换成 utf8 ,可配置
s.charAt(i) 也会把对应的 utf8 的数据,转换成 unicode
3)把多个 unicode 联系放到一起,难以区分从哪到哪是一个完整的字符
utf8 可以做到区分
可以认为 utf8 是针对连续多个字符进行传输时的一种改进方案
2. Write 类
1. 一次写一个字符
2. 一次写一个字符串
3. 一次写多个字符(字符数组)
4,5. offset 是从数组/字符串中的第几个字符开始写
Write 写入文件,默认情况下会把原文件的内容清空掉
若不想清空,需要在构造方法中添加一个参数
此时回见内容写到原有文件的末尾
3. OutputStream类
和 Write 类似, OutputStream 打开一个文件,默认会清空文件的原有内容
写入的数据,会成为文件中的新数据
若不想清空,可以使用追加写的方式(在构造方法中,第二个参数传入 true )
此处已经写入了字符串,但在文件中却未写入
缓冲区
PrintWriter 这样的类,在进行写入的时候,不一定是直接写硬盘,而是先把数据写入到一个内存构成的“缓冲区”中(buffer) ——引入缓冲区是为了提高效率,减少读硬盘的次数
当写入缓冲区后,如果还没来及将缓冲区的数据写入硬盘,进程就结束了,此时数据就丢了
有时候需要手动使用 flush 方法将数据写入硬盘 ——刷新缓冲区
4. InputStream类
以上是将文件中的数据全部读完的两种方式,前者的IO次数更少,性能更好
Scanner 进行字符读取Scanner(InputStream is, String charset) 使用 charset 字符集进行 is 的扫描读取
文件练习
扫描指定目录,找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
1)list 列出目录内容
2)判断文件类型
3)删除文件
找到目录中的所有文件,以及子目录中的所有文件,只要遇到子目录就往里找
可以使用“递归”的方式,把所有的子目录都扫描一遍