File和IO
元数据与文件读写
Java 的 File 表示文件的元数据。元数据是什么?元数据就是描述数据的数据。文件的元数据就是文件名、文件大小、文件路径等信息。
要读写文件内容,需要用到 Java 的 IO 流,而 IO 流需要提供 File 对象。
File 的常用方法
File 的构造方法需要提供文件的路径,支持绝对路径、相对路径以及网络路径,不过这个路径可以不存在。
- boolean exists():判断传入的路径是否对应一个真实存在的文件
- 如果不存在则可以新建文件
- boolean createNewFile(),新建文件也只会创建文件,文件可以没有后缀
- boolean mkdir(),创建单级目录
- boolean mkdirs(),创建多级目录
- 如果文件存在,可以判断是文件夹还是文件,如果文件不存在则这两个方法都返回 false
- boolean isFile()
- boolean isDirectory()
- 如果不存在则可以新建文件
- 获取文件信息
- String getAbsolutePath(),返回文件的绝对路径
- String getPath(),返回构造方法中传入的路径
- String getParent(),返回父级路径字符串
- File getParentFile(),返回父级路径 File 对象
- String getName(),返回文件夹名称、文件名(有后缀带后缀)
- long length(),返回文件的大小(字节),如果是文件夹也有返回值但是不正确
- long lastModified():最近修改的时刻
- 文件权限相关
- boolean canRead()
- boolean canWrite()
- boolean isHidden()
- list,如果 File 对象不存在、File 对象不是文件夹、File 对象需要的权限不足返回 null,否则返回文件夹下的所有文件(如果是空文件夹则返回一个空数组)
- String[] list()
- File[] listFiles()
- 危险操作
- boolean delete(),删除文件和空目录,不走回收站
- boolean renameTo(File dest),重命名或者移动文件,原文件不会保留
基础 IO 流
基础 IO 流可以分成字节流、字符流、输入流和输出流。两两组合就得到了四种搭配:
字节输入流:InputStream
int read():每次读取一个字节,取值在 [0, 255] 区间,读完返回 -1。
int read(byte[] b) : 读取是尽量将数组填满,返回实际读取的字节数,读完返回-1。
int read(byte[] b, int off, int len):跟上一个方法一样,只不过加了一个偏移量,返回实际读取的字节数,读完返回-1。
void close()
字节输出流:OutputStream
void write(int b):每次写一个字节
void write(byte[] b):一个字节一个字节写效率太低,每次写一整组的字节数据
void write(byte[] b, int off, int len)
void flush()
void close()
字符输入流:Reader
int read()
int read(char[] cbuf)
int read(char[] cbuf, int off, int len)
void close()
字符输出流:Writer
void write(int c)
void write(char[] cbuf)
void write(char[] cbuf, int off, int len)
void write(String str)
void write(String str, int off, int len)
void flush()
void close()
InputStream、OutputStream、Reader、Writer 都是抽象类,都实现了 Closeable 接口,输出流额外实现了 Flushable 接口。
字符流拥有一个长度为 8192 的字节数组缓冲区,充当了内存的作用,Java 程序先将数据读入缓冲区之后每次再到缓冲区中读,写数据的时候先将数据写到缓冲区中,缓冲区写满了就刷盘。输出流的 flush 方法可以主动将缓冲区的数据刷盘。
拷贝文件的示例代码如下,InputStream、OutputStream、Reader、Writer 都是抽象类,需要用到实现类 FileInputStream、FileOutputStream、FileReader、FileWriter。
public void test() throws IOException { File source = new File("C:\\Users\\admin\\Downloads\\python.md"); File dest = new File("C:\\Users\\admin\\Documents\\python.md"); FileInputStream fis = new FileInputStream(source); // 输出流的append参数表示是追加写还是覆盖写,默认是false也就是覆盖写 FileOutputStream fos = new FileOutputStream(dest, true); int res; while (true) { res = fis.read(); if (res == -1) break; fos.write(res); } fos.close(); fis.close(); }不基础 IO 流
1.缓冲流
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter,需要传入基础 IO 流对象。
字节缓冲流新增了长度为 8192 的字节数组;而字符缓冲流的缓冲区从长度为 8192 的字节数组升级成了字符数据,长度不变。
字符缓冲流新增了两个方法:
- String readLine():一次读取一行,遇到换行符结束,读完返回 null
- void newLine():换行
这两个方法可以区分不同操作系统的换行符。
2.序列化流
将 Java 对象转成字节数组,可以在不同设备之间进行传输。
- 只有字节流,没有字符流
- Java 对象所属的类必须实现 Serializable 接口,这是一个标记性空接口,里面没有抽象方法
- 固定 private static final long serialVersionUID 属性
- private,外部不可见
- static,版本号是跟类相关的属性,跟对象没关系
- final,常量不可二次修改
- long serialVersionUID,按英语的说法是固定搭配
如果某个属性不想被序列化,可以用 transient 瞬态关键字修饰,反序列化后得到的值是 0 或 null。
public void test() throws IOException, ClassNotFoundException { File file = new File("C:\\Users\\admin\\Documents\\1.txt"); String str = "1234"; // 序列化:将Java对象转成字节数据 FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(str); oos.close(); // 反序列化:将字节数据转成Java对象 FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); String s = (String) ois.readObject(); ois.close(); // 只关序列化流就可以了,像这些需要传入IO流对象的实例对象在调用close方法时会自动将传入的IO流对象一起关闭 System.out.println(s); }在我们之前学习的 IO 流中,会返回 -1,返回 null 表示读完了。
但是序列化流不会进行提醒,如果写入了 10 个对象,但是进行了 11 次读取操作就会直接报错,非常粗暴。
比较好的做法是将实例对象存入集合中,然后将集合对象序列化。
3.打印流
打印流可以做到原样输出,在方法中传递什么参数,就直接输出到文件中。如果传入的是一个对象,将这个对象的 toString 方法的返回值输出到文件中。
打印流的特点是只支持输出流:PrintStream、PrintWriter。
- void print()
- void println()
- void printf(String format, Object... args),跟 C 语言的 printf 用法差不多
4.转换流
InputStreamReader:字节输入流 -> 字符输入流,传入 InputStream,但是能调用字符流的方法
InputStreamReader(InputStream in)
InputStreamReader(InputStream in, String charsetName)
OutputStreamWriter:字节输出流 -> 字符输出流,传入 OutputStream,但是能调用字符流的方法
OutputStreamWriter(OutputStream in)
OutputStreamWriter(OutputStream in, String charsetName)
将 UTF8 编码的文件读取到程序中,用 GBK 编码写入到新文件中。
public static void main(String[] args) throws IOException { InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\admin\\Downloads\\1.txt"), "UTF8"); OutputStreamWriter isw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\admin\\Downloads\\2.txt"), "GBK"); char[] arr = new char[256]; int len = 0; while ((len = isr.read(arr)) != -1) { System.out.println(new String(arr, 0, len)); isw.write(arr, 0, len); } isw.close(); isr.close(); }Unicode 码点
ASCII 码:英文标点符号,一个字节就可以表示。
GBK:1 英2 中,兼容 ASCII 码,英文用一个字节表示,中文用两个字节表示。
Unicode:Unicode 是一个字符集,它为世界上所有字符分配了唯一的编号,这个编号就叫码点。但是,Unicode 并未规定这些字符如何保存和传输,需要具体的编码方式来实现。
UTF-8:Unicode 的一种变长编码方式,可以用 1 到 4 个字节来保存字符,1 英 3 中。
这个时候我想到了 Java 里面的 char 类型,char 可以用来表示中文,但是 char 类型只占用两个字节,这是怎么做到的呢?
其实 char 类型保存的是字符的码点这个唯一编号,并不是保存字符实际的编码,如果一个字符的码点无法用 16bit 表示,就无法用 char 类型存储。
平时我们可以用 char 来表示中文,是因为这些字符的码点可以用两个字节表示,但是有些字符不能,如 emoji 表情。
𠮷 是一个生僻字,就无法用一个 char 表示,需要用 String 表示。
因为 𠮷 要用两个 char 表示,所以 String 的 length 方法返回的是 2,也就是 String 中 char 的个数。
如果想真正统计字符串中有多少个字符,需要用 String 提供的 codePointCount 方法。
