Java基础部分

Java入门

参考JavaGuide:https://snailclimb.gitee.io/javaguide

1、Java语言有哪些特点?

  • 简单易学
  • 面向对象(封装、继承、多态)
  • 平台无关性(JVM)
  • 可靠性
  • 安全性
  • 支持多线程
  • 支持网络编程并且很方便
  • 编译与解释并存

2、JVM、JDK和JRE

JVM:JVM是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现,目的是使用相同的字节码,他们都会给出相同的结果。字节码和不同系统的JVM是怎么是Java实验“一次编译,随处可以运行”的关键所在。

JDK:Java开发工具包。它拥有JRE所拥有的的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。

JRE:Java运行时环境。它是运行已编译的Java程序所需的所有内容的集合,包括JVM,Java类库,java命令和其他的一些基础构件。

3、Open JDK和Oracle JDK的对比

  • Oracle JDK大概每六个月发一次主要版本,而Open JDK版本大概每三个月发布一次
  • Open JDK是一个参考模型并且是完全开源的,而Oracle JDK是Open JDK的一个实现,并不是完全开源的
  • Oracle JDk比Open JDK更稳定。Open JDK与Oracle JDK代码几乎相同。但Oracle JDK有更多的类和一些错误修复
  • 在响应性和JVM性能方面,Oracle JDK与Open JDK相比提供了更好的性能
  • Oracle JDK不会为即将发布的版本提供长期支持,用户必须每次通过更新到最新版本获得支持
  • Oracle JDK根据二进制代码许可协议获得许可,而Open JDK根据GPL v2许可获得许可

4、Java和C++的区别

  • 都是面向对象的语言,都支持封、继承、多态
  • Java吧是提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承
  • Java有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存
  • 在C语言中,字符串或字符数组最后都会有一个额外字符’\0’来表示结束。但是,Java语言中没有结束符这一概念

5、为什么说Java语言“编译与解释并存”?

​ Java语言既有编译型语言的特征,也具有解释型语言的特征。因为Java程序要经过先编译,后解释两个步骤,由Java编写的程序需要先经过编译步骤,生成字节码(*.class文件),这种字节码必须由Java解释器来解释执行。因此,我们可以认为Java语言编译与解释并存。

Java语法

1、字符型常量和字符串常量的区别?

  • 形式上:字符常量是单引号引起的一个字符;字符串常量是双引号引起的0个或若干个字符
  • 含义上:字符常量相当于一个整型值(ASCII值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放的位置)
  • 占内存大小:字符常量只占2字节;字符串常量占若干字节(char在Java中占2字节

2、关于注释?

Java中有三种注释:

  • 单行注释
  • 多行注释
  • 文档注释

3、自增自减运算符

++和–运算符可以放在变量之前,也可以放在变量之后,当运算符放在变量之前时,先自增/减,再赋值;当运算符放在变量之后时,先赋值,再自增/减。

4、continue、break和return的区别是什么?

  • continue:指跳出当前的这一次循环,继续下一次循环
  • break:指跳出整个循环体,继续执行循环下面的语句
  • return:结束方法执行,用于没有返回值函数的方法
  • return value:return一个特定值,用于有返回值函数的方法

5、Java泛型了解么?什么是类型擦除?介绍一下常用的通配符?

  • Java泛型是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型

  • 泛型的本质是参数化类型,也就是说锁操作的数据类型被指定为一个参数

  • Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦除,这也就是通常所说的类型擦除

  • 泛型一般有三种使用方式:

    • 泛型类
    • 泛型接口
    • 泛型方法
  • 常用的通配符:T,E,K,V,?

    • ?表示不确定的Java类型
    • T(type)表示具体的一个java类型
    • K V(key value)分别代表java键值对中的Key Value
    • E(element)代表Element

6、==和equles的区别?

==:它的作用是判断两个对象的地址是否相等,即判断两个对象是否是同一个对象。(基本数据类型–>比较的是值,引用数据类型–>比较的是内存地址

equles():它的作用也是判断两个对象是否相等,它不能用于比较基本数据类型的变量。equles方法存在于Object类中,而Object类是所有类的直接或间接父类

  • 因为Java只有值传递,所有,对于==来说,不管是比较基本数据类型,还是引用数据类型,其本质比较的都是值,只是引用类变量存的值是对象的地址

  • equles()方法存在两种使用情况:

    • 类没有覆盖equles()方法。通过equles()方法比较该类的两个对象时,等价于通过”==“比较这两个对象。使用的默认是Object类的equles()方法
    • 类覆盖了equles()方法。一般,我们都覆盖equles()方法来判断两个对象的内容相等;若他们的内容相等,则返回true
  • String类中重写了Object类的equles()方法

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
    

7、hashCode()与equles()

1)hashCode()介绍

​ hashCode()的作用是获取哈希码,也称为散列码;他实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

​ 散列表存储的是键值对(key-value),它的特点是:能根据”键“快速的监所出对应的“值”。

2)为什么重写equles时必须重写hashCode方法?

​ 如果两个对象相等,则hashCode也一定是相同的。两个对象相等,对两个对象分别调用equles和hashCode方法都返回true。但是,两个对象有相同的hashCode值,他们也不一定是相等的。因此,equles方法被覆盖过,则hashCode方法也必须被覆盖。

3)为什么两个对象有相同的hashCode值,他们也不一定是相等的?

​ 因为hashCode()所使用的的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指不同的对象得到相同的hashCode)

基础数据类型

1、Java中的几种基本数据类型是什么?对应的包装类型是什么?各自占用多少字节呢?

Java中有8中基本数据类型,分别为:

  • 6种数字类型:byte、short、int、long、float、double
  • 1种字符类型:char
  • 1种布尔类型:boolean

这八种基本类型都有对应的包装类分别为:Byte、Short、Integer、Long、Float、Double、Character、Boolean

基本类型位数字节默认值
int3240
short1620
long6480L
byte810
char162‘u0000’
float3240f
double6480d
boolean1false

注意:

  • Java里使用long类型的数据一定要在数值后面加上L,否则将作为整型解析
  • char a = ‘h’ char:单引号;String a = “hello” String:双引号

2、自动装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来
  • 拆箱:将包装类型转换为基本数据类型

3、8种基本类型的包装类和常量池

Java基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;前面4种包装类默认创建了数值[-128,127]的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回True Or False。如果超出对应范围任然会去创建新的对象。

两种浮点数类型的包装类Float,Double并没有实现常量池技术。

方法

1、为什么Java中只有值传递?

  • 按值调用(call by value)表示方法接受的是调用者提供的值,而按引用调用(call by reference)表示方法接受的是调用者提供的变量地址。
  • 一个方法可以修改传递引用锁对应的值,而不能修改传递值调用所对应的变量值。
  • Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给他的任何参数变量的内容。
  • 总结:
    • Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的
    • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)
    • 一个方法可以改变一个对象参数的状态
    • 一个方法不能让对象参数引用一个新的对象

2、重载和重写的区别

  • 重载:发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法的返回值和访问修饰符可以不同
  • 重载就是统一个类中多个同名方法根据不同的传参来执行不同的逻辑处理
  • 重写:重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写
    • 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类
    • 如果父类方法访问修饰符为private/final/static,则子类就不能重写该方法,但是被static修饰的方法能够被再次声明
    • 构造方法我无法被重写
  • 重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变

3、浅拷贝和深拷贝

  • 浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,称为浅拷贝
  • 深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,称为深拷贝

Java面向对象

1、面向对象和面向过程的区别?

  • 面向过程:面向过程性能比面向对象高。因为类调用时需要实例化,开销比较大,比较耗资源,所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix当一般采用面向过程开发、但是,面向过程没有面向对象易维护、易复用、易扩展。
  • 面向对象:面向对象易维护、易复用、易扩展,因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护。但是,面向对象性能比面向过程低。

2、构造方法有哪些特性?

  • 方法名与类名相同
  • 没有返回值,但不能用void声明构造函数
  • 生成类的对象是自动执行,无需调用

3、面向对象三大特性

  • 封装:封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。
  • 继承:继承是使用已存在的类定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性的继承父类。
    • 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问的,只是拥有
    • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
    • 子类可以拥有自己的方式实现父类的方法。
  • 多态:表示一个对象具有多种的状态。具体表现为父类的引用指向子类的实例。
    • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系。
    • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定。
    • 多态不能调用“只在子类存在但在父类中不存在”的方法。
    • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

4、String、StringBuilder和StringBuffer的区别是什么?String为什么是不可变的?

可变性:

  • String类中使用了final关键字修饰字符数组来保存字符串,private final char[] value,所以String对象是不可变的
  • 在Java9之后,String、StringBuilder和StringBuffer的实现改用byte数组存储字符串 private final byte[] value
  • StringBuilder和StringBuffer都继承自AbstractStringBuilder类,在该类中也是使用字符数组保存字符串,但是没有使用final关键字修饰,所以这两种对象都是可变的。

线程安全性:

  • String中的对象是不可变的,也就可以理解为常量,线程安全。
  • AbstractStringBuilder是StringBuilder和StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
  • StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
  • StringBuilder并没有对方法进行加同步锁,所有是非线程安全的。

性能:

  • 每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。
  • StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。
  • 相同情况下使用StringBuilder相比使用StringBuffer仅能获得10%~15%左右的性能提升,但却要冒多线程不安全的风险。

总结:

  • 操作少量的数据:使用String
  • 单线程操作字符串缓冲区下操作大量数据:适用StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据:适用StringBuffer

5、Object类的常见方法总结

Object类是一个特殊的类,是所有类的直接或间接父类。主要提供了以下方法:

public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。

public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。

protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。

public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。

public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。

public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。

public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。

public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。

public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念

protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作

6、Java序列化中如果有些字段不想进行序列化,怎么办?

  • 对不想进行序列化的字段,适用transient关键字修饰
  • transient关键字的作用:阻止实例中那些用此关键字修饰的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。
  • transient只能修饰变量,不能修饰类和方法

7、获取键盘输入常用的两种方法

方法一:通过Scanner

Scanner input = new Scanner(System.in);
String s  = input.nextLine();
input.close();

方法二:通过BufferedReader

BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();

Java核心技术

1、反射机制

​ Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息及调用对象的方法的功能称为Java语言的反射机制。

静态编译和动态编译

静态编译:在编译时确定类型,绑定对象

动态编译:运行时确定类型,绑定对象

反射机制的优缺点

优点:运行期类型的判断,动态加载类,提高代码灵活度

缺点:

  • 性能瓶颈:反射相当于一系列及时操作,通知JVM要做的事情,性能比直接的Java代码要慢很多
  • 安全问题:让我们可以动态操作改变类的属性同时也增加了类的安全隐患

反射的应用场景:反射是框架设计的灵魂

2、异常

Java异常类层次结构图

img

​ 在Java中,所有的异常都有一个共同的祖先:java.lang包中的Throwable类。Throwable类有两个重要的子类:Exception(异常)和Error(错误)。Exception能被程序本身处理(try-catch),Error是无法处理的(只能尽量避免)。

受检查异常:CheckedException

​ Java代码在编译过程中,如果受检查异常没有被catch/throw处理的话,就没办法通过编译。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于受检查异常。

不受检查异常:RuntimeException

​ Java代码编译过程中,即使不处理不受检查异常也可以正常通过编译。RuntimeException及其子类都统称为非受检查异常。

Throwable类的常用方法

  • public String getMessage():返回异常发生时的简要描述信息
  • public String toString():返回异常发生时的详细信息
  • public String getLocalizedMessage():返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息是getMessage()相同
  • public void printStackTrace():在控制台上打印Throwable对象封装的异常信息

try-catch-finally

  • try块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块
  • catch块:用于处理try捕获到的异常
  • finally块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行

在一下3中情况下,finally块不会被执行

  • 在try或finally块中用了System.exit(int)退出程序。但是,如果System.exit(int)在异常语句之后,finally块还是会被执行
  • 程序所在的线程死亡
  • 关闭CPU

使用try-with-resource来代替try-catch-finally

  • 适用范围(资源的定义):任何实现java.lang.AutoCloseable或者java.io.Closeable的对象
  • 关闭资源和finally的执行顺序:在try-with-resource语句中,任何catch和finally块在声明的资源关闭后运行

《Effective Java》中明确指出:

​ 面对必须要关闭的资源,去我们总是应该优先使用try-with-resource而不是try-finally。随之产生的代码更简洁,更清晰,产生的异常对我们也更有用。try-with-resource语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。

例如:

//使用try-catch-finally
//读取文本文件的内容
Scanner scanner = null;
try {
  scanner = new Scanner(new File("D://read.txt"));
  while (scanner.hasNext()) {
    System.out.println(scanner.nextLine());
  }
} catch (FileNotFoundException e) {
  e.printStackTrace();
} finally {
  if (scanner != null) {
    scanner.close();
  }
}

//使用try-with-resource
try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

3、多线程

3.1、简述线程、程序和进程的基本概念,以及他们之间的关系是什么?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程。或是在各个线程之间切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行者,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中,线程是进程划分成更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

3.2、线程有哪些状态?

Java线程在运行的生命周期中的制指定时刻只可能处于下面6种不同状态的其中一个状态:

  • NEW:初始状态,线程被构建,但是还没有调用start()方法
  • RUNNABLE:运行状态,Java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”
  • BLOCKED:阻塞状态,表示线程阻塞于锁
  • WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)
  • TIME_WAITING:超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
  • TERMINATED:终止状态,表示当前线程已经执行完毕

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4 节):

Java线程状态变迁

4、文件与I/O流

4.1、Java中IO流分为几种?

  • 按照流的流向分:可以分为输入流和输出流

  • 按照操作单元划分:可以划分为字节流和字符流

  • 按照流的角色划分:可以划分为节点流和处理流

  • InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流

  • OutputStream/Writer:所有输出流的基类,前者是字节输出流,后者是字符输出流

IO-操作对象分类

4.2、既然有了字节流,为什么还要有字符流?

问题的本质:不管是文件读写还是网络发送接收,信息的最小单元都是字节,那为什么I/O流操作还要分为字节流和字符流操作呢?

​ 字符流是由Java虚拟机将字节码转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码六星就很容易出现乱码问题。所以,I/O流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

4.3、BIO、NIO和AIO有什么区别?

  • **BIO(Blocking I/O)😗*同步阻塞I/O模式,数据的读写必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的I/O并且变成模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的BIO模型是无能为力的。因此,我们需要一种更高效的IO处理模型来应对更高的并发量。
  • **NIO(Non-blocking/New I/O)😗*NIO是一种同步非阻塞的IO模型,在Java1.4中引入了NIO框架,对应java.nio包,提供了Channel,Selector,Buffer等抽象。NIO中的N可理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的IO操作方法。NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞IO来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。
  • **AIO(Asynchronous I/O)😗*也就是NIO2。在Java7中引入了NIO的改进版NIO2,它是异步非阻塞的IO模型。异步IO是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO是异步IO的缩写,虽然NIO在网络操作系统中,提供了非阻塞的方法,但是NIO的IO行为还是同步的。对于NIO来说,我们的业务线程是在IO操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。查阅网上相关资料,发现就目前来说AIO的应用还不是很广泛,Netty之前也尝试使用过AIO,不过又放弃了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值