Java中的IO流

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 流的分类
  • 以操作数据单位分类:

    • 字节流(8bit),操作二进制文件(音视频等)时可以保证无损(也可以操作文本,但是效率不高)

    • 字符流(具体字符大小由编码方式决定):用于操作文本文件

      抽象基类字节流字符流
      输入流InputStreamReader
      输出流OutputStreamWriter
  • 以数据流的流向分类:

    • 输入流
    • 输出流
  • 以流的角色分类:

    • 节点流
    • 处理流/包装流

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 基本介绍
  • 概念:

    • 节点流:可以从一个特定的数据源(存放数据的地方,可以是文件、数组、字符串、管道等)读写数据,如FileReaderFileWriter

    • 处理流(包装流):连接已经存在的流(节点流或处理流)之上,为程序提供更为强大的读写功能,也更加灵活(处理的数据源可以为多个),如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可以使得在进行序列化/反序列化的时候不会认为是一个全新的类
    
    • 序列化对象时,默认将类的所有属性进行序列化,但是除了statictransient修饰的成员:
    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.inInputStream键盘
System.outPrintStream显示器
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();
  • InputStreamReaderReader的子类,可以将字节流包装成字符流
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();  # 还是关闭最外层的流即可
  • OutputStreamReaderwriter的子类,可以将字节流包装成字符流
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码值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值