Java-IO流详解

File类

实例化一个File对象就可以表示某个“文件”或“目录”。在开发中,创建文件(目录)、删除文件(目录)、操作文件属性等操作就会使用File类,但是使用File无法操作文件中的内容(操作文件中的内容需要使用IO的技术)。

概念:代表物理盘符中的一个文件或者文件夹。

File类的常见构造方法:public File(String pathname)

public static void main(String[] args) throws IOException {
	// 获取当前工作目录路径
	System.out.println(System.getProperty("user.dir"));
	// 1.相对路径创建File对象:默认放到user.dir目录下面
	File file1 = new File("abc.txt");
	// 创建文件
	file1.createNewFile();
	// 2.绝对路径创建File对象
	File file2 = new File("D:/abc.txt");
	// 创建文件
	file2.createNewFile();
}

常用方法:

方法名

描述

createNewFile()

创建一个新文件。

mkdir()

创建一个新目录。

delete()

删除文件或空目录。

exists()

判断File对象所对象所代表的对象是否存在。

getAbsolutePath()

获取文件的绝对路径。

getName()

取得名字。

getParent()

获取文件/目录所在的目录。

isDirectory()

是否是目录。

isFile()

是否是文件。

length()

获得文件的长度。

listFiles()

列出目录中的所有内容。

public class DemoIo001 {
    public static void main(String[] args) {
        // 创建 File 对象
        File file = new File("D:\\LC工作文件\\lc-work\\java-basics-demo\\src\\com\\taiyuan\\javademo\\demoio\\testFile.txt");
        // 创建目录
        File dir = new File("D:\\LC工作文件\\lc-work\\java-basics-demo\\src\\com\\taiyuan\\javademo\\demoio\\testDirectory");

        // 1. createNewFile() - 创建新文件
        try {
            if (file.createNewFile()) {
                System.out.println("文件 " + file.getName() + " 已创建.");
            } else {
                System.out.println("文件已存在.");
            }
        } catch (IOException e) {
            System.out.println("发生错误: " + e.getMessage());
        }

        // 2. mkdir() - 创建新目录
        if (dir.mkdir()) {
            System.out.println("目录 " + dir.getName() + " 已创建.");
        } else {
            System.out.println("目录创建失败或已存在.");
        }

        // 3. exists() - 判断文件或目录是否存在
        System.out.println("文件是否存在: " + file.exists());
        System.out.println("目录是否存在: " + dir.exists());

        // 4. getAbsolutePath() - 获取文件的绝对路径
        System.out.println("文件的绝对路径: " + file.getAbsolutePath());
        System.out.println("目录的绝对路径: " + dir.getAbsolutePath());

        // 5. getName() - 获取文件或目录的名字
        System.out.println("文件名: " + file.getName());
        System.out.println("目录名: " + dir.getName());

        // 6. getParent() - 获取文件或目录的父目录
        System.out.println("文件的父目录: " + file.getParent());
        System.out.println("目录的父目录: " + dir.getParent());

        // 7. isDirectory() - 判断是否为目录
        System.out.println("文件是否是目录: " + file.isDirectory());
        System.out.println("目录是否是目录: " + dir.isDirectory());

        // 8. isFile() - 判断是否为文件
        System.out.println("文件是否是文件: " + file.isFile());
        System.out.println("目录是否是文件: " + dir.isFile());

        // 9. length() - 获取文件的长度
        System.out.println("文件大小: " + file.length() + " 字节");

        // 10. listFiles() - 列出目录中的所有内容
        if (dir.exists() && dir.isDirectory()) {
            File[] files = dir.listFiles();
            if (files != null && files.length > 0) {
                System.out.println("目录中的文件/目录:");
                for (File f : files) {
                    System.out.println(f.getName());
                }
            } else {
                System.out.println("目录为空或读取错误.");
            }
        }

//        // 11. delete() - 删除文件或目录
//        if (file.delete()) {
//            System.out.println("文件 " + file.getName() + " 已删除.");
//        } else {
//            System.out.println("文件删除失败.");
//        }
//
//        if (dir.delete()) {
//            System.out.println("目录 " + dir.getName() + " 已删除.");
//        } else {
//            System.out.println("目录删除失败.");
//        }
    }
}

FileFilter:文件过滤器接口

boolean accept(File pathname)。

当调用File类中的listFiles()方法时,支持传入FileFilter接口接口实现类,对获取文件进行过滤,只有满足条件的文件的才可出现在listFiles()的返回值中。

public class DemoIo002 {
    public static void main(String[] args) {
        // 创建一个目录对象
        File dir = new File("D:\\LC工作文件\\lc-work\\java-basics-demo\\src\\com\\taiyuan\\javademo\\demoio");
        // 检查目录是否存在
        if (dir.exists() && dir.isDirectory()) {
            // 使用匿名内部类创建一个 FileFilter 实现
            FileFilter txtFileFilter = new FileFilter() {
                @Override
                public boolean accept(File file) {
                    String name = file.getName();
                    // 只接受以 .txt 结尾的文件
                    return file.isFile() && name.endsWith(".java");
                }
            };
            // 获取目录中的所有 .txt 文件
            File[] txtFiles = dir.listFiles(txtFileFilter);
            // 输出所有符合条件的文件
            if (txtFiles != null && txtFiles.length > 0) {
                System.out.println("目录中的 .java 文件:");
                for (File file : txtFiles) {
                    System.out.println(file.getName());
                }
            } else {
                System.out.println("目录中没有 .txt 文件.");
            }
        } else {
            System.out.println("指定的路径不是一个有效的目录.");
        }
    }
}

Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。

IO分类

根据数据的流向分为:输入流和输出流。

输入流 :把数据从其他设备上读取到内存中的流。

输出流 :把数据从内存 中写出到其他设备上的流。

根据数据的类型分为:字节流和字符流。

字节流 :以字节为单位,读写数据的流。

字符流 :以字符为单位,读写数据的流。

输入流

输出流

字节输入流

InputStream

字节输出流

OutputStream

字符输入流

Reader

字符输出流

Writer

字节流和字符流的区别

字节流:以字节(8位)为单位进行数据传输,适用于处理任何类型的二进制数据,如图片、音频、视频等。Java中的InputStream和OutputStream是字节流的抽象基类。

字符流:以字符(16位Unicode)为单位进行数据传输,主要用于处理文本数据。Reader和Writer是字符流的抽象基类。

字节流

1. OutputStream类

java.io.OutputStream抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。

public void close() :关闭此输出流并释放与此流相关联的任何系统资源。

public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。

public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。

public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。

public abstract void write(int b) :将指定的字节输出流。

1.1.1. FileOutputStream类

构造方法:

public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。

public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

1、写出字节:write(int b) 方法,每次可以写出一个字节数据

2、写出字节数组:write(byte[] b),每次可以写出数组中的数据

3、写出指定长度字节数组:write(byte[] b, int off, int len) ,每次写出从off索引开始,len个字节

// 使用File对象创建流对象
File file = new File("a.txt");
FileOutputStream fos = new FileOutputStream(file);

// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("b.txt");

// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");     
// 写出数据:虽然参数为int类型四个字节,但是只会保留一个字节的信息写出
fos.write(97); // 写出第1个字节
fos.write(98); // 写出第2个字节
fos.write(99); // 写出第3个字节
// 关闭资源
fos.close();

// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");     
// 字符串转换为字节数组
byte[] b = "你好中国".getBytes();
// 写出字节数组数据
fos.write(b);
// 关闭资源
fos.close();

// 使用文件名称创建流对象
FileOutputStream fos = new FileOutputStream("fos.txt");     
// 字符串转换为字节数组
byte[] b = "abcde".getBytes();
// 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
fos.write(b,2,2);
// 关闭资源
fos.close();	

2. InputStream类

java.io.InputStream抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入流的基本共性功能方法。

public void close() :关闭此输入流并释放与此流相关联的任何系统资源。

public abstract int read(): 从输入流读取数据的下一个字节。

public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

2.1. FileInputStream类

构造方法:

FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。

FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。

1、读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1

2、使用字节数组读取:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1

	// 使用File对象创建流对象
        File file = new File("a.txt");
        FileInputStream fos = new FileInputStream(file);
      
        // 使用文件名称创建流对象
        FileInputStream fos = new FileInputStream("b.txt");
        
        // 使用文件名称创建流对象
       FileInputStream fis = new FileInputStream("read.txt");
        // 读取数据,返回一个字节
        int read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
        read = fis.read();
        System.out.println((char) read);
      	// 读取到末尾,返回-1
       	read = fis.read();
        System.out.println( read);
		// 关闭资源
        fis.close();
        
        // 使用文件名称创建流对象
       FileInputStream fis = new FileInputStream("read.txt");
      	// 定义变量,保存数据
        int b ;
        // 循环读取
        while ((b = fis.read())!=-1) {
            System.out.println((char)b);
        }
		// 关闭资源
        fis.close();
        
        // 使用文件名称创建流对象.
       FileInputStream fis = new FileInputStream("read.txt"); // 文件中为abcde
      	// 定义变量,作为有效个数
        int len ;
        // 定义字节数组,作为装字节数据的容器   
        byte[] b = new byte[2];
        // 循环读取
        while (( len= fis.read(b))!=-1) {
           	// 每次读取后,把数组的有效字节部分,变成字符串打印
            System.out.println(new String(b,0,len));//  len 每次读取的有效字节个数
        }
		// 关闭资源
        fis.close();

字符流

Reader类

java.io.Reader抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流读取一个字符。
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
FileReader类

构造方法

  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

构造时使用系统默认的字符编码和默认字节缓冲区。

  1. 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表。
    idea中UTF-8
  2. 字节缓冲区:一个字节数组,用来临时存储字节数据。

1、读取字符read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取

2、使用字符数组读取read(char[] cbuf),每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回-1

// 使用File对象创建流对象
        File file = new File("a.txt");
        FileReader fr = new FileReader(file);
      
        // 使用文件名称创建流对象
        FileReader fr = new FileReader("b.txt");

// 使用文件名称创建流对象
       	FileReader fr = new FileReader("read.txt");
      	// 定义变量,保存有效字符个数
        int len ;
        // 定义字符数组,作为装字符数据的容器
        char[] cbuf = new char[2];
        // 循环读取
        while ((len = fr.read(cbuf))!=-1) {
            System.out.println(new String(cbuf,0,len));
        }
    	// 关闭资源
        fr.close();

Writer类

java.io.Writer抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字符输出流的基本共性功能方法。

  • void write(int c) 写入单个字符。
  • void write(char[] cbuf)写入字符数组。
  • abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str)写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush()刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。
FileWriter类
  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

构造时使用系统默认的字符编码和默认字节缓冲区。

1、写出字符write(int b) 方法,每次可以写出一个字符数据

2、写出字符数组write(char[] cbuf)write(char[] cbuf, int off, int len) ,每次可以写出字符数组中的数据,用法类似FileOutputStream

3、写出字符串write(String str)write(String str, int off, int len) ,每次可以写出字符串中的数据,更为方便

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

// 使用File对象创建流对象
        File file = new File("fw.txt");
        FileWriter fw = new FileWriter(file);
      
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");

  // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");     
      	// 写出数据
      	fw.write(97); // 写出第1个字符
      	fw.write('b'); // 写出第2个字符
      	fw.write('C'); // 写出第3个字符
      	fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。
      
      	/*
        【注意】关闭资源时,与FileOutputStream不同。
      	 如果不关闭,数据只是保存到缓冲区,并未保存到文件。
        */
        // fw.close();

 // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();
      
      	// 写出数据,通过close
        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();


  // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");     
      	// 字符串转换为字节数组
      	char[] chars = "你好中国".toCharArray();
      
      	// 写出字符数组
      	fw.write(chars); 
        
		// 写出从索引2开始,2个字节。索引2是'中',两个字节,也就是'中国'。
        fw.write(b,2,2); //中国
      
      	// 关闭资源
        fos.close();

// 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");     
      	// 字符串
      	String msg = "你好中国";
      
      	// 写出字符数组
      	fw.write(msg); //你好中国
      
		// 写出从索引2开始,2个字节。索引2是'中',两个字节,也就是'中国'。
        fw.write(msg,2,2);	// 中国
      	
        // 关闭资源
        fos.close();

缓冲流

缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

字节缓冲流

构造方法:

  • public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

字符缓冲流

构造方法

  • public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
  • public BufferedWriter(Writer out): 创建一个新的缓冲输出流。

转换流

InputStreamReader类

转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
  • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

OutputStreamWriter类

转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

扩展:

JAVA中汉字占几个字节?

在 Java 中,汉字占用的字节数取决于所使用的编码方式

  • 在 Java 中的 char 类型(即 UTF-16 编码),每个汉字占用 2 个字节。
  • 如果你使用 UTF-8 编码(例如,将字符串转换为字节数组),一个汉字通常占 3 个字节。
  • 如果使用 GBK 编码,一个汉字占 2 个字节。
public class StringDemo {
    public static void main(String[] args) throws Exception {
        String str = "中";
        byte[] utf8Bytes = str.getBytes("UTF-8");
        System.out.println("UTF-8 编码下,汉字 '中' 占用字节数: " + utf8Bytes.length);

        byte[] gbkBytes = str.getBytes("GBK");
        System.out.println("GBK 编码下,汉字 '中' 占用字节数: " + gbkBytes.length);

        byte[] bytes = str.getBytes();
        System.out.println("默认编码下,汉字 '中' 占用字节数: " + bytes.length);

        char chars = '中';
        byte[] bytes1 = Character.toString(chars).getBytes("UTF-16BE");
        System.out.println("字符 '中' 的 UTF-16 编码字节数: " + bytes1.length);

        char chars2 = '中';
        byte[] bytes2 = Character.toString(chars).getBytes("UTF-16");
        System.out.println("字符 '中' 的 UTF-16 编码字节数: " + bytes2.length);
    }
}

使用 getBytes("UTF-16") 时,结果会是 4 字节,因为 UTF-16 编码除了包含字符本身的字节外,还包含 BOM 字节。如果不希望包含 BOM,可以使用 getBytes("UTF-16BE") 或 getBytes("UTF-16LE")(大端或小端 UTF-16 编码),这些编码方式不包含 BOM。

BOM(Byte Order Mark,字节顺序标记)是一个特殊的字节序列,用于表示文本文件中字符编码的字节顺序。它的主要作用是在没有外部标识的情况下,指示如何解释文件中的字节流顺序。BOM 通常出现在文件的开头,并且在不同的编码方式下,它的具体字节值是不同的。

序列化(Serialization)

什么是序列化?

序列化 是指将 Java 对象转换成可存储或者可传输的字节流的过程。在网络编程中,通常会把对象序列化后传输,通过网络将字节流传输到接收方。接收方再将字节流反序列化为原始对象。在 Java 中,序列化的过程通过实现 Serializable 接口来实现。

反序列化 是将字节流转换回 Java 对象的过程。反序列化可以使用 ObjectInputStream 类来实现。

Serializable 是一个标记接口,它的作用是告诉 Java 的序列化机制,当前类可以进行序列化处理。如果一个类希望支持序列化,它必须实现这个接口。

序列化的过程

在 Java 中,可以通过 ObjectOutputStream 类来执行序列化操作,具体步骤如下:

  1. 创建一个输出流对象,例如 ObjectOutputStream
  2. 将要序列化的对象写入该输出流。
  3. 输出流会将对象的状态(成员变量的值)转换成字节流,保存或发送。
import java.io.*;

public class SerializationExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            out.writeObject(person); // 序列化对象
            System.out.println("对象已序列化");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
反序列化的过程

反序列化是将存储或传输的字节流重新构造成对象的过程。反序列化使用 ObjectInputStream 类来实现。

import java.io.*;

public class DeserializationExample {
    public static void main(String[] args) {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person person = (Person) in.readObject(); // 反序列化对象
            System.out.println("反序列化后的对象: " + person.getName() + ", " + person.getAge());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

Java 的序列化机制不仅序列化对象的成员变量,还会序列化与该对象相关联的其他对象。具体工作原理如下:

  • 基本数据类型:如 intdoubleboolean 等,会直接序列化其值。
  • 对象引用:对象引用会被序列化为引用的实际对象,而对象的状态(即它的实例字段)会被递归地序列化。
  • 父类字段:如果一个类继承了父类,并且父类实现了 Serializable 接口,那么父类的字段也会被序列化。
  • transient 关键字:有些时候,你不希望某个字段被序列化,可以使用 transient 关键字修饰它。被 transient 修饰的字段会在序列化过程中被忽略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘子编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值