Java知识点汇总--字节字符流

1. 字节缓冲流

1.1 字节缓冲流构造方法

  • 字节缓冲流介绍
    • BufferedOutputStream :该类实现缓冲输出流。 通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
    • BufferedInputStream :创建BufferedInputStream将创建一个内部缓冲区数组。 当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节
  • 构造方法:
方法名说明
BufferedOutputStream(OutputStream out)创建字节缓冲输出流对象
BufferedInputStream(InputStream in)创建字节缓冲输入流对象
  • 示例代码
public class BufferStreamDemo {
  public static void main(String[] args) throws IOException {
    //字节缓冲输出流:BufferedOutputStream(OutputStream out)
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\bos.txt"));
    //写数据
    bos.write("hello\r\n".getBytes());
    bos.write("world\r\n".getBytes());
    //释放资源
    bos.close();
 
    //字节缓冲输入流:BufferedInputStream(InputStream in)
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("myByteStream\\bos.txt"));
    //一次读取一个字节数据
//    int by;
//    while ((by=bis.read())!=-1) {
//      System.out.print((char)by);
//    }
    //一次读取一个字节数组数据
    byte[] bys = new byte[1024];
    int len;
    while ((len=bis.read(bys))!=-1) {
      System.out.print(new String(bys,0,len));
    }
    //释放资源
    bis.close();
 }
}

1.2 字节流复制视频

  • 案例需求
    把“E:\itcast\字节流复制图片.avi”复制到模块目录下的“字节流复制图片.avi”
  • 实现步骤
    • 根据数据源创建字节输入流对象
    • 根据目的地创建字节输出流对象
    • 读写数据,复制视频
    • 释放资源
  • 代码实现
public class CopyAviDemo {
  public static void main(String[] args) throws IOException {
    //记录开始时间
    long startTime = System.currentTimeMillis();
    //复制视频
//    method1();     耗时:64565毫秒
//    method2();     耗时:108毫秒
//    method3();     耗时:405毫秒
    method4();        //耗时:60毫秒
    //记录结束时间
    long endTime = System.currentTimeMillis();
    System.out.println("共耗时:" + (endTime - startTime) + "毫秒");
 }
  //字节缓冲流一次读写一个字节数组
  public static void method4() throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\itcast\\字节流复制图片.avi"));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));
    byte[] bys = new byte[1024];
    int len;
    while ((len=bis.read(bys))!=-1) {
      bos.write(bys,0,len);
   }
    bos.close();
    bis.close();
     //字节缓冲流一次读写一个字节
  public static void method3() throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream("E:\\itcast\\字节流复制图片.avi"));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("myByteStream\\字节流复制图片.avi"));
    int by;
    while ((by=bis.read())!=-1) {
      bos.write(by);
   }
    bos.close();
    bis.close();
 }
  //基本字节流一次读写一个字节数组
  public static void method2() throws IOException {
    //E:\\itcast\\字节流复制图片.avi
    //模块目录下的 字节流复制图片.avi
    FileInputStream fis = new FileInputStream("E:\\itcast\\字节流复制图片.avi");
    FileOutputStream fos = new FileOutputStream("myByteStream\\字节流复制图片.avi");
    byte[] bys = new byte[1024];
    int len;
    while ((len=fis.read(bys))!=-1) {
      fos.write(bys,0,len);
   }
    fos.close();
    fis.close();
 }
  //基本字节流一次读写一个字节
  public static void method1() throws IOException {
    //E:\\itcast\\字节流复制图片.avi
    //模块目录下的 字节流复制图片.avi
    FileInputStream fis = new FileInputStream("E:\\itcast\\字节流复制图片.avi");
    FileOutputStream fos = new FileOutputStream("myByteStream\\字节流复制图片.avi");
    int by;
    while ((by=fis.read())!=-1) {
      fos.write(by);
   }
    fos.close();
    fis.close();
 }
}

2. 字符流

2.1 为什么会出现字符流

  • 字符流的介绍
    • 由于字节流操作中文不是特别的方便,所以Java就提供字符流
    • 字符流 = 字节流 + 编码表
  • 中文的字节存储方式
    • 用字节流复制文本文件时,文本文件也会有中文,但是没有问题,原因是最终底层操作会自动进行字节拼接成中文,如何识别是中文的呢?
    • 汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数

2.2 编码表

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

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

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

  • 相关方法
方法名说明
byte[] getBytes()使用平台的默认字符集将该 String编码为一系列字节
byte[] getBytes(String charsetName)使用指定的字符集将该 String编码为一系列字节
String(byte[] bytes)使用平台的默认字符集解码指定的字节数组来创建字符串
String(byte[] bytes, String charsetName)通过指定的字符集解码指定的字节数组来创建字符串
  • 代码演示
public class StringDemo {
	/*
		需求:字节流读文本文件数据
		
		一个汉字存储:
			如果是GBK编码,占用2个字节
			如果是UTF-8编码,占用3个字节
	*/
  public static void main(String[] args) throws UnsupportedEncodingException {
    //定义一个字符串
    String s = "中国";
    //byte[] bys = s.getBytes(); //[-28, -72, -83, -27, -101, -67]
    //byte[] bys = s.getBytes("UTF-8"); //[-28, -72, -83, -27, -101, -67]
    byte[] bys = s.getBytes("GBK"); //[-42, -48, -71, -6]
    System.out.println(Arrays.toString(bys));
    //String ss = new String(bys);
    //String ss = new String(bys,"UTF-8");
    String ss = new String(bys,"GBK");
    System.out.println(ss);
 }
}

2.4 字符流中的编码解码问题

  • 字符流中和编码解码问题相关的两个类
    • InputStreamReader :是从字节流到字符流的桥梁
      • 它读取字节,并使用指定的编码将其解码为字符
      • 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
    • OutputStreamWriter :是从字符流到字节流的桥梁
      • 是从字符流到字节流的桥梁,使用指定的编码将写入的字符编码为字节
      • 它使用的字符集可以由名称指定,也可以被明确指定,或者可以接受平台的默认字符集
  • 构造方法
方法名说明
InputStreamReader(InputStream in)使用默认字符编码创建InputStreamReader对象
InputStreamReader(InputStream in,String charset)使用指定的字符编码创建InputStreamReader对象
OutputStreamWriter(OutputStream out)使用默认字符编码创建OutputStreamWriter对象
OutputStreamWriter(OutputStream out,String charset)使用指定的字符编码创建OutputStreamWriter对象
  • 代码演示
public class ConversionStreamDemo {
  public static void main(String[] args) throws IOException {
    //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"));
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"),"GBK");
    osw.write("中国");
    osw.close();
    //InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"));
    InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\osw.txt"),"GBK");
    //一次读取一个字符数据
    int ch;
    while ((ch=isr.read())!=-1) {
      System.out.print((char)ch);
   }
    isr.close();
 }
}

2.5 字符流写数据的5种方式

  • 方法介绍
方法名说明
void write(int c)写一个字符
void write(char[] cbuf)写入一个字符数组
void write(char[] cbuf, int off, int len)写入字符数组的一部分
void write(String str)写一个字符串
void write(String str, int off, int len)写一个字符串的一部分
  • 刷新和关闭的方法
方法名说明
flush()刷新操作后数据才写进去(存在缓存),释放资源前JVM自动刷新一次。刷新流,之后还可以继续写数据
close()关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
  • 代码演示
public class OutputStreamWriterDemo {
  public static void main(String[] args) throws IOException {
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("myCharStream\\osw.txt"));
    //void write(int c):写一个字符
//    osw.write(97);
//    osw.write(98);
//    osw.write(99);
    //void writ(char[] cbuf):写入一个字符数组
    char[] chs = {'a', 'b', 'c', 'd', 'e'};
//    osw.write(chs);
    //void write(char[] cbuf, int off, int len):写入字符数组的一部分
//    osw.write(chs, 0, chs.length);
//    osw.write(chs, 1, 3);
    //void write(String str):写一个字符串
//    osw.write("abcde");
    //void write(String str, int off, int len):写一个字符串的一部分
//    osw.write("abcde", 0, "abcde".length());
    osw.write("abcde", 1, 3);
    //释放资源
    osw.close();
 }
}

2.6 字符流读数据的2种方式

  • 方法介绍
方法名说明
int read()一次读一个字符数据
int read(char[] cbuf)一次读一个字符数组数据
  • 代码演示
public class InputStreamReaderDemo {
  public static void main(String[] args) throws IOException {
    InputStreamReader isr = new InputStreamReader(new FileInputStream("myCharStream\\ConversionStreamDemo.java"));
    //int read():一次读一个字符数据
//    int ch;
//    while ((ch=isr.read())!=-1) {
//      System.out.print((char)ch);
//    }
    //int read(char[] cbuf):一次读一个字符数组数据
    char[] chs = new char[1024];
    int len;
    while ((len = isr.read(chs)) != -1) {
      System.out.print(new String(chs, 0, len));
   }
    //释放资源
    isr.close();
 }
}

2.7 字符流复制Java文件

  • 案例需求
    把模块目录下的“ConversionStreamDemo.java” 复制到模块目录下的“Copy.java”
  • 实现步骤
    • 根据数据源创建字符输入流对象
    • 根据目的地创建字符输出流对象
    • 读写数据,复制文件
    • 释放资源
  • 代码实现
public class CopyJavaDemo01 {
  public static void main(String[] args) throws IOException {
    //根据数据源创建字符输入流对象
    InputStreamReader isr = new InputStreamReader(new
FileInputStream("myCharStream\\ConversionStreamDemo.java"));
    //根据目的地创建字符输出流对象
    OutputStreamWriter osw = new OutputStreamWriter(new
FileOutputStream("myCharStream\\Copy.java"));
    //读写数据,复制文件
    //一次读写一个字符数据
//    int ch;
//    while ((ch=isr.read())!=-1) {
//      osw.write(ch);
//    }
    //一次读写一个字符数组数据
    char[] chs = new char[1024];
    int len;
    while ((len=isr.read(chs))!=-1) {
      osw.write(chs,0,len);
   }
    //释放资源
    osw.close();
    isr.close();
 }
}

2.8 字符流复制Java文件改进版

  • 案例需求
    使用便捷流对象,把模块目录下的“ConversionStreamDemo.java” 复制到模块目录下的“Copy.java”

  • 分析

    • 转换流的名字比较长,而我们常见的操作都是按照本地默认编码实现的,所以,为了简化书写,转换流提供了对应的子类
    • FileReader:用于读取字符文件的便捷类
      FileReader(String fileName)
    • FileWriter:用于写入字符文件的便捷类
      FileWriter(String fileName)
    • 数据源和目的地的分析
      数据源:myCharStream\ConversionStreamDemojava—读数据— Reader — InputStreamReader----FileReaden
      目的地:myCharStream\Copy.java —写数据— Witer—OutputSteamWiter—FileWhiter
  • 实现步骤

    • 根据数据源创建字符输入流对象
    • 根据目的地创建字符输出流对象
    • 读写数据,复制文件
    • 释放资源
  • 代码实现

public class CopyJavaDemo02 {
  public static void main(String[] args) throws IOException {
    //根据数据源创建字符输入流对象
    FileReader fr = new FileReader("myCharStream\\ConversionStreamDemo.java");
    //根据目的地创建字符输出流对象
    FileWriter fw = new FileWriter("myCharStream\\Copy.java");
    //读写数据,复制文件
//    int ch;
//    while ((ch=fr.read())!=-1) {
//      fw.write(ch);
//    }
    char[] chs = new char[1024];
    int len;
    while ((len=fr.read(chs))!=-1) {
      fw.write(chs,0,len);
   }
    //释放资源
    fw.close();
    fr.close();
 }
}

2.9 字符缓冲流

  • 字符缓冲流介绍
    • BufferedWriter :将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
    • BufferedReader :从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途
  • 构造方法
方法名说明
BufferedWriter(Writer out)创建字符缓冲输出流对象
BufferedReader(Reader in)创建字符缓冲输入流对象
  • 代码演示
public class BufferedStreamDemo01 {
  public static void main(String[] args) throws IOException {
    //BufferedWriter(Writer out)
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\bw.txt"));
    bw.write("hello\r\n");
    bw.write("world\r\n");
    bw.close();
    //BufferedReader(Reader in)
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\bw.txt"));
    //一次读取一个字符数据
//    int ch;
//    while ((ch=br.read())!=-1) {
//      System.out.print((char)ch);
//    }
    //一次读取一个字符数组数据
    char[] chs = new char[1024];
    int len;
    while ((len=br.read(chs))!=-1) {
    System.out.print(new String(chs,0,len));
   }
    br.close();
 }
}

2.10 字符缓冲流复制Java文件

  • 案例需求
    把模块目录下的ConversionStreamDemo.java 复制到模块目录下的 Copy.java
  • 实现步骤
    • 根据数据源创建字符缓冲输入流对象
    • 根据目的地创建字符缓冲输出流对象
    • 读写数据,复制文件,使用字符缓冲流特有功能实现
    • 释放资源
  • 代码实现
public class CopyJavaDemo01 {
  public static void main(String[] args) throws IOException {
    //根据数据源创建字符缓冲输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\ConversionStreamDemo.java"));
    //根据目的地创建字符缓冲输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\Copy.java"));
    //读写数据,复制文件
    //一次读写一个字符数据
//    int ch;
//    while ((ch=br.read())!=-1) {
//      bw.write(ch);
//    }
    //一次读写一个字符数组数据
    char[] chs = new char[1024];
    int len;
    while ((len=br.read(chs))!=-1) {
      bw.write(chs,0,len);
   }
    //释放资源
    bw.close();
    br.close();
 }
}

2.11 字符缓冲流特有功能

  • 方法介绍
    BufferedWriter:
方法名说明
void newLine()写一行行分隔符,行分隔符字符串由系统属性定义
  • BufferedReader:
方法名说明
String readLine()读一行文字。 结果包含行的内容的字符串,不包括任何行终止字符如果流的结尾已经到达,则为null
  • 代码演示
public class BufferedStreamDemo02 {
  public static void main(String[] args) throws IOException {
    //创建字符缓冲输出流
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\bw.txt"));
    //写数据
    for (int i = 0; i < 10; i++) {
      bw.write("hello" + i);
      //bw.write("\r\n");
      bw.newLine();
      bw.flush();
   }
    //释放资源
    bw.close();
    //创建字符缓冲输入流
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\bw.txt"));
    String line;
    while ((line=br.readLine())!=null) {
      System.out.println(line);
   }
    br.close();
 }
}

2.12 字符缓冲流特有功能复制Java文件

  • 案例需求
    使用特有功能把模块目录下的ConversionStreamDemo.java 复制到模块目录下的 Copy.java
  • 实现步骤
    • 根据数据源创建字符缓冲输入流对象
    • 根据目的地创建字符缓冲输出流对象
    • 读写数据,复制文件,使用字符缓冲流特有功能实现
    • 释放资源
  • 代码实现
public class CopyJavaDemo02 {
  public static void main(String[] args) throws IOException {
    //根据数据源创建字符缓冲输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\ConversionStreamDemo.java"));
    //根据目的地创建字符缓冲输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\Copy.java"));
    //读写数据,复制文件
    //使用字符缓冲流特有功能实现
    String line;
    while ((line=br.readLine())!=null) {
      bw.write(line);
      bw.newLine();
      bw.flush();
   }
    //释放资源
    bw.close();
    br.close();
 }
}

2.13 IO流小结

  • 字节流
    在这里插入图片描述
  • 字符流
    在这里插入图片描述

3. 练习案例

3.1 集合到文件

  • 案例需求
    把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个集合元素
  • 实现步骤
    • 创建字符缓冲输入流对象
    • 创建 ArrayList集合对象
    • 调用字符缓冲输入流对象的方法读数据
    • 把读取到的字符串数据存储到集合中
    • 释放资源
    • 遍历集合
  • 代码实现
public class ArrayListToTxtDemo {
  public static void main(String[] args) throws IOException {
    //创建ArrayList集合
    ArrayList<String> array = new ArrayList<String>();
    //往集合中存储字符串元素
    array.add("hello");
    array.add("world");
    array.add("java");
    //创建字符缓冲输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\array.txt"));
    //遍历集合,得到每一个字符串数据
    for(String s : array) {
      //调用字符缓冲输出流对象的方法写数据
      bw.write(s);
      bw.newLine();
      bw.flush();
   }
    //释放资源
    bw.close();
 }
}

3.2 文件到集合

  • 案例需求
    把ArrayList集合中的字符串数据写入到文本文件。要求:每一个字符串元素作为文件中的一行数据
  • 实现步骤
    • 创建 ArrayList集合
    • 往集合中存储字符串元素
    • 创建字符缓冲输出流对象
    • 遍历集合,得到每一个字符串数据
    • 调用字符缓冲输出流对象的方法写数据
    • 释放资源
  • 代码实现
public class TxtToArrayListDemo {
  public static void main(String[] args) throws IOException {
    //创建字符缓冲输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\array.txt"));
    //创建ArrayList集合对象
    ArrayList<String> array = new ArrayList<String>();
    //调用字符缓冲输入流对象的方法读数据
    String line;
    while ((line=br.readLine())!=null) {
      //把读取到的字符串数据存储到集合中
      array.add(line);
   }
    //释放资源
    br.close();
    //遍历集合
    for(String s : array) {
      System.out.println(s);
   }
 }
}

3.3 点名器

  • 案例需求
    我有一个文件里面存储了班级同学的姓名,每一个姓名占一行,要求通过程序实现随点名器
  • 实现步骤
    • 创建字符缓冲输入流对象
    • 创建 ArrayList集合对象
    • 调用字符缓冲输入流对象的方法读数据
    • 把读取到的字符串数据存储到集合中
    • 释放资源
    • 使用 Random产生一个随机数,随机数的范围在:[0,集合的长度)
    • 把第 6步产生的随机数作为索引到ArrayList集合中获取值
    • 把第 7步得到的数据输出在控制台
  • 代码实现
public class CallNameDemo {
  public static void main(String[] args) throws IOException {
    //创建字符缓冲输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\names.txt"));
    //创建ArrayList集合对象
    ArrayList<String> array = new ArrayList<String>();
    //调用字符缓冲输入流对象的方法读数据
    String line;
    while ((line=br.readLine())!=null) {
      //把读取到的字符串数据存储到集合中
      array.add(line);
   }
    //释放资源
    br.close();
    //使用Random产生一个随机数,随机数的范围在:[0,集合的长度)
    Random r = new Random();
    int index = r.nextInt(array.size());
    //把第6步产生的随机数作为索引到ArrayList集合中获取值
    String name = array.get(index);
    //把第7步得到的数据输出在控制台
    System.out.println("幸运者是:" + name);
 }
}

3.4 集合到文件改进版

  • 案例需求
    把ArrayList集合中的学生数据写入到文本文件。要求:每一个学生对象的数据作为文件中的一行数据 格式:学号,姓名,年龄,居住地 举例:itheima001,林青霞,30,西安
  • 实现步骤
    • 定义学生类
    • 创建 ArrayList集合
    • 创建学生对象
    • 把学生对象添加到集合中
    • 创建字符缓冲输出流对象
    • 遍历集合,得到每一个学生对象
    • 把学生对象的数据拼接成指定格式的字符串
    • 调用字符缓冲输出流对象的方法写数据
    • 释放资源
  • 代码实现
    学生类
public class Student {
  private String sid;
  private String name;
  private int age;
  private String address;
  public Student() {
 }
  public Student(String sid, String name, int age, String address) {
    this.sid = sid;
    this.name = name;
    this.age = age;
    this.address = address;
 }
  public String getSid() {
    return sid;
 }
  public void setSid(String sid) {
    this.sid = sid;
 }
  public String getName() {
    return name;
 }
  public void setName(String name) {
    this.name = name;
 }
  public int getAge() {
    return age;
 }
  public void setAge(int age) {
    this.age = age;
  }
  public String getAddress() {
    return address;
  }
  public void setAddress(String address) {
    this.address = address;
  }
}

测试类

public class ArrayListToFileDemo {
  public static void main(String[] args) throws IOException {
    //创建ArrayList集合
    ArrayList<Student> array = new ArrayList<Student>();
    //创建学生对象
    Student s1 = new Student("itheima001", "林青霞", 30, "西安");
    Student s2 = new Student("itheima002", "张曼玉", 35, "武汉");
    Student s3 = new Student("itheima003", "王祖贤", 33, "郑州");
    //把学生对象添加到集合中
    array.add(s1);
    array.add(s2);
    array.add(s3);
    //创建字符缓冲输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\students.txt"));
    //遍历集合,得到每一个学生对象
    for (Student s : array) {
      //把学生对象的数据拼接成指定格式的字符串
      StringBuilder sb = new StringBuilder();
      sb.append(s.getSid()).append(",").append(s.getName()).append(",").append(s.ge
tAge()).append(",").append(s.getAddress());
      //调用字符缓冲输出流对象的方法写数据
      bw.write(sb.toString());
      bw.newLine();
      bw.flush();
   }
    //释放资源
    bw.close();
 }
}

3.5 文件到集合改进版

  • 案例需求
    把文本文件中的数据读取到集合中,并遍历集合。要求:文件中每一行数据是一个学生对象的成员变量值 举例:itheima001,林青霞,30,西安
  • 实现步骤
    • 定义学生类
    • 创建字符缓冲输入流对象
    • 创建 ArrayList集合对象
    • 调用字符缓冲输入流对象的方法读数据
    • 把读取到的字符串数据用 split()进行分割,得到一个字符串数组
    • 创建学生对象
    • 把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值
    • 把学生对象添加到集合
    • 释放资源
    • 遍历集合
  • 代码实现
    学生类
    同上
    测试类
public class FileToArrayListDemo {
  public static void main(String[] args) throws IOException {
    //创建字符缓冲输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myCharStream\\students.txt"));
    //创建ArrayList集合对象
    ArrayList<Student> array = new ArrayList<Student>();
    //调用字符缓冲输入流对象的方法读数据
    String line;
    while ((line = br.readLine()) != null) {
      //把读取到的字符串数据用split()进行分割,得到一个字符串数组
      String[] strArray = line.split(",");
      //创建学生对象
      Student s = new Student();
      //把字符串数组中的每一个元素取出来对应的赋值给学生对象的成员变量值
      //itheima001,林青霞,30,西安
      s.setSid(strArray[0]);
      s.setName(strArray[1]);
      s.setAge(Integer.parseInt(strArray[2]));
      s.setAddress(strArray[3]);
      //把学生对象添加到集合
      array.add(s);
   }
    //释放资源
    br.close();
    //遍历集合
    for (Student s : array) {
      System.out.println(s.getSid() + "," + s.getName() + "," + s.getAge() + "," + s.getAddress());
   }
 }
}

4. IO 流案例

4.1 集合到文件数据排序改进版

  • 案例需求
    键盘录入 5个学生信息(姓名,语文成绩,数学成绩,英语成绩)。要求按照成绩总分从高到低写入文本文件
    格式:姓名 ,语文成绩,数学成绩,英语成绩 举例:林青霞,98,99,100
  • 分析步骤
    • 定义学生类
    • 创建TreeSet集合,通过比较器排序进行排序
    • 键盘录入学生数据
    • 创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量
    • 把学生对象添加到TreeSet集合
    • 创建字符缓冲输出流对象
    • 遍历集合,得到每一个学生对象
    • 把学生对象的数据拼接成指定格式的字符串
    • 调用字符缓冲输出流对象的方法写数据
    • 释放资源
  • 代码实现
    学生类
public class Student {
  // 姓名
  private String name;
  // 语文成绩
  private int chinese;
  // 数学成绩
  private int math;
  // 英语成绩
  private int english;
  public Student() {
    super();
 }
  public Student(String name, int chinese, int math, int english) {
    super();
    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;
 }
  public int getSum() {
    return this.chinese + this.math + this.english;
 }
}

测试类

public class TreeSetToFileDemo {
  public static void main(String[] args) throws IOException {
    //创建TreeSet集合,通过比较器排序进行排序
    TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() {
      @Override
      public int compare(Student s1, Student s2) {
        //成绩总分从高到低
        int num = s2.getSum() - s1.getSum();
        //次要条件
        int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num;
        int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2;
        int num4 = num3 == 0 ? s1.getName().compareTo(s2.getName()) : num3;
        return num4;
     }
   });
   //键盘录入学生数据
    for (int i = 0; i < 5; i++) {
      Scanner sc = new Scanner(System.in);
      System.out.println("请录入第" + (i + 1) + "个学生信息:");
      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 s = new Student();
      s.setName(name);
      s.setChinese(chinese);
      s.setMath(math);
      s.setEnglish(english);
      //把学生对象添加到TreeSet集合
      ts.add(s);
   }
    //创建字符缓冲输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("myCharStream\\ts.txt"));
    //遍历集合,得到每一个学生对象
    for (Student s : ts) {
      //把学生对象的数据拼接成指定格式的字符串
      //格式:姓名,语文成绩,数学成绩,英语成绩
      StringBuilder sb = new StringBuilder();
      sb.append(s.getName()).append(",").append(s.getChinese()).append(",").append(s
.getMath()).append(",").append(s.getEnglish()).append(",").append(s.getSum());
//      调用字符缓冲输出流对象的方法写数据
      bw.write(sb.toString());
      bw.newLine();
      bw.flush();
   }
    //释放资源
    bw.close();
 }
}

4.2 复制单级文件夹

  • 案例需求
  • 把 “E:\itcast”这个文件夹复制到模块目录下
  • 分析步骤
    • 创建数据源目录File对象,路径是E:\itcast
    • 获取数据源目录File对象的名称
    • 创建目的地目录File对象,路径由(模块名+第2步获取的名称)组成
    • 判断第3步创建的File是否存在,如果不存在,就创建
    • 获取数据源目录下所有文件的File数组
    • 遍历File数组,得到每一个File对象,该File对象,其实就是数据源文件
    • 获取数据源文件File对象的名称
    • 创建目的地文件File对象,路径由(目的地目录+第7步获取的名称)组成
    • 复制文件
      由于不清楚数据源目录下的文件都是什么类型的,所以采用字节流复制文件
      采用参数为File的构造方法
  • 代码实现
public class CopyFolderDemo {
  public static void main(String[] args) throws IOException {
    //创建数据源目录File对象,路径是E:\\itcast
    File srcFolder = new File("E:\\itcast");
    //获取数据源目录File对象的名称(itcast)
    String srcFolderName = srcFolder.getName();
    //创建目的地目录File对象,路径名是模块名+itcast组成(myCharStream\\itcast)
    File destFolder = new File("myCharStream",srcFolderName);
    //判断目的地目录对应的File是否存在,如果不存在,就创建
    if(!destFolder.exists()) {
      destFolder.mkdir();
   }
    //获取数据源目录下所有文件的File数组
    File[] listFiles = srcFolder.listFiles();
    //遍历File数组,得到每一个File对象,该File对象,其实就是数据源文件
    for(File srcFile : listFiles) {
      //数据源文件:E:\\itcast\\mn.jpg
      //获取数据源文件File对象的名称(mn.jpg)
      String srcFileName = srcFile.getName();
      //创建目的地文件File对象,路径名是目的地目录+mn.jpg组成  (myCharStream\\itcast\\mn.jpg)
      File destFile = new File(destFolder,srcFileName);
      //复制文件
      copyFile(srcFile,destFile);
  private static void copyFile(File srcFile, File destFile) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
    byte[] bys = new byte[1024];
    int len;
    while ((len=bis.read(bys))!=-1) {
      bos.write(bys,0,len);
   }
    bos.close();
    bis.close();
 }
}

4.3 复制多级文件夹

  • 案例需求
    把 “E:\itcast”这个文件夹复制到 F盘目录下
  • 分析步骤
    • 创建数据源File对象,路径是E:\itcast
    • 创建目的地File对象,路径是F:\
    • 写方法实现文件夹的复制,参数为数据源File对象和目的地File对象
    • 判断数据源File是否是文件
      • 是文件:直接复制,用字节流
      • 不是文件:
      • 在目的地下创建该目录
      • 遍历获取该目录下的所有文件的File数组,得到每一个File对象
      • 回到3继续(递归)
  • 代码实现
public class CopyFoldersDemo {
  public static void main(String[] args) throws IOException {
    //创建数据源File对象,路径是E:\\itcast
    File srcFile = new File("E:\\itcast");
    //创建目的地File对象,路径是F:\\
    File destFile = new File("F:\\");
    //写方法实现文件夹的复制,参数为数据源File对象和目的地File对象
    copyFolder(srcFile,destFile);
 }
  //复制文件夹
  private static void copyFolder(File srcFile, File destFile) throws IOException{
    //判断数据源File是否是目录
    if(srcFile.isDirectory()) {
      //在目的地下创建和数据源File名称一样的目录
      String srcFileName = srcFile.getName();
      File newFolder = new File(destFile,srcFileName); //F:\\itcast
      if(!newFolder.exists()) {
        newFolder.mkdir();
     }
      //获取数据源File下所有文件或者目录的File数组
      File[] fileArray = srcFile.listFiles();
      //遍历该File数组,得到每一个File对象
      for(File file : fileArray) {
        //把该File作为数据源File对象,递归调用复制文件夹的方法
        copyFolder(file,newFolder);
     }
   } else {
      //说明是文件,直接复制,用字节流
      File newFile = new File(destFile,srcFile.getName());
      copyFile(srcFile,newFile);
   }
 }
  //字节缓冲流复制文件
  private static void copyFile(File srcFile, File destFile) throws IOException {
    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
    BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
    byte[] bys = new byte[1024];
    int len;
    while ((len = bis.read(bys)) != -1) {
      bos.write(bys, 0, len);
   }
    bos.close();
    bis.close();
 }
}

4.4 复制文件的异常处理

  • 常见方法
    在这里插入图片描述

  • 代码实现

public class CopyFileDemo {
  public static void main(String[] args) {
  }
  //try...catch...finally
  private static void method2() {
    FileReader fr = null;
    FileWriter fw = null;
    try {
      fr = new FileReader("fr.txt");
      fw = new FileWriter("fw.txt");
      char[] chs = new char[1024];
      int len;
      while ((len = fr.read()) != -1) {
        fw.write(chs, 0, len);
     }
   } catch (IOException e) {
      e.printStackTrace();
   } finally {
      if(fw!=null) {
        try {
          fw.close();
       } catch (IOException e) {
          e.printStackTrace();
       }
     }
      if(fr!=null) {
        try {
          fr.close();
       } catch (IOException e) {
          e.printStackTrace();
       }
     }
   }
 }
  //抛出处理
  private static void method1() throws IOException {
    FileReader fr = new FileReader("fr.txt");
    FileWriter fw = new FileWriter("fw.txt");
    char[] chs = new char[1024];
    int len;
    while ((len = fr.read()) != -1) {
      fw.write(chs, 0, len);
   }
    fw.close();
    fr.close();
 }
}
  • JDK7版本改进(较好)
public class CopyFileDemo {
  public static void main(String[] args) {
 }
  //JDK7的改进方案
  private static void method3() {
    try(FileReader fr = new FileReader("fr.txt");
      FileWriter fw = new FileWriter("fw.txt");){
      char[] chs = new char[1024];
      int len;
      while ((len = fr.read()) != -1) {
        fw.write(chs, 0, len);
     }
   } catch (IOException e) {
      e.printStackTrace();
   }
 }
}
  • JDK9版本改进
public class CopyFileDemo {
  public static void main(String[] args) {
 }
  //JDK9的改进方案
  private static void method4() throws IOException {
    FileReader fr = new FileReader("fr.txt");
    FileWriter fw = new FileWriter("fw.txt");
    try(fr;fw){
      char[] chs = new char[1024];
      int len;
      while ((len = fr.read()) != -1) {
        fw.write(chs, 0, len);
     }
   } catch (IOException e) {
      e.printStackTrace();
   }
 }
}

5. IO 特殊操作流

5.1 标准输入流

  • System 类中有两个静态的成员变量
    • public static final InputStream in :标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
    • public static final PrintStream out :标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
  • 自己实现键盘录入数据
public class SystemInDemo {
  public static void main(String[] args) throws IOException {
    //public static final InputStream in:标准输入流
//    InputStream is = System.in;
//    int by;
//    while ((by=is.read())!=-1) {
//      System.out.print((char)by);
//    }
    //如何把字节流转换为字符流?用转换流
//    InputStreamReader isr = new InputStreamReader(is);
//    //使用字符流能不能够实现一次读取一行数据呢?可以
//    //但是,一次读取一行数据的方法是字符缓冲输入流的特有方法
//    BufferedReader br = new BufferedReader(isr);
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    System.out.println("请输入一个字符串:");
    String line = br.readLine();
    System.out.println("你输入的字符串是:" + line);
    System.out.println("请输入一个整数:");
    int i = Integer.parseInt(br.readLine());
    System.out.println("你输入的整数是:" + i);
    //自己实现键盘录入数据太麻烦了,所以Java就提供了一个类供我们使用
    Scanner sc = new Scanner(System.in);
 }
}

5.2 标准输出流

  • System 类中有两个静态的成员变量
    • public static final InputStream in :标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源
    • public static final PrintStream out :标准输出流。通常该流对应于显示输出或由主机环境或用户指定的另一个输出目标
  • 输出语句的本质:是一个标准的输出流
    • PrintStream ps = System.out;
    • PrintStream 类有的方法,System.out都可以使用
  • 示例代码
public class SystemOutDemo {
  public static void main(String[] args) {
    //public static final PrintStream out:标准输出流
    PrintStream ps = System.out;
    //能够方便地打印各种数据值
//    ps.print("hello");
//    ps.print(100);
//    ps.println("hello");
//    ps.println(100);
    //System.out的本质是一个字节输出流
    System.out.println("hello");
    System.out.println(100);
    System.out.println();
//    System.out.print();
 }
}

5.3 字节打印流

  • 打印流分类
    • 字节打印流: PrintStream
    • 字符打印流: PrintWriter
  • 打印流的特点
    • 只负责输出数据,不负责读取数据
    • 永远不会抛出 IOException
    • 有自己的特有方法
  • 字节打印流
    • PrintStream(String fileName) :使用指定的文件名创建新的打印流
    • 使用继承父类的方法写数据,查看的时候会转码;使用自己的特有方法写数据,查看的数据原样输出
    • 可以改变输出语句的目的地
      public static void setOut(PrintStream out):重新分配“标准”输出流
  • 示例代码
public class PrintStreamDemo {
  public static void main(String[] args) throws IOException {
    //PrintStream(String fileName):使用指定的文件名创建新的打印流
    PrintStream ps = new PrintStream("myOtherStream\\ps.txt");
    //写数据
    //字节输出流有的方法
//    ps.write(97);
    //使用特有方法写数据
//    ps.print(97);
//    ps.println();
//    ps.print(98);
    ps.println(97);
    ps.println(98);
   
    //释放资源
    ps.close();
 }
}

5.4 字符打印流

  • 字符打印流构造房方法
方法名说明
PrintWriter(String fileName)使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新
PrintWriter(Writer out, boolean autoFlush)创建一个新的PrintWriter out:字符输出流 autoFlush: 一个布尔值,如果为真,则println , printf ,或format方法将刷新输出缓冲区
  • 示例代码
public class PrintWriterDemo {
  public static void main(String[] args) throws IOException {
    //PrintWriter(String fileName) :使用指定的文件名创建一个新的PrintWriter,而不需要自动执行行刷新
//    PrintWriter pw = new PrintWriter("myOtherStream\\pw.txt");
//    pw.write("hello");
//    pw.write("\r\n");
//    pw.flush();
//    pw.write("world");
//    pw.write("\r\n");
//    pw.flush();
//    pw.println("hello");
    /*
      pw.write("hello");
      pw.write("\r\n");
    */
//    pw.flush();
//    pw.println("world");
//    pw.flush();
    //PrintWriter(Writer out, boolean autoFlush):创建一个新的PrintWriter
    PrintWriter pw = new PrintWriter(new FileWriter("myOtherStream\\pw.txt"),true);
//    PrintWriter pw = new PrintWriter(new FileWriter("myOtherStream\\pw.txt"),false);
    pw.println("hello");
    /*
      pw.write("hello");
      pw.write("\r\n");
      pw.flush();
    */
    pw.println("world");
    pw.close();
 }
}

5.5 复制Java文件打印流改进版

  • 案例需求
    把模块目录下的 PrintStreamDemo.java 复制到模块目录下的 Copy.java
  • 分析步骤
    • 根据数据源创建字符输入流对象
    • 根据目的地创建字符输出流对象
    • 读写数据,复制文件
    • 释放资源
  • 代码实现
public class CopyJavaDemo {
  public static void main(String[] args) throws IOException {
    /*
    //根据数据源创建字符输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myOtherStream\\PrintStreamDemo.java"));
    //根据目的地创建字符输出流对象
    BufferedWriter bw = new BufferedWriter(new FileWriter("myOtherStream\\Copy.java"));
    //读写数据,复制文件
    String line;
    while ((line=br.readLine())!=null) {
      bw.write(line);
      bw.newLine();
      bw.flush();
    }
    //释放资源
    bw.close();
    br.close();
    */
    //根据数据源创建字符输入流对象
    BufferedReader br = new BufferedReader(new FileReader("myOtherStream\\PrintStreamDemo.java"));
    //根据目的地创建字符输出流对象
    PrintWriter pw = new PrintWriter(new FileWriter("myOtherStream\\Copy.java"),true);
 //读写数据,复制文件
    String line;
    while ((line=br.readLine())!=null) {
      pw.println(line);
   }
    //释放资源
    pw.close();
    br.close();
 }
}

5.6 对象序列化流

  • 对象序列化介绍
    • 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
    • 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息
    • 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息
    • 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
  • 对象序列化流: ObjectOutputStream
    • 将 Java对象的原始数据类型和图形写入OutputStream。 可以使用ObjectInputStream读取(重构)对象。 可以通过使用流的文件来实现对象的持久存储。 如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象
  • 构造方法
方法名说明
ObjectOutputStream(OutputStream out)创建一个写入指定的OutputStream的ObjectOutputStream
  • 序列化对象的方法
方法名说明
void writeObject(Object obj)将指定的对象写入ObjectOutputStream
  • 示例代码
    学生类
public class Student implements Serializable {
  private String name;
  private int age;
  public Student() {
 }
  public Student(String name, int age) {
    this.name = name;
    this.age = age;
 }
  public String getName() {
    return name;
 }
  public void setName(String name) {
    this.name = name;
 }
  public int getAge() {
    return age;
 }
  public void setAge(int age) {
    this.age = age;
 }
  @Override
  public String toString() {
    return "Student{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
 }
}

测试类

public class ObjectOutputStreamDemo {
  public static void main(String[] args) throws IOException {
    //ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myOtherStream\\oos.txt"));
    //创建对象
    Student s = new Student("林青霞",30);
    //void writeObject(Object obj):将指定的对象写入ObjectOutputStream
    oos.writeObject(s);
    //释放资源
    oos.close();
 }
}
  • 注意事项
    • 一个对象要想被序列化,该对象所属的类必须必须实现 Serializable 接口
    • Serializable 是一个标记接口,实现该接口,不需要重写任何方法

5.7 对象反序列化流

  • 对象反序列化流: ObjectInputStream
    • ObjectInputStream 反序列化先前使用ObjectOutputStream编写的原始数据和对象
  • 构造方法
方法名说明
ObjectInputStream(InputStream in)创建从指定的InputStream读取的ObjectInputStream
  • 反序列化对象的方法
方法名说明
Object readObject()从ObjectInputStream读取一个对象
  • 示例代码
public class ObjectInputStreamDemo {
  public static void main(String[] args) throws IOException,ClassNotFoundException {
    //ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt"));
    //Object readObject():从ObjectInputStream读取一个对象
    Object obj = ois.readObject();
    Student s = (Student) obj;
    System.out.println(s.getName() + "," + s.getAge());
    ois.close();
 }
}

5.8 serialVersionUID&transient

  • serialVersionUID
    • 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
      • 会出问题,会抛出 InvalidClassException异常
    • 如果出问题了,如何解决呢?
      • 重新序列化
      • 给对象所属的类加一个 serialVersionUID(类的内容可改,序列号不会改)
        • private static final long serialVersionUID = 42L;
  • transient
    • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
      • 给该成员变量加 transient关键字修饰,该关键字标记的成员变量不参与序列化过程
  • 示例代码
    学生类
public class Student implements Serializable {
  private static final long serialVersionUID = 42L;
  private String name;
//  private int age;
  private transient int age;
  public Student() {
 }
  public Student(String name, int age) {
    this.name = name;
    this.age = age;
 }
  public String getName() {
    return name;
 }
  public void setName(String name) {
    this.name = name;
 }
  public int getAge() {
    return age;
 }
  public void setAge(int age) {
    this.age = age;
 }
//  @Override
//  public String toString() {
//    return "Student{" +
//        "name='" + name + '\'' +
//        ", age=" + age +
//        '}';
//  }
}

测试类

public class ObjectStreamDemo {
  public static void main(String[] args) throws IOException, ClassNotFoundException {
//    write();
    read();
 }
  //反序列化
  private static void read() throws IOException, ClassNotFoundException {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("myOtherStream\\oos.txt"));
    Object obj = ois.readObject();
    Student s = (Student) obj;
    System.out.println(s.getName() + "," + s.getAge());
    ois.close();
 }
  //序列化
  private static void write() throws IOException {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("myOtherStream\\oos.txt"));
    Student s = new Student("林青霞", 30);
    oos.writeObject(s);
    oos.close();
 }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值