JavaSE重要知识点

一、JavaSE基础部分

1.Java程序还会出现内存溢出和内存泄漏问题吗?

Java有自动的内存回收机制,会自动回收垃圾,但是如果内存中的所有东西都是我们需要使用的,那么就没有垃圾。如果堆满了内存,那么就会导致溢出。

GC如果判断我们不适用的东西不是垃圾,那么就是内存泄露,内存泄露会导致内存溢出。

2.Java中的8种基本数据类型

  • 整形:byte \ short \ int \ long 【大小分别是:1、2、4、8字节】
  • 浮点型: float \ double 【大小分别是:4、8字节】
  • 字符型:char 【大小:2字节】
  • 布尔型: boolean【编译的时候不谈占几个字节,但是JVM会分配4个字节】

3.基本数据类型之间的自动转换【7种】

byte\short\char—>int—>int—>long---->float—>double

特别的:byte和short之间的做运算结果是int

4.前++和后++的区别

前++:然后自增,然后赋值

int i=2;
i=++i;//i=3

后++:先赋值,然后自增

int i=2;
i=i++;//i=2

5.instanceof

判断一个对象是否是这个类的实例【要求这个对象必须是引用类型】

6.&和&&的区别

&:所有的条件都会判断一遍

&&:当前面的条件不成立的时候,后面的条件就不会判断了【开发中推荐这个】

7.switch分支

switch中的表达式只能是特定大的数据类型:byte、short、int、char、enum、String

case后只能跟常量,不能是表达式

8.多态

属性不满足多态

9.Object类的常用方法

提供了很多方法例如:equals( )、toString( )、clone( )、finalize( )、getClass( )、hashCode( )、notifyall( )、wait( )

  • clone方法:克隆一个当前对象的对象【复制品,但是hashCode不一样】
  • finalize方法:当GC要回收这个对象的时候,就会调用这个方法,现在不建议使用了【了解】
  • equals方法:看下面【equals()和==的区别】

10.equals()和==的区别。为什么重写equals要重写hashcode?

  1. == 是运算符,equals是来自于Object类的一个方法
  2. == 可以用于基本数据类型和引用类型,equals只能用于引用类型
  3. == 两端如果是基本类型,那么就是判断值是否相等,如果是引用类型,判断的是地址是否相同,equals如果不重写,就是等等号,equals在重写之后,判断两个对象的属性值是否相同
  4. 重写equals可以让我们自己定义判断两个对象是否相同的条件
  5. 如果只重写equals会导致通过自定义条件比较两个对象返回的是true,但是查看哈希码发现,这两个对象的hash码不同,从而和equals方法返回的结果违背。因此需要重写hashcode自定义哈希码的生成规则

11.重写和重载的区别

重载:是同一个类内的方法,只要求方法名必须一样,参数列表一定不一样。和返回值无关。

重写:是对于父类的方法,要求除了方法体里面的东西都一样,而且抛出的异常只能是父类方法抛出异常的子类【或者一样】,异常范围不能扩大

12.static的用法

  • 静态属性、静态方法:是两个最基本的用法,被static修饰,是所有实例共享的,可以被继承,不能被重写

  • 静态代码块:用于初始化数据

  • 静态内部类

  • 静态导包:可以用来指定导入某个类中的静态资源,并且不需要使用类名,可以直接使用资源名

    import static java.lang.Math.*;
    public class Test{
    	 public static void main(String[] args){
     		//System.out.println(Math.sin(20));传统做法
     		System.out.println(sin(20));
     	}
    }
    

13.代码块

静态代码块先执行,非静态代码块后执行

  • 静态代码块:被satic修饰,随着类的加载而执行,由于类加载只会执行一次,所以静态代码块只会执行一次。可以用来初始化类的信息
  • 非静态代码块:随着对象的创建而执行,每创建一个对象,非静态代码块都会执行一次。可以初始化对象的信息

14.类的初始化过程

  • 没有继承的情况:
    1. 静态代码块和静态方法
    2. 普通代码块和普通方法
    3. 构造器
  • 有继承的情况:
    1. 父类的静态代码块
    2. 子类的静态代码块
    3. 父类普通代码块
    4. 父类构造器
    5. 子类普通代码块
    6. 子类构造器

15.final的用法

  • 被修饰的类不能被继承
  • 被修饰的方法不能被重写
  • 被修饰的变量如果是普通类型不可以改变,如果是引用类型表示引用不可以改变,但是引用的地址可以改变

16.String转基本数据类型【包装类】

直接调用包装类的parseXXX方法

image-20230805153713096

17.Excption与Error

Throwable分为两大部分:Error和Exception。Error是JVM出现错误时候产生的。Exception分为两部分:运行时异常和编译时异常

运行时异常:

  • indexOutOfBoundsException(数组越界)
  • NullPointerException(空指针异常)
  • ClassCastException(类转换异常)【String转Date类型】
  • InputMissmatchException(输入不匹配异常)【scanner中的】
  • ArithmeticException(算数异常)【除以0】

编译时异常:

image-20230805162829154

18.try catch finally,try里有return,finally还执行么?

1、不管有木有出现异常,finally块中代码都会执行;

2、当try和catch中有return时,finally仍然会执行;

3、finally是在return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的 值保存起来,管finally中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在finally执行前确定的;

4、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。

19.throw和throws的区别

  1. 使用方法不同:throw:是手动在方法里抛出异常,throws:是写在方法的声明上抛出
  2. 使用场景不同:throw是抛出异常,也就是产生异常,throws是处理异常的一种方式

20.程序进程线程的概念

  • 程序:为完成特定任务,用某种语言编写的一组指令的集合,即一段静态代码。

  • 进程:程序的一次执行过程,或是正在内存中运行的应用程序【例如:运行中的QQ、音乐播放器】

    进程是操作系统调度和分配资源的最小单位

  • 线程:进程可以进一步细化为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。【例如:360同时扫描垃圾、杀病毒、体检】

    线程是CPU调度和执行的最小单位

21.线程调度

  • 分时调度
  • 抢占式调度

22.创建线程的方式

  1. 继承Thread类,重写run方法

    Thread类实现了Runable接口

  2. 实现Runable接口,实现run方法

  3. 实现Callable接口,将此线程需要执行的操作写道call方法中【可以有返回值,可以抛出异常】

  4. 使用线程池

23.启动线程的方式

  1. 如果这个类继承了Thread类,那么就创建这个类的对象,然后调用start方法【不能让已经start的线程再次start】

  2. 如果这个类实现了Runable接口那么就创建这个类,然后创建Thread对象,把实现Runable的对象放到Thread里面,调用start方法【因为Runable接口没有start方法】

    推荐实现Runable接口,因为共享数据更简单【直接设置属性就可以,如果是Thread,那么就需要设置为静态的属性】

24.线程的生命周期

image-20230806131037376

25.wait和sleep的区别

  • wait是Object提供的,他是无限等待,只能使用在同步代码块和同步方法中,一旦执行会释放同步监视器,需要使用Object类提供的notify方法唤醒
  • sleep是Thread类提供的,是计时等待,可以使用在任何位置,一旦执行不会释放同步监视器,时间到了自动唤醒,进入准备状态

26.怎么解决线程安全问题

什么是线程安全问题:以单例模式的懒汉式来说,两个线程同时进入了if判断,发现没有这个对象,那么这两个线程就会同时创建对象,从而违反了单例模式。【或者是卖票的例子】下面是解决方法

  • 解决方法1:使用关键字synchronzied定义一个同步代码块,将需要被同步的代码监视起来【监视对象随便,但是需要对象是同一个且唯一,所以最方便的是XXX.class】
  • 解决方法2:在方法中使用synchronized关键字
  • 解决方法3:使用JUC中的ReentranLock【简称Lock】,先创建Lock实例,然后在被监视的代码前面调用lock方法,执行完毕后调用unlock方法【需要确保是一个Lock对象】

27.死锁产生的原因

下面四个条件同时产生才会发生死锁:

  1. 资源互斥
  2. 占用且等待
  3. 不可抢夺
  4. 循环等待

28.解决死锁的方法

只要破坏一个条件就行:

image-20230806230755938

29.synchronized和Lock的对比

synchronized不管在同步代码块还是在同步方法中,都是在大括号结束后才释放锁

Lock可以在任意地方释放锁,更灵活

Lock作为接口,提供了多个实现类,适合更多复杂的场景,效率更高

30.字符串常量的位置

字符串常量都存在字符串常量池中,不允许重复,所以存放两个hello,那么他们的地址一样,字符串常量池在堆中

31.String、StringBuffer、StringBuilder三个的区别

  1. String是不可变的字符序列,也是线程不安全的,StringBuffer和StringBuilder是可变的字符序列
  2. StringBuffer是线程安全的,但是效率低,StringBuilder线程不安全,但是效率高
  3. jdk9之后,他们底层都是用Byte数组
  4. 如果开发中需要频繁的对字符串进行增删改操作,建议使用StringBuffer和StringBuilder替代String,因为String效率低。

32.集合框架介绍

  • Collection:存储一个一个的数据,子接口有:List、Set
    1. List:存储有序、可重复的数据。实现类:ArrayList,LinkedList,Vector
    2. Set:存储无序的、不可重复的数据。实现类:HashSet、LinkedHashSet、TreeSet
  • Map:存储一对一对的数据,主要实现类有:HashMap、LinkedHashMap、TreeMap【set的实现类底层和Map的子接口是一一对应的】、Hashtable、properties

33.Collection介绍

  1. 是ArrayList,LinkedList,Vector的父接口
  2. 有两种遍历方式:使用增强for循环或者使用迭代器

34.List及其实现类的特点

List是Collection的子接口,有三个实现类:ArrayList,LinkedList,Vector

  • ArrayList:List的主要实现类,线程不安全,效率高,底层使用Object数组,添加、查询快
  • LinkedList:底层使用双向链表的方式进行存储,插入、删除快
  • Vector:很老的实现类,现在使用的 很少,线程安全,效率低,底层使用Object数组

35.Set及其实现类

Set是Collection的子接口,有三个实现类:HashSet、LinkedHashSet、TreeSet。Set中常用的方法就是Collection中声明的15个方法。

  • HashSet:是Set的主要实现类,底层使用的是HashMap,即使用数组+单向链表+红黑树结构进行存储,判断数据是否相同的标准是使用equals和hashcode
  • LinkedHashSet:是HashSet子类,在HashSet结构基础上,又添加了一组双向链表,用于添加元素的先后顺序,所以可以按照元素的添加顺序进行遍历。便于频繁的查询操作
  • TreeSet:底层使用红黑树存储,要求添加到里面的元素必须是同一个类型的对象。判断是不是同一个对象是根据compareTo方法或者compare的返回值

36.Map及其实现类

Map是以键值对的形式存储数据,有以下几个实现类:

  • HashMap:主要实现类,线程不安全,效率高,底层使用数组+单向链表+红黑树结构

    如果索引i位置上的元素个数达到8,并且数组的长度达到64,我们就将此索引i位置上的元素改为红黑树的结构进行存储

    如果索引i位置上的元素个数小于6,那么就会从红黑树退化为单项链表

  • Hashtable:古老实现类,线程安全,效率低,底层使用数组+单向链表的结构

  • LinkedHashMap:是HashMap的子类,在HashMap的结构基础上增加了双向链表,用于记录元素添加的先后顺序,所以遍历的时候可以按照添加顺序显示。对于频繁遍历,建议使用此类

  • TreeMap:底层使用红黑树,可以按照key的属性的大小进行遍历

  • properties:是Hashtable的子类,键和值都是String类型

Map中的常用方法:

image-20230807160201491

37.HashMap和Hashtable的区别

  1. HashMap线程不安全,Hashtable线程安全
  2. HashMap中可以有一个为null的key,Hashtable的key和value都不能为null
  3. HashMap底层使用数组+单向链表+红黑树结构,Hashtable底层使用数组+单向链表的结构

38.区分Collection和Collections

  1. Collection是集合框架中用于存储一个一个元素的接口,又分为List和Set等子接口
  2. Collections是用于操作集合框架的工具类,可以操作List、Set、Map

39.二叉树的三种遍历方式

  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中

image-20230808134239230

40.红黑树

  1. 根节点是黑色的
  2. 每个子节点可以是红的也可以是黑色的
  3. 黑色节点的子节点可以是红的也可以是黑的,红色节点的子节点必须是黑色的
  4. 每个叶子节点是黑色的
  5. 不是平衡二叉树,是排序二叉树【左边小右边大】

41.ArrayList底层源码分析

  1. 创建ArrayList对象的时候,底层会初始化一个空的数组
  2. 当首次添加元素的时候,会将数组初始化为10
  3. 当要添加11个元素的时候,原来的数组已经满了,需要扩容。默认扩容为原来长度的1.5倍,然后将原有数组中的元素赋值到新的元素中

42.LinkedList底层源码分析

  1. 创建LinkedList对象的时候底层没做什么
  2. 添加第一个数据的时候,对象的first、last属性会指向这个节点
  3. 添加第二个数据的时候,节点1和节点2构成双向链表,同时last指向节点2

43.ArrayList和LinkedList的区别

  1. 两者都是List接口的实现类,但是他们底层的数据结构不同
  2. ArrayList可以理解为是一个变长数组,底层是数组,因此地址是连续的
  3. LinkedList底层是双向链表,因此节点地址可以不是连续的
  4. 然后说源码
  5. 如果添加、查询使用的多建议使用ArrayList,如果删除、插入使用的多建议使用LinkedList
  6. 如果能够确定长度,那么建议使用ArrayList,因为不需要扩容和复制数据

43.HashMap底层源码分析

jdk8中创建对象的时候,底层并没有初始化一个大小为16的数组,当首次添加k,v的时候,进行判断,如果发现table尚未初始化,则对数组进行初始化。

扩容条件【两个必须同时满足】:

  1. 元素的个数达到临界值【当前大小×临界因子】临界因子为0.75【例如:16*0.75】
  2. 当前要放元素的位置不为null

默认扩容为当前容量的2倍

数据添加过程:

首先调用key所在类的hashCode方法计算出key对应的哈希值1,然后哈希值1经过某种算法(hash算法),得到哈希值2

哈希值2经过某种算法(indexFor算法)之后,就确定了其数组再table中的索引位置i

如果索引位置i的数组上没有元素,则key1,v1添加成功

如果索引位置i的数组上有元素(key2,value2),则继续比较key1和key2的哈希值2,如果key1的哈希值2与key2的哈希值2不相同,则key1,v1添加成功;

如果key1和key2的哈希值2 相同,则需要继续比较key1和key2的equals方法。如果返回false,则k1添加成功,如果返回true,则认为k1和k2相同,默认value1替换原有的value2

44.File类

File类没有对文件内容读写的具体操作,只有对文件的创建、删除等操作,通常作为参数传给具体的IO读写类

45.IO流介绍

IO流主要有两大类:字节流【用来处理非文本文件】和字符流【只能操作文本文件】

都是从下面这4个抽象类派生出来的:字节流:InputStream、OutputStream 字符流:Reader、Writer

image-20230817112702113

46.TCP的三次握手

TCP/IP三次握手的过程简写如下:

  1. 客户端向服务器发送一个SYN报文,其中包含客户端的初始序列号。
  2. 服务器收到SYN报文后,向客户端发送一个SYN+ACK报文,其中包含服务器的初始序列号和确认号(ACK)。
  3. 客户端收到SYN+ACK报文后,向服务器发送一个ACK报文,其中包含客户端的最终序列号和确认号。

总结:这个过程可以简写为:客户端 SYN -> 服务器 SYN+ACK -> 客户端 ACK。

47.反射获取Class对象的方法

  1. 第一种:已经知道类的全路径,调用Class.forName方法

    Class aClass = Class.forName(classpath);
    
  2. 第二种:知道类名,调用class属性

    //有一个Person类
    Class class=Person.class;
    
  3. 第三种:已经有类的对象,调用对象的getClass方法

    Person p1=new Person();//获得一个对象
    Class class=p1.getClass();//调用对象的方法获得Class对象
    

48.类的加载过程

  1. 第一步:加载

    将类的class文件读入内存,并创建一个Class对象

  2. 第二步:连接【具体又分为3步】

    1. 验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

      可以考虑使用-Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。

    2. 准备:对静态变分配内存并初始化((对应数据类型的默认初始值,如0、0L、null、false 等)。

    3. 解析:虚拟机将常量池内的符号引用替换为直接引用

  3. 第三步:初始化

    真正开始执行类中定义的 Java程序代码,此阶段是执行( )方法的过程。

    ( )方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。

49.类加载的顺序

父类静态代码块–>子类静态代码块–>父类普通代码块–>父类构造器–>子类普通代码块–>子类构造器

50.反射中常用的方法

方法作用
getFields获取所有public修饰的属性,包含本类以及父类的
getDeclaredFields获取本类中所有属性
getMethods获取所有public修饰的方法,包含本类以及父类的
getDeclaredMethods获取本类中所有方法
getConstructors获取所有public修饰的构造器,包含本类
getDeclaredConstructors获取本类中所有构造器
getDeclaredAnnotation获取注解
invoke执行方法
newInstance创建对象

51.创建对象的几种方式

  1. 直接new一个
  2. 调用反射
  3. clone方法【不调用任何构造器,当前类需要实现Cloneable接口,实现clone方法】
  4. 反序列化

52.java8特性—Lambda表达式

Lambda表达式的作用:替代原本创建一个类,实现接口的过程

使用要求:接口中只能包含一个方法

  1. Lambda表达式的举例:

    (o1,o2) ->Integer.compare(o1,o2);
    
  2. Lambda表达式格式说明:

    ->:是lambda操作符

    ->的左边:是lambda形参列表,对应着要重写的接口中的抽象方法的形参列表

    ->的右边:是lambda体,对应着重写后方法的方法体

  3. Lambda表达式的使用说明:

    1. 如果方法中只需要一个参数,那么参数的小括号可以省略
    2. lambda表达式作为接口的实现类的对象
    3. 如果接口中只有一个抽象方法,那么此接口就称为函数式接口,只有函数式接口,才能使用lambda表达式
  4. Lambda表达式的具体案例:

    //原本的写法
    //Comparator接口只有一个compara方法
    Comparator<Integer> com1=new Comparator<Integer>(){
    	@Override
    	public int compara(Integer o1,Integer o2){
    		return Integer.compare(o1,o2);
    	}
    };
    //Lambda表达式的写法
    Comparator<Integer> com1=(o1,o2) ->Integer.compare(o1,o2);
    

53.java8特性—方法引用

在lambda表达式的基础上进一步简化

1.举例

Integer :: compare;

2.方法引用的理解

方法引用:可以看作是基于lambda表达式的进一步简化

当需要提供一个函数式接口的实例时,我们可以使用lambda表达式提供此实例。

当满足一定条件的情况下,我们还可以使用方法引用或构造器引用替换lambda表达式

3.格式

类(或对象):: 方法名

4.具体使用情况说明

  1. 情况1:对象 :: 实例方法

    使用要求:接口的抽象方法的返回类型和参数列表与方法体中调用方法的返回类型和方法体一样

    注意:方法体中调用的方法要求是非静态方法

    //情况说明
    //有一个consumer接口,接口里面有一个accept方法
    //现在要求不写接口的实现类,但是要调用consumer接口的方法
    
    //方式1:内部类
    Consumer<String> con1=new Consumer(){
    	public void accept(String s){
    	System.out.println(s);
    	}
    };
    con1.accept("hello!");
    
    //方式2:lambda表达式
    Consumer <String> con2=(s) -> System.out.println(s);
    con2.accept("hello!");
    
    //方式3:方法引用
    //因为accept方法和println方法的参数列表、返回类型一样,所以可以使用方法引用
    Consumer <String> con3=System.out :: println;
    con3.accept("hello!");
    
  2. 情况2:类 :: 静态方法

    使用场景:抽象方法中的方法里面调用了静态方法,需要使用这种(使用方法和情况1一样)

  3. 情况3:类 :: 实例方法

    要求:

    1. 函数时接口中的抽象方法a与其内部实现时调用的对象的某个方法b的返回值类型相同
    2. 同时,抽象方法a中有n个参数,方法b中有n-1个参数,且抽象方法a的第1个参数作为方法b的调用者,且抽象方法a的后n-1个参数与方法b的n-1个参数的类型相同

    注意:此方法b也是非静态方法

    //方式1:内部类
    Consumer<StringString> con1=new Consumer(){
    	public boolean accept(String s1,String s2){
    		return s1.equals(s2);
    	}
    };
    
    //方式二:方法引用
    Consumer<StringString> con2=String :: equals;
    

54.java8特性—SteamAPI

为什么需要StreamAPI?

在MySQL中,我们可以使用SQL的条件查询,查询出符合指定条件的数据,但是在非官关系型数据库(Redis)中,我们没有相关的指令,因此拓展出了StreamAPI,用来过滤数据

StreamAPI关注的是多个数据的计算(排序、查找、过滤、映射、遍历等),可以对集合进行计算

1.Stream的使用说明

  1. Steam 自己不会存储元素
  2. Stream 不会改变源对象,他们会返回一个持有结果的新Stream
  3. Stream操作时延迟执行的,这意味着他们会等到需要结果的时候才执行。即只有执行了终止操作,才会执行中间操作,才会产生操作结果
  4. Stream一旦执行了终止操作,就不能再调用其他中间操作或终止操作了

2.Stream的使用步骤

  1. 步骤1:Stream的实例化

    有三种方式实例化Stream

    1. 方法一:通过集合获取

      List<Employee> list=EmployeeData.getEmployee();
      list.stream();//返回一个顺序流
      list.parallelStream();//返回一个并行流
      
    2. 方法二:通过数组对象Arrays

      Integer[] arr=new Integer[]{1,2,3};
      Arrays.stream(arr);
      
    3. 方法三:通过Stream的of方法

      Stream.of("aa","bb","cc","dd");
      
  2. 步骤2:一系列中间操作

    1. 第一类:筛选与切片

      • filter:根据条件筛选
      • limit(n):截断流,使其元素不超过给定个数
      • skip(n):跳过前n个元素【不足n个返回空流】
      • distinct:去重【根据hashcode和equals方法】
    2. 第二类:映射【接收一个参数,将元素转化成其他形式】

      • map:将元素转换成其他形式
    3. 第三类:排序

      • sorted:排序【要求需要排序的类实现Comparable接口或者使用lambda表达式】
  3. 步骤3:执行终止操作

    • forEach:遍历数据
    • allMatch(规则):检查所有的元素是否匹配规则【返回布尔类型】
    • anyMatch(规则):检查是否至少有一个符合规则【返回布尔类型】
    • findFirst:返回第一个元素【返回Optional类型,如果需要返回原类型,再调用get方法】
    • count:返回流中元素的总个数
    • max(比较规则):返回流中最大值
    • min(比较规则):返回流中最小值
    • reduce([初始值],操作方法):可以将流中的元素反复结合,最后得到一个值
    • collect(Collectors类型):将找到的数据存起来

3.Stream的操作案例

//查询员工表中薪资大于7000的员工信息
List<Employee> list=EmployeeData.getEmployee();
Stream <Employee> stream=list.stream();
stream.filter(emp -> emp.getSalary() >7000).forEach(System.out :: println);

//只显示两个人
//因为上面已经使用了终止操作,所以需要再创建一个stream流
list.stream().limit(4).forEach(System.out :: println);

//跳过前两个元素
list.stream().skip(2).forEach(System.out :: println);

//去重
list.stream().distinct().forEach(System.out :: println);

//将元素全部转换成大写
List<String> list=Arrays.asList("aa","bb","cc","dd");
list.stream().map(str ->str.toUpperCase()).forEach(System.out :: println);

//是否存在年龄大于18岁的员工
list.stream().anyMatch(emp ->emp.getAge()>18);

//计算1-10的自然数的和
List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8);
System.out.println(list.stream().reduce(0,(x1,x2)->x1+x2));
//查询员工表中薪资大于7000的员工信息,并返回一个list集合
List<Employee> list=EmployeeData.getEmployee();
Stream <Employee> stream=list.stream();
stream.filter(emp -> emp.getSalary() >7000).collect(Collectors.toList());
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值