Java IO流详解


提示:以下是本篇文章正文内容,Java系列学习将会持续更新

一、File

java.io.File 类用于作为文件和目录路径信息的抽象表示,可以获取文件和目录的属性信息,如名称、大小等信息。

1.1 构造方法

// 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 
File(String pathname) 

// 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。 
File(File parent, String child) 

// 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 
File(String parent, String child) 

1.2 文件操作 方法

// 创建文件如果存在这样的文件,就不创建了
public  boolean createNewFile()

// Java中的删除不走回收站,要删除一个文件夹,注意文件夹内不能包含文件或文件夹
public  boolean delete()

// 重命名: 如果新旧两个File的路径一样,就是改名;如果不一致,就是 复制+剪切
public  boolean renameTo(File dest)

1.3 目录操作 方法

// 创建文件夹,如果存在这样的文件夹,就不创建了。如果忘了写盘符路径,默认在项目路径下
public  boolean mkdir()
// 创建文件夹,如果父文件夹不存在,会帮你创建出来
public  boolean mkdirs()

// 列出File对象的所有子文件名和路径名,返回String数组
public String[] list()
// 列出File对象的所有子文件和路径,返回File数组
public File[] listFiles()

1.4 文件检测 方法

public boolean isDirectory()  // 判断是否是目录
public boolean isFile()    	  // 判断是否是文件
public boolean exists()       // 判断是否存在
public boolean canRead()      // 判断是否可读
public boolean canWrite()     // 判断是否可写
public boolean isHidden()     // 判断是否隐藏

1.5 获取文件信息 方法

public String getAbsolutePath() // 获取绝对路径
public String getPath()         // 获取相对路径。如果定义时是绝对路径,那么也返回绝对路径
public String getName()         // 获取名称
public long length()            // 获取长度,字节数
public long lastModified()      // 获取最后一次修改时间,毫秒值

回到目录…

1.6 应用练习

  1. 判断某个目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称。
public class FileCase1 {
	public static void main(String[] args) {
		File file = new File("F:\\00 浏览器");
		File[] listFiles = file.listFiles();
		for(File file1: listFiles) {
			if(file1.isFile() && file1.getName().endsWith(".jpg")) {
				System.out.println(file1.getName());
			}
		}
	}
}
  1. 递归查询文件夹下以.jpg结尾的文件。
public class FileCase2 {
    public static void main(String[] args) {
        File file = new File("C:\\Users\\14156\\Pictures");
        getAllJPG(file);
    }
    public static void getAllJPG(File file) {
        // 先获取该目录下的所有文件及目录
        File[] listFiles = file.listFiles();
        if(listFiles.length == 0) {
            return;
        }
        for(File f: listFiles) {
            // 判断是否为 .jpg 结尾的文件
            if(f.isFile() && f.getName().endsWith(".jpg")) {
                System.out.println(f.getName());
            }else if(f.isDirectory()) { // 判断是否为目录
                // 递归的查询该目录
                getAllJPG(new File(f.getAbsolutePath()));
            }
        }
    }
}

回到目录…

二、IO 流

  • 基本概念

    • I/O 就是 Input/Output 的简写,也就是输入/输出,换句话说,也就是读取 / 写入操作。
    • I/O流就指像流水一样不间断地进行读写的操作。

  • 基本分类

    1. 根据读写操作的基本单位不同分为:字节流和字符流。
      字节流:读写的基本单位是按字节进行,各种类型的文件都可以读写。
          操作时本身不会用到缓冲区(内存),是文件本身直接操作的。
      字符流:读写的基本单位是按 字符(双字节) 进行,只能读写文本。
          操作时使用了缓冲区,通过缓存区再操作文件。
    2. 什么情况下使用哪种流呢 ?
      • 如果数据文件通过 windows 自带的记事本打开并能读懂内容,就用字符流。其他用字节流。
      • 如果你什么都不知道,就用字节流。
    3. 输入流 和 输出流
      站在程序的角度出发的,读就是输入,写就是输出。

  • IO流常用基类

    1. 字节流的抽象基类:InputStreamOutputStream
    2. 字符流的抽象基类:ReaderWriter
    3. 注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
      • 如:InputStream 的子类 FileInputStream
      • 如:Reader 的子类 FileReader

在这里插入图片描述

回到目录…

2.1 InputStream 字节输入流 (读)

方法介绍
public int read()每次从输入流中读取一个数据字节。当数据到末尾时返回-1,每次只读取一个字节,效率低。
public int read(byte[] b)每次从输入流中读入一个 byte 数组的数据字节。推荐使用
public int read(byte[] b,int off,int len)每次从输入流中读入一个 byte 数组 [off, len) 的数据字节。off:起始偏移量。

🍓FileInputStream

文件字节输入流 的 构造方法
FileInputStream(File file)
FileInputStream(String name)
// 读取数据
public static void main(String[] args) throws IOException {
	/******* 方式一  ********/
	InputStream is = new FileInputStream("hello.txt");
	int num = 0;
	while((num = is.read()) != -1) {
		System.out.print((char) num);
	}
	is.close();
	
	/******* 方式二  ********/
	InputStream is2 = new FileInputStream("hello.txt");
	// 设置缓冲区
	byte[] bytes = new byte[1024];
	int num2 = 0;
	while((num2 = is2.read(bytes)) != -1) {
		//将读取的字节序列转换成字符序列
		String str = new String(bytes, 0, num2);
		System.out.print(str);
	}
	is2.close();
}

🍓BufferedInputStream

字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这是加入了数组这样的缓冲区效果,java本身在设计的时候也考虑到了这样的设计思想 (装饰设计模式后面讲解),所以提供了
节缓冲区流

字节缓冲输入流 的 构造方法
BufferedInputStream(OutputStream out)
BufferedInputStream(OutputStream out, int size) // size表示输入缓冲区的长度,默认为8192
public static void main(String[] args) throws Exception {
	InputStream bis = new BufferedInputStream(new FileInputStream("斗破苍穹.txt"));

	byte[] by = new byte[1024];
	int num = 0;
	//读取数据
	while((num = bis.read(by))!=-1){
		System.out.println(new String(by,0,num));
	}

	bis.close();
}

回到目录…

2.2 OutputStream 字节输出流 (写)

方法介绍
public void write(int b)将指定字节写入此输出流
public void write(byte[] b)将一个 byte 数组的字节数据写入到输出流中去
public void write(byte[] b,int off,int len)指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流

🍌FileOutputStream

文件字节输出流 的 构造方法
FileOutputStream(File file)
FileOutputStream(String name)
FileOutputStream(File file,boolean append)
FileOutputStream(String fileName,boolean append)
// 写入数据
public static void main(String[] args) throws IOException {
	File file = new File("hello.txt");
	OutputStream os = new FileOutputStream(file, true);
	// 方式一
	os.write(97);
	// 方式二
	byte[] bytes = "hello,world".getBytes();
	os.write(bytes);
	os.write(bytes, 0, 5);
	// 释放资源
	os.close();
}

🍌BufferedOutputStream

字节缓冲输出流 的 构造方法
BufferedOutputStream(OutputStream out)
BufferedOutputStream(OutputStream out, int size) // size表示输出缓冲区的长度,默认为8192
public static void main(String[] args) throws Exception {
	OutputStream bos = new BufferedOutputStream(new FileOutputStream("斗破苍穹.txt"));
	bos.write("hello".getBytes());
	bos.close();
}

回到目录…

2.3 Reader 字符输入流 (读)

方法介绍
public int read()读取单个字符
public int read(char[] b)将字符读入数组
public int read(char[] b, int off, int len)将字符读入数组的某一部分

🍉FileReader

文件字符输入流 的 构造方法
FileReader(File file)
FileReader(String fileName)
public static void main(String[] args) throws IOException {
	// 1.字符输入流
	Reader r = new FileReader("斗破苍穹.txt");

	int num = 0;
	char[] ch = new char[1024];
	// 每次读取指定数组空间的字符
	while((num = r.read(ch)) != -1){
		String str = new String(ch,0,num);
		System.out.println(str);
	}
	r.close();
}

🍉BufferedReader

字符缓冲输入流 的 构造方法
BufferedReader(Reader in)
BufferedReader(Reader in, int sz) // sz 表示输出缓冲区的长度,默认为8192

特殊方法: String readLine() 按行读取,一次读取一行。读到末尾返回 null。

public static void main(String[] args) throws Exception {
	// 创建字符缓冲输入流  读取文件
	BufferedReader br = new BufferedReader(new FileReader("斗破苍穹.txt"));

	String str = null;
	// readLine() 按行读取,一次读取一个文本行,读到末尾时返回null
	while((str = br.readLine()) != null){
		System.out.println(str);
	}
	br.close();
}

回到目录…

2.4 Writer 字符输出流 (写)

方法介绍
public void write(String str)将指定字符串写入此输出流中
public void write(String str,int off,int len)将指定字符串写入此输出流中,指定区间
public void write(int c)将指定字符写入此输出流
public void writer(char[] cbuf)将一个 char 数组的字符写入到输出流中去
public void write(char[] b,int off,int len)指定 char 数组中从偏移量 off 开始的 len 个字节写入此输出流

🍍FileWriter

文件字符输出流 的 构造方法
FileWriter(File file)
FileWriter(String fileName)
FileWriter(File file,boolean append)
FileWriter(String fileName,boolean append)
public static void main(String[] args) throws IOException {
	//1.创建字符输出流
	Writer w = new FileWriter("斗破苍穹.txt", true);
	w.write("第一章  小飞与小红一起修炼的日子 \n");
	w.write("第二章  山洞机遇");

	//注意:如果不关闭流或者刷新缓冲区  那么数据将一直在缓冲区存放
	w.flush();
	w.close();
}

🍍bufferedWriter

字符缓冲输出流 的 构造方法
BufferedWriter(Writer out)
BufferedWriter(Writer out, int sz) // sz 表示输出缓冲区的长度,默认为8192

特殊方法: void newLine() 换行。

public static void main(String[] args) throws Exception {
	// 创建字符缓冲输出流   写入文件
	BufferedWriter bw = new BufferedWriter(new FileWriter("斗破苍穹.txt"));
	bw.write("你好,我是小明");
	bw.newLine();
	bw.write("nice to meet you\n");
	bw.close();
}

回到目录…

2.5 四大基类下的其它流

🏀打印输出流 PrintStream

基本概念: java.io.PrintStream 类用于方便地打印各种格式的数据。

方法方法描述
PrintStream(OutputStream out)根据参数指定的引用构造对象
void print(String s)用于打印参数指定的字符串
void println(String x)用于打印字符串并终止该行
// 演示打印输出流 PrintStream
public static void main(String[] args) throws IOException {
	// 1.构造PrintStream类型的对象与 mesg.txt文件关联
	PrintStream ps = new PrintStream(new FileOutputStream("mesg.txt"));
	// 2.向输出流中写入字符串内容
	ps.println("hello,world");
	// 3.关闭流
	ps.close();
}

回到目录…

🏀数据流 DataInputStream / DataOutputStream

数据流其实本质上就是操作基本数据类型的流。

  • DataInputStream 类用于将 Java 中基本数据类型从输入流中读出。
  • DataOutputStream 类用于将 Java 中的基本数据类型写入到输出流中。
/**
 * 演示数据流读写数据
 * 注意:数据流写入什么样的顺序,读取就是什么样顺序
 */
public class TestDataIO {
	public static void main(String[] args) throws IOException {
		write();
		read();
	}
	private static void read() throws IOException{
		DataInputStream dis = new DataInputStream(new FileInputStream("dos.txt"));
		byte b = dis.readByte();
		int i = dis.readInt();
		long l = dis.readLong();
		double d = dis.readDouble();
		char c = dis.readChar();
		boolean b1 = dis.readBoolean();
		System.out.println(b);
		System.out.println(i);
		System.out.println(l);
		System.out.println(d);
		System.out.println(c);
		System.out.println(b1);
		dis.close();
	}

	private static void write() throws IOException{
		DataOutputStream dos = new DataOutputStream(new FileOutputStream("dos.txt"));
		dos.writeByte(1);
		dos.writeInt(200);
		dos.writeLong(4000);
		dos.writeDouble(12.12);
		dos.writeChar('a');
		dos.writeBoolean(true);
		dos.close();
	}
}

回到目录…

🏀内存操作流 ByteArrayInputStream / CharArrayReader / StringReader

操作对象输入流输出流
操作字节数组ByteArrayInputStreamByteArrayOutputStream
操作字符数组CharArrayReaderCharArrayWriter
操作字符串StringReaderStringWriter
/**
 * 演示内存操作流
 * 		内存操作流一般用于处理临时数据,因为临时信息不需要保持,使用后就可以删除。
 */
public class TestByteArray {
	public static void main(String[] args) throws IOException {
	
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		bos.write("helloworld".getBytes());
		bos.close();

		byte[] bs = bos.toByteArray();
		ByteArrayInputStream bis = new ByteArrayInputStream(bs);

		int num = 0;
		while((num = bis.read())!=-1){
			System.out.print((char)num);
		}
		// 最后的 close可以不写,通过看源码可以知道这里什么都没有做。
		bis.close();
		bos.close();
	}
}

回到目录…

🏀序列化流 ObjectOutputStream

序列化流: 指把 Java 对象转换为字节序列的过程为对象的流。
反序列化流: 指把字节序列恢复为Java对象的过程称为对象的反序列化。

方法方法描述
序列化流ObjectOutputStreamwriteObject(Object obj)可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
反序列化流ObjectInputStreamreadObject()源输入流读取字节序列,再把它们反序列化成为一个对象,并将其返回。

注意: 只有实现了 SerializableExternalizable 接口的类的对象才能被序列化,否则抛出异常。

public class TestObject {
	public static void main(String[] args) throws IOException, ClassNotFoundException {

		Goods goods = new Goods(1, "葵花宝典", 12.0, 100);
		// 1.创建序列化流
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.txt"));
		// 2.将Goods对象序列化到项目下object.txt中持久存储
		oos.writeObject(goods); // Goods 类必须实现 Serializable 接口, 否则对象类序列化异常
		oos.close();

		System.out.println("---------利用反序列化读取文件中对象信息-----------");
		// 4.创建反序列化流
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt"));
		// 5.读取文件中对象信息
		Goods g = (Goods) ois.readObject();
		System.out.println(g);
		ois.close();
	}
}

序列化操作问题:
 ① 序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?

serialVersionUID
 它是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,当修改后的类去反序列化的时候发现该类的 serialVersionUID 值和之前保存在文件中的 serialVersionUID 值不一致,所有就会抛出异常。
 而显示的设置 serialVersionUID 值就可以保证版本的兼容性,如果你在类中写上了这个值,就算类变动了,它反序列化的时候也能和文件中的原值匹配上。而新增的值则会设置成null,删除的值则不会显示。

 ② 使用 transient 关键字声明不需要序列化的成员变量,反序列化时会将其设为 null。

public class Goods implements Serializable{

	private static final long serialVersionUID = -2442024880621232176L;
	private Integer id;
	private String name;
	private Double price;
	// transient 被他修饰的成员变量不参与序列化
	private transient Integer storage;

	public Goods(Integer id, String name, Double price, Integer storage) {
		super();
		this.id = id;
		this.name = name;
		this.price = price;
		this.storage = storage;
	}

	@Override
	public String toString() {
		return "Goods [id=" + id + ", name=" + name + ", price=" + price + ", storage=" + storage + "]";
	}
}

回到目录…

三、应用

3.1 复制文件内容

// 用字节流方式
public static void copy1(String src, String dest) throws IOException {
	// 1. 创建新文件
	File file = new File(dest);
	file.createNewFile();
	// 2. 读取旧文件
	InputStream is = new FileInputStream(src);
	// 3. 写入新文件
	OutputStream os = new FileOutputStream(file);

	byte[] bytes = new byte[1024];
	int num = 0;
	while((num = is.read(bytes)) != -1) {
		// 每读取一次向指定文件中写入一次
		os.write(bytes, 0, num);
		os.flush(); // 刷新输出缓冲区,防止这次残留的数据在下次输出
	}
	is.close();
	os.close();
}

// 用字符流方式
public static void copy2(String src, String dest) throws Exception {
	// 1.利用字符流读取文件
	Reader r = new FileReader(src);
	// 2.利用字符流写入文件
	Writer w = new FileWriter(dest);
	int num = 0;
	// 每次读取一个字符(2个字节)
	while((num = r.read()) != -1){
		w.write(num); // 写入指定文件
		w.flush(); // 刷新缓冲区
	}
	// 关闭流
	w.close();
	r.close();
}

// 用字符缓冲流方式,最常用
public static void copy3(String src, String dest) throws Exception {
	// 创建字符缓冲输入流  读取文件
	Reader br = new BufferedReader(new FileReader(src));

	// 创建字符缓冲输出流   写入文件
	Writer bw = new BufferedWriter(new FileWriter(dest));

	String str = null;
	// readLine() 按行读取,一次读取一个文本行,读到末尾时返回null
	while((str = br.readLine()) != null){
		bw.write(str); // 写入指定文件
		bw.newLine(); // 换行
		bw.flush(); // 刷新缓冲区
	}
	// 释放资源
	bw.close();
	br.close();
}

回到目录…

3.2 生成聊天记录

/**
 * 练习:使用PrintStream和BufferedReader类来生成聊天记录
 * 要求不断地提示用户输入要发送的内容,判断是否为"bye",若是则结束; 若不是则写入文件chat.txt中
 */
public class TestChatMsg {
	public static void main(String[] args) throws IOException {
		//System.in 是InputStream类型的,代表键盘输入
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		PrintStream ps = new PrintStream(new FileOutputStream("chat.txt"));
		boolean flg = true;
		while(true){
			//1.提示用户输入要发送的内容并记录到变量中
			System.out.println("请 "+(flg?"张三":"李四")+" 输入要发送的内容");
			String str = br.readLine();
			//2.判断用户输入的内容是否为"bye",若是则结束输入
			if("bye".equalsIgnoreCase(str)){
				System.out.println("聊天结束!");
				break;
			}
			//获取当前的系统时间
			Date d = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss  ");
			String format = sdf.format(d);

			//3.若不是则将用户输入的内容写入到文件a.txt中
			ps.println(format+(flg?"张三: ":"李四: ")+str);
			flg = !flg;
		}
	}
}
// chat.txt 内容:
// 2023-03-03 10:14:28  张三: 你好
// 2023-03-03 10:14:39  李四: 你好
// 2023-03-03 10:14:51  张三: 我叫张三
// 2023-03-03 10:14:58  李四: 我叫李四

回到目录…

3.3 序列化实现深拷贝

public class DeepCopy {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 创建学生对象
        Student stu1 = new Student("张三", 20, new ArrayList<>());
        stu1.grades.add(79);
        stu1.grades.add(81);
        stu1.grades.add(99);
        Student stu2 = deepCopy(stu1);
        System.out.println("----------- 拷贝 stu1 后 ---------------");
        System.out.println("stu1: " + stu1); // stu1: [name='张三', age=20, grades=[79, 81, 99]]
        System.out.println("stu2: " + stu2); // stu2: [name='张三', age=20, grades=[79, 81, 99]]
        System.out.println("----------- 修改 stu2 后 ---------------");
        stu2.name = "李四";
        stu2.age = 21;
        stu2.grades.clear();
        stu2.grades.add(82);
        stu2.grades.add(73);
        stu2.grades.add(92);
        System.out.println("stu1: " + stu1); // stu1: [name='张三', age=20, grades=[79, 81, 99]]
        System.out.println("stu2: " + stu2); // stu2: [name='李四', age=21, grades=[82, 73, 92]]
    }
    // 深拷贝的方法
    public static Student deepCopy(Student stu) throws IOException, ClassNotFoundException {
        // 创建文件,用于写序列化后的字节
        File file = new File("student.txt");
        // 创建对象序列化流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
        oos.writeObject(stu);
        oos.close();
        // 创建对象反序列化流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
        Student newStu = (Student) ois.readObject();
        ois.close();
        // 删除文件
        file.deleteOnExit();
        return newStu;
    }
}
// 学生类
class Student implements Serializable {
    public String name;
    public int age;
    public List<Integer> grades;

    public Student(String name, int age, List<Integer> grades) {
        this.name = name;
        this.age = age;
        this.grades = grades;
    }

    @Override
    public String toString() {
        return "[" + "name='" + name + '\'' + ", age=" + age + ", grades=" + grades + ']';
    }
}

回到目录…


总结:
提示:这里对文章进行总结:
本文是Java IO流的学习,先学习了File文件类的操作与应用,又学习了IO流的 InputStream字节流读、OutputStream字节流写、Reader字符流读、Writer字符流写,以及它们子类的应用。之后的学习内容将持续更新!!!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只咸鱼。。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值