IO(2) + 线程(1)

复习

1.Java.util.ArrayList
2.Java.util.HashMap需要在key 对应的类型中重写hashCode 和 equals方法,来保证key 的唯一性 和 无序。
3.ArrayList 和 LinkedList 的异同
4.Vector 和 ArrayList 的异同
5.Hashtable 和 HashMap 的异同
6.Collections 和 Collection 的异同
7.Iterator 和 Iterable 异同
8.TreeMap 元素的 key 是有序的,唯一的,不能是null。有序是升序,排序依赖于内部或者外部比较器。
9.根据流向:输入流、输出流。
10.数据单元:字节流、字符流。
11.数据源头:节点流、处理流(包装流、高级流)。
12.字符输入流的工作原理:读取底层的文本的字节数据,然后使用解码器,将字节数据解码为字符数据,要依赖字符集。
13.字符输出流工作原理:将内存中的字符数据,使用编码器,依赖于某一个字符集,将字符数据编码为字节数据,写入到底层。
14.InputStream、OutputStream、Reader、Writer、FileInputStream、FileOutputStream、FileReader、FileWriter、BufferedInputStream、BufferedOutputStream。

第一节 带缓冲区的字符流、转换流

1.BufferedReader、BufferedWriter

缓冲区默认大小8192 chars.

**
 *BufferedReader带缓冲区的字符输入流
 * BufferedWriter带缓冲区的字符输出流
 */
public class TestBufferedReaderWriter {
    public static void main(String[] args) {
        copy("e:/b.txt","e:/b_copy.txt");//使用的是默认的字符集utf-8;

    }
    static void copy(String srcPath,String destPath){
        BufferedReader br=null;
        BufferedWriter bw=null;
        try {
            //从输入流中读取一行文本数据,当到达数据源的末尾,返回null
            //该方法一旦读取到\r\n就立即返回,本行读取到的数据,返回的内容不包含换行字符
            br=new BufferedReader(new FileReader(srcPath));
            bw=new BufferedWriter(new FileWriter(destPath));
            String str=br.readLine();
            while(str!=null){
                //这样写存在一个问题,移植性,不同的国家或者平台,文件分隔符不同。
                //bw.write(str+"\n");
                //往输出流中写入一个系统当前的换行符
                bw.newLine();
                bw.flush();
                str=br.readLine();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                bw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.转换流

FileReader : 该类型对象具有解码器的功能。
BufferedReader:该类型对象只是为了提高其他流的工作的效率。没有解码器的功能。增强其他流的流。
FileWriter:该类型对象具有编码器的功能。
InputStreamReader 解码器
该流是字符流,是一个数据源为字节流的字符流。功能是,负责读取底层的字节数据,依赖于默认的或者是显式指定的字符集,进行解码,将字节数据解码为字符数据。解码器,
注意:所有的字符输入流的解码的功能都依赖于该类。
InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
吃的是字节,吐的是字符。

2.OutputStreamWriter 编码器

该流是字符输出流,负责将字符数据,使用平台默认的字符集,或者显式指定字符集,将字符数据编码为字节数据写出到底层。
是字符流通向字节流的桥梁,所有的字符输出流的编码工作都依赖于该类。
吃的是字符,吐的是字节。

import java.io.*;

/**
 *InputStreamReader 解码器
 * OutputStreamReader编码器
 */
public class TestInputStreamReader {
    public static void main(String[] args) throws Exception {
        test0();
        //test1();
    }
    static void test0() throws Exception{
        BufferedReader br=new BufferedReader(new FileReader("E:/1.txt"));
        String str=br.readLine();
        while(str!=null){
            System.out.println(str);
            str=br.readLine();

        }
        br.close();
    }
    //读取其他编码的文件,
    static void test() throws Exception{
        //读取底层的字节数据,然后使用GDK解码
        InputStreamReader isr=new InputStreamReader(new FileInputStream("e:/a.txt"),"GBK");
        BufferedReader br=new BufferedReader(isr);

        String str=br.readLine();
        while (str!=null){

            System.out.println(str);
            str=br.readLine();
        }
        br.close();
    }
    //考虑频繁从磁盘读写的问题
    static void test1() throws Exception{
        InputStreamReader isr=new InputStreamReader(new FileInputStream("e:/a.txt"),"GBK");
        char[] buf=new char[10];
        int count= isr.read(buf);
        while(count!=-1){
            System.out.println(new String(buf,0,count));
            count= isr.read(buf);
        }
    }
    //接收键盘输入,将输入的内容写入到指定的文件中。输入bye 的时候结束。
    static void test3() throws Exception{
        InputStreamReader isr=new InputStreamReader(System.in);
        BufferedReader br=new BufferedReader(isr);
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("1copy.txt"),"GBK"));
        String str=br.readLine();
        while(true){
            if(str.equals("bye")){
                break;
            }
            bw.write(str);
            bw.newLine();
            bw.flush();
        }
        br.close();
        bw.close();
    }
    //使用GBK编码,将字符数据写出到指定的文件
    static void test4() throws Exception {
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/a.txt"),"GBK"));
        bw.write("千万里我追寻着你");//这个write方法的这个“千万里。。。”写到了bw的8192的缓存区中去了,
        bw.close();
    }
}

说明:
1.FileReader说一下字符流的工作原理,从底层读取字节,使用解码器将底层的字节转化为字符(当然要依赖于某一个字符集),那么解码器在哪里呢?
这个解码器应该是这个对象内部的功能,那么该类型的对象具有解码器的功能,有可能是它的某个方法,也有可能是它的某个属性。
2.BufferedReader也是从底层读取字节转化为字符,那么 它的解码器在哪里呢?注意它只要是用来提高效率的,还要依赖于FileReader来读取底层,它并没有解码器,解码器的功能还是FileReader的。这个类就是为了增强FileReader这个流的,这就涉及到设计模式了,这是一个装饰设计模式的一个类。它的源头的谁就装饰了谁,就增强了谁。
3.FileReader的解码功能依赖于InputStreamReader,那么它俩是什么关系呢?Reader——>InputStreamReeader———>FileReader,FileReader的解码功能是从他的父类哪里继承下来的,所以它能直接用。

4.InputStreamReader 只能读文本,里面传的参数是字节流。
5.代码:new InputStreamReader(new FileInputStream(“e:/a.txt”),“GBK”);我们以 FileInputStream这个流以字节的形式读取底层的数据,"e:/a.txt"这个是文本,我们读取的是它底层的字节数据, InputStreamReader解码,解码的时候用的字符集是“GBk”,不写字符集的话,就使用默认的字符集utf-8;
这里我们不用InputerStreamReader 用FileReader也行,但是有时候必须用InputStreamReader,比如,上例中的a.txt我建文件的时候编码形式不是utf-8,而是用ANSI(不同的国家表示不同的编码,这里指GBK)编码的,这时候如果使用FileReader(FileReader只能使用平台默认的额字符集,我们这个Idea用的啥字符集,它就用啥字符集)会由于字符集的不同出现乱码的问题。所以我们必须要使用InputSreamReader来指出字符集。(InputStreamReader(new FileInputStream(“e:/a.txt”),“GBK”))所以这里就是把字节读进来解码的时候将这些字节数据使用GBK进行解码,所有的乱码问题都是由于编码和解码使用 的字符集不同导致的。

6.System.in就是出入流,这个in是InputStream类型的,in是InputStream的一个实例,就是我们的键盘。键盘输入的是字符数据,是纯文本,还是一个字节流,那么使用InputStreamTeader转为字符流。

7.InputStreamReader 这个是非常有用的,(字符集的指出可以隐式使用平台默认的,也可以显示式使用),比如在网络传输的时候,只能使用字节进行传输,所有的文本数据也要转化为字节,
所以要将这些字节数据,使用某个字符集,转化为字符数据进行处理。因为是字符数据,所以最终,用字符流好处理,如果使用字节流处理呢,最终还要转。所以当我们拿到一堆字节数据并且是纯文本数据的时候,一般要使用InputStreamReader.还有像我们本例中这种编码不一致的情况也要使用这个。

8.FileWriter将内存中的字符数据,编码为字节写入到底层。该类型的对象具有编码器的功能。OutputStreamReader是字符流,是字符流还是字节流是相对于我们自身来说的,我们操作的是字节呢?还是字符呢?我们操作的字节,就是字节流,我们操作的字符就是字符流。
如果我们用FileWriter写文件的话,那么这个编码字符集就是utf-8.如果要求把一堆字数据以GBK为编码字符集,写到文件中去这时候就得用OutputStreamReader了

第二节 对象流

1.对象的持久化存储相关的概念

序列化:将内存中的对象转换为字节数据的过程。称为对象的序列化。
反序列化:将字节数据还原为内存中的对象的过程,称为对象的反序列化。
public interface Serializable
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
标记性接口,实现了该接口,才可以进行序列化和反序列化。

2.序列化和反序列化注意的问题

1.所有需要序列化和反序列化的类的对象都需要实现java.io.Serializable 该接口。该接口是一个标记性接口,没有任何的内容,代表了一种可以被序列化和反序列化的能力。
2.如果一个类的对象可以被序列化,那么它的子类对象也可以。
3.如果想正确的实现序列化和反序列化,那么对应的类型中,有必要显式的定义public static final long serialVersionUID = 1L;。
4.如果一个对象可以被序列化,那么该对象内部的所有的属性应该都可以被序列化。
5.类中的哪些成员可以不被序列化。静态的成员变量是不可以被序列化的,静态的成员依赖于类,不依赖于对象。使用关键字 transient 修饰的实例成员变量,也不可以被序列化,那么反序列化得到的对象中的 使用 transient 修饰的成员 的值为默认值。

import java.io.*;

/**
 * ObjectStream流的学习
 */
public class TestObjectStream {
    public static void main(String[] args) throws Exception {
        writeObject();
        readObject();
    }
    //将指定的对象写出到文件中,序列化过程
    static void writeObject() throws Exception{
        //对象流:将对象转化为字节数据
        //缓冲流:负责提高效率,缓存数据,避免多次写入
        //文件流:将字节数据写入文件的
        ObjectOutputStream oos=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("1.txt")));

        Student s=new Student("韩梅梅",17);
        oos.writeDouble(1.1);//在内存中的字节形式
        oos.writeObject(s);

        oos.close();
    }
    //将文件中的对象读取到内存当中来,反序列化的过程
    static void readObject() throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new BufferedInputStream(new FileInputStream("1.txt")));
        //按写入的顺序进行读取
        double value=ois.readDouble();
        //反序列化过程中没有使用构造方法对对象进行初始化
        //所有对象的初始化操作是依赖于对象的字节数据(还原肯定要用到学生类,但是没有调用构造方法)
        Student o=(Student)ois.readObject();
        System.out.println(o);
        ois.close();

    }

}
class Student implements Serializable{       //所有需要序列化和反序列化的类的对象都需要实现java.io.Serializable 该接口,
                                              // 不实现会抛出异常NotSerializableException
    //把类的串行版本号显式定义,序列化前后 的版本号要求一致。
    private static final long serialVersionUID=1L;
    private String name;
    private transient int age;
    private int score;
    private int ID;
    private String gender;
    private Book book=new Book();//学生拥有一本书,拥有关系就是成员

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", score=" + score +
                ", book=" + book +
                '}';
    }
}class Book implements Serializable{

}
/*
如果某一个对象已经通过writeObject()写入到了文件中。这时对类进行添加属性,再使用方法readObject()从文件中读取对象的时候,
会报下面的错误。
 local class incompatible: stream classdesc serialVersionUID = 4861162618705949631, local class serialVersionUID = -706830872169961173
 为了解决这个问题,我们需要把类的串行版本号显示定义。

 可以不被序列化的两种方式:静态成员和,用transient修饰的成员,使用 transient 修饰的成员 的值为默认值。
 */

第三节 打印流 数据流 字节数组流

1 打印流

import java.io.*;

/**
 * PrintStream:打印流、字节流、输出流(往外写字节)
 * PrintWriter:是字符流(往外写字符)
 * PrintStream:该流只有输出流,没有输入流,该流不会抛出IOException。有自动刷新的功能。
 * 主要的作用:该类提供了针对于任何对象转换为字符串,再转换为字节写出到底层的功能
 */
public class TestPrintStream {
    public static void main(String[] args)  {
        test4();
    }

    static void test1() throws Exception{
        //源码的文档注释public PrintStream(OutputStream out)
        // ----The out  put stream to which values and objects will be printed)
        PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("e:/a.txt")));
        ps.print('A');
        ps.println(1.1);
        ps.print(111111111111L);
        ps.print(new TestPrintStream());
        ps.close();
    }

    static void test2() throws Exception{
        //第一个true尾部追加;
        //第二个true:A boolean; if true, the output buffer will be flushed whenever a byte array is written
        PrintStream ps=new PrintStream(new BufferedOutputStream(new FileOutputStream("E:/a.txt",true)),true);
        //System.out就是ps了
        System.setOut(ps);
        System.out.println("hello");
        System.out.println("world");
    }

    static void test3() throws Exception{
        FileInputStream fis=new FileInputStream("e:/txt");
        //System.in 就是fis 了
        System.setIn(fis);
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        //读取的是fis对应的数据源中的内容
        System.out.println(br.readLine());
        fis.close();
    }
    //面试题
    static void test4(){
        int x=10;
        PrintStream ps=new PrintStream(System.out){//重写一个类的方法最快速的就是使用方法匿名内部类
            @Override
            public void println(int x) {
                println("x="+x);
            }
        };
        System.setOut(ps);
        System.out.println(x);
    }
}
/*
System.in:键盘
System.out:控制台
 */

2 数据流

DataInputStream、DataOutputStream
作用:提供了对所有的基本数据类型的在内存中的原始字节样式的读写,以及String的字节的读写功能。

import java.io.*;

/**
 *DataInputeStream数据输入流
 * DataOutputStream数据输出流
 */
public class TestDataStream {
    public static void main(String[] args) throws Exception {
        test1();
        test2();
    }
    static void test1() throws Exception{
        DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("6.txt")));
        dos.writeByte(127);//1个字符
        dos.writeShort(0);//2个字符
        dos.writeInt(2100000000);//4个字符
        dos.writeLong(1L);//8个字符
        dos.writeFloat(1.1f);//4个字符
        dos.writeDouble(1.1);//8个字符
        dos.writeChar('A');//2
        //29个字符
        dos.writeBoolean(true);//1个字符
        //在真正的将 字符串的字节数组写入之前,先调用了writeShort 写入了 字符串的对应的字节数组的长度。
        dos.writeUTF("你好");
        //38个字符
        dos.close();
    }

    static void test2() throws Exception{
        DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("6.txt")));
        System.out.println(dis.readByte());
        System.out.println(dis.readShort());
        System.out.println(dis.readInt());
        System.out.println(dis.readLong());
        System.out.println(dis.readFloat());
        System.out.println(dis.readDouble());
        System.out.println(dis.readChar());
        System.out.println(dis.readBoolean());
        System.out.println(dis.readUTF());
        //在readUTF 方法内部,先调用了readShort ,来获得即将要使用多少个字节数据来还原字符串。
        dis.close();
    }
}

3.字节数组流

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 *ByteArrayInputStream字节数字输入流   (以前讲的那些类的源要么是流,要么是字节数据,而这个类的源是字节数组)
 * ByteArrayOutputStream字节数组输出流
 */
public class TestByteArrayStream {
    public static void main(String[] args) throws Exception {
        test1();
    }

    //得到一个对象和一个int类型的字节数组
    static void test1() throws Exception{
        //ByteArrayOutputStream 对象自带了一个字节数组缓冲区。我们往外写的所有的内容都写到缓存区了
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(baos);

        oos.writeObject("你好");
        oos.writeInt(10);
        
        oos.close();
        //字节数组中就包含了 写出的对象和数据内容。
        byte[] bytes=baos.toByteArray();   //如何得到自带的字节数组的缓冲区呢?

        //将字节数组中的数据还原
        //将字节数组转为流数组
        ByteArrayInputStream bais=new ByteArrayInputStream(bytes);
        //ois 负责读取底层的字节流数据去还原对象。
        ObjectInputStream ois=new ObjectInputStream(bais);
        System.out.println(ois.readObject());
        System.out.println(ois.readInt());

        ois.close();
    }
}

4 练习

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

/**
 * 文件夹的复制
 */
public class CopyDir {
    public static void main(String[] args) throws Exception {
        copyDir(new File("e:/first"),new File("e:/second"));
    }
    //复制单个文件
    private static void copyFile(File srcFile,File destFile) throws Exception{
        FileInputStream fis=new FileInputStream(srcFile);
        FileOutputStream fos=new FileOutputStream(destFile);
        byte[] buf=new byte[100];
        int count=fis.read(buf);
        while(count!=-1){
            fos.write(buf,0,count);
            count=fis.read(buf);
        }
        fis.close();
        fos.close();
    }
    //复制文件夹
    static void copyDir(File srcFile,File destDir) throws Exception{
        //创建目的目录
        if(!destDir.exists()){
            destDir.mkdir();
        }
        //获得所有的子文件和子文件夹
        File[] files=srcFile.listFiles();
        for (int i = 0; i < files.length; i++) {
            String fileName=files[i].getName();
            if(files[i].isFile()){   //子文件复制
                copyFile(files[i],new File(destDir,fileName));
            }else{
                copyDir(files[i],new File(destDir,fileName));
            }
        }
    }
}

5 总结:

1:读取文件内容
1:文本文件 br = new BufferedReader(new FileReader(file)) br.readLIne()
2:字节文件 bis = new BufferedInputStream(new FileInputStream(file)) bis.read
2:写入文件内容
1 : 文本文件 bw = new BufferedWriter(new FileWriter(file)) bw.writer newLine
2 : 字节文件 bos = new BufferedOutputStream(new FileOutputStream(file))
3 : 对象的读写操作 ObjectInpustream ObjectOutputStream
4:基本数据类型的读写 DataInputStream DataOutputStream
5 : 读取键盘输入 br = new BufferedReader(new InputStreamReader(System.in))
6:ByteArrayInputStream ByteArrayOutputStream 将内存中的某些数据转换为字节数组,或者从字节数组中还原某些数据使用。
7:System.out 是 PrintStream 的实例。 System.in 是 InpuStream 的实例。

6 IO体系图

第四节 线程

1 线程相关的概念

1.程序:program 是一个静态的概念。是计算机指令的有序的集合。
2.进程:process 动态的概念。程序的一次执行;执行中的程序。
进程特点:
1:多个进程可以并发执行
2:进程是系统进行资源分配的最小单位。
3:多个进程之间不共享相互的资源,每个进程都独享系统分配的资源。
4:系统创建和销毁进程消耗的资源是比较大的。
5:一个进程至少包含一个线程。可以包含多个线程。

线程:进程中的一条任务线;进程中一条完整的执行的路径。
线程的特点:

1:多个线程可以并发执行。
2:线程是不能独立存在,必须存在于某一个进程中。
3:进程中的所有的任务都是依靠线程来完成的。进程对应的程序中的每一行代码都是要靠某一个线程来执行的。
4:当一个进程启动的时候,系统会创建一个线程,java程序中称为main线程–主线程。主线程的执行的路径就是main 方法的主体部分。
5:一个进程中的所有的线程共享所在的进程的资源。
6:线程的创建和销毁消耗的资源相比进程是小的。线程是轻量级进程。
7:线程是cpu进行调度执行的最小单位。

今日练习:

1:使用BufferedReader 和 BufferedWriter 实现:将指定的文件中的字符数据复制到另一个文件中。
要求:使用readLine 方法。读取行数据,只将包含了数字的行数据进行复制。将不包含数字的行数据过滤掉。
2:将编码格式为utf-8的文本文件的内容读取并显示到控制台。
3:将编码格式为GBK的文本文件的内容读取并显示到控制台。
4:将“我要好好学习,天天向上”字符串,使用utf-8 编码写出到某一个文件中。
5:将“我要好好学习,天天向上”字符串,使用 gbk 编码写出到某一个文件中。
6:使用带缓冲区的字节流,实现对某一个文件的复制。

(转)ByteArrayOutputStream 理解

第一次看到ByteArrayOutputStream的时候是在Nutch的部分源码,后来在涉及IO操作时频频发现这两个类的踪迹,觉得确实是很好用,所以把它们的用法总结一下。

ByteArrayOutputStream的用法

以下是JDK中的记载:

public class ByteArrayOutputStream extends OutputStream

此类实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray()和 toString()获取数据。

关闭 ByteArrayOutputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何IOException。

我的个人理解是ByteArrayOutputStream是用来缓存数据的(数据写入的目标(output stream原义)),向它的内部缓冲区写入数据,缓冲区自动增长,当写入完成时可以从中提取数据。由于这个原因,ByteArrayOutputStream常用于存储数据以用于一次写入。

实例:

从文件中读取二进制数据,全部存储到ByteArrayOutputStream中。

FileInputStream fis=new FileInputStream("test");

BufferedInputStream bis=new BufferedInputStream(fis);

ByteArrayOutputStream baos=new ByteArrayOutputStream();

int c=bis.read();//读取bis流中的下一个字节

while(c!=-1){

     baos.write(c);

     c=bis.read();

}

bis.close();

byte retArr[]=baos.toByteArray();

 

ByteArrayInputStream的用法

相对而言,ByteArrayInputStream比较少见。先看JDK文档中的介绍:

public class ByteArrayInputStreamextends InputStreamByteArrayInputStream 包含一个内部缓冲区,该缓冲区包含从流中读取的字节。内部计数器跟踪 read 方法要提供的下一个字节。

关闭 ByteArrayInputStream 无效。此类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。

构造函数:

ByteArrayInputStream(byte[] buf)

注意它需要提供一个byte数组作为缓冲区。

与大部分Inputstream的语义类似,可以从它的缓冲区中读取数据,所以我们可以在它的外面包装另一层的inputstream以使用我们需要的读取方法。

个人认为一个比较好的用途是在网络中读取数据包,由于数据包一般是定长的,我们可以先分配一个够大的byte数组,比如byte buf[]=new byte[1024];

然后调用某个方法得到网络中的数据包,例如:

Socket s=…;

DataInputStream dis=new DataInputStream(s.getInputStream());

dis.read(buf);//把所有数据存到buf中

ByteArrayInputStream bais=new ByteArrayInputStream(buf); //把刚才的部分视为输入流

DataInputStream dis_2=new DataInputStream(bais);

//现在可以使用dis_2的各种read方法,读取指定的字节

比如第一个字节是版本号,dis_2.readByte();

等等……

上面的示例的两次包装看上去有点多此一举,但使用ByteArrayInputStream的好处是关掉流之后它的数据仍然存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值