Java 文件 I/O



File 类

File 类介绍

  • 它是文件和目录的路径名的抽象表示。
  • 文件和目录是可以通过 File 封装成对象的。
  • 对于 File 而言,其封装的并不是一个真正存在的文件,仅仅是一个路径名而已。它可以是实际存在的,也可以是不存在的,将来是要通过具体的操作把这个路径的内容转换为具体的存在。

File 类构造方法

方法名 说明
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例
File(String parent, String child) 通过父路径名字符串和子路径名字符串创建新的 File 实例
File(File parent, String child) 通过父抽象路径名和子路径名字符串创建新的 File 实例

示例代码:

public class FileDemo {
    public static void main(String[] args) {
        // File(String pathname): 通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例
        File f1 = new File("E:\\test\\java.txt");
        System.out.println(f1);

        // File(String parent, String child): 通过父路径名字符串和子路径名字符串创建新的 File 实例
        File f2 = new File("E:\\test", "java.txt");
        System.out.println(f2);

        // File(File parent, String child): 通过父路径对象和子路径名字符串创建新的 File 实例
        File f3 = new File("E:\\test");
        File f4 = new File(f3, "java.txt");
        System.out.println(f4);
    }
}

File 类常用方法

创建方法:

方法名 说明
public boolean createNewFile() 当文件不存在时,创建一个由该路径名命名的新空文件
public boolean mkdir() 创建由此路径名命名的目录
public boolean mkdirs() 创建由此路径名命名的目录,包括任何必需但不存在的父目录
public boolean renameTo(File dest) 如果目标文件与源文件在同一路径下,那么 renameTo 的作用是重命名(文件与文件夹);
如果不在同一路径下,那么 renameTo 的作用是剪切,并且不能操作文件夹

删除方法:

方法 说明
public boolean delete() 删除由此抽象路径名表示的文件或目录。删除成功则返回 true
deleteOnExit() 等到 JVM 退出时才删除文件,一般用于删除临时文件

判断方法:

方法 说明
public boolean isDirectory() 测试此抽象路径名表示的 File 是否为目录
public boolean isFile() 测试此抽象路径名表示的 File 是否为文件
public boolean exists() 测试此抽象路径名表示的 File 是否真实存在
public boolean isHidden() 测试此抽象路径名表示的 File 是否是一个隐藏的文件或文件夹
public boolean isAbsolute() 测试此抽象路径名表示的 File 是否为绝对路径

获取方法:

方法名 说明
public String getAbsolutePath() 返回绝对路径
public String getPath() 返回绝对路径(传什么返回什么)
public String getName() 获取文件或文件夹的名称,不包括上级路径
public String getAbsolutePath() 获取绝对路径(与文件是否存在没关系)
public long length() 获取文件或文件夹的字节大小(路径不存在则返回 0)
public String getParent() 获取文件的父路径
public long lastModified() 获取最后一次的修改时间(毫秒值)
public File[] listFiles() 把当前文件夹下面的所有子文件名与子文件夹名都使用一个 File 对象描述,然后将这些对象存储到一个 File 数组中返回(子文件或子目录的绝对路径)。
(若对文件操作则返回 null)
public String[] list() 把当前文件夹下面的所有子文件名与子文件夹名(包括隐藏文件与隐藏文件夹)存储到一个 String 数组中返回(文件或目录名称)。
(若对文件操作则返回 null)
public File[] list(FilenameFilter filter) 返回指定路径中符合过滤条件的文件或文件夹(若对文件操作则返回 null)
public String[] listFiles(FilenameFilter filter) 返回指定路径中符合过滤条件的文件或文件夹(若对文件操作则返回 null)

示例代码:

public class FileDemo {
    public static void main(String[] args) throws IOException {
        // 创建文件
        File f1 = new File("E:\\test\\java.txt");
        System.out.println(f1.createNewFile());
        System.out.println("--------");
        // 删除刚创建的文件
        f1.delete();

        // 创建单级目录
        File f2 = new File("E:\\test\\JavaSE");
        System.out.println(f2.mkdir());
        System.out.println("--------");

        // 创建多级目录
        File f3 = new File("E:\\test\\JavaWEB\\HTML");
//        System.out.println(f3.mkdir());  // 报错
        System.out.println(f3.mkdirs());
        System.out.println("--------");

        File f4 = new File("D:\\test\\test_delete.txt");
        System.out.println(f4.getAbsolutePath());  // D:\test\test_delete.txt
        System.out.println(f4.getAbsoluteFile());  // D:\test\test_delete.txt
        System.out.println(f4.getPath());  // D:\test\test_delete.txt
        System.out.println(f4.getName());  // test_delete.txt

        File f5 = new File("D:\\testa\\testb");
        System.out.println(f5.getName());  // testb

        File file = new File("D:\\softwares");
        File[] files = file.listFiles();
        for(File f : files){
            System.out.println(f);  // 输出绝对路径
        }

         File file = new File("D:\\softwares");
         String[] fileNames = file.list();
         for(String fileName:fileNames){
             System.out.println(fileName);  // 只有名称
         }
    }
}

案例:列出指定路径下所有的 java 文件

  • 方式 1:使用 listFiles()
import java.io.File;

public class JavaBase {

    public static void main(String[] args) {
        findJavaFile1("d:\\JavaDemo");
    }

    public static void findJavaFile1(String pathName) {
        File file = new File(pathName);
        File[] files = file.listFiles();
        for(File f : files){
            if (f.isFile()&&f.getName().endsWith(".java")) {
                System.out.println(f);
            } else if (f.isDirectory()) {
                findJavaFile1(f.getPath());
            }
        }
    }
}
  • 方式 2:使用 listFiles(FilenameFilter filter)
// 自定义一个文件名过滤器
class MyFilter implements FilenameFilter{
     
     public boolean accept(File dir, String name){
          return name.endsWith(".java");
     }    
}
public class Test {
     public static void main(String[] args){
          File targetFile = new File("D:\\JavaPractise");
          listJava(targetFile);
     }
     
     public static void listJava(File dir){
          File[] files = dir.listFiles(new MyFilter());
          for(File file : files){
              System.out.println(file.getName());
          }
     }
}

字节流

IO 流概述和分类

IO 流介绍:

  • IO:输入/输出(Input/Output)
  • 流:流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在设备间传输称之为流。
  • IO 流就是用来处理设备间数据传输问题的。常见的应用有文件复制、文件上传、文件下载等。

IO 流的分类:

  • 按照数据的流向:
    • 输入流:读数据
    • 输出流:写数据
  • 按照数据类型来分:
    • 字节流
      • 字节输入流
      • 字节输出流
    • 字符流
      • 字符输入流
      • 字符输出流

IO 流体系:

image

字节流:字节流读取的是文件中的二进制数据,并不会转换成我们看得懂的字符。

  • InputStream:所有字节输入流的超类、抽象类。

    • FileInputStream:读取文件的二进制数据。
    • BufferedInputStream:缓冲输入字节流。为了提高读取文件数据的效率。该类内部维护了一个 8KB 的字节数组。
  • OutputStream:所有字节输出流的超类、抽象类。

    • FileOutputStream:向文件输出数据。
    • BufferedOutputStream:缓冲输出字节流。内部也是维护了8KB的字节数组。

字符流:字符流会把读取到的二进制数据进行对应的编码与解码。字符流=字节流+编码/解码

  • Reader:所有字符输入流的超类、抽象类。

    • FileReader:读取文件的字符数据。
    • BufferedReader:缓冲输入字符流;出现的目的是为了提高读取文件字符的效率和拓展了 FileReader 的功能。该类内部维护了 8192 长度的字符数组。
  • Writer:所有字符输出流的超类、抽象类。

    • FileWriter:向文件输出字符数据;内部维护了一个 1KB 的字符数组。
    • BufferedWriter:缓冲输出字符流;出现的目的是为了提高写数据的效率和拓展了 FileWriter 的功能。该类只不过是内部维护了更大的 8192 长度的字符数组,与多了 newline 方法。

IO 流使用场景:

  • 如果操作的是纯文本文件,优先使用字符流。
  • 如果操作的是图片、视频、音频等二进制文件,优先使用字节流。
  • 如果不确定文件类型,优先使用字节流,字节流是万能的流。

字节输出流

字节流抽象基类:

  • InputStream:这个抽象类是表示字节输入流的所有类的超类。
  • OutputStream:这个抽象类是表示字节输出流的所有类的超类。
  • 子类名特点:子类名称都是以其父类名作为子类名的后缀。

字节输出流:

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

使用字节输出流写数据的步骤:

  1. 创建字节输出流对象(调用系统功能创建文件和字节输出流对象,让字节输出流对象指向文件)。
  2. 调用字节输出流对象的写数据方法,把字符串数据转换成字节数组写出到文件。
  3. 释放资源(关闭此文件输出流,并释放与此流相关联的任何系统资源),原则:先开后关,后开先关。

注意事项:

  1. 如果目标文件不存在,那么会自动创建目标文件。
  2. 如果目标文件已存在,那么会先清空目标文件中的数据,然后再写入。
  3. 如果想追加内容,使用 new FileOutputStream(file,true),如果第二个参数为 true,则字节将写入文件的末尾而不是开头。
  4. 虽然 write() 方法接收 int 类型的数据,但是真正写出的只是一个字节的数据,只是把低 8 位的二进制数据写出,其他 24 位数据全部丢弃。
  5. write(buf, 0, 2):从字节数组的指定索引值开始写,写出两个字节。
  6. 每新创建一个 FileInputStream 对象时,默认情况下 FileOutputStream 的指针是指向了文件的开头位置。每写出一次,指针都会相应地移动。

示例代码:

import java.io.FileOutputStream;
import java.io.IOException;

public class JavaBase {
    public static void main(String[] args) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream("d:\\test_output_stream.txt", true);
            fileOutputStream.write(100);  // 实时写入"d"
            String str = "abcd";
            fileOutputStream.write(str.getBytes(), 1, 2);  // 追加写入"bc"
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

字节输入流

字节输入流:

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

FileInputStream 读取方法:

  • public int read():一次读取一个字节,返回值是读取到的内容。返回 -1 则表示读取完毕。
  • public int read(byte[] b):一次读取 b 个字节大小的数据。内容是存储到缓冲数组(b)中,返回值是存储到缓冲数组中的字节个数。返回 -1 则表示读取完毕。

字节输入流读取数据的步骤:

  1. 创建字节输入流对象。
  2. 调用字节输入流对象的读数据方法。
  3. 释放资源(原则:先开后关,后开先关)。

示例:

import java.io.FileInputStream;
import java.io.IOException;

public class FileTest {
    public static void main(String[] args) throws IOException {
          // 1.读取目标文件
          File file = new File("D:\\abc.txt");
          // 2.建立数据的输入通道
          FileInputStream fileInputStream = new FileInputStream(file);

          // 保存每次读取到的字节个数
          int length = 0; 

          // 3.存储读取到的数据。缓冲数组的长度一般是1024的倍数
          byte[] buf = new byte[4];
          // read() 方法如果读取到了文件的末尾,就会返回 -1
          while((length=fileInputStream.read(buf)) != -1){ 
              System.out.println(new String(buf, 0, length));  // 每次读取的内容
          }

          // 4.关闭资源
          fileInputStream.close();
    }
}

示例:字节流复制文件

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class JavaBase {
    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            // 根据数据源创建字节输入流对象
            fileInputStream = new FileInputStream("E:\\mn.jpg");
            // 根据目的地创建字节输出流对象
            fileOutputStream = new FileOutputStream("myByteStream\\mn.jpg");

            // 读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
            byte[] bys = new byte[1024];
            int len;
            while ((len=fileInputStream.read(bys))!=-1) {
                fileOutputStream.write(bys, 0, len);
            }
        } catch (IOException e) {
/*            1. 首先要阻止后面的代码执行。因为前面的数据已经读取异常,后面的数据执行代码已无意义,并需要通知使用者这里出错了。
                 因此使用 throw,方法既会停止执行,并且也能抛出通知。
              2. 把IOException传递给RuntimeException包装一层,然后再抛出,目的是为了使用者使用变得更加灵活。
                 使用者对RuntimeException可处理也可不处理。
*/          System.out.println("读取文件出错...");
            throw new RuntimeException(e);
        } finally {
            // 关闭资源。原则:先开后关,后开先关
            try{
                if(fileOutputStream!=null){  // 如果读取的文件不存在,管道也就没建立起来,就会出现空指针异常,也就不需要关
                    fileOutputStream.close();
                    System.out.println("关闭输出流成功");
                }
            }catch(IOException e){  // 再出错一般就是硬件问题
                System.out.println("关闭输出流失败");
                throw new RuntimeException(e);
            }finally{
                try{
                    if(fileInputStream!=null){
                        fileInputStream.close();
                        System.out.println("关闭输入流成功");
                    }
                }catch(IOException e){
                    System.out.println("关闭输入流失败");
                    throw new RuntimeException(e);
                }
            }
        }
    }
}

字节缓冲流

image

构造方法:

方法名 说明
BufferedOutputStream(OutputStream out) 创建字节缓冲输出流对象
BufferedInputStream(InputStream in) 创建字节缓冲输入流对象

BufferedInputStream

使用步骤:

  1. 找到目标文件。
  2. 建立数据的输入通道。
  3. 建立缓冲输入字节流(需要传递 InputStream,因此传递 FileInputStream)。
  4. 关闭资源(调用 BufferedInputStream 的 close 方法实际上关闭的是 FileInputStream)。

疑问 1:为什么创建 BufferedInputStream 的时候需要传递 FileInputStream?

  • 答:因为凡是缓冲流都不具备读写文件的能力,因此需要借助 FileInputStream 来读取文件的数据。

疑问 2:BufferedInputStream 的出现是为了提高读取文件的效率,但是 BufferedInputStream 的 read 方法每次只读取一个字节的数据,而 FileInputStream 每次也是读取一个字节的数据,那么 BufferedInputStream 如何提高效率?

  • 答:BufferedInputStream 是先将硬盘数据存入内部数组,再从数组中读取数据,相当于从内存中读取数据的速度;而 FileInputStream 相当于每次直接从硬盘读取数据。

BufferedOutputStream

使用步骤:

  1. 找到目标文件
  2. 建立数据的输出通道
  3. 建立缓冲输出字节流
  4. 把数据写出
  5. 把数据写到硬盘中,flush 与 close 均可。

注意

使用 BufferedOutputStream 写数据的时候,write 方法是先把数据写到内部的字节数组中;如果需要把数据真正地写到硬盘上,需要调用 flush 方法或者 close 方法(而 FileOutputStream 的 write 则是实时写入文件内容),或者是内部维护的 8KB 的字节数组已经填满数据的时候。

综合示例:使用字节缓冲输入/输出流复制文件

public class CopyAviDemo {

    public static void main(String[] args) throws IOException {
        method1();
        method2();
    }

    // 方式一:字节缓冲流一次读写一个字节数组
    public static void method2() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\字节流复制图片.avi"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));
        byte[] bys = new byte[1024];
        int len;
        while ((len=bis.read(bys)) != -1) {
            bos.write(bys,0,len);
        }
        bos.close();
        bis.close();
    }

    // 方式二:字节缓冲流一次读写一个字节
    public static void method1() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\字节流复制图片.avi"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));
        int by;
        while ((by=bis.read()) != -1) {
            bos.write(by);
        }
        bos.close();
        bis.close();
    }
}

字符流

字符流介绍:

  • 由于字节流操作中文不是特别得方便,所以 Java 就提供字符流。
  • 字符流会把读取到的二进制数据进行对应的编码与解码(字符流 = 字节流 + 编码/解码)。

中文的字节存储方式:

用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,那么如何识别是中文的呢?

  • 字节流 write 能够写中文是因为借助了字符串的 getBytes 方法对字符串进行了编码(字符 ---> 数字);
  • 字节流 read 能够读中文是因为借助了字符串的 new String() 对字符串进行了解码(数字 ---> 字符);
  • 同时记事本本身具有解码的功能。

编码

编码介绍详见:《编码》

相关方法:

方法名 说明
byte[] getBytes() 使用平台的默认字符集将该 String 编码为一系列字节
byte[] getBytes(String charsetName) 使用指定的字符集将该 String 编码为一系列字节
String(byte[] bytes) 使用平台的默认字符集解码指定的字节数组来创建字符串
String(byte[] bytes, String charsetName) 通过指定的字符集解码指定的字节数组来创建字符串

代码示例:

public class StringDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        // 定义一个字符串
        String s = "中国";

        // 编码
        // byte[] bys = s.getBytes();  // [-28, -72, -83, -27, -101, -67]
        // byte[] bys = s.getBytes("UTF-8");  // [-28, -72, -83, -27, -101, -67]
        byte[] bys = s.getBytes("GBK");  // [-42, -48, -71, -6]
        System.out.println(Arrays.toString(bys));

        // 解码
        // String ss = new String(bys);
        // String ss = new String(bys, "UTF-8");
        String ss = new String(bys, "GBK");
        System.out.println(ss);
    }
}

字符输出流

介绍:

  • Writer: 用于写入字符流的抽象父类。
  • FileWriter: 用于写入字符流的常用子类。

构造方法:

方法名 说明
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(File file, boolean append) 根据给定的 File 对象构造一个 FileWriter 对象
FileWriter(String fileName) 根据给定的文件名构造一个 FileWriter 对象
FileWriter(String fileName, boolean append) 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象

成员方法:

方法名 说明
void write(int c) 写一个字符
void write(char[] cbuf) 写入一个字符数组
void write(char[] cbuf, int off, int len) 写入字符数组的一部分
void write(String str) 写一个字符串
void write(String str, int off, int len) 写一个字符串的一部分

刷新和关闭的方法:

方法名 说明
flush() 刷新流,之后还可以继续写数据
close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

注意事项:

  1. Filewriter 内部是维护了一个 1KB 的字符数组的,写数据的时候会先写入到内部的字符数组中,如果需要把数据真正写到硬盘上,需要调用 flush 或 close 方法。
  2. 如果目标文件不存在,会自动创建目标文件。
  3. 如果在原有的文件上追加数据,需要使用 new FileWriter(file, true),只有创建新的 FileWriter 对象时才会把指针放在文件开头处。

示例:

public class OutputStreamWriterDemo {

    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("myCharStream\\a.txt");

        // void write(int c):写一个整数
//        fw.write(97);
//        fw.write(98);
//        fw.write(99);

        // void writ(char[] cbuf):写入一个字符数组
        char[] chs = {'a', 'b', 'c', 'd', 'e'};
//        fw.write(chs);

        // void write(char[] cbuf, int off, int len):写入字符数组的一部分
//        fw.write(chs, 0, chs.length);
//        fw.write(chs, 1, 3);

        // void write(String str):写一个字符串(具备编码的功能)
//        fw.write("abcde");

        // void write(String str, int off, int len):写字符串的一部分
//        fw.write("abcde", 0, "abcde".length());
        fw.write("abcde", 1, 3);

        // 释放资源
        fw.close();
    }
}

字符输入流

介绍:

  • Reader: 用于读取字符流的抽象父类。
  • FileReader: 用于读取字符流的常用子类。

构造方法:

方法名 说明
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader
FileReader(String fileName) 在给定从中读取数据的文件名的情况下创建一个新 FileReader

成员方法:

方法名 说明
int read() 一次读一个字符数据
int read(char[] cbuf) 一次读一个字符数组数据

代码示例:

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
   
        FileReader fr = new FileReader("myCharStream\\b.txt");

        // int read():一次读一个字符数据
//        int ch;
//        while ((ch=fr.read())!=-1) {
//            System.out.print((char)ch);
//        }

        // int read(char[] cbuf):一次读一个字符数组数据
        char[] chs = new char[1024];
        int len;
        while ((len = fr.read(chs)) != -1) {
            System.out.print(new String(chs, 0, len));
        }

        // 释放资源
        fr.close();
    }
}

注意:

Java 默认使用的是 GBK 编码表,如果 FileReader 读到的数据找不到对应的字符,那么会返回一个未知字符对应的数字,未知字符占一个字节。因此如果用字符流拷贝图片,图片会有部分数据丢失而打不开。

综合示例:字符输入/输出流 边读边写拷贝数据

     public static void main(String[] args) throws IOException {  // 找到目标文件
          File inFile = new File("D:\\abc.txt");
          File outFile = new File("E:\\a.txt");
          // 建立数据的输入通道
          FileReader fileReader = new FileReader(inFile);
          FileWriter fileWriter = new FileWriter(outFile);
          char[] buf = new char[1024];
          if(fileReader.read(buf) != -1){
              fileWriter.write(buf);
          }
          fileWriter.close();
          fileReader.close();

     }

字符缓冲流

字符缓冲流介绍:

  • BufferedWriter:将文本写入字符输出流并缓冲字符,以提供单个字符、数组和字符串的高效写入。可以指定缓冲区大小或者使用默认大小。默认值已足够大,可用于大多数用途。
  • BufferedReader:从字符输入流读取文本并缓冲字符,以提供字符、数组和行的高效读取。可以指定缓冲区大小或者使用默认大小。 默认值已足够大,可用于大多数用途。

image

构造方法:

方法名 说明
BufferedWriter(Writer out) 创建字符缓冲输出流对象
BufferedReader(Reader in) 创建字符缓冲输入流对象

示例代码:

public class BufferedStreamDemo {
    public static void main(String[] args) throws IOException {
        // BufferedWriter(Writer out)
        BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\bw.txt"));
        bw.write("hello\r\n");
        bw.write("world\r\n");
        bw.close();

        // BufferedReader(Reader in)
        BufferedReader br = new BufferedReader(new FileReader("myCharStream\\bw.txt"));

        // 一次读取一个字符数据
//        int ch;
//        while ((ch=br.read())!=-1) {
//            System.out.print((char)ch);
//        }

        // 一次读取一个字符数组数据
        char[] chs = new char[1024];
        int len;
        while ((len=br.read(chs))!=-1) {
            System.out.print(new String(chs, 0, len));
        }
        br.close();
    }
}

字符缓冲流特有方法

BufferedWriter:

方法名 说明
void newLine() 写入一个行分隔符(行分隔符字符串由系统属性定义)

BufferedReader:

方法名 说明
String readLine() 读一行文字。结果包含行的内容的字符串,不包括任何行终止字符。如果流的结尾已经到达,则为 null

示例:使用步骤与自己实现 readline

     public static void main(String[] args) throws IOException {
          // 1.找到目标文件
          File file = new File("D:\\abc.txt");
          // 2.建立数据的输入通道
          FileReader fileReader = new FileReader(file);
          // 3.建立缓冲输入字符流
          BufferedReader bufferedReader = new BufferedReader(fileReader);
          // 4.读取数据

/*        char content = (char) bufferedReader.read();
          // 读取一个字符。
          System.out.println(content);*/
          // 使用BufferedReader拓展的功能readLine(): 一次读取一行,如果读到末尾返回null
          String line = null;
          while((line = bufferedReader.readLine())!=null){
              System.out.println(line);
          }  // 虽然readline每次读取一行数据,但是读到的line是不包含\r\n的
     }

     // 自己实现readLine方法
     public static String myReadLine(FileReader fileReader) throws IOException{
          // 创建一个字符串缓冲类用于存储读取到的数据
          StringBuilder sb = new StringBuilder();
          int content = 0;
          while((content=fileReader.read())!=-1){
              // 遇到\r则不读取数据,遇到\n则结束
              if(content =='\r'){
                   continue;
              }else if(content =='\n'){
                   break;
              }else{
                   // 普通字符
                   sb.append((char)content);
              }
          }
          // 代表已经读取完毕
          if(content==-1){
              return null;
          }
          return sb.toString();  // 如果没有内容,返回的是"",不是null
     }

     // 实现自己的readline方法循环输出全部数据
     while((line=myReaderLine(fileReader)) != null){
         System.out.println(line);
     }

示例:使用字符缓冲流读取文件中的数据,排序后再次写到本地文件

public class CharStreamDemo14 {
    public static void main(String[] args) throws IOException {

        // 1. 把文件中的数据读取进来
        BufferedReader br = new BufferedReader(new FileReader("charstream\\sort.txt"));
        // 输出流一定不能写在这里,否则会先清空文件中的内容
        // BufferedWriter bw = new BufferedWriter(new FileWriter("charstream\\sort.txt"));

        String line = br.readLine();
        System.out.println("读取到的数据为" + line);
        br.close();

        // 2. 按照空格进行切割
        String[] split = line.split(" ");  // 9 1 2 5 3 10 4 6 7 8
        // 3. 把字符串数组变成int数组
        int [] arr = new int[split.length];
        // 遍历split数组,可以进行类型转换
        for (int i=0; i < split.length; i++) {
            String smallStr = split[i];
            // 类型转换
            int number = Integer.parseInt(smallStr);
            // 把转换后的结果存入到arr中
            arr[i] = number;
        }
        // 4. 排序
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));

        // 5. 把排序之后结果写回到本地 1 2 3 4...
        BufferedWriter bw = new BufferedWriter(new FileWriter("charstream\\sort.txt"));
        // 写出
        for (int i = 0; i < arr.length; i++) {
            bw.write(arr[i] + " ");
            bw.flush();
        }
        // 释放资源
        bw.close();
    }
}

转换流

  • InputStreamReader:是从字节流到字符流的桥梁,父类是 Reader。

    • 它读取字节,并使用指定的编码将其解码为字符。
    • 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。
  • OutputStreamWriter:是从字符流到字节流的桥梁,父类是 Writer。

    • 使用指定的编码将写入的字符编码为字节。
    • 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。

作用:

  1. 如果目前所获取到的是一个字节流,且需要转换成字符流使用,这时就可以使用转换流。
  2. 使用转换流可以指定编码表进行读写文件(FileReader/Writer 不能指定码表)。

构造方法:

方法名 说明
InputStreamReader(InputStream in) 使用默认字符编码创建 InputStreamReader 对象
InputStreamReader(InputStream in, String chatset) 使用指定的字符编码创建 InputStreamReader 对象
OutputStreamWriter(OutputStream out) 使用默认字符编码创建 OutputStreamWriter 对象
OutputStreamWriter(OutputStream out, String charset) 使用指定的字符编码创建 OutputStreamWriter 对象

示例:

public class Test {
     
     public static void main(String[] args) throws IOException {
          readTest();
          writeTest();
          writeTest2();
          readTest2();
     }
     
     // 输出字节流的转换流
     public static void writeTest() throws IOException{
          File file = new File("D:\\writeTest.txt");
          FileOutputStream fileOutputStream = new FileOutputStream(file);
          //fileOutputStream.write("abc".getBytes());
          OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream);
          outputStreamWriter.write("大家好");
          outputStreamWriter.close();
     }
     
     // 使用输出字节流的转换流指定码表写出数据
     public static void writeTest2() throws IOException{
          File file = new File("D:\\writeTest2.txt");
          FileOutputStream fileOutputStream = new FileOutputStream(file);
          //fileOutputStream.write("abc".getBytes());
          OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream,"UTF-8");
          outputStreamWriter.write("大家好,这用了UTF-8");
          outputStreamWriter.close();
     }
     
     // 输入字节流的转换流
     public static void readTest() throws IOException{
          InputStream inputStream = System.in;  // 获取了标准的输入流
          // System.out.println("读到的字符:"+(char)inputStream.read());  // 只能读一个字节
          
          // 为了使用BufferedReader的readline方法,要先把字节流转换成字符流
          InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
          BufferedReader bufferedReader = new BufferedReader(inputStreamReader);  // 需要输入Reader,即字符流
          String line = null;
          while((line=bufferedReader.readLine())!=null){
              System.out.println("读到的字符:"+line);
          }
     }
     
     // 使用输入字节流的转换流指定码表读取数据
     public static void readTest2() throws IOException{
          File file = new File("D:\\writeTest2.txt");
          FileInputStream fileInputStream = new FileInputStream(file);
          // 创建字节流的转换流并且指定码表进行读取
          InputStreamReader inutStreamReader = new InputStreamReader(fileInputStream,"UTF-8");
          char[] buf = new char[1024];
          int length = 0;
          while((length=inutStreamReader.read(buf))!=-1){
              System.out.println(new String(buf));
          }
     }

}

Properties

Properties 介绍:

  • 是一个 Map 体系的集合类。
  • 主要用于生成配置文件与读取配置文件的信息。
  • 元素中的键及其对应的值都是一个字符串。
  • 可以保存到流中或从流中加载。

常用方法:

方法名 说明
Object setProperty(String key, String value) 设置集合的键和值,都是 String 类型,底层调用 Hashtable 方法 put
String getProperty(String key) 使用此属性列表中指定的键搜索对应的值
Set<String> stringPropertyNames() 从该属性列表中返回一个不可修改的键值对,其中键及其对应的值是字符串
void load(Reader reader) 从输入字符流读取属性列表(键和元素对)
void store(Writer writer, String comments) 将此属性列表(键和元素对)写入此 Properties 表中,以适合使用 load(Reader) 方法的格式写入输出字符流

示例:

public class Test {
     public static void main(String[] args) throws FileNotFoundException, IOException {
          //creatProperties();
          readProperties();
     }
     
     // 读取配置文件的信息
     public static void readProperties() throws FileNotFoundException, IOException{
          // 1.创建Properties对象
          Properties properties = new Properties();
          // 2.把配置文件的信息加载到Properties中
          properties.load(new FileReader("D:\\aa.properties"));
          // 3.遍历查看Properties中的信息是否加载成功
          Set<Entry<Object, Object>> entrys = properties.entrySet();
          for(Entry<Object, Object> entry : entrys){
              System.out.println("键:"+entry.getKey()+" 值"+entry.getValue());
          }
          // 修改狗娃的密码
          properties.setProperty("狗娃", "1234");
          properties.store(new FileWriter("D:\\aa.properties"), "hehe");
     }

     // 生成配置文件
     public static void creatProperties() throws FileNotFoundException, IOException{
          // 1.创建Properties对象
          Properties properties = new Properties();
          properties.setProperty("狗娃", "123");

          properties.setProperty("狗剩", "234");
          // 2.使用Properties写入配置文件
          // 第一个参数是一个输出流对象;第二个参数是使用一个字符串描述这个配置文件的信息。
          // properties.store(new FileOutputStream("D:\\aa.properties"), "haha");
          properties.store(new FileWriter("D:\\aa.properties"), "haha");
     }
}

案例:使用配置文件记录软件使用次数,若试用超过 3 次则提醒购买正版

public class Test {
     public static void main(String[] args) throws IOException {
		
          File file = new File("D:\\count.properties");
          if(!file.exists()){
              // 如果配置文件不存在,则创建配置信息
              file.createNewFile();
          }

          // 创建Properties对象
          Properties properties = new Properties();
          // FileOutputStream fileOutputStream = new FileOutputStream(file);
          // 注意:若使用上面语句会清空文件内容,因此永远都是使用一次,因此至少要放在load语句后面
          
          // 把配置文件的信息加载到Properties中
          properties.load(new FileInputStream(file));
          int count = 0;  // 定义该变量用于记录使用次数

          // 读取配置文件的运行次数
          String value = properties.getProperty("count");  // 通过key获取value
          if(value!=null){
              count = Integer.parseInt(value);
          }

          // 判断使用次数是否超过3次
          if(count==3){
              System.out.println("您的试用次数已超过3次,请购买正版!");
              System.exit(0);
          }

          count++;
          System.out.println("您已经试用了本软件第"+count+"次");
          properties.setProperty("count", count+"");

          // 写入配置文件
          properties.store(new FileOutputStream(file), "Run times");
     }
}
posted @ 2021-09-24 01:03  Juno3550  阅读(216)  评论(0编辑  收藏  举报