1.常见文件操作
1.1 创建文件对象
// 方式1: String filePath = "D:\\test.txt"; File file = new File(filePath); // 此时还未创建,只是在内存创建了对象 try { file.createNewFile(); // 此时才写入硬盘 } catch (IOException e) { e.printStackTrace(); } // 方式2:父目录文件+子路径 File parentFile = new File("D:\\"); String filename = "test2.txt"; File file = new File(parentFile, filename); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } // 方式3:父目录名称+子路径 String parentname = "D:\\"; String filename = "test3.txt"; File file = new File(parentname, filename); try { file.createNewFile(); } catch (IOException e) { e.printStackTrace(); }
1.2 获取文件相关信息
File file = new File("D:\\test.txt"); System.out.println("文件名:" + file.getName()); System.out.println("文件绝对路径:" + file.getAbsolutePath()); System.out.println("文件父级目录:" + file.getParent()); System.out.println("文件大小(字节):" + file.length()); // 一个汉字3个字节 System.out.println("文件是否存在:" + file.exists()); System.out.println("是否为目录:" + file.isDirectory());
1.3 目录的操作和文件删除
File file = new File("D:\\testdir"); // 删除操作 if (file.exists()) { if (file.delete()) { System.out.println("删除成功"); }else { System.out.println("删除失败"); } } else { System.out.println("不存在"); } // 创建操作 File file = new File("D:\\testdir\\dir1"); if (file.exists()) { System.out.println(file+"存在"); } else { if (file.mkdirs()){ // 创建多级目录,mkdir()只能创建一级目录 System.out.println("创建成功"); }else { System.out.println("创建失败"); } }
2.IO流原理和流的分类
2.1 Java IO 流原理
- Java程序中,对于数据的输入/输出操作是以流的方式进行
- 输入:读取外部数据(磁盘、网络、另一个程序)到程序(即内存)中
- 输出:将程序(即内存)数据输出到外部
2.2 流的分类
以操作数据单位分类:
字节流(8
bit
),操作二进制文件(音视频等)时可以保证无损(也可以操作文本,但是效率不高)字符流(具体字符大小由编码方式决定):用于操作文本文件
抽象基类 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer 以数据流的流向分类:
- 输入流
- 输出流
以流的角色分类:
- 节点流
- 处理流/包装流
tips:
- Java中的IO流类都是从表格中4个基类派生的,由它们派生的子类名称都以其父类为名字后缀,如
FileReader
等
3.字节流和字符流
3.1 IO流体系图
3.2 文件和流
- 要实现内存和文件之间的数据转换,就要通过流
3.3 Input/OutputStream
3.3.1 FileInputStream
String filePath = "D:\\test.txt"; int read = 0; byte[] buf = new byte[8]; // 一次读取8个字节 // 创建对象,用于读取文件 FileInputStream fileInputStream = null; // 拿出来是因为finally中需要使用 try { fileInputStream = new FileInputStream(filePath); // 从输入流中读取一个字节的数据,返回-1表示读取完毕 //while ((read = fileInputStream.read()) != -1) { // System.out.print((char)read); //} // 从输入流中读取最多b.length个字节的数据到字节数组中 // 读取正常返回实际读取的字节数,读取完毕则返回-1 while ((readLen = fileInputStream.read(buf)) != -1) { System.out.println(readLen); System.out.println(new String(buf, 0, readLen)); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭文件流,释放资源 try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
tips:
- 如果文件中出现字符,以上述方式读取文件会出现乱码,因为1个字符由3个字节组成,读取单独的一个字节必然会乱码
3.3.2 FileOutputStream
String filePath = "D:\\test1.txt"; // 创建对象,用于写入文件 FileOutputStream outputStream = null; try { // 该方法以覆盖的方式写入,可以调用另一个构造器以追加形式写入 // outputStream = new FileOutputStream(filePath); // 文件不存在会创建 outputStream = new FileOutputStream(filePath, true); // 写入单个字节 outputStream.write('J'); // 再写入多个字节 outputStream.write("IO".getBytes()); } catch (IOException e) { e.printStackTrace(); }finally { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } }
tips:
- 将一个文件内容读取到另一个文件:
String srcFile = "D:\\test.txt"; String destFile = "D:\\test1.txt"; FileInputStream inputStream = null; FileOutputStream outputStream = null; try { inputStream = new FileInputStream(srcFile); outputStream = new FileOutputStream(destFile); byte[] buf = new byte[1024]; int readLen = 0; while ((readLen = inputStream.read(buf)) != -1) { // 边读边写 // 如果一个文件有1025个字节,需要执行两次while循环,剩余的那一个字节就覆盖buf[0] // 如果使用write(buf),会把buf[1-1023]再次写入 outputStream.write(buf, 0, readLen); // 使用该方法 } } catch (IOException e) { e.printStackTrace(); }finally { try { if (inputStream!=null){ inputStream.close(); } if (outputStream!=null){ outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } }
3.4 Writer/Reader
3.4.1 FileReader
String filePath = "D:\\test.txt"; // 创建对象,用于读取文件 FileReader fileReader = null; int read = 0; int readLen = 0; char[] buf = new char[8]; // 一次读取8个字符 try { // 该方法以覆盖的方式写入,可以调用另一个构造器以追加形式写入 // outputStream = new FileOutputStream(filePath); // 文件不存在会创建 fileReader = new FileReader(filePath); // 每次读取一个字符 //while ((read = fileReader.read()) != -1) { // System.out.println((char) read); //} // 从输入流中读取最多buf.length个字符的数据到字符数组中 // 读取正常返回实际读取的字符数,读取完毕则返回-1 while ((readLen = fileReader.read(buf)) != -1) { System.out.println(new String(buf, 0, readLen)); } } catch (IOException e) { e.printStackTrace(); } finally { try { fileReader.close(); } catch (IOException e) { e.printStackTrace(); } }
3.4.2 FileWriter
String filePath = "D:\\test1.txt"; // 创建对象,用于写入文件 FileWriter fileWriter = null; char[] chars = {'P','杰'}; try { // 该方法以覆盖的方式写入,可以调用另一个构造器以追加形式写入 // fileWriter = new FileWriter(filePath); // 文件不存在会创建 fileWriter = new FileWriter(filePath, true); // 写入单个字节 fileWriter.write('杰'); // 再写入多个字节 fileWriter.write(chars); } catch (IOException e) { e.printStackTrace(); }finally { try { fileWriter.close(); // 一定要关闭,不然无法往文本写入内容 } catch (IOException e) { e.printStackTrace(); } }
tips:
FileWriter
使用后,必须要关闭或者刷新,否则写入不到指定文件,还是在内存而已(因为在执行close方法或者flush方法时,会调用writeBytes
方法)
4.节点流和处理流
4.1 基本介绍
概念:
节点流:可以从一个特定的数据源(存放数据的地方,可以是文件、数组、字符串、管道等)读写数据,如
FileReader
、FileWriter
等处理流(包装流):连接已经存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活(处理的数据源可以为多个),如
BufferReader
等
区别:
- 节点流是底层流,直接和数据源相接;而处理流对节点流进行包装时,使用了修饰器设计模式,不会直接与数据源相连
// 定义抽象类 public abstract class Reader_ { public abstract void read(); } // 定义两个节点流 public class FileReader_ extends Reader_ { @Override public void read() { System.out.println("对文件进行操作..."); } } public class StringReader_ extends Reader_{ @Override public void read() { System.out.println("对字符串进行操作..."); } } // 定义处理流 public class BufferReader_ { private Reader_ reader_; // 属性是Reader_类型 public BufferReader_(Reader_ reader_) { this.reader_ = reader_; } // 扩展read方法,实现大量读取不同数据源的内容 public void readMore(int num){ for (int i = 0; i < num; i++) { reader_.read(); } } } // 使用处理流对不同数据源进行操作 // 通过BufferReader对文件操作 BufferReader_ bufferReader_ = new BufferReader_(new FileReader_()); bufferReader_.readMore(5); // 动态绑定机制 // 通过BufferReader对字符串操作 BufferReader_ bufferReader_2 = new BufferReader_(new StringReader_()); bufferReader_2.readMore(5); // 动态绑定机制
- 处理流包装节点流,既可以消除不同节点流的实现差异(即处理的数据源可以为多个),也可以提供更方便的方法完成输入输出:
public class BufferedReader extends Reader { private Reader in; ... // 在BufferReader中有属性Reader,说明封装了节点流,该节点流可以是Reader的子类中的任意一个,如FileReader、PipedReader等
处理流的优势:
- 提高性能:主要以增加缓冲的方式提高输入输出的效率
- 操作便捷:提供了一系列便捷方法来一次输入输出大量的数据
4.2 处理流
4.2.1 字符流BufferedReader和BufferedWriter
String path = "D:\\test.txt"; BufferedReader bufferedReader = new BufferedReader(new FileReader(path)); String line; try { // 按行读取,性能效率高 // 返回null时表示读取完毕 while ((line = bufferedReader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { e.printStackTrace(); }finally { try { bufferedReader.close(); // 只需要关闭BufferReader即可 } catch (IOException e) { e.printStackTrace(); } }
String path = "D:\\test1.txt"; // 实现追加方式写入文件的话是在节点流上不同的构造器 BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(path, true)); bufferedWriter.write("hello IO"); bufferedWriter.newLine(); // 插入一个和系统相关的换行符 bufferedWriter.write("hello2 IO"); bufferedWriter.close();
tips:
- 关闭处理流时,只需要关闭外层流即可,底层会自动关闭被包装的节点流:
public void close() throws IOException { synchronized (lock) { if (in == null) // in就是节点流对象 return; try { in.close(); } finally { in = null; cb = null; } } }
4.2.2 字节流BufferedInputStream和BufferedOutputStream
String srcFilePath = "D:\\1.jpg"; String destFilePath = "D:\\2.jpg"; //创建 BufferedOutputStream 对象 BufferedInputStream 对象 BufferedInputStream bis = null; BufferedOutputStream bos = null; try { //因为 FileInputStream 是 InputStream 子类 bis = new BufferedInputStream(new FileInputStream(srcFilePath)); //因为 FileOutputStream 是 OutputStream 子类 bos = new BufferedOutputStream(new FileOutputStream(destFilePath)); //循环的读取文件,并写入到 destFilePath byte[] buff = new byte[1024]; int readLen = 0; //当返回 -1 时,就表示文件读取完毕 while ((readLen = bis.read(buff)) != -1) { bos.write(buff, 0, readLen); } System.out.println("文件拷贝完毕"); } catch (IOException e) { e.printStackTrace(); } finally { //关闭流 , 关闭外层的处理流即可,底层会去关闭节点流 try { if (bis != null) { bis.close(); } if (bos != null) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } }
tips:
BufferedInputStream
在创建时会创建一个内部缓冲区数组BufferedOutputStream
可以将多个字节写入底层输出流,而不需要每次将字节写入时调用底层系统- 字节流可以操作二进制文件,也可以操作文本文件
4.2.3 对象流ObjectInputStream和ObjectOutputStream
- 作用:假设需要保存
int num=100
这个int
数据保存到文件中,之所以强调为int
类型是因为100可能是String
类型,要能将基本数据类型或对象进行序列化和反序列化操作。而对象流提供了对基本类型或对象类型的序列化和反序列化的方法class Person implements Serializable{ private String name; public Person(String name) { this.name = name; } } // ObjectInputStream String filePath = "D:\\data.dat"; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath)); try { oos.writeInt(100);// int -> Integer (实现了Serializable) oos.writeBoolean(true);// boolean -> Boolean (实现了Serializable) oos.writeObject(new Person("psj")); } catch (IOException e) { e.printStackTrace(); }finally { oos.close(); } // ObjectOutputStream ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\data.dat")); try { System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readObject()); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { ois.close(); }
注意事项:
ObjectOutputStream
读取数据的顺序要和储存的顺序一致- 序列化的类中建议添加
SerialVersionID
,提高版本的兼容性:class Person implements Serializable { private String name; private static final long serialVersionUID = 1L; public Person(String name) { this.name = name; } } // 当该类添加了某个属性或方法时,serialVersionUID可以使得在进行序列化/反序列化的时候不会认为是一个全新的类
- 序列化对象时,默认将类的所有属性进行序列化,但是除了
static
或transient
修饰的成员:class Person implements Serializable { private String name; private static String nation; public Person(String name, String nation) { this.name = name; this.nation = nation; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + "nation='" + nation + '\'' + '}'; } } // 测试反序列化 Person p = (Person) ois.readObject(); System.out.println(p); // 输出:Person{name='psj'nation='null'}
- 序列化对象时,要求属性的类型也实现序列化接口,如果存在属性没有序列化会报错
- 序列化具有可继承性
tips:
什么是序列化和反序列化?
- 序列化:在保存数据时,保存数据的值和数据类型。要让某个类是可序列化的,需要实现以下两个接口之一
Serializable
:标记接口,没有方法Externalizable
:有方法需要实现- 反序列化:在恢复数据时,恢复数据的值和数据类型
序列化后保存的文件格式不是文本,而是按照其他格式保存
假设在序列化后修改了对象的属性/方法或者改变了该类的路径,此时直接进行反序列化会报错,需要重新进行序列化
4.2.4 标准输入/输出流System.in和System.out
运行类型 默认设备 System.in InputStream 键盘 System.out PrintStream 显示器
4.2.5 转换流InputStreamReader和OutputStreamReader
- 使用场景:当
test.txt
的编码方式不是UTF-8
时,使用下面代码读取文件会出现乱码(因为默认读取方式为UTF-8
),所以需要一个能指定编码方式的方式String filePath = "D:\\test.txt"; BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath)); System.out.println(bufferedReader.readLine()); # 输出乱码 bufferedReader.close();
InputStreamReader
:Reader
的子类,可以将字节流包装成字符流String filePath = "D:\\test.txt"; // 1.使用FileInputStream以字节形式读入文件内容 // 2.将FileInputStream转为InputStreamReader,并指定编码 InputStreamReader inputStreamReader = new InputStreamReader(new FileInputStream(filePath), "gbk"); // 2.把InputStreamReader传入BufferedReader(可以按行读取,处理效率高) BufferedReader bufferedReader = new BufferedReader(inputStreamReader); System.out.println(bufferedReader.readLine()); bufferedReader.close(); # 还是关闭最外层的流即可
OutputStreamReader
:writer
的子类,可以将字节流包装成字符流OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:\\test1.txt"), "gbk"); osw.write("hello杰"); osw.close();
tips:
- 处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换为字符流
4.2.6 打印流PrintStream和PrintWriter
PrintStream
:// out的类型就是PrintStream PrintStream out = System.out; // 默认情况下输出数据的位置是标准输出,即显示器 // 可以修改输出位置 System.setOut(new PrintStream("D:\\test.txt")); System.out.print("helloIO");
PrintWriter
:PrintWriter printWriter = new PrintWriter(new FileWriter("D:\\test1.txt")); printWriter.print("hello杰"); printWriter.close();
tips:
- 打印流只有输出流,没有输入流
- 不管是处理流还是节点流都需要关闭,不关闭不会报错,但是只有关闭了才会写入文件
5.Properties类
使用传统的
BufferReader
类读出文件的代码如下://读取mysql.properties文件 BufferedReader br = new BufferedReader(new FileReader("src\\mysql.properties")); String line = ""; while ((line = br.readLine()) != null) { //循环读取 String[] split = line.split("="); if("ip".equals(split[0])) { System.out.println(split[0] + "值是: " + split[1]); } } br.close();
- 基本介绍:
- 专门用于读写配置文件的集合类
- 配置文件的格式:键=值
- 使用:
// 读取文件 //1. 创建 Properties 对象 Properties properties = new Properties(); //2. 加载指定配置文件 properties.load(new FileReader("src\\mysql.properties")); //3. 把 k-v 显示控制台 properties.list(System.out); //4. 根据 key 获取对应的值 String user = properties.getProperty("user"); // 创建文件并修改 Properties prop // key存在就是修改,没有就是创建 erties = new Properties(); properties.setProperty("charset", "utf8"); properties.setProperty("user", "汤姆"); // 保存的是中文的unicode码值 // 将设置的k-v保存到文件中 // 第二个参数是注释,会写在文件开头 properties.store(new FileOutputStream("src\\mysql2.properties"), null);
tips:
- 键值对不需要空格,值也不需要引号,默认类型为
String
Properties
类保存中文字符时保存的是其unicode
码值