十七、IO流

1 File 类

1.1 File 类概述与构造方法

File :Java.io.File 文件和目录名的抽象表示

Java.io 包的 File 类是非流类,是文件和目录路径的抽象表

File 不是表示一个真正的文件,而是表示文件或目录的路径

创建 File 对象:

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

实例:

import java.io.File;

public class FileDemo {
    public static void main(String[] args) {
        //创建File 对象
        File file1 = new File("E:\\IO\\Hello.java");
        System.out.println(file1);
        File file2 = new File("E:\\IO","Hello.java");
        System.out.println(file2);
        File file3 = new File("E:\\","IO\\Hello.java");
        System.out.println(file3);
        File p = new File("E:\\IO");
        File file4 = new File(p,"Hello.java");
        System.out.println(file4);
    }
}

运行结果:

E:\IO\Hello.java
E:\IO\Hello.java
E:\IO\Hello.java
E:\IO\Hello.java

1.2 路径分隔符

表示的是目录层级之间的分割符号

Windows 的默认分隔符为:"\" , 也可使用"/"

Unix 系统的默认分隔符为: “/”

Java 是跨平台的,为了解决不同平台路径分隔符的差异,有如下方法:

static Stringseparator 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串。

实例:

    File file5 = new File("E:" + File.separator + "IO" + File.separator + "Hello.java");
    System.out.println(file5);
E:\IO\Hello.java

1.3 File 类判断和获取

判断

返回值类型方法
booleanexists() 判断File表示的文件或目录是否存在
booleanisDirectory() 判断此File表示的是否是一个目录。
booleanisFile() 判断此File表示的是否是一个文件。
booleancanExecute() 判断此路径表示的 文件是否是可执行文件
booleancanRead() 判断此File表示的文件是否可读。
booleancanWrite() 判断次File表示的文件是否可写
booleanisHidden() 判断此File表示的是否是一个隐藏文件。

获取

返回值类型方法
FilegetAbsoluteFile() 返回此抽象路径名的绝对形式。
StringgetAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
StringgetName() 返回由此抽象路径名表示的文件或目录的名称。
StringgetParent() 返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。
FilegetParentFile() 返回此抽象路径名的父,或抽象路径名 null如果此路径名没有指定父目录。
StringgetPath() 将此抽象路径名转换为路径名字符串。
File[]listFiles() 返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件。
File[]listFiles(FileFilter filter) 返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。
File[]listFiles(FilenameFilter filter) 返回一个抽象路径名数组,表示由此抽象路径名表示的满足指定过滤器的目录中的文件和目录。

重命名

返回值类型方法
booleanrenameTo(File dest) 重命名由此抽象路径名表示的文件。

实例:

  1. 获取
import java.io.File;

public class GetWayFile {
    public static void main(String[] args) {
        File file1 = new File("E:\\IO\\test\\test1\\test2");
        if(!file1.exists()){
            file1.mkdirs();  //创建路径
        }
        System.out.println(file1.getAbsoluteFile());  //绝对形式
        System.out.println(file1.getAbsolutePath());  //绝对路径名字字符串
        System.out.println(file1.getName());  //返回由此抽象名表示的文件或目录的名称
        System.out.println(file1.getParent());  //返回父目录
        String s = file1.getPath();  //抽象路径名转换为路径字符串
        System.out.println(s);
        File[] str = file1.listFiles();  //返回抽象路径名数组
        System.out.println(str);
    }
}

在这里插入图片描述
绝对路径和相对路径的区别:

  • 绝对路径:完整的路径名,不需要其他任何信息就可以定位它所表示的文件,例如E:\IO\java.txt
  • 相对路径:必须使用取自其他路径名的信息进行解释,就是相对于自己的目标文件位置,例如 myFIle\java.txt
  1. 重命名
public class ReNameDemo {
    public static void main(String[] args) {
        File file = new File("E:\\IO\\test");
        File[] files = file.listFiles();  //返回抽象路径名数组
        //遍历数组
        for(File file1 : files){
            String srcName = file1.getName();
            //subString 返回一个字符串,该字符串是此字符串的子字符串。
            //lastIndexOf(String str) 返回指定子字符串最后一次出现的字符串中的索引。
            String newName = srcName.substring(srcName.lastIndexOf("241") + 3);
            File fil = new File(file,newName);
            file1.renameTo(fil);  //重命名
        }
    }
}

运行前:
在这里插入图片描述

运行后:
在这里插入图片描述

当然,我们也能再加上前缀:

public class ReNameDemo {
    public static void main(String[] args) {
        File file = new File("E:\\IO\\test");
        File[] files = file.listFiles();  //返回抽象路径名数组
        /*//遍历数组
        for(File file1 : files){
            String srcName = file1.getName();
            //subString 返回一个字符串,该字符串是此字符串的子字符串。
            //lastIndexOf(String str) 返回指定子字符串最后一次出现的字符串中的索引。
            String newName = srcName.substring(srcName.lastIndexOf("241") + 3);
            File fil = new File(file,newName);
            file1.renameTo(fil);  //重命名
        }*/
        for(File file1 : files){
            /*
            思想:
                1.将File 类型的转换为String类型
                2.对String类型的数据进行更改
                3.将String添加到File中,File fis = new File(file,newName)
             */
            String oldName = file1.getName();
            String newName = "17080241" + oldName;
            File fis = new File(file,newName);
            file1.renameTo(fis);
        }
    }
}

在这里插入图片描述

1.4 File 类创建与删除

返回值类型方法
booleancreateNewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件。
booleanmkdir() 创建由此抽象路径名命名的目录。
booleanmkdirs() 创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
booleandelete() 删除由此抽象路径名表示的文件或目录。 删除操作不经过回收站
static FilecreateTempFile(String prefix, String suffix) 在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称。
static FilecreateTempFile(String prefix, String suffix, File directory) 在指定的目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
voiddeleteOnExit() 请求在虚拟机终止时删除由此抽象路径名表示的文件或目录。

以下实现之前,本地只有 E:\IO

实现:

  1. 创建一级目录
public class FileDemo_1 {
    public static void main(String[] args) {
        // 创建一级目录,之前本地目录只有 E:\\
        File file1 = new File("E:\\IO\\test");
        if(!file1.exists()){  //判断目录是否存在,不存在则创建
            file1.mkdir();
        }
        System.out.println(file1);
    }
}
  1. 创建多级目录
public class FileDemo_1 {
    public static void main(String[] args) {
        // 创建多级目录
        File file1 = new File("E:\\IO\\test\\test1\\test2");
        if(!file1.exists()){  //判断目录是否存在,不存在则创建
            file1.mkdirs();
        }
        System.out.println(file1);
    }
}

在这里插入图片描述

  1. 创建文件
import java.io.File;
import java.io.IOException;

public class FileDemo_1 {
    public static void main(String[] args) throws IOException {
        // 创建新文件
        File file1 = new File("E:\\IO\\test\\test1\\test2");
        File file2 = new File(file1,"Hello.java");
        if(!file2.exists())
            System.out.println("创建文件成功");
            file2.createNewFile();  //创建空的新文件
    }
}

在这里插入图片描述

  1. 删除文件
public class FileDemo_1 {
    public static void main(String[] args) {
        // 删除文件
        File file1 = new File("E:\\IO\\test\\test1\\test2");
        File file2 = new File(file1,"Hello.java");
        if(file2.isFile()){  //当文件存在且为普通文件时
            System.out.println("删除文件");
            file2.delete();
        }
    }
}
  1. 删除多级目录及其中的文件
import java.io.File;

public class FileDemo_1 {
    public static void main(String[] args) {
        // 删除多级目录
        File file1 = new File("E:\\IO");
        deleteDir(file1);  //删除file1 表示的目录下的所有文件及其目录
    }

    public static void deleteDir(File srcDir){
        File[] fileArr = srcDir.listFiles();  //返回一个路径名数组
        if(fileArr != null){  //如果路径不为空
            for(File file : fileArr){  //遍历数组
                if(file.isDirectory()){  //判断file表示的对象是否是目录
                    deleteDir(file);  //如果是,递归调用
                }else{
                    file.delete();
                }
                file.delete();
            }
        }
    }
}

运行前:
在这里插入图片描述
运行后:
在这里插入图片描述

1.5 文件属性

Java 使用 File 类表示文件或者目录,可以通过 File 类获取文件或者目录的相关属性。

static booleanexists() 测试此抽象路径名表示的文件或目录是否存在。
StringgetAbsolutePath() 返回此抽象路径名的绝对路径名字符串。
StringgetName() 返回由此抽象路径名表示的文件或目录的名称。
StringgetParent() 返回此抽象路径名的父 null的路径名字符串,如果此路径名未命名为父目录,则返回null。
String[]list() 返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录。
static booleanisDirectory(Path path, LinkOption… options) 测试文件是否是目录。
static booleanisHidden(Path path) 告知文件是否被 隐藏 。

实现:

import java.io.File;
import java.util.Arrays;

public class FileInfo {
    public static void main(String[] args) {
        File file = new File("/home/project");
        System.out.println("文件或目录名:" + file.getName());
        System.out.println("绝对路径:" + file.getAbsolutePath());
        System.out.println("父路径:" + file.getParent());
        System.out.println("文件路径:" + file.getPath());
        //判读文件是否是目录
        if(file.isDirectory()){
            //打印目录中文件
            Arrays.stream(file.list()).forEach(System.out::println);
        }
        System.out.println("是否隐藏:" + file.isHidden());
        System.out.println("是否存在:" + file.exists());
    }
}

2 IO 流

在这里插入图片描述
图中蓝色的部分均为抽象类,而绿色的部分则为派生类,是可以直接使用的。

在这里插入图片描述

2.1 IO 流概述

IO :输入/输出(Input/Output)

流:是一种抽象概念,数据在设备间的一种传输形式,流的本质是数据传输

常见操作:文件复制、文件上传、文件下载

在这里插入图片描述

IO 流中的输入输出是相对于程序而言的;将外部存储设备中的文件读入到程序中,则为输入;将程序中的数据输入到外部存储设备称为输出

IO 流的分类:

  • 按照数据的流向:输入流(读) 输出流(写)
  • 按照数据的单位:字节流(bit) 字符流(char)

IO 流的基本:

  • 字节输入流,字节输出流,字符输入流,字符输出流

字节流和字符流的使用场景:

  • 字符流:纯文本文件(.txt)
  • 字节流:图片、视频、音频、图文混合

2.2 字节流读写

  • abstract class InputStream 是所有字节输入流的基类
  • abstract class OutputStream 是所有字节输出流的基类
返回值类型方法说明
voidclose() 关闭此输出流并释放与此流相关联的任何系统资源。关闭
voidflush() 刷新此输出流并强制任何缓冲的输出字节被写出。强制刷新
voidwrite(byte[] b) 将 b.length字节从指定的字节数组写入此输出流。写字节数组
voidwrite(byte[] b, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流。写字节数组的一部分
abstract voidwrite(int b) 将指定的字节写入此输出流。写一个整数

所有的字节输入输出流的子类都是以父类的名称作为子类名称的后缀

2.3 使用字节输出流写数据(FileOutputStream)

FileOutputStream 文件输出流是用于将数据写入到输出流 File

数据写出到文件:

  1. 创建字节输出流对象
FileOutputStream(File file)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(File file, boolean append)创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(String name)创建文件输出流以指定的名称写入文件。
FileOutputStream(String name, boolean append)创建文件输出流以指定的名称写入文件。
  1. 调用输出流的相关方法,将数据写出到指定的文件

  2. 关闭流(释放资源:file.close())

实现:

  1. 使用字节输出流完成数字的输出
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class OutPutDemo {
    public static void main(String[] args) throws IOException {
        File file = new File("E:\\IO\\test\\java.txt");
        if (!file.isFile()){
            file.createNewFile();
        }
        //创建字节流输出对象,方式一
        FileOutputStream fos = new FileOutputStream(file);
        //方式二  FileOutputStream fos = new FileOutputStream("E:\\IO\\test\\java.txt");
        fos.write(101);  //向其中写入数据 void write(int b)
        fos.close();  //释放资源
    }
}
  1. 使用字节输出流完成字节数组的输出
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class OutputByteDemo {
    public static void main(String[] args) throws IOException {
        //用相对路径表示,此处的相对路径指相对当前工程
        File file = new File("work100\\Out\\Java.txt");
        if(!file.isFile()){
            file.createNewFile();
        }
        FileOutputStream fos = new FileOutputStream(file);
        //使用字节数组
        byte[] bytes = new byte[]{101,102,103,105,106};
        fos.write(bytes);
        fos.close();
    }
}
        //写字节数组的一部分
        String str = "Hello World!";
        byte[] bytes1 = str.getBytes();  //获取字符串对应的字节数组
        fos.write(bytes1,0,5);
        fos.close();

字节输出流的两个问题:

  • 使用字节输出流实现追加写
    将append的值置为true即可实现追加写
FileOutputStream(String name, boolean append)创建文件输出流以指定的名称写入文件。
FileOutputStream(File file, boolean append)创建文件输出流以写入由指定的 File对象表示的文件。

实例:

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

public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        //实现追加写
        FileOutputStream fos = new FileOutputStream("work100\\Output\\Hello.txt",true);
        String str = "Hello java ";  //写一个字符串
        byte[] bytes = str.getBytes();  //获取字符串向对应的字节数组
        fos.write(bytes);
        fos.write(bytes,0,5);  //写字节数组的一部分
        fos.close();
    }
}

运行结果:

Hello java Hello

再运行一次:

Hello java HelloHello java Hello

可以看到,第二次的输出结果没有覆盖第一次的输出结果

  • 支持换行写的功能
    换行功能:
系统换行功能实现
Windows\r\n
Linux\n
Mac\r

实例:

public class FileOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("work100\\Output\\Hello.txt",true);
        String str = "Hello java ";
        byte[] bytes = str.getBytes();
        fos.write(bytes);
        fos.write("\r\n".getBytes());  //换行符
        fos.write(bytes,0,5);  //截取数组的一段
        fos.write("\r\n".getBytes());
        fos.close();
    }
}

运行结果:

Hello java 
Hello

再运行一次:

Hello java 
Hello
Hello java 
Hello

可以看到实现了换行操作

2.4 IO 中异常的处理方式

实现:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class OutPutDemo {
    public static void main(String[] args){
        File file = new File("work100\\Output\\java.txt");
        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream(file);
            fos.write(101);  //向其中写入数据 void write(int b)
            fos.write("中北大学".getBytes(StandardCharsets.UTF_8));  //使用UTF-8 字符集获取字节数组
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(fos != null){  //当fos 不为 null 时,才需要关闭
                try {
                    fos.close();  //关闭也可能出现异常,也需要处理
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

2.5 使用字节输入流读数据(FileInputStream)

FileInputStream 从文件系统中的文件获取输入字节

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

读的方法:

返回值类型方法
intread() 从该输入流读取一个字节的数据。
intread(byte[] b) 从该输入流读取最多 b.length个字节的数据为字节数组。
intread(byte[] b, int off, int len) 从该输入流读取最多 len字节的数据为字节数组。
voidclose() 关闭此文件输入流并释放与流相关联的任何系统资源。

实现:

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

public class FileInputStreamDemo {
    public static void main(String[] args) throws IOException {
        //创建一个字节输入流
        FileInputStream fis = new FileInputStream("work100\\Output\\笔记.txt");
        //r=fis.read r=-1时,表示得到了文档末尾
        int r;
        while((r = fis.read()) != -1){  //如果每次返回的不是-1,证明文件还没有读完
            System.out.print((char)r);
        }
        fis.close();  //释放资源
    }
}

运行结果:
在这里插入图片描述

注意:
使用字节输出流写数据时,如果文件不存在,系统会自动为我们创建一个新文件
使用字节输入流读数据时,如果文件不存在,体统会报错 FileNotFoundExcepion

读取的过程:

  1. 创建文件表示要读取的文件路径
  2. 创建 FileInputStream 对象
  3. 让 FileInputStream 对象指向文件,如果文件存在,则可以使用相应的方法去读取;如果文件不存在,则会报错 FileNotFoundException

2.6 实现文件的复制

2.6.1 每次读取一个字节

实现:

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

public class FileCopy {
    public static void main(String[] args) throws IOException {
        //实现读数据
        FileInputStream fis = new FileInputStream("E:\\IO\\test\\许三观卖血记.txt");
        //实现写数据
        FileOutputStream fos = new FileOutputStream("work100\\Output\\许三观卖血记.txt");
        //使用数据流来读数据,每次读取一个字节,读完后立即使用输出流将数据写出到目标文件
        int len;
        long begin = System.currentTimeMillis();
        while ((len = fis.read()) != -1){
            fos.write(len);
        }
        long end = System.currentTimeMillis();
        System.out.println("复制文件共耗时:" + (end - begin) + "毫秒");  //复制文件共耗时:2663毫秒
        //释放资源
        fos.close();
        fis.close();
    }
}

运行结果:
在这里插入图片描述

2.6.2 每次读取一个字节数组(文档复制)

实现:

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

public class FileReadByteArray {
    public static void main(String[] args) throws IOException {
        //实现读数据
        FileInputStream fis = new FileInputStream("E:\\IO\\test\\许三观卖血记.txt");
        //实现写数据
        FileOutputStream fos = new FileOutputStream("work100\\Output\\许三观卖血记_1.txt");
        byte[] buff = new byte[1024];  //每次读取一个字节数组,数组大小根据文件大小酌情改变
        int len;
        long begin = System.currentTimeMillis();
        while((len = fis.read(buff)) != -1){  //每次读取1024个数据
            //读取一个字节数组,读了len个,就写入len个
            fos.write(buff,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println("复制文件共耗时:" + (end - begin) + "毫秒");  //复制文件共耗时:8毫秒
        fos.close();
        fis.close();
    }
}

实现:
在这里插入图片描述

2.6.3 每次读取一个字节数组(图片复制)

实现:

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

//将路径为E:\IO\test\奶牛.jpg 的图片复制到 work100\Output\奶牛.jpg
public class PictureCopy {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("E:\\IO\\test\\奶牛.jpg");
        FileOutputStream fos = new FileOutputStream("work100\\Output\\奶牛.jpg");
        byte[] buff = new byte[10];
        int len;
        long begin = System.currentTimeMillis();
        while ((len = fis.read(buff)) != -1){
            fos.write(buff,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println("复制图片共消耗:" + (end - begin) + "毫秒");  //复制图片共消耗:287毫秒
        fis.close();
        fos.close();
    }
}

运行结果:
在这里插入图片描述

3 字节缓冲流(处理流)(BufferedInputStream)

  1. BufferedInputStream 内部有一个缓冲区数组

    当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节

构造方法
BufferedInputStream(InputStream in) 创建一个 BufferedInputStream并保存其参数,输入流 in ,供以后使用。
BufferedInputStream(InputStream in, int size) 创建 BufferedInputStream具有指定缓冲区大小,并保存其参数,输入流 in ,供以后使用。
  1. BufferedOutputStream 提高输出效率
构造方法
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。

实例:

import java.io.*;

//将路径为E:\IO\test\奶牛.jpg 的图片复制到 work100\Output\奶牛.jpg
public class BufferTest {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\IO\\test\\奶牛.jpg"));
        // 默认的缓冲区大小8192
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("work100\\Output\\奶牛_1.jpg"));
        byte[] buff = new byte[10];
        int len;
        long begin = System.currentTimeMillis();
        while ((len = bis.read(buff)) != -1) {
            bos.write(buff,0,len);
        }
        long end = System.currentTimeMillis();
        System.out.println("复制图片消耗时间:" + (end - begin) + "毫秒");  //复制图片消耗时间:19毫秒
        bis.close();
        bos.close();
    }
}

之前未用字节缓冲流复制该图片的时间为:
在这里插入图片描述
使用字节缓冲流复制的结果:
在这里插入图片描述

可以看到,使用字节缓冲流大大缩短了运行时间。

使用缓冲流实现复制视频:

import java.io.*;

public class BufferedMovieCopy {
    public static void main(String[] args) throws IOException {
        long begin = System.currentTimeMillis();
        byteArrayCopy();
        long end = System.currentTimeMillis();
        System.out.println("复制视频共消耗:" + (end - begin) + "毫秒");  //复制视频共消耗:1128毫秒
        /*long begin1 = System.currentTimeMillis();
        buffStreamCopy();
        long end1 = System.currentTimeMillis();
        System.out.println("复制视频共消耗:" + (end1 - begin1) + "毫秒");  //复制视频共消耗:701毫秒*/
    }

    //每次读取一个字节数组
    public static void byteArrayCopy() throws IOException {
        FileInputStream fis = new FileInputStream("E:\\IO\\test\\Java高级-14.avi");
        FileOutputStream fos = new FileOutputStream("work100\\Output\\java_1.avi");
        byte[] buff = new byte[1024*1024];
        int len;
        while ((len = fis.read(buff)) != -1){
            fos.write(buff,0,len);
        }
        fis.close();
        fos.close();
    }

    //使用字节缓冲流类来实现
    public static void buffStreamCopy() throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\IO\\test\\Java高级-14.avi"));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("work100\\Output\\java_2.avi"));
        byte[] buff = new byte[1024*1024];
        int len;
        while ((len = bis.read(buff)) != -1){
            bos.write(buff,0,len);
        }
        bis.close();
        bos.close();
    }
}

在这里插入图片描述

可以看到使用缓冲流复制时间较短

字节流与字节缓冲流的区别:
在这里插入图片描述

4 字符流

字符流的出现主要是解决中文等特殊字符的处理

字符流的本质: 字节流 + 编码

中文的识别:在编码中,如果存储的是中文,那么中文的第一个字节肯定是负数

4.1 编码表

按照某种规则,将字符存储到计算机中,称为编码。

将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。

在编码和解码的过程中,使用的编码表必须一致,否则就有可能出现乱码。

字符编码:就是一套自然语言的字符与二进制数之间的对应规则。

字符集:

  • 是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等;
  • 计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然性至少有一套字符编码。常见的字符集有ASCII字符集、GBXXX字符集、Unicode字符集等。

常见的字符集:

  • ASCII
    • (American Standard Code for Information Interchange,美国信息交换标准代码):是基于拉丁字母的一套电脑编码系统,用于显示现代英语,主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
    • 基本的ASCII 字符集,使用7位表示一个字符,共128字符。ASCII 的扩展字符集使用使用8位表示一个字符,共256字符。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。
  • GBXXX 字符集
    • GB2312:简体中文码表
    • GBK:最常用的中文码表。是在GB2312标准基础上的扩展规范,使用了双字节编码方案,共收录了21003个汉字,完全兼容GB2312标准,同时支持繁体汉字以及日韩汉字等
    • GB18030:最新的中文码表
  • Unicode 字符集
    • 为表达任意语言的任意字符而设计,是业界的一种标准,也成为统一码、标准万国码。
    • UTF-8编码:可以用来表示Unicode标准中任意字符,它是电子邮件、网页及其他存储或传送文字的应用 中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。它使用一至四个字节为每个字符编码
    • 编码规则:
      • 128个US-ASCII字符,只需一个字节编码
      • 拉丁文等字符,需要二个字节编码
      • 大部分常用字(含中文),使用三个字节编码
      • 其他极少使用的Unicode辅助字符,使用四字节编码

4.2 字符串中的编码解码问题

编码:

返回值类型方法
byte[]getBytes() 使用平台的默认字符集将此 String编码为字节序列,将结果存储到新的字节数组中。
byte[]getBytes(String charsetName) 使用命名的字符集将此 String编码为字节序列,将结果存储到新的字节数组中。

解码:

String(byte[] bytes) 通过使用平台的默认字符集解码指定的字节数组来构造新的 String 。
String(byte[] bytes, int offset, int length) 通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String 。
String(byte[] bytes, int offset, int length, String charsetName) 构造一个新的 String通过使用指定的字符集解码指定的字节子阵列。
String(byte[] bytes, String charsetName) 构造一个新的String由指定用指定的字节的数组解码charset 。

实现:

import java.io.UnsupportedEncodingException;

public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = new String("山西太原");
        //byte[] buff = str.getBytes();  不写时,为系统设置编码
        byte[] buff = str.getBytes("UTF-8");  //将字符串转换为字符数组,编码
        for(byte b : buff){
            System.out.println(b);
        }
        //解码
        //String byteStr = new String(buff);
        String byteStr = new String(buff,"UTF-8");
        System.out.println(byteStr);
    }
}

运行结果:

-27
-79
-79
-24
-91
-65
-27
-92
-86
-27
-114
-97
山西太原

当然也可以选择解码一部分:

        //解码
        String byteStr = new String(buff,6,6,"UTF-8");
        System.out.println(byteStr);
//UTF-8 中,一个汉字是三个字节,GBK 编码中,一个饿汉字是两个字节
//起始位置是从0开始计数的,长度为6,所以输出以下结果:

太原

4.3 字符流读写数据

字符流的抽象基类:public abstract class Reader

intread() 读一个字符
intread(char[] cbuf) 将字符读入数组。
abstract intread(char[] cbuf, int off, int len) 将字符读入数组的一部分。

public abstract class Writer:

voidwrite(char[] cbuf) 写入一个字符数组。
abstract voidwrite(char[] cbuf, int off, int len) 写入字符数组的一部分。
voidwrite(int c) 写一个字符
voidwrite(String str) 写一个字符串
voidwrite(String str, int off, int len) 写一个字符串的一部分。

4.4 字符流中的实现类(InputStreamReader)

  1. OutputStreamWriter

OutputStreamWriter 是从字符流到字节流的桥梁,向其写入的字符编码使用指定的字节charset。它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集。它的实现使用的设计模式为适配器模式。

OutputStreamWriter(OutputStream out) 创建一个使用默认字符编码的OutputStreamWriter。
OutputStreamWriter(OutputStream out, Charset cs) 创建一个使用给定字符集的OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc) 创建一个使用给定字符集编码器的OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName) 创建一个使用命名字符集的OutputStreamWriter。
StringgetEncoding() 返回此流使用的字符编码的名称。
voidwrite(char[] cbuf, int off, int len) 写入字符数组的一部分。
voidwrite(int c) 写一个字符
voidwrite(String str, int off, int len) 写一个字符串的一部分。

:字符流默认带有缓冲区,如果写出的内容没有填满缓冲区,则需要 flush 或者 close 。

  1. InputStreamReader

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

InputStreamReader(InputStream in) 创建一个使用默认字符集的InputStreamReader。
InputStreamReader(InputStream in, Charset cs) 创建一个使用给定字符集的InputStreamReader。
InputStreamReader(InputStream in, CharsetDecoder dec) 创建一个使用给定字符集解码器的InputStreamReader。
InputStreamReader(InputStream in, String charsetName) 创建一个使用命名字符集的InputStreamReader。
StringgetEncoding() 返回此流使用的字符编码的名称。
intread() 读一个字符
intread(char[] cbuf, int offset, int length) 将字符读入数组的一部分。

实例:

import java.io.*;

public class StreamWriterDemo {
    public static void main(String[] args) throws IOException {
        //使用字节流存储文件
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("work100\\Output\\write.txt"),"UTF-8");
        osw.write("山西太原"+"\r\n");
        osw.write("上兰街道");
        osw.flush();
        osw.close();  //.close 就包含了.flush 的刷新功能,二者写其一即可

        //使用字节流读取文件
        InputStreamReader isr = new InputStreamReader(new FileInputStream("work100\\Output\\write.txt"),"UTF-8");
        int len;
        char[] chars = new char[1024];
        while((len = isr.read(chars)) != -1){
            System.out.println(new String(chars));
        }
        /*int ln;
        while((ln = isr.read()) != -1){
            System.out.println((char)ln);
        }*/
        isr.close();
    }
}

运行结果:

山西太原
上兰街道                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
  1. 使用字符流实现文件的复制,使用UTF-8 编码

实现:

import java.io.*;

public class CharCopyDemo {
    public static void main(String[] args) throws IOException {
        //创建一个字符输入流
        InputStreamReader isr = new InputStreamReader(new FileInputStream("work100\\Output\\write.txt"),"utf-8");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("work100\\Output\\write_Copy.txt"),"utf-8");
        int len;
        char[] chars = new char[1024];
        while ((len = isr.read(chars)) != -1){
            osw.write(chars,0,len);
        }
        osw.close();
    }
}

运行结果:
在这里插入图片描述

InputStreamReader 和 OutputStreamWriter 实现了将字节流转换为字符流的功能,因此也称为转换流

转换流的简写方式:

对于上述两个转换流的简写形式,它们的直接子类:FileReader 与 FileWriter

  • 使用转换流的简写方式完成文件的拷贝:

实现:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class FileReaderDemo {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("work100\\Output\\许三观卖血记.txt");
        FileWriter fw = new FileWriter("work100\\Output\\许三观卖血记_副本.txt");
        int len;
        char[] chars = new char[1024];
        while ((len = fr.read(chars)) != -1){
            fw.write(chars,0,len);
        }
        fw.close();
        fr.close();
    }
}

运行结果:

在这里插入图片描述

复制过程中,一开始复制的新文件中是乱码,发现原文档的编码为GBK,而idea中设置的为UTF-8,,二者要保持一致,找到源文件

在这里插入图片描述

用记事本打开,将其另存为:
在这里插入图片描述
选择合适的编码即可。

  • 字符流和字节流的使用场景:
    • 字符流主要用来完成纯文本文件的读写
    • 字节流可以完成纯文本文件、图片、视频、音频等各种文件的读写。当我们在使用的时候,不知道用什么流时,优先使用字节流

在这里插入图片描述

注:Word 文档不是纯文本文件

刷新与关闭的区别:

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

4.5 字符缓冲流(BufferedReader)

  1. BufferedWriter:

    将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。
    可以指定缓冲区大小,或者可以接受默认大小。 默认值足够大,可用于大多数用途。
    创建对象

BufferedWriter(Writer out) 创建使用默认大小的输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz) 创建一个新的缓冲字符输出流,使用给定大小的输出缓冲区。

常用方法:

返回值类型方法
voidclose() 关闭流,先刷新。
voidflush() 刷新流。
void*newLine() 写一行行分隔符。
voidwrite(char[] cbuf, int off, int len) 写入字符数组的一部分。
voidwrite(int c) 写一个字符
voidwrite(String s, int off, int len) 写一个字符串的一部分。
  1. BufferedReader

    从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。
    可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途。

BufferedReader(Reader in) 创建使用默认大小的输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz) 创建使用指定大小的输入缓冲区的缓冲字符输入流。

常用方法:

返回值类型方法
intread() 读一个字符
intread(char[] cbuf, int off, int len) 将字符读入数组的一部分。
String*readLine() 读一行文字。

实例:

  1. 使用缓冲流创建对象并输出到控制台
    实现:
import java.io.*;

//使用缓冲流创建对象并输出到控制台
public class BufferedDemo_1 {
    public static void main(String[] args) throws IOException {
        //创建字节缓冲流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("work100\\Output\\message.txt"));
        bw.write("Spring");
        bw.newLine();  //写一行分隔符
        bw.write("Summer");
        bw.newLine();  //由jdk自动根据系统判定,在行末写出行分隔符
        bw.write("autumn");
        bw.newLine();
        bw.write("winter");
        bw.close();
        //读取数据
        BufferedReader br = new BufferedReader(new FileReader("work100\\Output\\message.txt"));
        //方式一
        /*int len;
        //创建字符数组
        char[] buff = new char[1024];
        //遍历
        while ((len = br.read(buff)) != -1){
            System.out.println(new String(buff));
        }*/
        //方式二,采用BufferedReader 特有的读的方式,每次读一行 readLine
        //创建数组
        String str;
        while ((str = br.readLine()) != null){
            System.out.println(str);
        }
    }
}

运行结果:

Spring
Summer
autumn
winter
  1. 复制文件
import java.io.*;
//使用字符缓冲流复制文件
public class File2Copy {
    public static void main(String[] args) throws IOException {
        //创建字符输入流 读
        BufferedReader br = new BufferedReader(new FileReader("work100\\Output\\笔记.txt"));
        //创建字符输出流 写
        BufferedWriter bw = new BufferedWriter(new FileWriter("work100\\Output\\笔记_副本.txt"));
        //读写操作
        String str;
        while ((str = br.readLine()) != null){
            bw.write(str);
            bw.newLine();  //换行
        }
        //释放资源
        bw.close();
        br.close();
    }
}

5 Files 工具类

5.1 拷贝文件或目录(copy)

static longcopy(InputStream in, Path target, CopyOption… options) 将输入流中的所有字节复制到文件。
static longcopy(Path source, OutputStream out) 将文件中的所有字节复制到输出流。
static Pathcopy(Path source, Path target, CopyOption… options) 将文件复制到目标文件。

可以使用 Files 工具类的 copy(Path source,Path target,CopyOption… options) 拷贝文件或者目录。如果目标文件存在,那么赋值将失败,除非我们在 options 中指定了 REPLACE_EXISTING 属性。当该命令复制目录时,如果目录中已经有了文件,目录中的文件将不会被复制。CopyOption 参数支持以下 StandardCopyOption 和 LinkOption 枚举:

  • REPLACE_EXISTING:即使目标文件已存在,也执行复制。如果目标是符号链接,则复制链接本身(而不是链接的目标)。如果目标是非空目录,则复制将失败并显示 FileAlreadyExistsException 异常。
  • COPY_ATTRIBUTES:将与文件关联的文件属性复制到目标文件。支持的确切 - 文件属性是文件系统和平台相关的,但 last-modified-time 跨平台支持并复制到目标文件。 NOFOLLOW_LINKS:表示不应遵循符号链接。如果要复制的文件是符号链接,则复制链接(而不是链接的目标)。

在 /home/project/ 目录下新建文件 1.txt。 在 /home/project/ 目录下新建源代码文件 CopyDemo.java。

实现:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class CopyDemo {
    public static void main(String[] args) throws IOException {
        Files.copy(Paths.get("/home/project/1.txt"), Paths.get("/home/project/2.txt"), StandardCopyOption.REPLACE_EXISTING);
        //REPLACE_EXISTING:即使目标文件存在,也执行复制
    } 
}

5.2 移动和重命名

Files 类的 move(Path, Path, CopyOption… options) 方法移动文件或者目录,同样目标目录存在,那么比如使用REPLACE_EXISTING。 options 参数支持 StandardCopyOption 的以下枚举:

  • REPLACE_EXISTING:即使目标文件已存在,也执行移动。如果目标是符号链接,则替换符号链接,但它指向的内容不受影响。
  • ATOMIC_MOVE:将移动作为原子文件操作执行。如果文件系统不支持原子移动,则抛出异常。使用,ATOMIC_MOVE 您可以将文件移动到目录中,并保证观察目录的任何进程都可以访问完整的文件。

move 方法除了可以移动之外,也可以用与重命名。在原路径移动至不同的文件名,即实现了文件的重命名。

实现:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;

public class MoveDemo {
    public static void main(String[] args) throws IOException {
        Files.move(Paths.get("/home/project/1.txt"),Paths.get("/home/project/3.txt") ,StandardCopyOption.REPLACE_EXISTING);
    }
}

5.3 删除

可以通过 Files 的 delete(Path path) 方法或者 deleteIfExists(Path path) 方法删除文件。

实现:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class DeleteDemo {
    public static void main(String[] args) {
        try{
            //删除文件,文件必须存在,否则抛出异常
            Files.delete(Paths.get("/home/project/3.txt"));
        } catch(IOException e){
            e.printStackTrace();
        }  
    }
}

6 小结

6.1 流程图

在这里插入图片描述
图中蓝色的部分均为抽象类,而绿色的部分则为派生类,是可以直接使用的。

在这里插入图片描述

在这里插入图片描述

6.2 实例

6.2.1 集合到文件

将 ArrarList 集合中的字符串数据写入到文本文件。要求每一个字符串元素作为文件中的一行数据。

思路:
- 创建 ArrayList 集合 
- 向集合中存储元素
- 创建字符缓冲输出流对象,因为要求每一个字符串元素作为文件中的一行数据
- 遍历集合,得到字符串数据
- 写数据
- 释放资源

实现:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class List2File {
    /*
    需求:将 ArrarList 集合中的字符串数据写入到文本文件。要求每一个字符串元素作为文件中的一行数据。
    思路:
    - 创建 ArrayList 集合
	- 向集合中存储元素
	- 创建字符缓冲输出流对象,因为要求每一个字符串元素作为文件中的一行数据
	- 遍历集合,得到字符串数据
	- 写数据
	- 释放资源
	*/
    public static void main(String[] args) throws IOException {
        List<String> list = new ArrayList<>();
        list.add("Spring");
        list.add("Summer");
        list.add("Autumn");
        list.add("Winter");
        BufferedWriter bw = new BufferedWriter(new FileWriter("work100\\Output\\Season.txt"));
        //增强for循环遍历
        /*for(String str : list){
            bw.write(str);
            bw.newLine();
        }*/
        //迭代器迭代
        Iterator<String> iter = list.iterator();
        while(iter.hasNext()){
            String str = iter.next();
            bw.write(str);
            bw.newLine();
        }
        bw.close();
    }
}

运行结果:

在这里插入图片描述

6.2.2 文件到集合

把文本文件中的数据读取到集合中 ,并遍历集合。要求:文件中每一行数据是一个集合元素

思路:
- 创建字符缓冲输入流对象
- 创建 ArrayLIst 集合
- 读数据
- 将读取到的字符串数据存储到集合中
- 释放资源
- 遍历集合

实现:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class File2List {
/*    需求:把文本文件中的数据读取到集合中 ,并遍历集合。要求:文件中每一行数据是一个集合元素
    思路:
    - 创建字符缓冲输入流对象
	- 创建 ArrayLIst 集合
	- 读数据
	- 将读取到的字符串数据存储到集合中
	- 释放资源
	- 遍历集合*/
    public static void main(String[] args) throws IOException {
        //创建输入流
        BufferedReader br = new BufferedReader(new FileReader("work100\\Output\\省份.txt"));
        //创建集合
        List<String> list = new ArrayList<>();
        String str;
        //将读取的数据保存到集合
        while ((str = br.readLine()) != null){
            list.add(str);
        }
        //释放资源
        br.close();
        //遍历集合
        for(String s : list){
            System.out.println(s);
        }
    }
}

6.2.3 点名器

有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器

分析:
- 同学的姓名存在文件中,要利用文件中的数据 ,必然要读取文件获取数据
- 每个姓名占一行 可以使用 BufferedReader 中的 readLine
- 随机: 肯定会使用到随机数
- 将读取到的数据保存到集合或数组中,使用产生的随机数作为元素的索引,这样就可以实现随机获取数据

实现:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class RandomName {
/*    需求:我有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随机点名器
    分析:
    - 同学的姓名存在文件中,要利用文件中的数据,必然要读取文件获取数据
	- 每个姓名占一行 可以使用 BufferedReader 中的 readLine
	- 随机: 肯定会使用到随机数
	- 将读取到的数据保存到集合或数组中,使用产生的随机数作为元素的索引,这样就可以实现随机获取数据*/
    public static void main(String[] args) throws IOException {
        //创建字符输入缓冲流
        BufferedReader br = new BufferedReader(new FileReader("work100\\Output\\name.txt"));
        //创建集合
        List<String> list = new ArrayList<>();
        String str;
        while((str = br.readLine()) != null){
            list.add(str);
        }
        br.close();
        //产生一个随机数
        int index = new Random().nextInt(list.size());
        //用索引输出对应的姓名
        System.out.println(list.get(index));
    }
}

运行结果:
在这里插入图片描述
将自定义对象存储到文件:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class SaveStu {
    public static void main(String[] args) throws IOException {
        //将学生存入文件work100\Output\name.txt
        List<Stu> list = new ArrayList<>();
        //创建数据
        Stu st1 = new Stu("001","赵六",18,"山西");
        Stu st2 = new Stu("002","刘三",19,"山西");
        Stu st3 = new Stu("003","崔四",23,"山西");
        Stu st4 = new Stu("004","王五",20,"山西");
        Stu st5 = new Stu("005","李二",15,"山西");
        Stu st6 = new Stu("006","孙九",16,"山西");
        //将数据添加到集合中
        list.add(st1);
        list.add(st2);
        list.add(st3);
        list.add(st4);
        list.add(st5);
        list.add(st6);

        //创建输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("work100\\Output\\name.txt"));
        for(Stu s : list){
            String str = s.getNumber() + "\t" + s.getName() + "\t" + s.getAge() + "\t" + s.getAddress();
            bw.write(str);
            bw.newLine();
        }
        //关闭流
        bw.close();
    }
}

6.2.4 集合到文件(数据排序改进版)

需求:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩)。要求按照成绩总分从高到低写入文本文件

格式:姓名,语文成绩,数学成绩,英语成绩 举例:林青霞,98,99,100

实现:

public class Student {
    private String name;
    private int chinese;
    private int math;
    private int english;

    public Student() {
    }

    public Student(String name, int chinese, int math, int english) {
        this.name = name;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getChinese() {
        return chinese;
    }

    public void setChinese(int chinese) {
        this.chinese = chinese;
    }

    public int getMath() {
        return math;
    }

    public void setMath(int math) {
        this.math = math;
    }

    public int getEnglish() {
        return english;
    }

    public void setEnglish(int english) {
        this.english = english;
    }

    @Override
    public String toString() {
        return name + ',' + chinese + ',' + math + ',' + english;
    }
}
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Comparator;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;

public class ScannerStu {
    public static void main(String[] args) throws IOException {
        //创建集合,按照总分排序
        Set<Student> studentSet = new TreeSet<>(new Comparator<Student>() {
            @Override
            public int compare(Student st1, Student st2) {
                return( st1.getChinese()+st1.getMath()+st1.getEnglish()) - (st2.getChinese()+st2.getMath()+st2.getEnglish());
            }
        });
        for (int i = 0 ; i < 5 ; i++){
            Scanner sc = new Scanner(System.in);
            System.out.println("姓名:");
            String name = sc.nextLine();
            System.out.println("语文成绩:");
            int chinese = sc.nextInt();
            System.out.println("数学成绩:");
            int math = sc.nextInt();
            System.out.println("英语成绩:");
            int english = sc.nextInt();
            Student student = new Student(name,chinese,math,english);
            //将其存入集合中
            studentSet.add(student);
        }
        //将集合存入文件中
        BufferedWriter bw = new BufferedWriter(new FileWriter("work100\\Output\\message_1.txt"));
        //遍历集合
        for(Student s : studentSet){
            bw.write(String.valueOf(s));
            bw.newLine();
        }
        bw.close();
    }
}

运行结果:

在这里插入图片描述

6.2.5 复制单级文件夹

需求:把“E:\IO\test1”这个文件夹复制到 D盘目录下

思路:
- 创建D盘中相应的文件路径
- 遍历原文件夹中的文件,将文件复制到D盘中对应的路径中

实现:

import java.io.*;

//把“E:\\IO\\test1”这个文件夹复制到 D盘目录下
public class FileCopy1 {
    public static void main(String[] args) throws IOException {
        File file = new File("E:\\IO\\test1");
        //获取当前目录的父目录的名称
        String parentName = file.getParentFile().getName();
        //获取数据源所在目录的名称
        String srcName = file.getName();
        File file1 = new File("D:\\" + File.separator + parentName + File.separator + srcName);
        //判断目标目录是否存在,如果不存在则创建
        if (!file1.exists()){
            file1.mkdirs();
        }
        //获取数据源下的所有文件
        File[] files = file.listFiles();
        //遍历当前目录下的所有文件
        for (File f : files){
            //此时的f为目录中的每一个文件
            String fileName = f.getName();
            //创建目标目录中文件的File路径
            File targetFile = new File(file1,fileName);
            //写一个方法实现文件复制
            copyFile(f,targetFile);
        }
    }
    public static void copyFile(File f1,File f2) throws IOException {
        //使用字节流实现文件的复制
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f1));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f2));
        byte[] bytes = new byte[1024];
        int len;
        while ((len = bis.read(bytes)) != -1){
            bos.write(bytes,0,len);
        }
        bos.close();
        bis.close();
    }
}

6.2.6 复制多级文件夹

需求:将E:\IO 目录下的所有文件(文件及其子目录中的文件)复制到D:\ 目录下

思路:

  • 获取数据源目录中的所有文件和目录
  • 遍历,查看每个文件是文件还是目录
    • 若为目录,需要在目的地创建该目录,并遍历该目录,实现递归操作
    • 如果是文件,写一个方法,直接复制该文件

实现:

import java.io.*;

public class MoreDirCopy {
    public static void main(String[] args) throws IOException{
        File srcFile = new File("E:\\IO");
        //目标文件
        File destFile = new File("D:\\");

    }
    public static void copyDir(File srcDir,File destDir) throws IOException{
        //判断数据源是否为目录
        if(srcDir.isDirectory()){
            //若为目录,则在目的地创建和当前原目录相同的目录
            String srcDirName = srcDir.getName();
            //将原目录的抽象路径在目的地进行抽象表示
            File folder = new File(destDir,srcDirName);
            //判断是否存在,若不存在则创建
            if (!folder.exists()){
                folder.mkdirs();
            }
            //获取数据源目录下的所有文件和目录
            File[] dirList = srcDir.listFiles();
            //遍历数据源中的文件列表
            for (File file : dirList){
                //在目的地复制当前目录
                copyDir(file,folder);
            }
        }else {
            //此时不是目录,即为文件,复制文件
            File newFile = new File(destDir,srcDir.getName());
            copyDir(srcDir,newFile);
        }
    }
    //复制文件
    public static void copyFile(File srcF,File destF) throws IOException {
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcF));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destF));
        byte[] buff = new byte[1024];
        int len;
        while ((len = bis.read(buff)) != -1){
            bos.write(buff,0,len);
        }
        bos.close();
        bis.close();
    }
}

7 IO 特殊流操作

7.1 打印流

7.1.1 字节打印流(PrintStream)

为另一个输出流添加了功能,即能够方便的打印各种数值的表示

PrintStream(File file) 使用指定的文件创建一个新的打印流,而不需要自动换行。
PrintStream(File file, String csn) 使用指定的文件和字符集创建新的打印流,而不需要自动换行。
PrintStream(OutputStream out) 创建一个新的打印流。
PrintStream(OutputStream out, boolean autoFlush) 创建一个新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding) 创建一个新的打印流。
PrintStream(String fileName) 使用指定的文件名创建新的打印流,无需自动换行。
PrintStream(String fileName, String csn) 创建一个新的打印流,不需要自动换行,具有指定的文件名和字符集。

常见方法:

返回值类型方法
voidprint(boolean b) 打印布尔值。
voidprint(char c) 打印一个字符
voidprint(char[] s) 打印字符数组。
voidprint(double d) 打印双精度浮点数。
voidprint(float f) 打印浮点数。
voidprint(int i) 打印一个整数。
voidprint(long l) 打印一个长整数。
voidprint(Object obj) 打印一个对象。
voidprint(String s) 打印字符串。
voidprintln() 通过写入行分隔符字符串来终止当前行。
voidprintln(boolean x) 打印一个布尔值,然后终止该行。
voidprintln(char x) 打印一个字符,然后终止该行。
voidprintln(char[] x) 打印一个字符数组,然后终止该行。
voidprintln(double x) 打印一次,然后终止行。
voidprintln(float x) 打印一个浮点数,然后终止该行。
voidprintln(int x) 打印一个整数,然后终止行。
voidprintln(long x) 打印很长时间,然后终止行。
voidprintln(Object x) 打印一个对象,然后终止该行。
voidprintln(String x) 打印一个字符串,然后终止行。
voidwrite(byte[] buf, int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始到此流。
voidwrite(int b) 将指定的字节写入此流。

7.1.2 字符打印流(PrintWriter)

将对象的格式表示打印到文本输出流

PrintWriter(File file) 使用指定的文件创建一个新的PrintWriter,而不需要自动的线路刷新。
PrintWriter(File file, String csn) 使用指定的文件和字符集创建一个新的PrintWriter,而不需要自动进行线条刷新。
PrintWriter(OutputStream out) 从现有的OutputStream创建一个新的PrintWriter,而不需要自动线路刷新。
PrintWriter(OutputStream out, boolean autoFlush) 从现有的OutputStream创建一个新的PrintWriter。
PrintWriter(String fileName, String csn) 使用指定的文件名和字符集创建一个新的PrintWriter,而不需要自动线路刷新。
PrintWriter(Writer out) 创建一个新的PrintWriter,没有自动线冲洗。
PrintWriter(Writer out, boolean autoFlush) 创建一个新的PrintWriter。

打印流的特点:

  • 打印流只能输出数据,不能读取数据
  • 永远不会抛出IOException

print() 与 write() 的区别:

  • print方法可以将各种类型的数据转换成字符串的形式输出。
  • 重载的write方法只能输出字符、字符数组、字符串等与字符相关的数据。
  • 最终都是重写了抽象类Writer里面的write方法

实例:

import java.io.*;

public class PrintDemo1 {
    public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {
        //创建新的对象
        PrintStream ps = new PrintStream("work100\\Output\\笔记_5.txt");
        //使用打印流打印数据
        ps.write(100);  //字节输出流的共有方法
        ps.println();  //特有方法,打印换行符、
        ps.print(100);  //打印流的特有方法
        ps.println();
        ps.close();
        //字符打印流
        PrintWriter pw = new PrintWriter("work100\\Output\\笔记_5.txt");
        pw.write(100);  //指对应的Hash值
        pw.write("Hello");
        pw.print("山西");
        pw.print(101);
        pw.print(true);
        pw.close();
    }
}

使用打印流来实现文件的拷贝:

import java.io.*;

//使用打印流实现文件的拷贝
public class PrintCopy {
    public static void main(String[] args) throws IOException {
        //创建输入流读取数据
        BufferedReader br = new BufferedReader(new FileReader("work100\\Output\\笔记_5.txt"));
        //创建字符打印流
        PrintWriter out = new PrintWriter("work100\\Output\\笔记_6.txt");
        //数据读写
        String line;
        while ((line = br.readLine()) != null){
            out.println(line);
        }
        //释放资源
        out.close();
        br.close();
    }
}

7.2 标准的输入输出流

在System 类中的两静态属性:

static InputStreamin “标准”输入流。 通过键盘输入
static PrintStreamout “标准”输出流。输出到输出设备

即:

Scanner sc = new Scanner(System.in);
System.out.println();

流的重定向:

static voidsetErr(PrintStream err) 重新分配“标准”错误输出流。
static voidsetIn(InputStream in) 重新分配“标准”输入流。
static voidsetOut(PrintStream out) 重新分配“标准”输出流

实现:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class ScannerDemo {
    public static void main(String[] args) throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("work100\\Output\\笔记_5.txt");
        System.setIn(fis);  //该表的输入流
        Scanner sc = new Scanner(System.in);
        String line;
        while (sc.hasNextLine()){
            line = sc.nextLine();
            System.out.println(line);
        }
    }
}

7.3 对象流(ObjectInputStream)

  1. ObjectOutputStream将Java对象的原始数据类型和图形写入OutputStream。

  2. ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储

  • 只有支持java.io.Serializable接口的对象才能写入流中。 每个可序列化对象的类被编码,包括类的类名和签名,对象的字段和数组的值以及从初始对象引用的任何其他对象

7.3.1 对象序列化

对象的序列化:将对象保存到磁盘或通过网络传输,为了保证对象在传输过程中的数据安全,在传输或保存之前,需要对对象进行编码。

7.3.2 对象反序列化

序列化的逆序,指将磁盘上的对象或网络传输得到的对象进行解码。

序列化的方式:实现java.io.Serializable接口及完成类的序列化。

对象反序列化流:ObjectInputStream

判断流结尾:遇到流结尾抛出异常

构造方法:

|ObjectOutputStream(OutputStream out)

创建一个写入指定的OutputStream的ObjectOutputStream。

常用方法:

voidwrite(byte[] buf) 写入一个字节数组。
voidwrite(byte[] buf, int off, int len) 写入一个子字节数组。
voidwrite(int val) 写一个字节。
voidwriteBoolean(boolean val) 写一个布尔值。
voidwriteByte(int val) 写入一个8位字节。
voidwriteBytes(String str) 写一个字符串作为字节序列。
voidwriteChar(int val) 写一个16位的字符。
voidwriteChars(String str) 写一个字符串作为一系列的字符。
protected voidwriteClassDescriptor(ObjectStreamClass desc) 将指定的类描述符写入ObjectOutputStream。
voidwriteDouble(double val) 写一个64位的双倍。
voidwriteFields() 将缓冲的字段写入流。
voidwriteFloat(float val) 写一个32位浮点数。
voidwriteInt(int val) 写一个32位int。
voidwriteLong(long val) 写一个64位长
voidwriteObject(Object obj) 将指定的对象写入ObjectOutputStream。
voidwriteUTF(String str) 此字符串的原始数据写入格式为 modified UTF-8 。

构造方法:

ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象。
ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream

常用方法:

返回值类型方法
ObjectreadObject() 从ObjectInputStream读取一个对象。

实例:使用对象流读写数据

import java.io.*;

public class StudentDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("work100\\Output\\笔记_5.txt"));
        Student stu = new Student("小明",18);
        os.writeObject(stu);  //将序列化的对象写出到文件
        os.close();
        //读取序列化的对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("work100\\Output\\笔记_5.txt"));
        Object obj = ois.readObject();  //读取数据进行反序列化
        Student student = (Student)obj;
        System.out.println(student);
        ois.close();
    }
}

7.4 serialVersionUID &transient

使用场景:用对象序列化流序列化出一个对象后,加入我们修改了对象所属的类文件,读取数据会出现问题,抛出InvalidClassException 异常,为了解决该问题:

  • 重新序列化
  • 给对象所属的类加一个serialVersionUID
  • 如果一个对象中的某个成员变量的值不像被序列化,我们给该成员加transient 关键字修饰,该关键字标记的成员变量不参与序列化过程
public class Student implements Serializable {
    private static final long serialVersionUID = 42L;//序列化之后的每一个类都将有一个唯一的序列化标识  避免重复序列化
    private String name;
    private transient int age;//表述该属性将不被序列化  在反序列化时  改属性的值将称为默认值

8 Properties 集合

  1. Properties 作为Map集合的使用:

    是一个Map 体系的集合类

    Properties 可以保存到流中或从流中加载

    属性中的每个键及对应的值都是一个字符串

使用:

import java.util.Properties;
import java.util.Set;

public class PropertiesDemo {
    public static void main(String[] args) {
        //创建集合对象
        Properties prop = new Properties();
        //保存元素
        prop.put("1001","Spring");
        prop.put("1002","Summer");
        prop.put("1003","Winter");
        //遍历
        Set<Object> keySet = prop.keySet();
        for (Object key : keySet){
            Object value = prop.get(key);
            System.out.println(key + "-" + value);
        }
    }
}
  1. Properties 作为Map 集合的特有方法:
返回值类型方法
StringgetProperty(String key) 使用此属性列表中指定的键搜索属性。
StringgetProperty(String key, String defaultValue) 使用此属性列表中指定的键搜索属性。
SetstringPropertyNames() 返回此属性列表中的一组键,其中键及其对应的值为字符串,包括默认属性列表中的不同键,如果尚未从主属性列表中找到相同名称的键。
ObjectsetProperty(String key, String value) 致电 Hashtable方法 put 。

实例:

import java.util.Properties;
import java.util.Set;

public class PropertiesDemo1 {
    public static void main(String[] args) {
        Properties prop = new Properties();
        prop.setProperty("1001","Spring");
        prop.setProperty("1002","Summer");
        //通过getProperties 获取值,如果当前集合中不存在key 的时候,则默认使用默认值来作为该key所对应的值
        System.out.println(prop.get("1001"));
        //Properties 特有的遍历方式
        Set<String> keySet = prop.stringPropertyNames();
        for (String key : keySet){
            String value = prop.getProperty(key);
            System.out.println(key + "-" + value);
        }
    }
}
  1. Properties 和 IO 流的结合使用
返回值类型方法
voidload(InputStream inStream) 从输入字节流读取属性列表(键和元素对)。
voidload(Reader reader) 以简单的线性格式从输入字符流读取属性列表(关键字和元素对)。
voidstore(OutputStream out, String comments) 将此属性列表(键和元素对)写入此 Properties表中,以适合于使用 load(InputStream)方法加载到 Properties表中的格式输出流。
voidstore(Writer writer, String comments) 将此属性列表(键和元素对)写入此 Properties表中,以适合使用 load(Reader)方法的格式输出到输出字符流

实例:

  1. 读取属性配置文件
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class PropertiesAndIO {
    public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        FileReader fr = new FileReader("work100\\Output\\笔记_5.txt");
        //将字符输入流加载到集合中
        prop.load(fr);
        String name = prop.getProperty("name");  //name对应其中的键
        String password = prop.getProperty("password");
        System.out.println(name + "-" + password);
        fr.close();
    }
}
  1. 通过集合写出到属性文件
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

public class Properties2File {
    public static void main(String[] args) throws IOException {
        //将集合中的数据保存到属性文件
        Properties prop1 = new Properties();
        prop1.setProperty("1001","张三");
        prop1.setProperty("1002","李四");
        prop1.setProperty("1003","赵六");
        FileWriter fw = new FileWriter("p.txt");
        prop1.store(fw,"名单");
        //释放资源
        fw.close();
        //创建集合对象
        Properties prop2 = new Properties();
        FileReader fr = new FileReader("copy.properties");
        //将字符输入流加载到集合中
        prop2.load(fr);  //load从输入字节流读取属性列表
        String name = prop2.getProperty("name");  //name对应prop.properties文件中的键‘
        String password = prop2.getProperty("password");
        System.out.println(name + "-" + password);
        fr.close();
    }
}

9 随机存取文件流(RandomAccessFile)

  • 该类的实例支持读取和写入随机访问文件。随机访问文件的行为类似于存储在文件系统中的大量字节。
  • 有一种游标或索引到隐含的数组,称为文件指针;随机操作读取从文件指针开始的字节,并使文件指针超过读取的字节。
  • 如果在读或写模式下创建随机访问文件,则输出操作也可用;输出操作从文件指针开始写入字节,并将文件指针提前到写入的字节。
  • 写入隐式数组的当前端的输出操作会导致扩展数组。
  • 文件指针可以通过读取getFilePointer方法和设置seek方法。
RandomAccessFile(File file, String mode) 创建一个随机访问文件流从File参数指定的文件中读取,并可选地写入文件。
RandomAccessFile(String name, String mode)
创建随机访问文件流,以从中指定名称的文件读取,并可选择写入文件。

Mode:

  • r:只读
  • rw“读写模式
  • rws/rwd:读写的同时可以更新数据

实例:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.StandardCharsets;

public class RandomTest1 {
    public static void main(String[] args) throws IOException {
        //若操作的文件不存在,会自动创建
        RandomAccessFile raf = new RandomAccessFile("raf.txt","rw");
        //写数据
        System.out.println(raf.getFilePointer());
        raf.write("hello".getBytes(StandardCharsets.UTF_8));
        raf.write("world".getBytes(StandardCharsets.UTF_8));
        raf.seek(5);  //调整文件指针
        raf.write("\r\n".getBytes(StandardCharsets.UTF_8));
        //读数据
        raf.seek(0);  //将指针移动到文件的开始位置
        byte[] bytes = new byte[1024];
        int len = raf.read(bytes);
        System.out.println(new String(bytes,0,len));
        System.out.println(raf.getFilePointer());
        raf.close();
    }
}

运行结果:

hello
rld
10

通过读文件指针的控制,可以实现任意位置 的读写操作。

流是用来处理数据的

处理数据时:数据流 数据的目的地

数据源可以为实文件,也可以是键盘

数据的目的地:可以是文件,也可以是显示器或者其他设备

流知识帮助数据进行传输,并对数据的传输进行处理,比如可以对数据进行规律,转换等。

10 NIO :非阻塞式IO

10.1 IO与NIO

  • BIO:是指blocking IO

  • NIO:是指noblocking IO(非阻塞io),也可以理解为newio(新io)

  • 区别与联系:

    • 数据传输的方式:Bio通过流的方式进行传输 ,NIo是通过包(数据块)的方式进行数据传输
    • BIO读写分离 ,NIO即可读也可以写
    • BIO与NIO的作用与目的相同,但使用方式完全不同,NIO支持面向缓冲区的(BIO是面向流的),NIO是基于通道的,NIO将以更加高效的方式进行文件的读写操作。
IONIO
面向流面向缓冲区
阻塞式非阻塞式
选择器

Java中的两套NIO,一套是针对标准输入输出流的NIO,令一套是网络编程使用的NIO

10.2 通道:Channel

FileChannel :处理本地文件

DatagramChannel(UDP协议): ServerSocketChannel , SocketChannel(TCP) 与网络编程相关的

Java 的 NIO 是基于通道(channel)面向缓冲区(Buffer)

通道是表示打开设备的链接

缓冲区主要是用来做数据处理的

Channel负责传输 Buffer负责的存储

将数据存储在Buffer中,通过Channel来传输

数据的读写:

  • Channel --读–>Buffer
  • Buffer --写–>Channel

缓冲区:用于特定原始类型的数据的容器。
缓冲器是特定原始类型的元素的线性有限序列。 除了其内容,缓冲区的基本属性是其容量,限制和位置。

ByteBuffer , CharBuffer , DoubleBuffer , FloatBuffer , IntBuffer , LongBuffer , ShortBuffer

缓冲区的重要属性:

  • 容量(capacity):表示Buffer最大的数据容量,缓冲区不能为负,并且创建之后不能更改
  • 限制(limit):第一个不应该读取或写入的 数据的索引,位于limit后的数据是不可读写的。缓冲的限制同样不能为负,并且不能大于容量
  • 位置(position):标记下一个要读取或写入的数据的索引。不能为负,不能大于限制

mark和reset 标记索引,通过mark 方法指定buffer中的一个特定的位置,之后可以通过reset方法回复到position的位置

标记 位置 限制 容量 的位置关系:
0 <=mark<=position<=limit<= capactity

Buffer 中的常见方法:

Bufferclear() 清除此缓冲区。
Bufferflip() 翻转这个缓冲区。
int capacity() 返回此缓冲区的容量。
intlimit() 返回此缓冲区的限制。
intposition() 返回此缓冲区的位置。
Bufferreset() 将此缓冲区的位置重置为先前标记的位置。

数据的操作方法:

CharBufferget(char[] dst) 相对批量 获取方法。
CharBufferCharBufferget(char[] dst) 相对批量 获取方法。
CharBufferget(char[] dst, int offset, int length) 相对批量 获取方法。
CharBufferput(char[] src) 相对大容量 put方法 (可选操作) 。
CharBufferput(char[] src, int offset, int length) 相对大容量 put方法 (可选操作) 。
CharBufferput(CharBuffer src) 相对大容量 put方法 (可选操作) 。

通道Channel

  • 通道表示与诸如硬件设备,文件,网络套接字或能够执行一个或多个不同I / O操作(例如读取或写入)的程序组件的实体的开放连接
  • 通道一旦关闭,将不能再次打开
  • 通道本身不能直接访问数据,只能通过Buffer来进行数据的交互
abstract intread(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
longread(ByteBuffer[] dsts) 从该通道读取到给定缓冲区的字节序列。
abstract longread(ByteBuffer[] dsts, int offset, int length) 从该通道读取字节序列到给定缓冲区的子序列中。
abstract intread(ByteBuffer dst, long position) 从给定的文件位置开始,从该通道读取一个字节序列到给定的缓冲区
abstract intwrite(ByteBuffer src) 从给定的缓冲区向该通道写入一个字节序列。
longwrite(ByteBuffer[] srcs) 从给定的缓冲区向该通道写入一系列字节。
abstract longwrite(ByteBuffer[] srcs, int offset, int length) 从给定缓冲区的子序列将一个字节序列写入该通道。
abstract intwrite(ByteBuffer src, long position) 从给定的缓冲区向给定的文件位置开始,向该通道写入一个字节序列。

使用通道和缓冲区实现数据的交互:

  • 将Buffer中的数据写入Channel
  • 从Channel中读取数据到Buffer
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class ChannelDemo {
    public static void main(String[] args) throws IOException {
        //创建字节输入流和字节输出流  FileChannel getChannel()返回与此文件输入流相关联的唯一的FileChannel对象。
        FileInputStream fis = new FileInputStream("day_14_IODemo\\dir\\out.java");
        FileOutputStream fos = new FileOutputStream("day_14_IODemo\\dir\\nio.java");
        //创建通道
        FileChannel inChannel = fis.getChannel() ;//输入通道
        FileChannel outChannel = fos.getChannel();//输出通道
        // 创建缓冲区,并指定缓冲区的大小
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 将通道的数据存入到缓冲区
        while(inChannel.read(buf)!= -1) {
            //切换读取数据的模式
            buf.flip();
            //将缓冲区的数据写出到输出通道
            outChannel.write(buf);
            //清空缓冲区
            buf.clear();
        }
        //释放资源
        inChannel.close();
        outChannel.close();
        fos.close();
        fis.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

BORN(^-^)

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

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

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

打赏作者

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

抵扣说明:

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

余额充值