8/22 Java学习博客
文件IO
所谓的"文件”是一个广义的概念.
可以代表很多的东西.
操作系统里,会把很多的硬件设备和软件资源都抽象成"文件",统一进行管理.
但是大部分情况下,谈到的文件,都是指硬盘的文件.
文件就相当于是针对"硬盘”数据的一种抽象.
我们可以使用目录的层次结构,来描述,文件所在的位置."路径”
1.绝对路径:是以C:D:盘符开头的.这种路径称为"绝对路径”往往是以.或者..开头的(.这种情况可以省略)
2.相对路径:需要先指定一个目录,作为基准目录,从基准目录出发,看看沿着啥样的路线能够找到指定文件,.此时涉及到的路径就是”相对路径“ 往往是以.或者..开头的
. 基准目录
..上一目录
例子: 绝对路径:D:\project\1课堂笔记\java109_0917_多线程.png
相对路径:
1)假定当前的基准目录就是D:\project\1课堂笔记
.\java109_0917_多线程.png
2)假定当前的基准目录是 D:\project\
.\1课堂笔记java109_0917_多线程.png
3)假定当前的基准目录是D:\project\1课堂笔记\111
..java109_0917_多线程.png
对于IDEA来说,基准目录,就是项目目录!!!
文件类型
从编程的角度看,文件类型,主要就是两大类
1.文本(文件中保存的数据,都是字符串.保存的内容都是合法的字符)
2.二进制(文件中保存的数据,仅仅是二进制数据.不要求保存的内容是合法的字符)
本身计算机存储的数据都是二进制的.
啥叫合法的字符?
字符集/字符编码.
utf8
有一个大的表格.就列出了什么字符,对应到什么编码
如果你的文件是 utf8编码的
此时文件中的每个数据都是合法的utf8编码的字符
就可以认为这个文件是文本文件了.
如果存在一些不是utf8合法字符的情况,就是二进制了.
如何判定一个文件是文本还是二进制?
就直接使用记事本,打开这个文件.
如果打开之后,乱码,文件就是二进制.
否则就是文本.
(记事本就是尝试按照字符的方式来展示内容,这个过程就会自动查码表)
很多文件都是二进制的.docx,pptx...都属于二进制的.
区分文本和二进制,是很重要的!!
写代码的时候,文本文件和二进制文件,代码编写方式是不同的!!!
文件的操作
1.文件系统的操作** File**
创建文件,删除文件,判定文件是否存在,判定文件类型,重命名
2.文件内容的操作 流对象
读文件/写文件
那我们先认识下这个路径中分割目录的符号
Windows上 \ 和 / 都可以作为分割符
Linux和Mac 则是 / 作为分隔符
一般来说我们最好用/ 因为\在代码中通常还得搭配转义字符来使用
认识下File的简单方法

public class Demo1 {
public static void main(String[] args) throws IOException {
File f = new File("./test.txt");
System.out.println(f.getParent());
System.out.println(f.getName());
System.out.println(f.getAbsolutePath());
System.out.println(f.getCanonicalFile());
}
}
分别对应
返回File对象的父目录文件路径
返回Flle对象的纯文件名称
返回File对象的绝对路径
返回File对象的修饰过的绝对路径
对于这个修饰过的绝对路径其实就是
getAbsolutePath() 是相对路径直接拼接到基准目录上得到
D:\Java\system_code.\test.txt
getCanonicalFile()修饰呢就是**简化了中间的 . **
D:\Java\system_code\test.txt
还有个Windows上的盘符,是不区分大小写的.
deleteOnExit()
删除动作等到JVM运行结束时候再执行
这个word里面的临时文件就是这么个意思,如果你关闭了word文档,这个临时文件就会被删除。

其实通过这个临时文件 word就能知道,你是正常关闭还是异常关闭
通过这个临时文件,就可以恢复到之前按正在编辑,还没来的及保存的内容。
要注意的是我们目前的File对象指向的test.txt普通文件 其实可能是不存在的,
所以我们真的要进行读写操作的话要进行createNewFile()来创建这个普通文件
当我们要是想获取目录底下有哪些文件的话
File f = new File("d:/");//想要list前提是这里是目录
String[] files =f.list();
System.out.println(files);
//当前打印的是哈希值 在Java代码中是没有任何办法获得“内存地址”
//[Ljava.lang.String;@4eec7777
System.out.println(Arrays.toString(files));
//就是类似于这样 要转换成字符串
for (String file : files) {
System.out.println(file);
}
创建目录的话就是 mkdir 和 mkdirs(递归创建必要目录)
改名
//src 源 dst 目标
File srcFile = new File("d:/test.txt");
srcFile.createNewFile();
File dstFile = new File("d:/test2.txt");
boolean ret = srcFile.renameTo(dstFile);
System.out.println("ret="+ret);
boolean ret1 = dstFile.delete();
System.out.println("ret1="+ret1);
认识下读写操作文件流
就如名字一样 流,stream 生生不息
在我们接水时候 要接100ml水
1.直接一次性100ml
2.分两次,一次接50ml
3.分五次,一次20ml
。。。。。。。。
完成这个接水可以有无数种方法 , 这是水的特点 称为水流
那我们要读写100字节的文件
也可以1.一次性读/写100字节
2.分两次 一次读写50字节
.。。。。。。。
完成这个读写操作同样也有“无数种”方案
所以称之为文件流
认识下读写文件流的流对象
在Java标准库中,提供的读写文件流对象有很多类,这很多类实际上可以归结到两个大类。
1、字节流(对应着二进制文件) 每次读/写的最小单位,都是字节(8bit)
2、字符流(对应着是文本文件) 每次读/写的最小单位,是’字符‘(一个字符可能对应着多个字节)
比如GBK 一个中文字符=>两个字节
UTF8 一个中文字符 =>三个字节
其实字符流本质上是对字节流进行了有一层封装~~
字符流,就能自动的帮咱们把文件中几个相邻的字节,转换成一个字符(帮我们完成了一个自动查字符集表)
认识下读/写操作
字节流
输入InputStream
输出 OutputStream
字符流
输入Reader
输出 Writer
那到底啥叫输入,啥叫输出?
我们都是站在CPU角度来的
所以把文件保存到硬盘这个过程 算输出
接下来就是来代码实现来熟悉下
字符流 读
Reader reader =new FileReader("d:/test.txt");
这里是因为Reader其实是个**接口 **所以只能new它的实现类
创建Reader对象的过程,就是"打开文件"过程.
那reader.read()就是开始读
这个read分为
@read()
@read(char[]cbuf)
@read (char[] cbuf, int off, int len)
1.无参数read:一次读取一个字符.
2.一个参数read:一次读取若干个字符.会把参数指定的cbuf 数组给填充满.
应该往这个read里传入的是一个空的字符数组(不是Null)然后由read方法内部,对这个数组内容进行填充,此时cbuf这个参数,称为"输出型参数”
就好像.
(1).先拿餐盘(空着的)
(2).把餐盘交给打饭阿姨,阿姨拿着勺子咔咔盛菜还有饭.
(3).阿姨把餐盘交给你~~
3.三个参数read:一次读取若干个字符.会把参数执行的cbuf数组,中的从of这个位置开始,到len这么长的范围尽量填满~~
Reader.read() 返回值是 int 类型。
正常读取时返回 0~65535,表示读取到的一个 UTF-16 char;返回 -1 表示读取结束。
注意:这并不代表每次从文件中只读取两个字节。
如果文件采用 UTF-8 编码,一个常见中文字符通常占 3 个字节,Reader 会先按照 UTF-8 将这 3 个字节解码成一个 Unicode 字符,然后以 char 的形式返回。
int 的作用主要是同时容纳 char 的 0~65535 和特殊值 -1。
代码实现
1.一次读一个字符
while(true) {
int c = reader.read();//一次读一个字符
if (c == -1) {
break;
}
char ch = (char)c;
System.out.println(ch);
}
2.一次read多个字符
while(true){
char[] cbuf = new char[1024];
int n = reader.read(cbuf);
if(n == -1) {
break;
}
System.out.println("n="+n);
for (int i = 0; i < n; i++) {
System.out.println(cbuf[i]);
}
}
加while作用
如果文件内容特别长,超出了数组的范围,就可以循环执行多次,保证最终能把文件读完
最重要的需要close方法
使用close方法,最主要的目的,是为了释放文件描述符!!!
PCB
这里会包含很多的属性~~
- pid
2.内存指针
3.文件描述符表
等等
其中文件描述符表 :顺序表(数组)
一个进程每次打开一个文件,就需要在这个表里,分配一个元素
这个数组的长度是存在上限的!!
如果你的代码,一直打开文件,而不去关闭文件,
就会使这个表里的元素,越来越多,一直到把这个数组占满了,
后续再尝试打开文件,就会出错了!!!
也就是文件资源泄露
那我们就往最后直接加个reader.close(); 好了
那其实仍有资源泄露方法 就是close可能执行不到
比如我们逻辑抛出异常导致close执行不到
我们可以用try catch finally 在finally里加一个close方法
但是这样有点不美观 所以 我们可以用 try with resource
将这个对象放到try(里)
try( Reader reader =new FileReader("d:/test.txt");){
while(true){
char[] cbuf = new char[1024];
int n = reader.read(cbuf);
if(n == -1){
break;
}
System.out.println("n="+ n);
for (int i = 0; i < n; i++) {
System.out.println(cbuf[i]);
}
}
}
这样子也可以解决这个问题。 其中有个要求就是要求写到()里的对象必须要实现Closeable接口 但是我们流对象刚好能写。
**字符流 写操作 **
write()

Writer写入文件,默认情况下就会把原有文件的内容清空掉!!如果不想清空,需要在构造方法中多加个参数~~
try(Writer writer = new FileWriter("d:/test.txt",true))
此时就不会清空原来文件的内容,而是写入到原有文件的末尾.
public static void main(String[] args) throws IOException {
try(Writer writer = new FileWriter("d:/test.txt",true)){
//直接用write方法可以写入数据
writer.write("我在学习文件IO");
}
}
字节流的读写操作
字节流的read 返回值也是int 实际上也是byte 0-255之间的数据 -1 表示读到文件末尾
read(byte[] b ,int off , int len)
read(byte[] b)
read()
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("d:/test.txt");){
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);
System.out.println("n="+n);
for(int i=0;i<n;i++){
System.out.printf("%x\n",buffer[i]);
}
} catch (IOException e) {
e.printStackTrace();
}
}
我们执行这个代码 就是以字节流的形式读出来的
返回的是ascii码值
这里的write 就少了字符串版本了,因为是字节流
但是可以通过 但是可以通过getbyte [] 来写入字符串
public static void main(String[] args) {
try(OutputStream outputStream =newFileOutputStream("d:/test.txt",true)){
String s = "你好世界";
outputStream.write(s.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
和Writer 类似,OutputStream打开一个文件,默认就会清空文件的原有内容.写入的数据,就会成为文件中新的数据~~如果不想清空,就可以使用追加写的方式.(在构造方法中,第二个参数传入true)
Java标准库中支持的流对象,种类非常多,功能也非常丰富.
上述介绍的,只是最基础的四个流对象的使用.
像刚才写的这个字节流的代码,是可以转成字符流的~~
别人给你提供的是字节流对象,但是你知道实际的数据内容是文本数据,还可以把上述字节流转成字符流
说一个简单的方法
先说下读
try(InputStream inputStream = new FileInputStream("d:/test.txt")😉
很明显这是个字节流对象
我们使用Scanner
//此时scanner从文件中读取
Scanner scanner = new Scanner(inputStream);
//就可以使用Scanner读取后续数据
String s = scanner.next();
System.out.println(s); ;
最后得到的是字符对象
再说下写
try(OutputStream outputStream = new FileOutputStream("d:/test.txt",true)😉
我们用PrintWriter这个类
//相当于把一个字节流 转换成字符流了
PrintWriter writer = new PrintWriter(outputStream)
writer.printf("hello");
}
这时候我们运行了 结果发现没写入
这是因为
PrintWriter这样的类,在进行写入的时候,不一定是直接写硬盘,而是先把数据写入到一个内存构成的“缓冲区”中(buffer)
为什么有这个缓冲区呢
引入缓冲区,目的是为了提高效率!!!
此处,我用来抓一把瓜子皮的手,就是缓冲区!!!
把瓜子皮放到手里,非常快的.把瓜子皮丢垃圾桶,非常慢的~~
为了提高效率,就应该想办法减少丢垃圾桶的次数~~
把数据写入内存,非常快的。把数据写入硬盘,非常慢的!!!(比内存慢个几千倍,上万倍)为了提高效率,就也应该想办法减少写硬盘的次数.
听起来美好,但是有个问题!!当我们写入缓冲区之后,如果还没来得及把缓冲区的数据写入硬盘,进程就结束了,此时数据就丢了!!没有真正写入硬盘呢!!
为了能够确保数据确实被写入硬盘就应该在合适的时机,使用**flush方法手动**刷新缓冲区~
{
PrintWriter writer = new PrintWriter(outputStream);
writer.printf("hello");
writer.flush();
}
其实还有个方法就是把这个将 PrintWriter 也放入 try-with-resources
try(OutputStream outputStream = new FileOutputStream("d:/test.txt", true);
PrintWriter writer = new PrintWriter(outputStream)) {
writer.printf("123312");
} catch (IOException e) {
throw new RuntimeException(e);
}
这样 try 块结束时,会先关闭 writer(自动 flush 缓冲区),然后关闭 outputStream,顺序正确。

浙公网安备 33010602011771号