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的简单方法

07d4fdf6ded00de3d035f60a7190a9e3

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文档,这个临时文件就会被删除。

952faa2cfb6188e58b2bacc84589d58c

其实通过这个临时文件 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
这里会包含很多的属性~~

  1. 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()
4d365bfcf2ddccd78707d3d418579c91

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,顺序正确。

posted @ 2026-08-30 19:16  曾章吉的浩吧  阅读(2)  评论(0)    收藏  举报