Java IO
I : Input 硬盘 -> 内存
O:Output 内存 -> 硬盘
1.1 IO流的分类
1、按照流的方向进行分类,以内存为参照物。
->内存:叫做输入,即读; 内存->:叫做输出,即写。
2、按照读取数据的方式不同进行分类
有的流是按照字节的方式读取数据,一次读取一个字节 (byte) ,等同于一次读取8个二进制位。 这种流是万能的,什么类型的文件都能读,包括文本文件、图片、声音文件….
- 第一次读:一个字节,正好读到 ‘h’
- 第二次读:一个字节,读到 ‘中’ 的一半
- 第三次读:一个字节,读到 ‘中’ 的另一半
有的流是按照字符的方式读取,一次读取一个字符,这种流是为了方便读取普通文本而存在的, 这种流不能读取图片、声音、视频等文件,只能读取文本文件,甚至连word文件都无法读取。
- 第一次读:读到 ‘a’ 字符(’a’ 字符在windows系统中占1个字节)
- 第二次读:读到 ‘中’ 字符(’中’ 字符在windows系统中占用2个字节)
Java IO流的四大家族:
以 Stream 结尾的都是字节流java.io.InputStreamjava.io.OutputStream
以 Reader 或 Writer 结尾的都是字符流java.io.Readerjava.io.Writer
这四个流都是抽象类 abstract class , 所有的流都实现了 java.io.Closeable 接口,都有 close() 方法。流毕竟是一个管道,这是内存和硬盘之间的通道,用完之后一定要关闭,不然会耗费很多资源。
所有的输出流都实现了 java.io.Flushable 接口,都有 flush() 方法。输出流在最终输出的时候,一定要记得调用 flush 方法,将管道中的剩余数据输出,以清空管道,否则可能导致丢失数据。
1.2 文件流
java.io.FileInputStreamjava.io.FileOutputStreamjava.io.FileReaderjava.io.FileWriter
1.2.1 FileInputStream
int read() 从输入流中读取一个字节,返回读取的字节码值
try (FileInputStream fis = new FileInputStream("/Users/fangshiqi/dev/java-io/src/main/resources/file/test.txt")) {// 标准写法,当读到-1时,表示已经读到文件末尾int readData = 0;while ((readData = fis.read()) != -1) {System.out.println(readData);}} catch (IOException e) {e.printStackTrace();}
以上程序的缺点:
- 一次读一个字节,这样内存和硬盘交互太频繁,基本上时间/资源都耗费在IO上;
int read(byte[] b) 从输入流中将最多b.length个字节的数据读如一个byte数组中,返回读取到的字节数
// 在idea中,默认当前路径是工程的根目录String filePath = "./file/test.txt";try (FileInputStream fis = new FileInputStream(filePath)) {byte[] bytes = new byte[5];int n = 0;// 将读到的byte数组转换为字符串// 返回-1表示没读到while ((n = fis.read(bytes)) != -1) {// 读取多少个字节,转换多少个System.out.print(new String(bytes, 0, n));}} catch (Exception e) {e.printStackTrace();}
int available() 返回文件剩余还没读的字节数量;
public static void available() {try(FileInputStream fis = new FileInputStream(filePath)) {System.out.println("文件总字节数: " + fis.available());} catch (IOException e) {e.printStackTrace();}}
long skip(int n) 跳过几个字节不读取,返回实际跳过的字节数;
/*** long skip(int n) 跳过几个字节不读取*/public static void skip() {try (FileInputStream fis = new FileInputStream(filePath)) {long skip = fis.skip(3);System.out.println("跳过3" + skip + "个字节,"还剩" + fis.available() + "个字节");} catch (IOException e) {e.printStackTrace();}}
1.2.2 FileOutputStream
文件字节输出流,负责写文件,内存 —-> 硬盘
void write(byte[] b) 将b.length个字节从指定byte数组写入次文件输出流中。
// ./file/out.txt 文件在不存在时会自动新建,但如果文件夹都没有就会报错// java.io.FileNotFoundException// 并且该方式会将原文件清空,然后重新写入try (FileOutputStream fos = new FileOutputStream("./file1/out1.txt")) {String message = "hello world";byte[] bytes = message.getBytes(StandardCharsets.UTF_8);fos.write(bytes);fos.flush();} catch (IOException e) {e.printStackTrace();}
void write(byte[] b, int start, int end) 将指定的byte数组中从 start 到 end 的字节写入文件输出流中。 FileOutputStream(String name) 创建一个向具有指定 name 的文件中写入数据的文件输出流,当指定的文件不存在时会自动创建该文件,但如果指定的目录不存在则会报 java.io.FileNotFoundException 异常。 FileOutputStream(String name, boolean append) 创建一个向具有指定 name 的文件中写入数据的输出文件流
- append 为 true 时表示以追加的方式在文件的末尾写入;
FileOutputStream(File file, boolean append) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流
1.2.3 综合案例—-拷贝文件
/*** 拷贝文件,从{@code src}->{@code dst}* <p>目标文件如果不存在则会自动创建,但其目录必须存在** @param src 原文件路径* @param dst 目标文件路径*/public static void copyFile(String src, String dst) {FileInputStream fis = null;FileOutputStream fos = null;try {// 创建一个输入流对象fis = new FileInputStream(src);fos = new FileOutputStream(dst);byte[] bytes = new byte[1024 * 1024]; // 一次拷贝1Mint readCount = 0;while ((readCount = fis.read(bytes)) != -1) {fos.write(bytes, 0, readCount);}// 刷新输出流fos.flush();} catch (IOException e) {e.printStackTrace();} finally {// 分开try, 如果一起try,其中一个流的异常会影响到另一个流的关闭.if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}if (fos != null) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}
1.2.4 FileReader
文件字符输入流,只能读取普通文本。
在读取文本内容时,比较方便、快捷。
public static void readByte() {// 创建文件字符输入流try (FileReader reader = new FileReader("file/reader.txt")) {// 开始读char[] chars = new char[4]; // 一次读取4个字符int readCount = 0;while ((readCount = reader.read(chars))!= -1) {System.out.println(new String(chars, 0, readCount));}} catch (IOException e) {e.printStackTrace();}}
1.2.5 FileWriter
void write(char[] chars)
public static void writerBytes() {try(FileWriter writer = new FileWriter("./file/writer.txt")) {// 开始写char[] chars = {'中', '华', '有', '为'};writer.write(chars);// 刷新writer.flush();} catch (IOException e) {e.printStackTrace();}}
void write(String str)
public static void writerString() {try(FileWriter writer = new FileWriter("./file/writer.txt")) {// 开始写String message = "尔奉尔禄,民脂民膏";writer.write(message);// 刷新writer.flush();} catch (IOException e) {e.printStackTrace();}}
1.3 缓冲流
1.3.1 BufferedReader
- 节点流:当一个流的构造方法中需要另一个流时,这个被传进来的流称为节点流
- 包装流:包装流也即处理流,外部负责包装的这个流叫做包装流
对于包装流来说,只需要关闭最外层的流即可,里面的节点流会自动关闭。
使用 BufferedReader 可以每次读一行数据
public static void readLine() {BufferedReader br = null;try {FileReader reader = new FileReader("./file/reader.txt");br = new BufferedReader(reader);// 当一个流的构造方法中需要一个流时,这个被传进来的流称为节点流// 外部负责包装的这个流,叫做包装流,也即处理流// 像当前程序,FileReader被称为节点流,BufferedReader就是包装流/处理流String line = null;// 当读到最后时,会返回nullwhile ((line = br.readLine()) != null) {// 注意这里并不会读到换行符System.out.println(line);}} catch (IOException e) {e.printStackTrace();} finally {// 关闭流// 对于包装流来说,只需要关闭最外层的流即可// 里面的节点流会自动关闭if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}}}
1.3.2 BufferedWriter
/*** 通过使用 {@link OutputStreamWriter} 将 字节流 转换为 字符流** @throws IOException io异常*/public static void write() throws IOException {FileOutputStream fos = new FileOutputStream("out.txt");OutputStreamWriter osw = new OutputStreamWriter(fos);BufferedWriter bufferedWriter = new BufferedWriter(osw);bufferedWriter.write("fuck this world");bufferedWriter.flush();}
1.3.3 使用转换流
BufferedReader(Reader reader) 构造器只能传字符流,不能传字节流,若需要传字节流,需要使用转换流进行转换
stream -> reader
/*** 通过转换流来使用 {@link java.io.InputStream}*/public static void readStream() throws IOException {// 字节流// 第二个参数设置为true表示追加FileInputStream fis = new FileInputStream("./file/reader.txt");// 使用转换流进行转换InputStreamReader reader = new InputStreamReader(fis);// 读取字节流的数据BufferedReader br = new BufferedReader(reader);String line = null;while ((line = br.readLine()) != null) {System.out.println(line);}br.close();}
1.4 数据流
1.4.1 DataOutputStream
- 数据专属流,他是一个包装流
- 这个流可以将数据连同数据的类型,一并写入文件
这个文件不是普通文本文档,该文件用记事本打不开
public static void output() throws IOException {// 创建数据专属的字节输出流// 数据专属流还是一个包装流final DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));// 写数据byte b = 100;short s = 200;int i = 300;long l = 400L;float f = 3.0F;double d = 3.14;char c = 'a';// 写入// 将数据于数据类型一并写入文件当中dos.writeByte(b);dos.writeShort(s);dos.writeInt(i);dos.writeLong(l);dos.writeFloat(f);dos.writeDouble(d);dos.writeChar(c);dos.writeBoolean(true);dos.flush(); // 刷新dos.close(); // 关闭}
1.4.2 DataInputStream
java.io.DataInputStream数据字节输入流java.io.DataOutputStream写的文件只能由 {@link java.io.DataInputStream} 去读并且读的顺序需要和写入的顺序一致才能正常读到数据
/*** {@link java.io.DataInputStream} 数据字节输入流* <p> {@link DataOutputStream} 写的文件只能由 {@link java.io.DataInputStream} 去读* <p> 并且读的顺序需要和写入的顺序一致才能正常读到数据*/public static void input() throws IOException {final DataInputStream data = new DataInputStream(new FileInputStream("data"));// 开始读// 读之前需要知道写入的顺序// 读的顺序要和写的顺序一致byte b = data.readByte();short s = data.readShort();int i = data.readInt();long l = data.readLong();float f = data.readFloat();double d = data.readDouble();boolean bool = data.readBoolean();char c = data.readChar();System.out.println(b + " " + s + " " + i + " " + l + " " + f + " " + d +" " + bool + " " + c);data.close();}
1.5 标准流
1.5.1 PrintStream/PringWriter
获取标准输出流 (字节的方式)
// 获取标准输出流PrintStream ps = System.out;
改变输出目标
/*** 改变输出目标*/public static void out() throws FileNotFoundException {// 标准输出流不再指向控制台// 而是指向log.txt// 这是日志框架的实现原理PrintStream ps = new PrintStream(new FileOutputStream("log.txt"));System.setOut(ps);System.out.println("hello world");}
1.6 序列化与对象流
序列化(serialize): Java对象存储到文件中。将 Java 对象的状态保存下来的过程
反序列化(deserialize):将硬盘上的数据重新恢复到内存当中,恢复成 Java 对象
1.6.1 Serializable接口
Serializable接口起到一个标志的作用,- Java虚拟机看到这个类实现了该接口, 会为该类自动生成一个序列化版本号
序列化版本号的作用:
用于区分类的。 当源代码改动之后,重新便衣后生成了全新的字节码文件
并且class文件再次运行的时候,Java虚拟机生成的序列化版本号也会发生相应的改变
这时若用改动之后的类去读取未改动之前所保存的文件内容,会出现序列化版本不一致的错误.
Java语言中是采用什么机制来区分类的?
第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类 第二:如果类名一致,会通过序列化版本号进行区分。 也就是说,当实现了
Serializable接口后,如果对代码进行后续的修改, 编译后会生成全新的序列化版本号码,此时Java虚拟机会认为这是一个全新的类。
最佳实践:**
凡是一个类实现了 Serializable 接口,建议给该类提供一个固定不变的序列化版本号。
这样,以后这个类即使代码修改了,但是版本号不变,JVM会认为是同一个类。
// 手动写明序列化版本号private static final long serialVersionUID = -1214344247376595885L;
1.6.2 transient 关键字
1.6.3 ObjectInputStream
public static void readObject() throws IOException, ClassNotFoundException {final FileInputStream fs = new FileInputStream("./sebuntin");final ObjectInputStream objectInputStream = new ObjectInputStream(fs);final Object object = objectInputStream.readObject();if (object instanceof Student) {Student s = (Student) object;System.out.println(s.toString());}
其中 Student 类实现了 Serializable 接口
public class Student implements Serializable {// 手动写明序列化版本号private static final long serialVersionUID = -1214344247376595885L;private int no;private String name;// transient关键字表示password不参与序列化private transient String password;...}
1.6.4 ObjectOutputStream
public static void writeObject() throws IOException {final Student sebuntin = new Student(23, "sebuntin", "123456");final ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./sebuntin"));objectOutputStream.writeObject(sebuntin);objectOutputStream.flush();objectOutputStream.close();}
1.7 File类
⚠️注意:File 并不是一个流,所以通过 File 是无法完成文件的读和写的
关于File类的解释: 1、在Java中File是一个文件或目录路径名的抽象表示形式。
C:\Drivers这是一个File对象C:\Drivers\Lan\Realtek\Readme.txt也是File对象 2、File类和四大家族没有关系,File类不能完成文件的读和写。
1.7.1 构造方法
File(File parent, String child) 根据 parent 抽象路径名和child路径名字符串创建一个新
File实例 File(String pathname) 通过将给定路径字符床转换为抽象路径名来创建一个新File实例File(String parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新
File实例File(URI uri) 通过将给定的 file:URI 转换为一个抽象路径名来创建一个新的
File实例
1.7.2 实例方法
boolean exists() 判断文件或目录是否存在
public static void test01() {// 创建一个file对象File f1 = new File("./file");// 判断文件或目录是否存在System.out.println(f1.exists());}
boolean createNewFile() 创建文件,如果文件或目录不存在,则会以文件的形式创建。
boolean mkdir() 创建目录,如果文件或目录不存在,则会以目录的形式创建。
boolean mkdirs() 以多重目录形式创建。
String getParent() 获取文件的父路径(以String的形式返回)。
File getParentFile() 获取文件的父路径(以File的形式返回)
File getAbsolutePath() 获取文件或目录的绝对路径。
boolean isDirectory() 判断是否为目录
boolean isFile() 判断该抽象路径名是否为文件
long lastModified() 获取文件最后一次修改时间,返回的数值的单位为毫秒(从1970年到现在的总毫秒数)
案例:io+properties联合应用
/*** Properties 是一个map集合,key和value都是String类型* 想将一个userinfo文件中的数据加载到Properties对象当中*/public static void createProperties() throws IOException {final FileReader reader = new FileReader("file/userinfo");// 新建一个map集合Properties properties = new Properties();// 调用load方法, 将文件中的数据加载到内存中// 文件中的数据顺着管道加载到map集合中// 其中=左边作key,右边作为valueproperties.load(reader);System.out.println(properties.getProperty("username"));System.out.println(properties.getProperty("age"));}
其中 userinfo文件内容为
username=sebuntinage=23
需要了解的是: Java规范中有要求,属性配置文件建议以
.properties结尾,但这不是必须的 这种以.properties结尾的文件在 Java 中被称为:属性配置文件。 其中Properties类是专门存放属性配置文件内容的一个类在properties文件中,key于value可以使用
=隔开,也可以使用:隔开在properties文件中,key重复时,value会自动覆盖。 在properties文件中,# 为注释符 在properties文件中,=两边不要有空格。
