Java基础(二十二):File类与IO流

news2024/9/22 5:31:41

Java基础系列文章

Java基础(一):语言概述

Java基础(二):原码、反码、补码及进制之间的运算

Java基础(三):数据类型与进制

Java基础(四):逻辑运算符和位运算符

Java基础(五):流程控制语句

Java基础(六):数组

Java基础(七):面向对象编程

Java基础(八):封装、继承、多态性

Java基础(九):Object 类的使用

Java基础(十):关键字static、代码块、关键字final

Java基础(十一):抽象类、接口、内部类

Java基础(十二):枚举类

Java基础(十三):注解(Annotation)

Java基础(十四):包装类

Java基础(十五):异常处理

Java基础(十六):String的常用API

Java基础(十七):日期时间API

Java基础(十八):java比较器、系统相关类、数学相关类

Java基础(十九):集合框架

Java基础(二十):泛型

Java基础(二十一):集合源码

Java基础(二十二):File类与IO流


目录

  • 一、java.io.File类的使用
    • 1、概述
    • 2、构造器
    • 3、常用方法
      • 3.1、获取文件和目录基本信息
      • 3.2、列出目录的下一级
      • 3.3、File类的重命名功能
      • 3.4、判断功能的方法
      • 3.5、创建、删除功能
  • 二、IO流原理及流的分类
    • 1、Java IO原理
    • 2、流的分类
    • 3、流的API
  • 三、节点流之一:FileReader\FileWriter
    • 1、字符输入流:Reader
    • 2、字符输出流:Writer
    • 3、FileReader
    • 4、FileWriter
    • 6、关于flush(刷新)
  • 四、节点流之二:FileInputStream\FileOutputStream
    • 1、字节输入流:InputStream
    • 2、字节输出流:OutputStream
    • 3、FileInputStream
    • 4、FileOutputStream
  • 五、处理流之一:缓冲流
    • 1、构造器
    • 2、效率测试
    • 3、字符缓冲流特有方法
  • 六、处理流之二:转换流
    • 1、问题引入
    • 2、转换流的理解
    • 3、InputStreamReader
    • 4、OutputStreamWriter
  • 七、处理流之三/四:数据流、对象流
    • 1、数据流与对象流说明
    • 2、对象流API
    • 3、认识对象序列化机制
    • 4、如何实现序列化机制
    • 5、反序列化失败问题
    • 6、序列化和反序列化源码
  • 八、其他流的使用
    • 1、标准输入、输出流
    • 2、Scanner类
  • 九、apache-common包的使用


一、java.io.File类的使用

1、概述

  • File类及本章下的各种流,都定义在java.io包下
  • 一个File对象代表硬盘或网络中可能存在的一个文件或者文件目录(俗称文件夹),与平台无关
  • File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身
    • 如果需要访问文件内容本身,则需要使用输入/输出流
    • File对象可以作为参数传递给流的构造器
  • 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象
    • 但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录
    • 也就是说File对象所表示的文件或目录可能不存在,访问或操作文件内容才会报错

2、构造器

  • public File(String pathname) :以pathname为路径创建File对象
    • 可以是绝对路径或者相对路径
    • 如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储
  • public File(String parent, String child) :以parent为父路径,child为子路径创建File对象
  • public File(File parent, String child) :根据一个父File对象和子文件路径创建File对象

关于路径:

  • 绝对路径:从盘符开始的路径,这是一个完整的路径
  • 相对路径:相对于项目目录的路径,这是一个便捷的路径,开发中经常使用
    • IDEA中,main中的文件的相对路径,是相对于"当前工程"
    • IDEA中,单元测试方法中的文件的相对路径,是相对于"当前module"

举例:

public class FileObjectTest {
    public static void main(String[] args) {
        // 文件路径名
        String pathname = "D:\\aaa.txt";
        File file1 = new File(pathname);

        // 文件路径名
        String pathname2 = "D:\\aaa\\bbb.txt";
        File file2 = new File(pathname2);

        // 通过父路径和子路径字符串
        String parent = "d:\\aaa";
        String child = "bbb.txt";
        File file3 = new File(parent, child);

        // 通过父级File对象和子路径字符串
        File parentDir = new File("d:\\aaa");
        String childFile = "bbb.txt";
        File file4 = new File(parentDir, childFile);
    }
}

注意:

  • 无论该路径下是否存在文件或者目录,都不影响File对象的创建
  • window的路径分隔符使用“\”,而Java程序中的“\”表示转义字符
    • 所以在Windows中表示路径,需要用“\\”,或者直接使用“/”也可以
    • Java程序支持将“/”当成平台无关的路径分隔符
    • 或者直接使用File.separator常量值表示
      • File file2 = new File(“d:” + File.separator + “atguigu” + File.separator + “info.txt”);

3、常用方法

3.1、获取文件和目录基本信息

  • public String getName() :获取名称
  • public String getPath() :获取路径
  • public String getAbsolutePath():获取绝对路径
  • public File getAbsoluteFile():获取绝对路径表示的文件
  • public String getParent():获取上层文件目录路径。若无,返回null
  • public long length() :获取文件长度(即:字节数)。不能获取目录的长度
  • public long lastModified() :获取最后一次的修改时间,毫秒值

在这里插入图片描述

  • 如果File对象代表的文件或目录存在,则File对象实例初始化时,就会用硬盘中对应文件或目录的属性信息(例如,时间、类型等)为File对象的属性赋值
  • 否则除了路径和名称,File对象的其他属性将会保留默认值
  • 当构造路径是绝对路径时,那么getPath和getAbsolutePath结果一样
  • 当构造路径是相对路径时,那么getAbsolutePath的路径 = user.dir的路径 + 构造路径

举例:

  • 相对路径
@Test
public void test1() {
    File file1 = new File("hello.txt");
    System.out.println(file1.getName()); // hello.txt
    System.out.println(file1.getPath()); // hello.txt
    System.out.println(file1.getAbsolutePath()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher/hello.txt
    System.out.println(file1.getAbsoluteFile()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher/hello.txt
    System.out.println(file1.getParent());// null
    System.out.println(file1.getAbsoluteFile().getParent());// /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher
    System.out.println(file1.length()); // 19
    System.out.println(file1.lastModified()); // 1670807450000
}
  • 绝对路径
@Test
public void test2() {
    File file1 = new File("/Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher/hello.txt");
    System.out.println(file1.getName()); // hello.txt
    System.out.println(file1.getPath()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher/hello.txt
    System.out.println(file1.getAbsolutePath()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher/hello.txt
    System.out.println(file1.getAbsoluteFile()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher/hello.txt
    System.out.println(file1.getParent()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher
    System.out.println(file1.getAbsoluteFile().getParent()); // /Users/xuchang/Documents/javaCode/study/java基础/JavaSECode/chapter15_io_teacher
    System.out.println(file1.length()); // 19
    System.out.println(file1.lastModified()); // 1670807450000
}

3.2、列出目录的下一级

  • public String[] list() :返回一个String数组,表示该File目录中的所有子文件或目录
  • public File[] listFiles() :返回一个File数组,表示该File目录中的所有的子文件或目录

举例:

@Test
public void test3() {
    //public String[] list()
    File file1 = new File("/Users/xuchang/Documents/txt文件");
    String[] fileArr = file1.list();
    for (String s : fileArr) {
        System.out.println(s);
    }

    System.out.println();
    
    //public File[] listFiles()
    File[] files = file1.listFiles();
    for (File f : files) {
        System.out.println(f);
    }
}

输出结果:

环境.txt
.DS_Store
我的文件

/Users/xuchang/Documents/txt文件/环境.txt
/Users/xuchang/Documents/txt文件/.DS_Store
/Users/xuchang/Documents/txt文件/我的文件

3.3、File类的重命名功能

  • public boolean renameTo(File dest):把文件重命名为指定的文件路径
    • 即:剪切并修改文件名称
  • file1必须存在,且file2必须不存在,且file2所在的文件目录需要存在
@Test
public void test4() {
    File file1 = new File("hello.txt");

    File file2 = new File("\\io\\abc.txt");

    boolean renameSuccess = file1.renameTo(file2);
    System.out.println(renameSuccess ? "重命名成功" : "重命名失败");
}

3.4、判断功能的方法

  • public boolean exists() :此File表示的文件或目录是否实际存在
  • public boolean isDirectory() :此File表示的是否为目录
  • public boolean isFile() :此File表示的是否为文件
  • public boolean canRead() :判断是否可读
  • public boolean canWrite() :判断是否可写
  • public boolean isHidden() :判断是否隐藏

注意:

如果文件或目录不存在,那么exists()、isFile()和isDirectory()都是返回false

3.5、创建、删除功能

  • public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
  • public boolean mkdir() :创建文件目录
    • 如果此文件目录存在,就不创建了
    • 如果此文件目录的上层目录不存在,也不创建
  • public boolean mkdirs() :创建文件目录
    • 如果上层文件目录不存在,一并创建
  • public boolean delete() :删除文件或者文件夹
    • ① Java中的删除不走回收站
    • ② 要删除一个文件目录,请注意该文件目录内不能包含文件或者文件目录
    • API中说明:delete方法,如果此File表示目录,则目录必须为空才能删除

二、IO流原理及流的分类

在这里插入图片描述

1、Java IO原理

  • Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行,可以看做是一种数据的流动

在这里插入图片描述

  • I/O流中的I/O是Input/Output的缩写, I/O技术是非常实用的技术,用于处理设备之间的数据传输。如读/写文件,网络通讯等
  • 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中
  • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中

在这里插入图片描述

2、流的分类

  • 按数据的流向不同分为:输入流输出流
    • 输入流 :把数据从其他设备上读取到内存中的流
      • 以InputStream、Reader结尾
    • 输出流 :把数据从内存 中写出到其他设备上的流
      • 以OutputStream、Writer结尾
  • 按操作数据单位的不同分为:字节流(8bit)字符流(16bit)
    • 字节流 :以字节为单位,读写数据的流
      • 以InputStream、OutputStream结尾
    • 字符流 :以字符为单位,读写数据的流
      • 以Reader、Writer结尾
  • 根据IO流的角色不同分为:节点流处理流
    • 节点流:直接从数据源或目的地读写数据
      在这里插入图片描述
    • 处理流:不直接连接到数据源或目的地,而是“连接”在已存在的流(节点流或处理流)之上,通过对数据的处理为程序提供更为强大的读写功能
      在这里插入图片描述

小结:图解
在这里插入图片描述

3、流的API

  • Java的IO流共涉及40多个类,实际上非常规则,都是从如下4个抽象基类派生的
(抽象基类)输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter
  • 由这四个类派生出来的子类名称都是以其父类名作为子类名后缀

在这里插入图片描述

常用的节点流:

  • 文件流: FileInputStream、FileOutputStrean、FileReader、FileWriter
  • 字节/字符数组流: ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter
    • 对数组进行处理的节点流(对应的不再是文件,而是内存中的一个数组)

常用处理流:

  • 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
    • 作用:增加缓冲功能,避免频繁读写硬盘,进而提升读写效率
  • 转换流:InputStreamReader、OutputStreamReader
    • 作用:实现字节流和字符流之间的转换
  • 对象流:ObjectInputStream、ObjectOutputStream
    • 作用:提供直接读写Java对象功能

三、节点流之一:FileReader\FileWriter

  • Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件
  • 不能操作图片,视频等非文本文件
  • 常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py等
  • 注意:.doc、.xls、.ppt这些都不是文本文件

1、字符输入流:Reader

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

  • public int read(): 从输入流读取一个字符
    • 虽然读取了一个字符,但是会自动提升为int类型
    • 返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
    • 每次最多读取cbuf.length个字符
    • 返回实际读取的字符个数
    • 如果已经到达流末尾,没有数据可读,则返回-1
  • public int read(char[] cbuf,int off,int len):从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储
    • 每次最多读取len个字符
    • 返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1
  • public void close() :关闭此流并释放与此流相关联的任何系统资源

注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏

2、字符输出流:Writer

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

  • public void write(int c) :写出单个字符
  • public void write(char[] cbuf) :写出字符数组
  • public void write(char[] cbuf, int off, int len) :写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数
  • public void write(String str) :写出字符串
  • public void flush() :刷新该流的缓冲
  • public void close() :关闭此流

3、FileReader

java.io.FileReader 类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区

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

举例:

  • 方式一:使用read()返回的字节数
  • 1和2步骤也可以合并,FileReader fr = new FileReader(“hello.txt”)
@Test
public void test1() throws IOException {
    //1. 创建File类的对象,对应着物理磁盘上的某个文件
    File file = new File("hello.txt");
    //2. 创建FileReader流对象,将File类的对象作为参数传递到FileReader的构造器中
    FileReader fr = new FileReader(file);
    //3. 通过相关流的方法,读取文件中的数据
//        int data = fr.read(); //每调用一次读取一个字符
//        while (data != -1) {
//            System.out.print((char) data);
//            data = fr.read();
//        }
    int data;
    while ((data = fr.read()) != -1) {
        System.out.print((char) data);
    }

    //4. 关闭相关的流资源,避免出现内存泄漏
    fr.close();
}
  • 方式二:在方式1的基础上改进,使用try-catch-finally处理异常。保证流是可以关闭的
@Test
public void test2() {
    FileReader fr = null;
    try {
        //1. 创建File类的对象,对应着物理磁盘上的某个文件
        File file = new File("hello.txt");
        //2. 创建FileReader流对象,将File类的对象作为参数传递到FileReader的构造器中
        fr = new FileReader(file);
        //3. 通过相关流的方法,读取文件中的数据
        int data;
        while ((data = fr.read()) != -1) {
            System.out.println((char) data);
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4. 关闭相关的流资源,避免出现内存泄漏
        try {
            if (fr != null)
                fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 方式三:调用read(char[] cbuf),每次从文件中读取多个字符
@Test
public void test3() {
    FileReader fr = null;
    try {
        //1. 创建File类的对象,对应着物理磁盘上的某个文件
        File file = new File("hello.txt");
        //2. 创建FileReader流对象,将File类的对象作为参数传递到FileReader的构造器中
        fr = new FileReader(file);
        //3. 通过相关流的方法,读取文件中的数据
        char[] cbuf = new char[5];
        /*
         * read(char[] cbuf) : 每次将文件中的数据读入到cbuf数组中,并返回读入到数组中的
         * 字符的个数。
         * */
        int len; //记录每次读入的字符的个数
        while ((len = fr.read(cbuf)) != -1) {
            //处理char[]数组即可
            //错误:
//                for(int i = 0;i < cbuf.length;i++){
//                    System.out.print(cbuf[i]);
//                }
            //错误:
//                String str = new String(cbuf);
//                System.out.print(str);
            //正确:
//                for(int i = 0;i < len;i++){
//                    System.out.print(cbuf[i]);
//                }
            //正确:
            String str = new String(cbuf, 0, len);
            System.out.print(str);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4. 关闭相关的流资源,避免出现内存泄漏
        try {
            if (fr != null)
                fr.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4、FileWriter

java.io.FileWriter 类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区

  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称
  • FileWriter(File file,boolean append): 创建一个新的 FileWriter,指明是否在现有文件末尾追加内容

举例:

@Test
public void test01()throws IOException {
    // 使用文件名称创建流对象
    FileWriter fw = new FileWriter(new File("fw.txt"));
    // 写出数据
    fw.write(97); // 写出第1个字符
    fw.write('b'); // 写出第2个字符
    fw.write('C'); // 写出第3个字符
    fw.write(30000); // 写出第4个字符,中文编码表中30000对应一个汉字。

    // 字符串转换为字节数组
    char[] chars = "尚硅谷".toCharArray();
    // 写出字符数组
    fw.write(chars); // 尚硅谷
    // 写出从索引1开始,2个字符。
    fw.write(chars,1,2); // 硅谷

    // 写出字符串
    fw.write("尚硅谷");
        
    //关闭资源
    fw.close();
}
  • 1和2步骤可以合并:fw = new FileWriter(“personinfo.txt”)
@Test
public void test02(){
    FileWriter fw = null;
    try {
        //1. 创建File的对象
        File file = new File("personinfo.txt");
        //2. 创建FileWriter的对象,将File对象作为参数传递到FileWriter的构造器中
        //如果输出的文件已存在,则会对现有的文件进行覆盖
        fw = new FileWriter(file);
//            fw = new FileWriter(file,false);
        //如果输出的文件已存在,则会在现有的文件末尾写入数据
//            fw = new FileWriter(file,true);

        //3. 调用相关的方法,实现数据的写出操作
        //write(String str) / write(char[] cbuf)
        fw.write("I love you,");
        fw.write("you love him.");
        fw.write("so sad".toCharArray());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4. 关闭资源,避免内存泄漏
        try {
            if (fw != null)
                fw.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

小结

  • 因为出现流资源的调用,为了避免内存泄漏,需要使用try-catch-finally处理异常
  • 对于输入流来说,File类的对象必须在物理磁盘上存在,否则执行就会报FileNotFoundException
    • 如果传入的是一个目录,则会报IOException异常
  • 对于输出流来说,File类的对象是可以不存在
    • 如果File类的对象不存在,则可以在输出的过程中,自动创建File类的对象
    • 如果File类的对象存在
      • 如果调用FileWriter(File file)或FileWriter(File file,false),输出时会新建File文件覆盖已有的文件
      • 如果调用FileWriter(File file,true)构造器,则在现有的文件末尾追加写出内容

6、关于flush(刷新)

  • 因为内置缓冲区的原因,如果FileWriter不关闭输出流,无法写出字符到文件中
  • 但是关闭的流对象,是无法继续写出数据的
  • 如果我们既想写出数据,又想继续使用流,就需要flush() 方法了
  • flush() :刷新缓冲区,流对象可以继续使用
  • close() :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了

注意:即便是flush()方法写出了数据,操作的最后还是要调用close方法,释放系统资源

举例:

public class FWWriteFlush {
    //注意:应该使用try-catch-finally处理异常。这里出于方便阅读代码,使用了throws的方式
    @Test
    public void test() throws IOException {
        // 使用文件名称创建流对象
        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();
    }
}

四、节点流之二:FileInputStream\FileOutputStream

如果我们读取或写出的数据是非文本文件,则Reader、Writer就无能为力了,必须使用字节流

1、字节输入流:InputStream

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

  • public int read(): 从输入流读取一个字节。返回读取的字节值。虽然读取了一个字节,但是会自动提升为int类型
    • 如果已经到达流末尾,没有数据可读,则返回-1
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组 b中
    • 每次最多读取b.length个字节
    • 返回实际读取的字节个数
    • 如果已经到达流末尾,没有数据可读,则返回-1
  • public int read(byte[] b,int off,int len):从输入流中读取一些字节数,并将它们存储到字节数组 b中
    • 从b[off]开始存储,每次最多读取len个字节
    • 返回实际读取的字节个数
    • 如果已经到达流末尾,没有数据可读,则返回-1
  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源

说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源

2、字节输出流:OutputStream

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

  • public void write(int b) :将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出
  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源

3、FileInputStream

java.io.FileInputStream 类是文件输入流,从文件中读取字节

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

举例:

  • 方式一:使用read()一次一次读取
@Test
public void test() throws IOException {
    // 使用文件名称创建流对象
    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();
    /*
    文件内容:abcde
    输出结果:
    a
    b
    c
    d
    e
    -1
     */
}
  • 方式二:遍历读取
@Test
public void test02()throws IOException{
    // 使用文件名称创建流对象
    FileInputStream fis = new FileInputStream("read.txt");
    // 定义变量,保存数据
    int b;
    // 循环读取
    while ((b = fis.read())!=-1) {
        System.out.println((char)b);
    }
    // 关闭资源
    fis.close();
}
  • 方式三:读取有效字节长度
@Test
public void test04()throws IOException{
    // 使用文件名称创建流对象.
    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();
    /*
    输出结果:
    ab
    cd
    e
     */
}

4、FileOutputStream

java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件

  • public FileOutputStream(File file):创建文件输出流,写出由指定的 File对象表示的文件
  • public FileOutputStream(String name): 创建文件输出流,指定的名称为写出文件
  • public FileOutputStream(File file, boolean append): 创建文件输出流,指明是否在现有文件末尾追加内容

举例:

@Test
public void test01() throws IOException {
    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("fos.txt");
    // 写出数据
    fos.write(97); // 写出第1个字节
    fos.write(98); // 写出第2个字节
    fos.write(99); // 写出第3个字节
    // 关闭资源
    fos.close();
  /*  输出结果:abc*/
}
@Test
public void test02()throws IOException {
    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("fos.txt");
    // 字符串转换为字节数组
    byte[] b = "abcde".getBytes();
    // 写出从索引2开始,2个字节。索引2是c,两个字节,也就是cd。
    fos.write(b,2,2);
    // 关闭资源
    fos.close();
}
//这段程序如果多运行几次,每次都会在原来文件末尾追加abcde
@Test
public void test03()throws IOException {
    // 使用文件名称创建流对象
    FileOutputStream fos = new FileOutputStream("fos.txt",true);
    // 字符串转换为字节数组
    byte[] b = "abcde".getBytes();
    fos.write(b);
    // 关闭资源
    fos.close();
}
  • 使用FileInputStream\FileOutputStream,实现对文件的复制
@Test
public void test05() {
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        //1. 造文件-造流
        //复制图片:成功
//            fis = new FileInputStream(new File("pony.jpg"));
//            fos = new FileOutputStream(new File("pony_copy1.jpg"));

        //复制文本文件:成功
        fis = new FileInputStream(new File("hello.txt"));
        fos = new FileOutputStream(new File("hello1.txt"));

        //2. 复制操作(读、写)
        byte[] buffer = new byte[1024];
        int len;//每次读入到buffer中字节的个数
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
//                String str = new String(buffer,0,len);
//                System.out.print(str);
        }
        System.out.println("复制成功");
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        //3. 关闭资源
        try {
            if (fos != null)
                fos.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            if (fis != null)
                fis.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

五、处理流之一:缓冲流

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类:缓冲流
  • 缓冲流要“套接”在相应的节点流之上,根据数据操作单位可以把缓冲流分为
    • 字节缓冲流BufferedInputStreamBufferedOutputStream
    • 字符缓冲流BufferedReaderBufferedWriter
  • 缓冲流的基本原理:在创建流对象时,内部会创建一个缓冲区数组(缺省使用8192个字节(8Kb)的缓冲区),通过缓冲区读写,减少系统IO次数,从而提高读写的效率

在这里插入图片描述

在这里插入图片描述

1、构造器

  • public BufferedInputStream(InputStream in) :创建一个 新的字节型的缓冲输入流
  • public BufferedOutputStream(OutputStream out): 创建一个新的字节型的缓冲输出流
  • public BufferedReader(Reader in) :创建一个 新的字符型的缓冲输入流
  • public BufferedWriter(Writer out): 创建一个新的字符型的缓冲输出流

2、效率测试

查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率

  • 方法1:使用FileInputStream\FileOutputStream实现非文本文件的复制(耗时:7677毫秒
public void copyFileWithFileStream(String srcPath,String destPath){
    FileInputStream fis = null;
    FileOutputStream fos = null;
    try {
        //1. 造文件-造流
        fis = new FileInputStream(new File(srcPath));
        fos = new FileOutputStream(new File(destPath));

        //2. 复制操作(读、写)
        byte[] buffer = new byte[100];
        int len;//每次读入到buffer中字节的个数
        while ((len = fis.read(buffer)) != -1) {
            fos.write(buffer, 0, len);
        }
        System.out.println("复制成功");
    } catch (IOException e) {
        throw new RuntimeException(e);
    } finally {
        //3. 关闭资源
        try {
            if (fos != null)
                fos.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            if (fis != null)
                fis.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 方法2:使用BufferedInputStream\BufferedOuputStream实现非文本文件的复制(耗时:415毫秒
public void copyFileWithBufferedStream(String srcPath,String destPath){
    FileInputStream fis = null;
    FileOutputStream fos = null;
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;
    try {
        //1. 造文件
        File srcFile = new File(srcPath);
        File destFile = new File(destPath);
        //2. 造流
        fis = new FileInputStream(srcFile);
        fos = new FileOutputStream(destFile);

        bis = new BufferedInputStream(fis);
        bos = new BufferedOutputStream(fos);

        //3. 读写操作
        int len;
        byte[] buffer = new byte[100];
        while ((len = bis.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        System.out.println("复制成功");
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //4. 关闭资源(如果有多个流,我们需要先关闭外面的流,再关闭内部的流)
        try {
            if (bos != null)
                bos.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try {
            if (bis != null)
                bis.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }
}

3、字符缓冲流特有方法

  • BufferedReader:public String readLine(): 读一行文字
  • BufferedWriter:public void newLine(): 写一行行分隔符,由系统属性定义符号
public class BufferedIOLine {
    @Test
    public void testReadLine()throws IOException {
        // 创建流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
        // 定义字符串,保存读取的一行文字
        String line;
        // 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) {
            System.out.println(line);
        }
        // 释放资源
        br.close();
    }

    @Test
    public void testNewLine()throws IOException{
        // 创建流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
        // 写出数据
        bw.write("尚");
        // 写出换行
        bw.newLine();
        bw.write("硅");
        bw.newLine();
        bw.write("谷");
        bw.newLine();
        // 释放资源
        bw.close();
    }
}

注意:

1、涉及到嵌套的多个流时,如果都显式关闭的话,需要先关闭外层的流,再关闭内层的流

2、其实在开发中,只需要关闭最外层的流即可,因为在关闭外层流时,内层的流也会被关闭

六、处理流之二:转换流

1、问题引入

情况1:

  • 使用FileReader 读取项目中的文本文件
  • 由于IDEA设置中针对项目设置了UTF-8编码,当读取Windows系统中创建的文本文件时
  • 如果Windows系统默认的是GBK编码,则读入内存中会出现乱码
public class Problem {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("E:\\File_GBK.txt");
        int data;
        while ((data = fileReader.read()) != -1) {
            System.out.print((char)data);
        }
        fileReader.close();
    }
}

输出结果:
���

引入情况2:

  • 针对文本文件,现在使用一个字节流进行数据的读入,希望将数据显示在控制台上
  • 此时针对包含中文的文本数据,可能会出现乱码

2、转换流的理解

作用:转换流是字节与字符间的桥梁!

在这里插入图片描述

具体来说:

在这里插入图片描述

3、InputStreamReader

  • 转换流java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁
  • 它读取字节,并使用指定的字符集将其解码为字符
  • 它的字符集可以由名称指定,也可以接受平台的默认字符集
  • 构造器
    • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流
    • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流

举例:

public class InputStreamReaderDemo {
    public static void main(String[] args) throws IOException {
        // 定义文件路径,文件为gbk编码
        String fileName = "E:\\file_gbk.txt";
        //方式1:
        // 创建流对象,默认UTF8编码
        InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName));
        // 定义变量,保存字符
        int charData;
        // 使用默认编码字符流读取,乱码
        while ((charData = isr1.read()) != -1) {
            System.out.print((char)charData); // ��Һ�
        }
        isr1.close();
		
        //方式2:
        // 创建流对象,指定GBK编码
        InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName) , "GBK");
        // 使用指定编码字符流读取,正常解析
        while ((charData = isr2.read()) != -1) {
            System.out.print((char)charData);// 大家好
        }
        isr2.close();
    }
}

4、OutputStreamWriter

  • 转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁
  • 使用指定的字符集将字符编码为字节
  • 它的字符集可以由名称指定,也可以接受平台的默认字符集
  • 构造器
    • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流
    • OutputStreamWriter(OutputStream in,String charsetName): 创建一个指定字符集的字符流

举例:

public class OutputStreamWriterDemo {
    public static void main(String[] args) throws IOException {
        // 定义文件路径
        String FileName = "E:\\out_utf8.txt";
        // 创建流对象,默认UTF8编码
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
        // 写出数据
        osw.write("你好"); // 保存为6个字节
        osw.close();

        // 定义文件路径
        String FileName2 = "E:\\out_gbk.txt";
        // 创建流对象,指定GBK编码
        OutputStreamWriter osw2 = new OutputStreamWriter(new                     
                                                FileOutputStream(FileName2),"GBK");
        // 写出数据
        osw2.write("你好");// 保存为4个字节
        osw2.close();
    }
}

七、处理流之三/四:数据流、对象流

1、数据流与对象流说明

如果需要将内存中定义的变量(包括基本数据类型或引用数据类型)保存在文件中,那怎么办呢?

int age = 300;
char gender = '男';
int energy = 5000;
double price = 75.5;
boolean relive = true;

String name = "巫师";
Student stu = new Student("张三",23,89);

Java提供了数据流和对象流来处理这些类型的数据:

  • 数据流:DataOutputStream、DataInputStream
    • DataOutputStream:允许应用程序将基本数据类型、String类型的变量写入输出流中
    • DataInputStream:允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型、String类型的变量
  • 数据流DataInputStream中的方法
byte readByte()                short readShort()
int readInt()                  long readLong()
float readFloat()              double readDouble()
char readChar()				 boolean readBoolean()					
String readUTF()               void readFully(byte[] b)
  • 对象流DataOutputStream中的方法:将上述的方法的read改为相应的write即可
  • 数据流的弊端:只支持Java基本数据类型和字符串的读写,而不支持其它Java对象的类型
  • 对象流:ObjectOutputStream、ObjectInputStream
    • ObjectOutputStream:将 Java 基本数据类型和对象写入字节输出流中。通过在流中使用文件可以实现Java各种基本数据类型的数据以及对象的持久存储
    • ObjectInputStream:ObjectInputStream 对以前使用 ObjectOutputStream 写出的基本数据类型的数据和对象进行读入操作,保存在内存中
    • 所以重点介绍对象流ObjectOutputStream和ObjectInputStream

2、对象流API

  • ObjectOutputStream中的构造器:
    • public ObjectOutputStream(OutputStream out) : 创建一个指定的ObjectOutputStream
  • ObjectOutputStream中的方法:
    • public void writeBoolean(boolean val):写出一个 boolean 值
    • public void writeByte(int val):写出一个8位字节
    • public void writeShort(int val):写出一个16位的 short 值
    • public void writeChar(int val):写出一个16位的 char 值
    • public void writeInt(int val):写出一个32位的 int 值
    • public void writeLong(long val):写出一个64位的 long 值
    • public void writeFloat(float val):写出一个32位的 float 值
    • public void writeDouble(double val):写出一个64位的 double 值
    • public void writeUTF(String str)
      • 将表示长度信息的两个字节写入输出流,后跟字符串 s 中每个字符的 UTF-8 修改版表示形式
      • 根据字符的值,将字符串 s 中每个字符转换成一个字节、两个字节或三个字节的字节组
    • public void writeObject(Object obj):写出一个obj对象
    • public void close() :关闭此输出流并释放与此流相关联的任何系统资源
  • ObjectInputStream中的构造器:
    • public ObjectInputStream(InputStream in) : 创建一个指定的ObjectInputStream
  • ObjectInputStream中的方法: 与输出流一样,将write替换成read即可

举例:

public class ReadWriteDataOfAnyType {
    @Test
    public void save() throws IOException {
        String name = "巫师";
        int age = 300;
        char gender = '男';
        int energy = 5000;
        double price = 75.5;
        boolean relive = true;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("game.dat"));
        oos.writeUTF(name);
        oos.writeInt(age);
        oos.writeChar(gender);
        oos.writeInt(energy);
        oos.writeDouble(price);
        oos.writeBoolean(relive);
        oos.close();
    }
    @Test
    public void reload()throws IOException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("game.dat"));
        String name = ois.readUTF();
        int age = ois.readInt();
        char gender = ois.readChar();
        int energy = ois.readInt();
        double price = ois.readDouble();
        boolean relive = ois.readBoolean();

        System.out.println(name+"," + age + "," + gender + "," + energy + "," + price + "," + relive);

        ois.close();
    }
}

3、认识对象序列化机制

1、何为对象序列化机制?

  • 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流
    • 从而允许把这种二进制流持久地保存在磁盘上
    • 或通过网络将这种二进制流传输到另一个网络节点
  • 当其它程序获取了这种二进制流,就可以恢复成原来的Java对象

在这里插入图片描述

  • 序列化过程:
    • 用一个字节序列可以表示一个对象,该字节序列包含该对象的类型对象中存储的属性等信息
    • 字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息
  • 反序列化过程:
    • 该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
    • 对象的数据对象的类型对象中存储的数据信息,都可以用来在内存中创建对象
  • 实现原理
    • 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制
      • public final void writeObject (Object obj) : 将指定的对象写出
    • 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制
      • public final Object readObject () : 读取一个对象

在这里插入图片描述

4、如何实现序列化机制

  • 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化
  • 为了让某个类是可序列化的,该类必须实现java.io.Serializable 接口
  • Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
  • 如果对象的某个属性也是引用数据类型,那么如果该属性也要序列化的话,也要实现Serializable 接口
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰
  • 静态(static)变量的值不会序列化。因为静态变量的值不属于某个对象

举例:

@Data
public class Employee implements Serializable {
    //static final long serialVersionUID = 23234234234L;
    public static String company; //static修饰的类变量,不会被序列化
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
}
public class ReadWriteObject {
    @Test
    public void save() throws IOException {
        Employee.setCompany("尚硅谷");
        Employee e = new Employee("小谷姐姐", "宏福苑", 23);
        // 创建序列化流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("employee.dat"));
        // 写出对象
        oos.writeObject(e);
        // 释放资源
        oos.close();
        System.out.println("Serialized data is saved"); // 姓名,地址被序列化,年龄没有被序列化。
    }

    @Test
    public void reload() throws IOException, ClassNotFoundException {
        // 创建反序列化流
        FileInputStream fis = new FileInputStream("employee.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 读取一个对象
        Employee e = (Employee) ois.readObject();
        // 释放资源
        ois.close();
        fis.close();

        System.out.println(e);
    }
}

文本文件:

在这里插入图片描述

idea转为字符的文件内容:

在这里插入图片描述

5、反序列化失败问题

问题1:

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常

问题2:

当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException异常

  • 该类的序列版本号与从流中读取的类描述符的版本号不匹配
  • 该类包含未知数据类型

解决办法:

  • Serializable 接口给需要序列化的类,提供了一个序列版本号:serialVersionUID
  • 凡是实现 Serializable接口的类都应该有一个表示序列化版本标识符的静态变量:
static final long serialVersionUID = 234242343243L; //它的值由程序员随意指定即可
  • serialVersionUID用来表明类的不同版本间的兼容性
    • 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的
    • 在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较
      • 如果相同就认为是一致的,可以进行反序列化
      • 否则就会出现序列化版本不一致的异常(InvalidCastException)
  • 如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自动生成
    • 若类的实例变量做了修改,serialVersionUID 可能发生变化。因此,建议显式声明
  • 如果声明了serialVersionUID,即使在序列化完成之后修改了类导致类重新编译,则原来的数据也能正常反序列化,只是新增的字段值是默认值而已
public class Employee implements Serializable {
    private static final long serialVersionUID = 1324234L; //增加serialVersionUID
    
    //其它结构:略
}

6、序列化和反序列化源码

序列化对象写出

  • 判断是否实现序列化接口Serializable,没有则抛异常
//  你会看到通过对象的Class创建了ObjectStreamClass
// ObjectStreamClass会保存类相关的信息,包括获取serialVersionUID
// 之后也会调用ObjectStreamClass的write方法进行写入
ObjectStreamClass desc = ObjectStreamClass.lookup(cl, true);
...
// 此处代码判断对象的类型是否可以被序列化
// Class
if (obj instanceof Class) {
         writeClass((Class) obj, unshared);
} 
// ObjectStreamClass
else if (obj instanceof ObjectStreamClass) {
         writeClassDesc((ObjectStreamClass) obj, unshared);
} 
// String
else if (obj instanceof String) {
         writeString((String) obj, unshared);
 }
// 数组
 else if (cl.isArray()) {
         writeArray(obj, desc, unshared);
 }
// 枚举
 else if (obj instanceof Enum) {
         writeEnum((Enum<?>) obj, desc, unshared);
} 
// Serializable实现序列化接口
else if (obj instanceof Serializable) {
         writeOrdinaryObject(obj, desc, unshared);
}
// 其他情况都会抛出异常
 else {
         if (extendedDebugInfo) {
                 throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
         }
}

  • 写出操作,序列化版本ID也会写入伴随着类名称属性等
// 写入ObjectOutputStream,此处用到了serialVersionUID
void writeNonProxy(ObjectOutputStream out) throws IOException {
	// 写入类的名称
    out.writeUTF(name);
    // 写入SerialVersionUID
    out.writeLong(getSerialVersionUID());
    // 写入其他标记,这里就直接省略了
	...
	// 开始写入属性
    out.writeShort(fields.length);
    for (int i = 0; i < fields.length; i++) {
        ObjectStreamField f = fields[i];
        out.writeByte(f.getTypeCode());
        out.writeUTF(f.getName());
        if (!f.isPrimitive()) {
            out.writeTypeString(f.getTypeString());
        }
    }
}

反序列化对象读取

private Object readOrdinaryObject(boolean unshared)
        throws IOException
{
    if (bin.readByte() != TC_OBJECT) {
        throw new InternalError();
    }
// 读取序列化中的ObjectStreamClass
    ObjectStreamClass desc = readClassDesc(false);
    ...
// 通过反射,创建对象
    Object obj;
    try {
        obj = desc.isInstantiable() ? desc.newInstance() : null;
    } catch (Exception ex) {
        ...
    }
// 判断是否实现了Externalizable接口
    if (desc.isExternalizable()) {
        readExternalData((Externalizable) obj, desc);
    } 
// 我们并没有实现Externalizable接口,会进入到else判断
else {
// 读取序列化的数据
        readSerialData(obj, desc);
    }

// 判断是否自定义了readObject方法,如果有会调用,如果没有直接返回obj
// 此处省略
   ...
    return obj;
}
  • 用现有的Class和读取到的Class进行对比,例如判断serialVersionUID是否一致,否则会抛出InvalidClassException异常
if (model.serializable == osc.serializable && !cl.isArray() && suid != osc.getSerialVersionUID()) {
       throw new InvalidClassException(osc.name,
                 "local class incompatible: " +
                  "stream classdesc serialVersionUID = " + suid +
                  ", local class serialVersionUID = " +
                   osc.getSerialVersionUID());
}
  • 对属性进行赋值操作
private void defaultReadFields(Object obj, ObjectStreamClass desc)
        throws IOException
{	
	// 异常检查等操作
	...
// 开始遍历类的属性列表
    ObjectStreamField[] fields = desc.getFields(false);
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    for (int i = 0; i < objVals.length; i++) {
        ObjectStreamField f = fields[numPrimFields + i];
        // 注意此处会回到最开始的反序列化位置,完成属性值的反序列化
        objVals[i] = readObject0(f.isUnshared());
        if (f.getField() != null) {
            handles.markDependency(objHandle, passHandle);
        }
    }
    // 通过反射进行赋值
    if (obj != null) {
        desc.setObjFieldValues(obj, objVals);
    }
    passHandle = objHandle;
}

八、其他流的使用

1、标准输入、输出流

  • System.in和System.out分别代表了系统标准的输入和输出设备
  • 默认输入设备是:键盘,输出设备是:显示器
  • System.in的类型是InputStream
  • System.out的类型是PrintStream,其是OutputStream的子类FilterOutputStream 的子类
  • 重定向:通过System类的setIn,setOut方法对默认设备进行改变
    • public static void setIn(InputStream in)
    • public static void setOut(PrintStream out)

举例:

  • 从键盘输入字符串,要求将读取到的整行字符串转成大写输出
  • 然后继续进行输入操作,直至当输入“e”或者“exit”时,退出程序
public class OtherStreamTest {
    public static void main(String[] args) {
        System.out.println("请输入信息(退出输入e或exit):");
// 把"标准"输入流(键盘输入)这个字节流包装成字符流,再包装成缓冲流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String s = null;
        try {
            while ((s = br.readLine()) != null) { // 读取用户输入的一行数据 --> 阻塞程序
                if ("e".equalsIgnoreCase(s) || "exit".equalsIgnoreCase(s)) {
                    System.out.println("安全退出!!");
                    break;
                }
                // 将读取到的整行字符串转成大写输出
                System.out.println("-->:" + s.toUpperCase());
                System.out.println("继续输入信息");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (br != null) {
                    br.close(); // 关闭过滤流时,会自动关闭它包装的底层节点流
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 }

输出结果:

请输入信息(退出输入e或exit):
abc
-->:ABC
继续输入信息
1aA
-->:1AA
继续输入信息
exit
安全退出!!

进程已结束,退出代码0

拓展:

  • System类中有三个常量对象:System.out、System.in、System.err
  • 查看System类中这三个常量对象的声明:
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;

奇怪的是:

  • 这三个常量对象有final声明,但是却初始化为null。final声明的常量一旦赋值就不能修改,那么null不会空指针异常吗?
  • 这三个常量对象为什么要小写?final声明的常量按照命名规范不是应该大写吗?
  • 这三个常量的对象有set方法?final声明的常量不是不能修改值吗?set方法是如何修改它们的值的?

final声明的常量,表示在Java的语法体系中它们的值是不能修改的,而这三个常量对象的值是由C/C++等系统函数进行初始化和修改值的,所以它们故意没有用大写,也有set方法

2、Scanner类

  • 构造方法
    • Scanner(File source) :构造一个新的 Scanner,它生成的值是从指定文件扫描的
    • Scanner(File source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定文件扫描的
    • Scanner(InputStream source) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的
    • Scanner(InputStream source, String charsetName) :构造一个新的 Scanner,它生成的值是从指定的输入流扫描的
  • 常用方法
    • boolean hasNextXxx(): 如果通过使用nextXxx()方法,此扫描器输入信息中的下一个标记可以解释为默认基数中的一个 Xxx 值,则返回 true
    • Xxx nextXxx(): 将输入信息的下一个标记扫描为一个Xxx

举例:

public class TestScanner {
    @Test
    public void test01() throws IOException {
        Scanner input = new Scanner(System.in);
        PrintStream ps = new PrintStream("1.txt");
        while(true){
            System.out.print("请输入一个单词:");
            String str = input.nextLine();
            if("stop".equals(str)){
                break;
            }
            ps.println(str);
        }
        input.close();
        ps.close();
    }
    
    @Test
    public void test2() throws IOException {
        Scanner input = new Scanner(new FileInputStream("1.txt"));
        while(input.hasNextLine()){
            String str = input.nextLine();
            System.out.println(str);
        }
        input.close();
    }
}

九、apache-common包的使用

  • IO技术开发中,代码量很大,而且代码的重复率较高,为此Apache软件基金会,开发了IO技术的工具类commonsIO,大大简化了IO开发
  • Apahce软件基金会属于第三方,(Oracle公司第一方,我们自己第二方,其他都是第三方)我们要使用第三方开发好的工具,需要添加jar包
  • 在导入commons-io-2.5.jar包之后,内部的API都可以使用
  • IOUtils类的使用
- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。

举例:

public class Test01 {
    public static void main(String[] args)throws Exception {
        //- 静态方法:IOUtils.copy(InputStream in,OutputStream out)传递字节流,实现文件复制。
        IOUtils.copy(new FileInputStream("E:\\Idea\\io\\1.jpg"),new FileOutputStream("E:\\Idea\\io\\file\\柳岩.jpg"));
        //- 静态方法:IOUtils.closeQuietly(任意流对象)悄悄的释放资源,自动处理close()方法抛出的异常。
       /* FileWriter fw = null;
        try {
            fw = new FileWriter("day21\\io\\writer.txt");
            fw.write("hahah");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
           IOUtils.closeQuietly(fw);
        }*/
    }
}
  • FileUtils类的使用
- 静态方法:void copyDirectoryToDirectory(File src,File dest):整个目录的复制,自动进行递归遍历
          参数:
          src:要复制的文件夹路径
          dest:要将文件夹粘贴到哪里去
             
- 静态方法:void writeStringToFile(File file,String content):将内容content写入到file中
- 静态方法:String readFileToString(File file):读取文件内容,并返回一个String
- 静态方法:void copyFile(File srcFile,File destFile):文件复制

举例:

public class Test02 {
    public static void main(String[] args) {
        try {
            //- 静态方法:void copyDirectoryToDirectory(File src,File dest);
            FileUtils.copyDirectoryToDirectory(new File("E:\\Idea\\io\\aa"),new File("E:\\Idea\\io\\file"));


            //- 静态方法:writeStringToFile(File file,String str)
            FileUtils.writeStringToFile(new File("day21\\io\\commons.txt"),"柳岩你好");

            //- 静态方法:String readFileToString(File file)
            String s = FileUtils.readFileToString(new File("day21\\io\\commons.txt"));
            System.out.println(s);
            //- 静态方法:void copyFile(File srcFile,File destFile)
            FileUtils.copyFile(new File("io\\yangm.png"),new File("io\\yangm2.png"));
            System.out.println("复制成功");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/511336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL的内,外,自连接复习

目录 1.找出每个员工的薪资等级&#xff0c;要求显示员工名&#xff0c;薪资&#xff0c;薪资等级 2.查询员工的上级领导&#xff0c;要求显示员工名和对应的领导名 外连接的引入 五月 1.找出每个员工的薪资等级&#xff0c;要求显示员工名&#xff0c;薪资&#xff0c;薪资等…

【笔试强训选择题】Day10.习题(错题)解析

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;笔试强训选择题 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 文章目录…

Vue电商项目--开发ListContainer模块

swiper基本使用 上节&#xff0c;我们使用了mock把数据成功的存储到了banner组件当中。现在先复习一下swiper这个轮播图插件的使用 Swiper中文网-轮播图幻灯片js插件,H5页面前端开发 下载swiper 首先我们需要css和js。然后把这俩个捞走 看说明书&#xff0c;引入js和css 这里…

深度学习笔记之卷积神经网络(三)卷积示例与池化操作

深度学习笔记之卷积神经网络——卷积示例与池化操作 引言卷积神经网络&#xff1a;卷积层卷积层的计算过程 池化层描述池化层的作用——降低模型复杂度&#xff0c;防止过拟合池化层执行过程池化层代码示例 池化层的作用——平移不变性卷积加池化作为一种无限强的先验池化层的反…

在vs2019中调试qt5.9.3为例

vs2019中其实可以调试qt&#xff0c;此环境配置qt5.9.3和vs2019&#xff0c;当前配置&#xff0c;作为一个记录&#xff0c;也方便大家查看。 vs配置qt环境 首先需要配置好qt在vs2019&#xff0c;可以打开网址https://download.qt.io/archive/vsaddin/2.8.1/&#xff0c; 我准…

【C++】继承和多态、public、private、protected、重写

区分继承与多态、辨别public、protected、private 继承与多态的概念继承与多态的区别与联系区别&#xff1a;联系&#xff1a;示例结果&#xff1a; 继承和访问的权限说明示例&#xff1a;结果 结论 继承与多态的概念 面向对象三大原则&#xff1a;封装、继承、多态。继承是一种…

探索Vue的组件世界-自定义指令

目录 自定义指令 钩子函数参数 使用 什么时候用 在Vue体系下创建一个自定义指令 使用自定义指令及示例要求 全局注册一个自定义指令 自定义指令 全局创建vue自定义指令 Vue.directive("demo", {// 只调用一次&#xff0c;指令第一次绑定到元素时调用。// 在…

MySQL常用SQL

目录 库操作 查询数据库 创建数据库 删除数据库 选择数据库 表操作 查看表 创建表 查看表结构 查看建表sql 删除表 &#xff08;整表删除&#xff09; CRUD操作 insert增加 update修改 delete删除 select查询 去重distinct 空值查询 union合并查询 带in子查询 …

浏览器从输入URL到页面渲染加载的过程(浏览器知识体系整理)

文章目录 前言一、梳理主干流程二、浏览器接收url并开启一个新进程1. 浏览器是多进程的2. 浏览器内核是多线程的3. JS引擎单线程的原因4. GUI渲染线程与JS引擎线程互斥 二、解析URL三、DNS域名解析1. DNS是什么&#xff1f;2. IP和域名的关系3. 域名服务器概念图4. DNS域名解析…

使用 OpenCV 进行基于 ESP32 CAM 的目标检测和识别

概述:基于 ESP32 CAM 的目标检测和识别 本教程介绍了使用OpenCV基于 ESP32 CAM的目标检测和识别主题。OpenCV 是一个开源的图像处理库,不仅在工业界而且在研发领域都得到了非常广泛的应用。 这里对于对象检测,我们使用了cvlib 库。该库使用 COCO 数据集上的预训练 AI 模型…

Flume系列:Flume Sink使用

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;HDFS Sink HDFS小文件的处理 HDFS存入大量小文件的影响&#xff1a; HDFS小文件处理&#xff1a; 2&#xff1a;logger Sink 3&#xff1a;写入Kafka - 可以使用kafka channel代替 Apache Hadoop生态-目录汇总-持…

力扣sql中等篇练习(十八)

力扣sql中等篇练习(十八) 1 银行账户概要 1.1 题目内容 1.1.1 基本题目信息1 1.1.2 基本题目信息2 1.1.3 示例输入输出 1.2 示例sql语句 # Write your MySQL query statement below SELECT u.user_id,u.user_name,u.creditIFNULL(t1.c1,0) credit,case when u.creditIFNULL…

分享一个无需账号完全免费的 ChatGPT-4 的平台

AIGC从入门到精通教程 1. 访问 SteamShip2. 进入创建页面,选择新建一个 GPT-4 实例。3. 完成创建后,您便可以尽情体验 GPT-4本教程收集于: AIGC从入门到精通教程 大家都知道,ChatGPT4.0是要订阅Plus(一个月20美元)后才能体验的。 今天就给大家弄一个白嫖ChatGPT4.0的教程…

DEJA_VU3D - Cesium功能集 之 107-卫星探测效果

前言 编写这个专栏主要目的是对工作之中基于Cesium实现过的功能进行整合,有自己琢磨实现的,也有参考其他大神后整理实现的,初步算了算现在有差不多实现小140个左右的功能,后续也会不断的追加,所以暂时打算一周2-3更的样子来更新本专栏(每篇博文都会奉上完整demo的源代码,…

c++STL之常用的算法

目录 常用的遍历算法 for_each() transform() for_each()和transform&#xff08;&#xff09;算法比较 常用的查找算法 adjacent_find() binary_search count() count_if() find() 常用的排序算法 merge() sort() random_shuffle() reverse() 常用的拷…

基于`IRIS`,动态解析`HL7`消息

文章目录 基于IRIS&#xff0c;动态解析HL7消息什么是HL7HL7 版本HL7 消息结构段&#xff08;Segment&#xff09;字段&#xff08;Field&#xff09; HL7 数据类型在IRIS中查看HL7数据结构传统方式拼写HL7消息结构动态对象解析HL7消息结构。 基于IRIS&#xff0c;动态解析HL7消…

SpringCloud:微服务保护之隔离和降级

1.FeignClient整合Sentinel SpringCloud中&#xff0c;微服务调用都是通过Feign来实现的&#xff0c;因此做客户端保护必须整合Feign和Sentinel。 1.1.修改配置&#xff0c;开启sentinel功能 修改OrderService的application.yml文件&#xff0c;开启Feign的Sentinel功能&…

详细版易学版TypeScript - 类

一、类 - 类的属性和方法 class MyPreson {// 类的属性// 属性需要在类里先定义并确定类型&#xff0c;才可以在constructor里面用this访问name: stringage: numberconstructor(name: string, age: number) {this.name name;this.age age;}// 类的方法sendStr(str: string) {…

工业4.0时代来临,POWERLINK协议在千兆网卡下的性能

“工业 4.0”的高歌猛进&#xff0c; “智能制造”&#xff0c;“智慧工厂”的呼声越来越响亮。这些需求使得数据传输量越来越大&#xff0c;实时性越来越高&#xff0c;因此我们将 POWERLINK 从 100Mbps 升级到1000Mbps。测试下POWERLINK这种工业总线协议的性能&#xff0c;最…

jest基础指示

describ&#xff08;类似java中的class&#xff0c;在这里定义的变量可以在所有it中使用&#xff09; 针对某一方面的测试&#xff0c;一个描述性的东西&#xff0c;针对某一个方面的测试&#xff0c;或者说是一个作用域 一组测试用例的集合。 有两个参数&#xff0c;参数1 &…