Java面试(基础篇)——解构Java常见的基础面试题 & 结合Java源码分析_java源码面试题(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

在这里插入图片描述

Executors 里面默认提供的几个线程池是有一些弊端的,如果是不懂多线程、或者是新手直接盲目使用,就可能会造成比较严重的生产事故。

为什么不能用

  • 1.FixedThreadPool 和SingleThreadPool 中,阻塞队列长度是Integer.Max_Value,一旦请求量增加,就会堆积大量请求阻塞在队列中,可能会造成内存溢出的问题;

在这里插入图片描述

  • 2.CachedThreadPool 和ScheduledThreadPool 中最大线程数量是Integer.Max_value,一旦请求量增加,导致创建大量的线程,使得处理性能下降。

在这里插入图片描述

JDK 动态代理为什么只能代理有接口的类

在Java 里面,动态代理是通过Proxy.newProxyInstance()方法来实现的,它需要传入被动态代理的接口类。

在这里插入图片描述
加入如下代码进行运行:

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

或者加入下面这句

System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");

在这里插入图片描述
JDK 动态代理会在程序运行期间动态生成一个代理类$Proxy0,这个动态生成的代理类会继承java.lang.reflect.Proxy 类,同时还会实现被代理类的接口。

在Java 中,是不支持多重继承的,而每个动态代理类都会继承Proxy 类(这也是JDK动态代理的实现规范) ,所以就导致JDK 里面的动态代理只能代理接口,而不能代理实现类。

在这里插入图片描述

spring中的代理

Spring进阶(AOP的理解)——静态/动态代理 & 面向切面编程AOP(Aspect Oriented Programming) & 日志记录 & 增强方法

在这里插入图片描述

如果一定要针对普通类来做动态代理,可以选择cglib 这个组件,它会动态生成一个被代理类的子类,子类重写了父类中所有非final 修饰的方法,在子类中拦截父类的所有方法调用从而实现动态代理。

Java对象相关的面试题

对象的创建过程

在这里插入图片描述

(1)类加载检查
JVM 首先会去检查目标对象是否已经被加载并初始化了。

如果没有,JVM 需要立刻去加载目标类,然后调用目标类的构造器完成初始化。目标类的加载是通过类加载器来实现的,主要就是把一个类加载到内存里面。

然后初始化的过程,主要是对目标类里面的静态变量、成员变量、静态代码块进行初始化。

(2)分配内存空间

当目标类被初始化以后,就可以从常量池里面找到对应的类元信息,并且目标对象的大小在类加载之后就已经确定了,所以这个时候就需要为新创建的对象,根据目标对象的大小在堆内存里面分配内存空间。

内存分配的方式一般有两种,一种指针碰撞,另一种是空闲列表,JVM 会根据Java 堆内存是否规整来决定内存分配方式。

(3)初始化 ”零值”

JVM 会把目标对象里面的普通成员变量初始化为零值,比如int 类型初始化为0 ,对象类型初始化为null, (类变量在类加载的准备阶段就已经初始化过了) 。

这一步操作主要是保证对象里面的实例字段,不用初始化就可以直接使用,也就是程序能够获得这些字段对应数据类型的零值。

(3)设置对象头”

然后,JVM 还需要对目标对象的对象头做一些设置,比如对象所属的类元信息、对象的GC 分代年龄、hashcode、锁标记等等。

(4)执行init方法

完成这些步骤以后,对于JVM 来说,新对象的创建工作已经完成。但是对于Java 语言来说,对象创建才算是开始。

接下来要做的,就是执行目标对象内部生成的init 方法,初始化成员变量的值、执行构造块、最后执行目标对象的构造方法,完成对象的创建。

其中,init 方法是Java 文件编译之后在字节码文件中生成的,它是一个实例构造器,这个构造器会把语句块、变量初始化、调用父类构造器等操作组织在一起。

所以调用init方法能够完成一系列的初始化动作。

什么是深拷贝和浅拷贝?

Java进阶(4)——结合类加载JVM的过程理解创建对象的几种方式:new,反射Class,克隆clone(拷贝),序列化反序列化

浅拷贝

在这里插入图片描述

深拷贝

在这里插入图片描述

深拷贝和浅拷贝是用来描述对象或者对象数组这种引用数据类型的复制场景的。

浅拷贝, 就是只复制某个对象的指针,而不复制对象本身。这种复制方式意味着两个引用指针指向被复制对象的同一块内存地址。

在Java 里面,无论是深拷贝还是浅拷贝,都需要通过实现Cloneable 接口,并实现clone()方法。然后我们可以在clone()方法里面实现浅拷贝或者深拷贝的逻辑。

实现深拷贝的方法有很多,比如

  1. 通过序列化的方式实现,也就是把一个对象先序列化一遍,然后再反序列化回来,就会得到一个完整的新对象。
  2. 在clone()方法里面重写克隆逻辑,也就是对克隆对象内部的引用变量再进行一次克隆。

Book.java实体类

implements Cloneable{ // 可以克隆的

package com.tianju.auth.reflect;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Cloneable{ // 可以克隆的
    private String title;
    private Author author;
    public double price;

    static {
        System.out.println("book的静态代码块");
    }

    // protected:代表本包或者继承
    // 继承的时候,可以将子类的访问控制符扩大,但不能缩小;
    // 子类不能比父类抛出更多的异常
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public Book deepClone(){
        Book book = new Book();
        Author au = new Author();
        au.setName(author.getName());
        book.setAuthor(au);
        book.setTitle(this.title);
        book.setPrice(this.price);
        return book;
    }
}


进行测试

在这里插入图片描述

package com.tianju.auth.reflect;

public class TestDemo{
    public static void main(String[] args) throws CloneNotSupportedException {
        Author author = new Author();
        author.setName("吴承恩");
        Book book = new Book("三国演义", author,12.56);
        Book book1 = book;

        System.out.println(book1==book);// == 两个引用是否指向同一个对象

        // clone创建了一个新的对象,只是值一样
        Book bookClone = (Book) book.clone();
        // 深拷贝,创建了新的对象,上面的浅拷贝,只是拷贝了引用
        Book deepClone = book.deepClone();

        System.out.println(bookClone==book);
        System.out.println("克隆前:"+book);
        System.out.println("克隆后:"+bookClone);

        author.setName("小柯基");
        System.out.println("修改后的原对象:"+book);
        System.out.println("修改后的clone对象:"+bookClone);

        // 深拷贝
        System.out.println("\*\*\*\*\*\*\*\*\*\*\*");
        System.out.println("深拷贝的方法:"+deepClone);
    }
}


String相关的面试题

new String(“abc”)到底创建了几个对象

  1. 如果abc 这个字符串常量不存在,则创建两个对象,分别是abc 这个字符串常量,以及new String 这个实例对象。
  2. 如果abc 这字符串常量存在,则只会创建一个对象

Java基础(1)——数据类型&包装类,引用类型String&StringBuilder,正则表达式,定点数,日期类

(1)String str = new String(“hello”);

执行上述代码,底层进行了如下工作,在栈内存里存放变量str,在堆内存新创建一个String的对象,在常量池空间中创建常量hello,如果存在则不创建;创建了一个或两个对象。
在这里插入图片描述

(2)String str = “hello”;

栈内存中的str指向常量池中的hello
在这里插入图片描述

String、StringBuffer、StringBuilder 区别

(1)可变性
String 内部的value 值是final 修饰的,所以它是不可变类。所以每次修改String 的值,都会产生一个新的对象。

StringBuffer 和StringBuilder 是可变类,字符串的变更不会产生新的对象。

在这里插入图片描述

StringBuffer 和StringBuilder 是可变类

在这里插入图片描述

(2)线程安全性
String 是不可变类,所以它是线程安全的。

StringBuffer 是线程安全的,因为它每个操作方法都加了synchronized 同步关键字。
StringBuilder 不是线程安全的,所以在多线程环境下对字符串进行操作,应该使用StringBuffer ,否则使用StringBuilder

在这里插入图片描述

(3)性能方面
String 的性能是最的低的,因为不可变意味着在做字符串拼接和修改的时候,需要重新
创建新的对象以及分配内存。

其次是StringBuffer 要比String 性能高,因为它的可变性使得字符串可以直接被修改,最后是StringBuilder ,它比StringBuffer 的性能高,因为StringBuffer 加了同步锁。

在这里插入图片描述

(4)存储方面
String 存储在字符串常量池里面;
StringBuffer 和StringBuilder 存储在堆内存空间。

最后再补充一下, StringBuilder 和StringBuffer 都是派生自AbstractStringBuilder这个抽象类。

Integer相关的面试题

Integer 和int 的区别?Java 为什么要设计封装类?

Integer 是基本数据类型int 的封装类,在Java 里面,有八种基本数据类型,他们都有一一对应的封装类型。
基本类型和封装类型的区别有很多,比如

  • int 类型,我们可以直接定义一个变量名赋值即可,但是Integer 需要使用new 关键字创建对象;
  • 基本类型和Integer 类型混合使用时,Java 会自动通过拆箱和装箱实现类型转换
  • Integer 作为一个对象类型,封装了一些方法和属性,我们可以利用这些方法来操作数据。
  • 作为成员变量, Integer 的默认值是null ,而int 的默认值是0

在Java 里面,之所以要对基础类型设计一个对应的封装类型。是因为Java 本身是一门面向对象的语言,对象是Java 语言的基础单元,我们时时刻刻都在创建对象,也随时都在使用对象,很多时候在传递数据时也需要对象类型,比如像ArrayList、HashMap 这些集合,只能存储对象类型,因此从这个点来说,封装类型存在的意义就很大。

其次,封装类型还有很多好处,比如

  • 安全性较好,可以避免外部操作随意修改成员变量的值,保证了成员变量和数据传递的安全性
  • 隐藏了实现细节,对使用者更加友好,只需要调用对象提供的方法就可以完成对应的操作

Integer 和int 的区别有很多,总结以下3点:
(1)Integer 的初始值是null ,int 的初始值是0
(2)Integer 存储在堆内存,int 类型是直接存储在栈空间
(3)Integer 是对象类型,它封装了很多的方法和属性,我们在使用的时候更加灵活。

至于为什么要设计封装类型,最主要的原因是Java 本身是面向对象的语言,一切操作都是以对象作为基础。比如像集合里面存储的元素,也只支持存储Object 类型,普通类型无法通过集合来存储。

Integer 使用不当导致生产的事故

为什么两个Integer 的对象不能用==号来判断?

在这里插入图片描述

Integer 是一个封装类型。它是对应一个int 类型的包装。

在Java 里面之所以要提供Integer 这种基本类型的封装类,是因为Java 是一个面向对象的语言,而基本类型不具备对象的特征,所以在基本类型上做了一层对象的包装并且提供了相关的属性和访问方法来完善基本类型的操作。

在Integer 这个封装类里面,除了基本的int 类型的操作之外,还引入了享元模式的设计,对-128 到127 之间的数据做了一层缓存,也就是说,如果Integer 类型的目标值在-128 到127 之间,就直接从缓存里面获取Integer 这个对象实例并返回,否则创建一个新的Integer 对象。

在这里插入图片描述

这么设计的好处是减少频繁创建Integer 对象带来的内存消耗从而提升性能。

因此在这样一个前提下,如果定义两个Integer 对象,并且这两个Integer 的取值范围正好在-128 到127 之间。

如果直接用==号来判断,返回的结果必然是true ,因为这两个Integer 指向的内存地址是同一个。否则,返回的结果是false。

Integer a1 =100 Integer a2 =100 ,a1 ==a2?的运行结果?

按照大家对于Java 基础的认知,两个独立的对象用==进行比较,是比较两个对象的内存地址。那得到的结果必然是false。但是在这个场景中,得到的结果是true。

为什么呢?

首先, Integer a1 =100, 把一个int 数字赋值给一个封装类型,Java 会默认进行装箱操作,也就是调用Integer.valueOf()方法,把数字100 包装成封装类型Integer。

在这里插入图片描述

其次,在Integer 内部设计中,用到了享元模式的设计,享元模式的核心思想是通过复用对象,减少对象的创建数量,从而减少内存占用和提升性能。

在这里插入图片描述

Integer 内部维护了一个IntegerCache,它缓存了-128 到127 这个区间的数值对应的Integer 类型。

一旦程序调用valueOf 方法,如果数字是在-128 到127 之间就直接在cache 缓存数组中去取Integer 对象;否则,就会创建一个新的对象。

所以,对于这个面试题来说,两个Integer 对象,因为值都是100,并且默认通过装箱机制调用了valueOf 方法。从IntegerCache 中拿到了两个完全相同的Integer 实例。因此用等号比较得到的结果必然是true。

总结,a1 ==a2 的执行结果是true。原因是Integer 内部用到了享元模式的设计,针对-128 到127 之间的数字做了缓存。使用Integer a1 =100 这个方式赋值时,Java 默认会通过valueOf 对100 这个数字进行装箱操作,从而触发了缓存机制,使得a1 和a2 指向了同一个Integer 地址空间。

**Tips:**在工作中直接把两个Integer 封装类型用等号去比较,就有可能导致生产故障。

ArrayList 的自动扩容机制

Java进阶(3)——手动实现ArrayList & 源码的初步理解分析 & 数组插入数据和删除数据的问题

在这里插入图片描述

ArrayList 是一个数组结构的存储容器,默认情况下,数组的长度是10.
当然我们也可以在构建ArrayList 对象的时候自己指定初始长度。

随着在程序里面不断的往ArrayList 中添加数据,当添加的数据达到10 个的时候,ArrayList 就没有多余容量可以存储后续的数据。

这个时候ArrayList 会自动触发扩容。

扩容的具体流程很简单,

  1. 首先,创建一个新的数组,这个新数组的长度是原来数组长度的1.5 倍。
  2. 然后使用Arrays.copyOf 方法把老数组里面的数据拷贝到新的数组里面。扩容完成后再把当前要添加的元素加入到新的数组里面,从而完成动态扩容的过程。

强引用、软引用、弱引用、虚引用有什么区别

不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响。

(1)强引用
就是普通对象的引用,只要还有强引用指向一个对象,就能表示对象还“活着”,垃圾收集器无法回收这一类对象。

只有在没有其他引用关系,或者超过了引用的作用域,再或者显示的把引用赋值为null的时候,垃圾回收器才能进行内存回收。

(2)软引用
是一种相对强引用弱化一些的引用,可以让对象豁免一些垃圾收集,只有当JVM 认为内存不足时,才会去试图回收软引用指向的对象。

软引用通常用来实现内存敏感的缓存,如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

(3)弱引用
相对强引用而言,它允许在存在引用关联的情况下被垃圾回收的对象在垃圾回收器线程扫描它所管辖的内存区域的过程中,

一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,垃圾回收期都会回收该内存

(4)虚引用
它不会决定对象的生命周期,它提供了一种确保对象被finalize 以后,去做某些事情的机制。

当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收,然后我们就可以在引用的对象的内存回收之前采取必要的行动。

Java 有几种文件拷贝方式,哪一种效率最高

  • 第一种,使用java.io 包下的库,使用FileInputStream 读取,再使用FileOutputStream写出。
  • 第二种,利用java.nio 包下的库,使用transferTo 或transfFrom 方法实现。
  • 第三种,Java 标准类库本身已经提供了Files.copy 的实现。

对于Copy 的效率,这个其实与操作系统和配置等情况相关,在传统的文件IO 操作里面,我们都是调用操作系统提供的底层标准IO 系统调用函数read()、write() ,由于内核指令的调用会使得当前用户线程切换到内核态,然后内核线程负责把相应的文件数据读取到内核的IO 缓冲区,再把数据从内核IO 缓冲区拷贝到进程的私有地址空间中去,这样便完成了一次IO 操作。

而NIO 里面提供的NIO transferTo 和transfFrom 方法,也就是常说的零拷贝实现。

它能够利用现代操作系统底层机制,避免不必要拷贝和上下文切换,因此在性能上表现比较好。

谈谈什么是零拷贝DMA ( Direct Memory Access,直接内存存取)?

零复制(英语:Zero-copy;也译零拷贝)技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。 这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。

在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上,那么它必须要经过几个拷贝的过程。

(1)从磁盘中读取目标文件内容拷贝到内核缓冲区;
(2)CPU 控制器再把内核缓冲区的数据赋值到用户空间的缓冲区中;【浪费】
(3)接着在应用程序中,调用write() 方法,把用户空间缓冲区中的数据拷贝到内核下的Socket Buffer 中;【浪费】
(4)最后,把在内核模式下的SocketBuffer 中的数据赋值到网卡缓冲区( NIC Buffer)网卡缓冲区再把数据传输到目标服务器上;

在这里插入图片描述
在这个过程中我们可以发现,数据从磁盘到最终发送出去,要经历4 次拷贝,而在这四次拷贝过程中,有两次拷贝是浪费的,分别是:

  • 从内核空间赋值到用户空间
  • 从用户空间再次复制到内核空间

除此之外,由于用户空间和内核空间的切换会带来CPU 的上线文切换,对于CPU 性能也会造成性能影响。

而零拷贝,就是把这两次多于的拷贝省略掉,应用程序可以直接把磁盘中的数据从内核中直接传输给Socket ,而不需要再经过应用程序所在的用户空间。

(1)零拷贝通过DMA ( Direct Memory Access) 技术把文件内容复制到内核空间中的Read Buffer;
(2)接着把包含数据位置和长度信息的文件描述符加载到Socket Buffer 中,DMA 引擎直接可以把数据从内核空间中传递给网卡设备;

在这里插入图片描述

在这个流程中,数据只经历了两次拷贝就发送到了网卡中,并且减少了2 次cpu 的上下文切换,对于效率有非常大的提高

所以,所谓零拷贝,并不是完全没有数据复制,只是相对于用户空间来说,不再需要进行数据拷贝。对于前面说的整个流程来说,零拷贝只是减少了不必要的拷贝次数而已。

在程序中如何实现零拷贝呢?

  • 在Linux 中,零拷贝技术依赖于底层的sendfile()方法实现
  • 在Java 中,FileChannal.transferTo()方法的底层实现就是sendfile()方法
  • 除此之外,还有一个mmap 的文件映射机制

设计模式

大致按照模式的应用目标分类,设计模式可以分为创建型模式、结构型模式和行为型模式。

(1)创建型模式
是对对象创建过程的各种问题和解决方案的总结,包括各种工厂模式、单例模式、构建器模式、原型模式。

(2)结构型模式
是针对软件设计结构的总结,关注于类、对象继承、组合方式的实践经验。

常见的结构型模式,包括桥接模式、适配器模式、装饰者模式、代理模式、组合模式、
外观模式、享元模式等。

(3)行为型模式
行为型模式,是从类或对象之间交互、职责划分等角度总结的模式。

比较常见的行为型模式有策略模式、解释器模式、命令模式、观察者模式、迭代器模式、模板方法模式、访问者模式。

在Java 中实现单例模式有哪些方法

单例模式,就是一个类在任何情况下绝对只有一个实例,并且提供一个全局访问点来获取该实例。
要实现单例,至少需要满足两个点:

  • 私有化构造方法,防止被外部实例化造成多实例问题
  • 提供一个静态方位作为全局访问点来获取唯一的实例对象

(1)第一种,是最简单的实现,通过延迟加载的方式进行实例化,并且增加了同步锁机制避免多线程环境下的线程安全问题。但是这种加锁会造成性能问题,而且同步锁只有在第一次实例化的时候才产生作用,后续不需要。

在这里插入图片描述

(2)第二种改进方案,通过双重检查锁的方式,减少了锁的范围来提升性能

在这里插入图片描述

(3)第三种,通过饿汉式实现单例。

这种方式在类加载的时候就触发了实例化,从而避免了多线程同步问题。
在这里插入图片描述

还有一种与这个方式类似的实现,通过在静态块里面实例化,而静态块是在类加载的时候触发执行的,所以也只会执行一次。

在这里插入图片描述

package com.tianju.test;

public class Singleton {
    private static Singleton instance = null;

    static {
        instance = new Singleton();
    }
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}


上面两种方式,都是在类加载的时候初始化,没有达到延迟加载的效果,当然本身影响不大,但是其实还是可以更进一步优化,就是可以在使用的时候去触发初始化(如图) 。

像这种写法,把INSTANCE 写在一个静态内部类里面,

在这里插入图片描述

package com.tianju.demo;

public class Singleton {
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){}
    public static final Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

(4)使用枚举类来实现
既能避免多线程同步问题,又能防止反序列化重新创建新对象,也是一个比较好的方案

在这里插入图片描述

第一种是通过双重检查锁的方式,它是一种线程安全并且是延迟实例化的方式,但是因为加锁,所以会有性能上的影响。

第二种是通过静态内部类的方式实现,它也是一种延迟实例化,由于它是静态内部类,所以只会使用的时候加载一次,不存在线程安全问题。

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

leton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}


(4)使用枚举类来实现  
 既能避免多线程同步问题,又能防止反序列化重新创建新对象,也是一个比较好的方案


![在这里插入图片描述](https://img-blog.csdnimg.cn/b104b52d2f674bae9f151d9fd92e5e85.png)


第一种是通过双重检查锁的方式,它是一种线程安全并且是延迟实例化的方式,但是因为加锁,所以会有性能上的影响。


第二种是通过静态内部类的方式实现,它也是一种延迟实例化,由于它是静态内部类,所以只会使用的时候加载一次,不存在线程安全问题。


[外链图片转存中...(img-pmTb31tc-1715792442832)]
[外链图片转存中...(img-Grb0wStB-1715792442833)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618631832)**

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值