简单学习 --> 文件IO

文件

文件是一个广泛的概念,操作系统里,把很多硬件设备和软件资源,都抽象成了 '文件' , 进行统一管理 ;但大部分情况,谈到文件, 指的都是硬盘;

以文件的方式,操作硬盘,在计算机上,这些文件用"文件系统'进行组织 ; 文件系统是操作系统提供的

操作系统-使用功能 '目录',来组织文件; -> 使用目录 的层次结构, 就可以用来描述 文件位置的 路径 ; (然后可以通过路径,确定文件当前所在的位置)

绝对路径:从盘符开始到文件(C:, D:........)

相对路径:从某个目录为基准,从基准目录到文件, 以..或.开始(.\文件)

文件类型(从编程角度)

文本文件: 文件里保存的数据都是字符串 ; 保存的内容,都是合法的字符

二进制文件: 文件里保存的数据是 二进制数据 ; 不要求保存的内容都是 合法的字符

(合法的字符-> 和字符集/编码有关, 例如utf8->类似于一个大表格,列出了什么字符,对应什么编码; 如果一个文件里的字符都是utf8编码的字符,那么这个文件就是合法的文本文件, 如果有的字符不在 上面,那么就是二进制文件) ;

如何判断,一个文件是文本还是二进制, 用记事本打开,如果乱码就是二进制;(记事本就是用字符的方式来展示内容)

(很多文件是二进制文件, 例如doxc,ppt这种后缀的都是二进制)

文件操作

java中针对文件操作分为2中

  1. 文件系统操作:创建/删除文件 , 判断文件是否存在 , 重命名....

  2. 文件内容操作: 读写文件 (流对象)

分隔符

在windows中 , 正斜杠/ 和 反斜杠 \ 都可以作为 路径分隔符 , 在Linux和 max里 / 作为分隔符 ;

一般 还是 / 更好用 , \ 还要搭配转义字符 ;

操作文件

File (java.io)

java中用File类 , 来操作文件

File的构造方法

一个File对象代表一个文件 , 构造一个File对象就要有文件路径(绝对或相对都可以)

File的方法

复制代码
File file = new File("./test.txt");
System.out.println(file.getName());    //完整的文件名
System.out.println(file.getPath());    // 路径
System.out.println(file.getParent());    //父母路径
System.out.println(file.getAbsolutePath());  // 获取绝对路径   如果路径是相对路径带.  , 就是项目路径+ .相对路径
System.out.println(file.getCanonicalPath());  //相当于简化的绝对路径 , 将绝对路径中不必要的去处 (例如.)
输出结果
test.txt
.\test.txt
.
F:\word\java_yan\j2024_6_7\.\test.txt
F:\word\java_yan\j2024_6_7\test.txt

虽然没有test.txt文件,但是也可以创建出file对象 ;

复制代码
  File file = new File("f:/test.txt");
System.out.println(file.exists());  // 判断是否存在
System.out.println(file.isDirectory()); // 判断是否为目录
System.out.println(file.isFile()); // 判断是否是一个文件
boolean newFile = file.createNewFile(); // 创建文件 返回创建是否成功
​
boolean delete =  file.delete(); // 删除文件, 返回是否删除成功
        
        file.deleteOnExit();  // 等到进程结束之后才会删除文件
        sout (5秒后进程结束)
        Thread.sleep(5000); //  5秒后main线程结束 , 进程也结束 ;

等进程结束后的机制有时候非常有用, 类似于我们使用word时, word也会创建一个临时的文件,直到我们关闭word,word才会删除这个临时文件

临时文件 , 为了避免计算机突然断点,数据来不及保存的问题 ;

复制代码
list () 返回 目录下的所有文件 , 首先file 对象 要是一个目录, 而不是一个文件
File file = new File("f:/");
String[] list  = file.list();        // 返回 当前目录的所有文件名
System.out.println(Arrays.toString(list)); 
sout(list)得到的是一个哈希值 ;
返回 一堆文件名 , 和一些我们目录里没有的东西 (这些没有的东西,是系统自带的隐藏文件) 
​
File file = new File("f:/");
String[] list  = file.listFiles();   返回 一个file 数组 ;    

创建目录;

复制代码
File file = new File("f:/ttt");
boolean flg = file.mkdir() ;  // 创建目录 ,只能创建一个目录, 不能创建多个目录
File file1 = new File("f:/ttt/aaa/bbb/ccc");
boolean flg1 = file1.mkdirs() ;       //可以创建多层目录
// 返回值 , 是否创建目录成功

重命名

复制代码
//src:source"源" ,  dest:destination  "目标"
File src = new File("f:/test.txt");
File dest = new File("f:/tt.txt");
boolean ret = src.renameTo(dest);       // 把源的名字 ,修改成目标的名字 ;
System.out.println(ret);
//首先 要要有源文件,才能重命名

操作文件内容

在java里 通过流对象(Stream) ,操作文件内容

如何形容流?

和水流类似, 我要 解100L水 , 我可以有无数种方法 , 1.一次接100L , 2.一次接50,一次解50 ........(这种叫水流)

我要读100字节文件 , 也有无数种方法 , 1.一次100 , 2. 一次50,一次50 ........(这种事文件流)

在java中提供的读写文件的流对象,有很多类 ;这么多类,可以归结到两个大类

  1. 字节流(对应二进制文件) , 读写最小单位 : 字节(8bit)

  2. 字符流(对应文本文件) , 读写最小单位: 字符(可能对应多个字节) 看字符集是那种

(字符流 本质是 字节流 的进一步封装 , 帮我们自动把相邻的几个字节转换成一个字符(相当于帮我们查字符集了))

字节流: InputStream , OutStream

字符流:Reader , Writer ;

输入和输出的区别

往把数据保存到 硬盘里 , 是输入 还是 输出 (输出)

站在CPU的角度 为标准 , 所以是输出

数据流的使用

Reader (是一个抽象类) 字符流

复制代码
Reader reader = new FileReader("文件地址");          new 一个 Reader的子类  FileReader
                 前提:地址文件要存在 , 如果文件不存在就抛异常

创建一个对象就相当于是打开这个文件,来进行操作 ;

读方法

read(); 返回一个int类型 , 读取一个字符

read()的其他用法

代码

复制代码
Reader reader = new FileReader("f:/tt.txt");
//       int n =  reader.read();   // read 返回一个int类型
//        System.out.println(n);
//        char[] chs = new char[10];
//
//        n =  reader.read(chs);      // 读到数组里
//        System.out.println(n);
//        System.out.println(Arrays.toString(chs));
​
        char[] chs = new char[10];
         reader.read(chs , 1 , 3);    // 读字符到数组里从 1下标 ,读取多少个  就是 3
        System.out.println(Arrays.toString(chs));
        reader.close()       关闭文件

这里的数组 ,就是一个"输出型参数" , 一般方法不方便用返回值来返回修改 ,就用 输出型参数来, 进行修改 ;

给一个空的数组,最后虽然没有返回,但是数组里已经填充有东西了 ;

关闭文件

使用完这个文件了, 要close , 关闭这个文件;

使用 close , 释放资源, 释放什么资源?

主要的目的就是 -> 释放 " 文件描述符"

(为了管理进程 ,有PCB , PCB里包含 文件描述符 , 这个文件描述符表: 就相当于一个顺序表(数组) ,进程里, 每个打开一个文件,就在表里分配一个元素 , 但是 这个数组的长度 是有限的 , 如果 不close释放 , 把这个数组里堆满, 后面在 尝试打开文件 , 就会出错!!

这个出错的问题 , 叫 " 文件资源泄露" ,(比较严重) , 和 "内存泄露"有点类似 ;

这个文件资源泄露完毕之前不会 ,影响 程序 , 而是等到问题出来的时候, 才会影响程序

这个最大问题 是 不会 马上暴漏 , 而是一定时间之后 突然报错 (这个时间就不能确定了)

不科学的close
复制代码
Reader reader = new FileReader("f:/tt.txt");
/**    工作内容 
       reader.close();           close()可能会执行不到
                                如果上面工作内容出问题了,例如抛异常了, close()就执行不到了
科学的close
复制代码
try(Reader reader = new FileReader("f:/tt.txt")){      定义对象在 try() 里面
    reader.read();
    //工作内容,各种操作
}

把这种文件操作对象定义在try() 里面 ,这样 ,try代码块在结束时 , 会自动调用 对象的close 方法释放资源 ;

无论 try代码块里的操作是否出现异常,报错 ; 都会自动 执行到close 方法 ;

try(){} 的用法

要使用try(){} ,在括号里定义的对象 ,在代码块结束时 , 自动调用close方法释放资源,

要保证()里创建的对象是

Writer (输出) 字符流

writer() 写方法

把你好写进文件 , 但是会默认把 原来的文件给清空了 在写 你好 ;

复制代码
public static void main(String[] args) throws IOException {
    try(Writer writer = new FileWriter("f:/tt.txt")){
        writer.write("你好");
    }
}

如果在构造对象时 , 给一个参数 true , 那么就是在原有的文件上继续写 ;

复制代码
try(Writer writer = new FileWriter("f:/tt.txt" ,true)){
    writer.write("你好");
}

字节流

InputStream

read()

复制代码
try(InputStream inputStream = new FileInputStream("f:/tt.txt")) { //抛两个异常,但是可以合并成一个
    byte[] array = new byte[1024];
    int n = inputStream.read(array) ;
    System.out.println("读了几个字节:"+n);
    for (int i = 0; i < n; i++) {
        System.out.printf("%x\n" , array[i]);    //一般使用16进程来,打印1个字节的数据
    }
} catch (IOException e) {
      e.printStackTrace();
}
如果tt.txt里的是utf8编码的 , 那么读到  "你好"
6 个字节
​

OutputStream

但是也可以通过一些方法来输出 ;

复制代码
try(OutputStream outputStream = new FileOutputStream("f:/tt.txt")) {
    String str = "你好世界"; //  创建字符串,用来输出
  outputStream.write(str.getBytes());  //把字符串转成字节类型的数据输出
} catch (IOException e) {
   e.printStackTrace();
}

字节流的writer 也和 字符流的writer一样 , 新建文件(如果文件已有就清空文件),在写入 ;

也和字节流的一样 ,在构造方法里传 一个true , 就不会清空文件,而是继续写入

java中支持非常多的流对象 , 上面的只是最基本的4中方法

字节流转成字符流,来使用

Scanner

把inputStream 传给Scanner , 这时候 Scanner 读的就是直接从文件里读取数据 ;

原来 给Scanner 的 System.in 其实就是一个 inputStream

复制代码
try(InputStream inputStream = new FileInputStream("f:/tt.txt")){
    Scanner scanner = new Scanner(inputStream);
    System.out.println(scanner.next());      // scanner.next() 返回一个字符串 
} catch (IOException e) {
   e.printStackTrace();
}
writer() 输出也可以 将 字节流转出 字符流

前面的 把字符串转成 字节输出 ,再输出 不够方便,方法也比较少 ;

用 PrintWriter 来 输出 , 用 outputStream 来构造 PrintWriter , 这是 pint输出的地方就是 文件里的

复制代码
 try(OutputStream outputStream = new FileOutputStream("f:/tt.txt")) {
         //这里就相当于 把字节流 准成 字符流输出
         PrintWriter writer = new PrintWriter(outputStream);
         // 这里printf  的 就是输出到文件里了
         writer.printf("hello");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

问题 , 明明已经用PrintWriter输出了 字符串 到文件里了, 可是文件里确没有东西

缓冲区

PrintWriter这样的类 , 在进行写入时 不一定是直接写到硬盘里 ,而是先写在内存构成 "缓冲区"中 (buffer) , 缓存区的作用:提高效率 ;

用缓冲区,减少直接写入硬盘的次数 , 写入内存的缓存区, 来提供效率 ;

缓冲区的提高效率,一个很大的问题-> 数据还没有来得及写进硬盘,进程就结束了 , 这样数据没有写进硬盘,数据就丢了

为了确保缓冲区里的数据,写入硬盘上 ,在合适的时机 使用 flush() 刷新 缓冲区;

复制代码
 writer.println("hello");
 writer.println("word");
writer.flush(); //刷新缓冲区,
当我们 用两个println来输出 , 然后用 flush刷新缓冲区, 文件里就改变了
相关推荐
学习使我快乐011 小时前
Express 学习
学习·node.js·express
熠熠仔1 小时前
《Agentic Design Patterns》概览
学习·设计模式
吴声子夜歌1 小时前
Java——Arrays
java·算法·排序算法
Tutankaaa2 小时前
从单场到多场并发:知识竞赛平台的弹性扩展能力
服务器·笔记·学习·职场和发展
fanzhonghong2 小时前
javaWeb开发之Maven高级
java·开发语言·spring boot·spring cloud·私服
xu_ws2 小时前
spring通过三级缓存解决循环依赖
java·spring·缓存·循环依赖
Chase_______2 小时前
Java 基础语言 ③:流程控制与数组——从条件分支到数组遍历,一篇通关
java·数据库·python
luck_bor2 小时前
Lambda表达式 算法异常
java·开发语言
码上小翔哥2 小时前
Jackson 配置深度解析
java·后端