宇哥的面试题

1、基础相关面试题

1. java三大基本特征?String算是java的基本数据类型?

  1. java三大基本特征

    • 封装:将属性私有化,保证属性的安全性。

    • 继承:将共性的内容进行封装,子类|子接口 继承了 父类|父接口可以使用允许被继承的内容。

    • 多态:父类型|接口类型存储子类|实现类创建的对象。

  2. String不是java的基本数据类型

    String类型为引用数据类型,根据Java中标识符规范,基本数据类型为全小写,引用类型使用大驼峰。

    String的值存储在堆内存的字符串常量池中。基本数据类型的值存储在栈空间中。

  3. String字面量和new String的区别

    1. String str = “内容”;

      将内容存储在字符串常量池中,向字符串常量池中添加之前会判断字符串常量池中是否存在要添加的内容,存在直接返回存在的地址,不存在申请新的地址存储内容,将新申请的地址返回。

    2. String str = new String("内容");

      将内容存储在字符串常量池中,向字符串常量池中添加之前会判断字符串常量池中是否存在要添加的内容,存在直接返回存在的地址,不存在申请新的地址存储内容,将新申请的地址返回。返回的地址存储在堆空间中。

2. 介绍一下你了解的集合类

集合类分为两个接口:Collection接口与Map接口。

  1. Collection接口的子接口Queue,List,Set

    1. List接口实现类:ArrayList,LinkedList,Vector

      1. ArrayList和Vector:

        • 底层为数组实现,Api也十分相似。存储的元素有序,可以重复。

        • ArrayList线程不全,效率高。

        • Vector线程安全,效率低。

      2. ArrayList和LinkedList:

        • ArrayList,底层实现为数组,查询,遍历效率高。

        • LinkedList,底层为链表(双向非循环链表),增删效率高。

        • 存储的元素有序,可以重复。

    2. Set接口实现类:HashSet,TreeSet

      1. HashSet:

        • 底层实现为HashMap

        • 存储的元素无序,不可以重复。

      2. TreeSet:

        • 底层实现为TreeMap

        • 存储的元素有序(值大小顺序),不可以重复。

  2. Map接口实现类:HashMap,TreeMap,HashTable

    1. HashMap:

      • Jdk1.8及之前底层实现,散列表(数组+链表)

      • Jdk1.8及之后底层实现,散列表(数组+链表+红黑树)

      • 存储元素无序,key不可以重复,value可以重复

    2. TreeMap:

      • 底层实现为红黑树

      • 存储元素有序(值大小顺序)key不可以重复,value可以重复

    3. HashTable:

      • 底层实现为散列表(数组+链表)

      • 存储元素无序,key不可以重复,value可以重复

      • 和HashMap的Api类似,HashTable线程安全,效率低

3. 创建线程的方式

  1. 继承Thread类,重写Thread类中的run方法(run方法没有返回值)

  2. 实现Runnable接口,实现Runnable接口中的run方法(run方法没有返回值)

  3. 实现Callable接口,实现Callable接口中的call方法(call方法有返回值)

  4. 线程池

4. 线程池的参数

  1. 核心线程数

  2. 最大线程数

  3. 任务队列(阻塞队列)

  4. 最大空闲时间

  5. 最大空闲时间单位

  6. 线程工厂

  7. 拒绝策略

5. 多线程项目中是否用过?怎么用?

使用多线程就是为了充分利用cpu的资源,提高程序执行效率,当你发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程。不过CPU执行哪个线程的时间和顺序是不确定的,即使设置了线程的优先级,因此使用多线程的风险也是比较大的,会出现很多预料不到的问题,一定要多熟悉概念,多构造不同的场景去测试才能够掌握。

场景:

需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整型属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户。

考虑共享变量线程安全问题,共享变量使用了JUC中的AtomicInteger(原子操作类,可以保证线程安全。底层实现为cas乐观锁+volatile)

使用了JUC中提供的延时线程池,可以定期的执行指定的任务。

6. jdbc是什么

java连接数据库技术。jdbc提供了规范,由数据库厂商提供具体的实现。

7. 设计模式?项目中哪里用过

常见设计模式为23种。

  1. 工厂模式应用场景: 在需要根据不同条件创建具有相同行为或者接口的对象时,可以使用工厂模式。比如,一个电商网站可能会销售多种类型的商品(例如服装、数码产品等),每个商品都需要实现一些基本操作(例如上架、下架、查询库存等),因此可以使用工厂模式来创建商品对象。

    使用方法: 定义一个工厂接口或抽象类,根据不同的需求定义不同的工厂类,每个工厂类负责生产相关的产品。

  2. 观察者模式应用场景: 当一个对象状态发生变化需要通知其他对象时,可以使用观察者模式。比如,在一个在线购物网站中,当某个商品库存量发生变化时,需要通知订阅该商品的用户,此时就可以使用观察者模式。

    使用方法: 定义一个主题接口或者抽象类,其中包含添加、删除、通知观察者的方法;然后定义具体主题类和观察者类,并实现相应的方法。

  3. 策略模式应用场景: 当需要根据不同条件选择不同算法执行时,可以使用策略模式。比如,在一个电商网站中,计算商品价格可能会根据不同地区而有所不同,此时就可以使用策略模式。

    使用方法: 定义一个策略接口或者抽象类,其中包含执行算法的方法;然后定义不同的具体策略类,并实现相应的方法。

  4. 模板方法模式应用场景: 当需要继承多个类并实现其中部分方法时,可以使用模板方法模式。比如,在一个游戏开发中,各种角色可能会有不同的行动方案,但是每个角色都有相同的攻击方法,此时就可以使用模板方法模式。

    使用方法: 定义一个抽象类,其中包含一个模板方法和若干个抽象方法;然后定义具体子类,并实现相应的抽象方法。

  5. 代理模式应用场景: 当需要控制客户端与实际对象之间的交互时,可以使用代理模式。比如,在一个大型系统中,某些对象需要保护起来,只允许特定的用户进行访问,此时就可以使用代理模式。

    使用方法: 定义一个代理类,在代理类中创建实际对象,并提供与实际对象相同的接口。

  6. 装饰器模式应用场景: 当需要在运行时扩展对象功能时,可以使用装饰器模式。比如,在一个文字处理软件中,某些文本需要加粗、斜体等样式,此时就可以使用装饰器模式。

  7. 使用方法: 定义一个装饰器类,该类实现与被装饰类相同的接口,并在其中包含被装饰对象的引用;然后定义具体装饰器类,继承自装饰器类,并在其中添加额外的功能。

  8. 建造者模式应用场景: 当需要构建复杂对象且构建过程需要多个步骤时,可以使用建造者模式。比如,在一个汽车制造厂中,汽车的制造需要分多个步骤进行,此时就可以使用建造者模式。

    使用方法:

  9. 迭代器模式:应用场景: 需要遍历一个集合中的元素,但是又不想暴露该集合的内部结构。比如对于一个网站的用户列表,管理员需要对用户进行操作,但是不想暴露用户列表的实现细节。

    使用方法: 首先定义一个迭代器接口,包含 hasNext() 和 next() 方法;然后定义一个实现了迭代器接口的具体迭代器类,该类实现了具体的遍历逻辑;最后定义一个集合类,实现迭代器接口,并且返回具体迭代器类的实例。

  10. 责任链模式:应用场景: 当需要处理一个请求,并且有多个对象可以处理该请求,但是每个对象处理请求的方式不同,且具有处理请求的先后顺序,就可以使用责任链模式。比如一个报销流程,需要多个领导审批,但是每个领导审批的标准不同,且有些领导需要在前面审批通过后才能审批。

    使用方法: 首先定义一个抽象处理器类,包含一个处理方法和一个指向下一个处理器的指针。然后定义具体的处理器类,它们实现了处理方法,并且设置好下一个处理器的指针。最后定义一个责任链类,负责将请求传递给责任链中的第一个处理器,并且将处理结果返回给请求者。

8. threadloacal 了解过么

ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。

ThreadLocal与Synchronized的区别:ThreadLocal<T>其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。

ThreadLocal与synchronized有本质的区别:

1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本

,使得每0个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

9. 了解IO流吗?知道IO流有几种吗

字节流,字符流,缓冲流,转换流,打印流,数据流,序列化流

BIO是同步阻塞,NIO是同步非阻塞,AIO是异步非阻塞

BIO是同步阻塞模式,即当一个线程调用I/O操作时,它会一直阻塞直到操作完成或出错。NIO是同步非阻塞模式,即当一个线程调用I/O操作时,它不会一直阻塞,而是可以执行其他任务,当操作完成后再回来处理。AIO是异步非阻塞模式,即当一个线程启动一个I/O操作后,不会等待操作完成,而是继续执行其他任务,当操作完成后会得到通知并进行相应的处理。

10. jvm了解多少

  1. 内存结构:堆,方法区,栈,本地方法栈,程序计数器,执行引擎,本地库接口,本地方法库

  2. 源文件:源文件就是我们编写Java代码的文件。文件扩展名为.java。

  3. 字节码文件:字节码文件是源文件编译后的文件。字节码文件是二进制文件,需要通过特定的工具才能查看。里面存放了源文件编译后的字节码指令。

  4. 类加载器 Class Loader:Java 程序运行时会由类加载器负责把.class的字节码文件装在到内存中,供虚拟机执行。

    1. 加载 Loading

      1. 启动类加载器 BootStrap Class Loader,负责从启动类中加载类。具有最高执行优先级。即:rt.jar等。

      2. 扩展类加载器 Extension Class Loader,负责加载扩展相关类。即:jre/lib/ext 目录。

      3. 应用程序加载器 Application Class Loader,加载应用程序类路径(classpath)中相关类。

    2. 链接 Linking

      1. 校验 Verify,校验器会校验字节码文件是否正确。

      2. 准备 Prepare,所有静态变量初始化并赋予默认值。

      3. 解析 Resolve,符号引用被换成直接引用。

    3. 初始化 Initialization,所有静态变量赋予初值,静态代码块执行。

  5. 执行引擎:运行时数据区的字节码会交给执行引擎执行。

    1. 解释器 Interpreter,解释器负责解释字节码文件。每次方法调用都会被重新解释。

    2. JIT编译器,ava程序在运行的时候,主要就是执行字节码指令,一般这些指令会通过解释器(Interpreter)进行解释执行,这种就是解释执行。

      当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为 热点代码。为了提高热点代码的执行效率,在运行时虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,简称 JIT 编译器)。

    3. 探测器,负责探测多次被调用的代码。

    4. 垃圾回收器 GC,负责回收不在被使用的对象。GC是JVM中非常重要的一块,在后面我们会单独讲解GC。

  6. 本地库接口,在Java代码中使用native修饰的方法表示方法具体实现使用其他编程语言实现的。例如:C语言。通过本地库接口为Java程序提供调用其他语言的实现方案。

  7. 本地方法库,所有的本地方法,通过本地库接口调用。

  8. 程序计数器,程序计数器是一块较小的内存空间。记录了当前线程执行到的字节码行号。每个线程都有自己的程序计数器,相互不影响。如果是native方法,计数器为空。

  9. 虚拟机栈,虚拟机栈跟随线程创建而创建,所以每个线程都有一个虚拟机栈。

    虚拟机栈中存储的是栈帧(frames),每个栈帧对应一个方法,每个栈帧都有自己的局部变量表、操作数栈、动态链接和返回地址等。当前正在执行的方法称为当前方法,当前方法所在的帧称为当前帧。方法执行时帧就是一个入栈操作,方法执行完成之后栈帧就是一个出站操作。

    1. 局部变量表

      局部变量表存储的8大基本数据类型和返回值以及方法参数及对象的引用。其中long和double占用2倍长度。

      局部变量表就是一个数组,数组的长度在编译期确定。通过从0开始的索引调用局部变量表的内容。

    2. 操作数栈

      操作数栈存在于栈帧中,其大小在编译期确定。

      操作数栈中存储了class文件中虚拟机指令以及准备要传递的参数和接收对方的返回结果。

      运行时常量池中数据以及局部变量表中得值都可以由操作数栈进行获取。

    3. 动态链接

      符号引用转换为直接引用分为两种情况。

      在JVM加载或第一次使用转换时称为静态链接或静态解析。而在运行期间把符号转换为直接引用时就称为动态链接。

    4. 方法返回地址

      方法返回地址分为两种情况:

      ​ 正常结束执行。例如碰见return关键字。调用程序计数器的值后当前栈帧直接出栈就可以了。

      ​ 异常结束。可能需要恢复上层方法的局部变量表和操作数栈,然后把返回值压如到栈帧的操作数栈中,之后调用程序计数器的值后获取到下条指令。

  10. 堆,堆是所有线程共享的,存储类的实例和数组。

    堆是在虚拟机启动时创建的,由GC负责回收。

    堆可以是一块不连续的内存空间。

    在Java 8 中,String是存在于堆中的。

    堆被分为二大部分:

    ​ 在Java 7时分为:新生代(Young Generation)、老年代(Old Generation)。在HotSpot中使用永久代来实现方法区的规范。且新生代、老年代和永久代是连续的。

               新生代又被分为Eden区、From Survivor区、To Survivor区。官方说明默认分配比例为8:1:1。但是使用jmap工具进行测试时发现比例为6:1:1。
      ​
           在Java 8时把永久代替换为元空间(MetaSpace),也就是说在Java8中使用元空间来实现方法区。且在Java8中把元空间移植到本地内存上(Native Memory),其实在Java 7 时,部分数据已经移植到本地内存上了。例如:符号引用(Symbols)。
  11. 方法区,方法区是线程共享的。

    在虚拟机启动时自动创建方法区,方法区可以是一块不连续的内存空间。方法区可以理解为编译代码存储区。在方法区中存储每个类的结构、运行时常量池、字段、方法、构造方法。

    在JVM规范上方法区是一个独立的区域,但是在Java SE7 的HotSpot 上方法区使用永久代作为实现,永久代和堆是一块连续空间。在Java SE8的JVM规范实现上,HotSpot使用元空间实现方法区。

11. 网络编程知道那些,socket通信

OSI网络通信7层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层

TCP/IP 5层模型:应用层,传输层,网络层,数据链路层,物理层

TCP/IP 4层模型:应用层,传输层,网络层,网络接口层

TCP协议为安全可靠的连接:

  1. 建立连接:三次握手

  2. 断开连接:四次挥手

2、Spring相关面试题

1. IOC/DI

IOC为控制反转,DI为依赖注入,创建对象的权利交由spring,创建对象的同时可以完成属性值的注入。

2. spring的beanfactory和factorybean的区别

beanfactory:对象由spring完成创建

factorybean:spring加载对象工厂,对象在工厂中自定义

3. 什么是循环依赖,怎么解决循环依赖的

Spring的单例对象的初始化主要分为三步:

  1. 实例化:其实也就是调用对象的构造方法实例化对象。

  2. 注入:填充属性,这一步主要是对bean的依赖属性进行填充。

  3. 初始化:属性注入后,执行自定义初始化操作。

A的某个field依赖了B的实例对象,同时B的某个field依赖了A的实例对象,这种情况为循环依赖。

解决循环依赖:

1、使用三级缓存

  • 一级缓存(singletonObjects):存放实例化,属性注入,初始化完成的对象。

  • 二级缓存(earlySingletonObjects):存放早期暴露出来的Bean对象,属性还未填充完整。

  • 三级缓存(singletonFactories):存放bean创建工厂,以便于后面扩展有机会创建代理对象。

A、B两个类相互依赖,初始化A的时候,第一步实例化A完成(生成对象工厂实例放入三级缓存),注入依赖属性B,一级缓存查询B没有,二级缓存查询B没有,

初始化B(生成对象工厂实例放入三级缓存),注入依赖属性A,一级缓存查询A没有,二级缓存查询A没有,三级缓存查询到A的对象工厂,需要AOP增强则生成A的代理对象,没有则直接创建A实例对象,并将A放入到二级缓存,注入A的代理对象完成,生成代理对象B,B移入一级缓存。

继续A属性注入(B的代理对象),所有依赖注入完成后A初始化,生成A的代理对象,发现A的代理对象已存在,则跳过,放入一级缓存。此时A的代理对象也是提前生成的,但是仅针对循环依赖提前生成。

2、延迟加载@Lazy注解

解决Spring循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。

3、不能解决的循环依赖

  1. 使用构造器注入

  2. 注入的bean为prototype(原型模式)

4. spring事务传播行为

首先,事务的传播行为,可以拆成两部分理解,即事务的传播,和事务的行为。指的是,当有两个或以上的方法同时声明为事务方法(事务方法:即加了事务管理的增删改方法)时,如果在一次程序执行过程中,这些事务方法彼此间相互调用,那么这些事务方法的事务,应该如何来进行管理?其中,事务的传播,指的是嵌套调用的多个事务方法,是否会共享同一个事务,即调用者所处的事务是否会传播给被调用者(前提:两者都是属于事务方法)。而事务的行为,主要指的就是事务的提交或者回滚。

REQUIRED(默认值):如果当前有事务则加入到事务中。如果当前没有事务则新增事务。

NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错。

NESTED:必须在事务状态下执行。如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务(子事务)。

REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起, 重新建个事务。(调用者统一提交回滚)

SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。

NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起。

MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错。(可以配置在入口方法)

5. spring中的事务,三个业务类,a b c 开启默认事务,a调用b b调用c c异常怎么回滚?

  1. 事务

    一条事务中可以包含多条sql语句,多条sql要么同时成功,要么全部失败,不会出现部分成功部分失败。

    事务的四大特性(ACID):

    ​ 原子性:原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。

    ​ 一致性:事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

    ​ 隔离性:事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

    ​ 持久性:持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

    事务的隔离级别:

    1. 读未提交:出现脏读,不可重复读,幻读

    2. 读已提交:出现不可重复读,幻读

    3. 可重复读:解决正常情况的幻读(底层采用MVCC版本控制机制)

    4. 串行化:完全解决所有问题(级别最高,运行效率最低)

  2. spring中事务的传播行为

    开启了Spring声明式事务,默认使用的传播行为为REQUIRED(默认值):如果当前有事务则加入到事务中。如果当前没有事务则新增事务。

    整个调用最终都是在调用者里面统一提交回滚。

6. 说一下你这个项目中的AOP+自定义注解的日志增强是怎么做的?

自定义注解:

创建注解,自定义的注解需要添加元注解(使用的位置,什么时候生效,等)

AOP:

AOP叫做面向切面编程,属于对OOP的扩展。其实现是基于动态代理设计模式,在IoC基础上实现的。

AOP就是对某个切入点做了通知进行增强扩展,形成横切面。可以实现在不修改原有代码的情况下,做额外扩展。

重要概念:

  1. 切面

  2. 切入点

  3. 切点表达式

  4. 通知

  5. 织入

  6. 动态代理对象

通过切点表达式获取切入点(目标方法),为目标方法所在的类创建代理对象,将通知织入切入点。

使用切入点表达式时,使用@Annotation表达式,例如"@annotation(com.zqwl.aop.LogConfig)",

方法添加了@LogConfig注解,就会完成功能的增强。

7. springmvc的执行流程

  1. 客户端向服务端发起请求,请求会被Spring MVC总体入口前端控制器DispatcherServlet匹配,前端控制器会进行请求分发。

  2. 前端控制器DispatcherServlet将请求交给映射处理器HandlerMapping解析URL,找到匹配的处理器(Controller中的方法)和处理器的拦截器。

  3. 映射处理器HandlerMapping将请求封装为HandlerExecutionChain处理器执行链(包含了处理器和拦截器)。返回处理器执行链HandlerExecutionChain到前端控制器DispatcherServlet。

  4. DispatcherServlet根据返回的处理器执行链HandlerExecutionChain获得到处理器Handler,根据处理器Handler选择处理器适配器HandlerAdapter。

    1. 执行拦截器的preHandle()方法。

    2. 调用具体的Handler处理器(处理Controller),在填充Handler的入参过程中会执行数据转换、数据格式化、数据验证,调用具体的Controller完成处理功能,并创建ModelAndView对象。

    3. 执行拦截器的postHandle()方法。

  5. 将ModelAndView对象返回到处理器适配器HandlerAdapter。

  6. 处理器适配器HandlerAdapter将ModelAndView对象返回到前端控制器DispatcherServlet。

  7. 前端控制器DispatcherServlet调用视图解析器ViewResolver解析视图,渲染视图,执行拦截器afterCompletion()方法。

3、Sql相关面试题

1. sql了解多少?怎么知道sql需要优化,需要添加索引?索引失效?

  1. sql了解多少

    sql为结构化查询语言,用于查询、更新(添加,修改,删除)和管理关系数据库系统。

    sql分为:

    • DML:数据操作语言,操作数据的增删改

    • DQL:数据查询语言,查询数据DDL:数据定义语言,操作库,表,视图,索引...

    • DCL:数据控制语言,操作用户,权限,角色

    • TCL:事务控制语言,操作事物

  2. 怎么知道sql需要优化,需要添加索引?索引失效?

    可以开启慢查询日志,如果在慢查询日志中出现被记录的查询sql,需要对查询的sql进行优化。

    sql优化时需要考虑,是否建立了合适的索引或已经建立了索引,索引是否失效。

    通过explain执行计划分析可以分析出sql是否正确的使用了索引。

  3. Mysql索引的底层数据结构为什么使用B+Tree

    1. 如果使用红黑树,索引较多时导致树的高度会变高,降低了查询效率。

    2. B-Tree,可以控制树的高度,整棵树所有的节点都存储索引值和数据,导致每一层中存储的索引个数变少,叶子节点没有指针相连。

    3. B+Tree,可以控制树的高速,非叶子节点只存储索引值,叶子节点存储索引值和数据。

  4. 聚集索引和非聚集索引

    Innodb存储引擎:(索引和数据在同一个文件中)

    聚集索引 | 聚簇索引:并不是索引的分类,索引值和行数据存储在一起,数据会按照索引的顺序进行存储。主键索引为聚集索引的一种,也可以自定义聚集索引(很少自定义)。表中没有主键索引,自动找一个唯一非空的索引作为聚集索引,自动创建一个隐藏的字段作为聚集索引。

      
            非聚集索引 | 非聚簇索引 | 二级索引 | 辅助索引:并不是索引的分类,索引值和主键值存储在一起,根据索引值找到主键, 根据主键找到行数据(回表查询)。                                      
      ​
       MyISAN存储引擎:(索引和数据在不同的文件中)

    ​ 非聚集索引:索引值和行数据的地址存储在一起。

2. sql执行顺序

from -》on -》join -》where -》group by -》having -》select -》order by -》limit

4、Redis相关面试题

1. redis有哪些数据结构

  1. String(字符串)

    常规计数:因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。

    分布式锁:SET 命令有个 NX PX 毫秒数,NX 不存在才生效,PX过期时间,为了避免客户端发生异常而无法释放锁。

    共享 Session 信息:使用 Session 来保存用户的会话(登录)状态,这些 Session 信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。

  2. Hash(哈希)

    缓存对象:Hash 类型的 (key,field, value) 的结构与对象的(对象id, 属性, 值)的结构相似,可以用来存储对象。

    购物车:以用户 id 为 key,商品 id 为 field,商品数量为 value,恰好构成了购物车的3个要素。

  3. List(列表)

  4. Set(集合)

    点赞:Set 类型可以保证一个用户只能点一个赞,这里举例子一个场景,key 是文章id,value 是用户id。

    抽奖活动:存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。

  5. Zset(有序集合)

    排行榜:有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。

  6. Bitmap(位图)

    是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景

    签到统计:在签到打卡的场景中,我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。

  7. HyperLogLog

    Redis HyperLogLog 是 Redis 2.8.9 版本新增的数据类型,是一种用于「统计基数」的数据集合类型,基数统计就是指统计一个集合中不重复的元素个数。但要注意,HyperLogLog 是统计规则是基于概率完成的,不是非常准确,标准误算率是 0.81%。

    所以,简单来说 HyperLogLog 提供不精确的去重计数

    百万级网页 UV 计数:Redis HyperLogLog 优势在于只需要花费 12 KB 内存,就可以计算接近 2^64 个元素的基数,和元素越多就越耗费内存的 Set 和 Hash 类型相比,HyperLogLog 就非常节省空间。所以,非常适合统计百万级以上的网页 UV 的场景。

  8. GEO

    Redis GEO 是 Redis 3.2 版本新增的数据类型,主要用于存储地理位置信息,并对存储的信息进行操作。

    附近的人,滴滴打车等。

  9. Stream(5.0 版新增)

    Redis Stream 是 Redis 5.0 版本新增加的数据类型,Redis 专门为消息队列设计的数据类型。

    在 Redis 5.0 Stream 没出来之前,消息队列的实现方式都有着各自的缺陷,例如:

    • 发布订阅模式,不能持久化也就无法可靠的保存消息,并且对于离线重连的客户端不能读取历史消息的缺陷;

    • List 实现消息队列的方式不能重复消费,一个消息消费完就会被删除,而且生产者需要自行实现全局唯一 ID。

    基于以上问题,Redis 5.0 便推出了 Stream 类型也是此版本最重要的功能,用于完美地实现消息队列,它支持消息的持久化、支持自动生成全局唯一 ID、支持 ack 确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠。

2. redis底层哪些特性支持高并发?

  1. 内存存储:Redis将数据存储在内存中,使得读写速度非常快。同时,Redis也提供持久化机制,可以将内存数据异步地写入磁盘中,保证数据的安全性和可靠性。

  2. 非阻塞I/O多路复用机制:Redis使用非阻塞I/O模型,避免了线程上下文切换和系统调用带来的开销,从而大幅提高了并发吞吐量。

  3. 单线程架构:Redis采用单线程架构,避免了多线程间的同步和锁竞争等问题,简化了代码实现和维护。

  4. 数据结构优化:Redis内置了多种数据结构(如哈希表、有序集合等),并对其进行了优化,使得操作复杂度低,并能在很短的时间内完成大量的数据处理。

  5. Redis 6.0+ 引入多线程IO,但只是用来处理网络数据的读写、协议的解析及日志操作等,而执行命令依旧是单线程,所以不需要去考虑set/get、事务、lua等的并发问题。

3. 什么场景下会用的redis

  1. 缓存存储

    Redis最常用的场景之一就是缓存存储,因为Redis是一种内存数据库,它的读写速度非常快,能够快速存取数据。在Web应用中,如果使用MySQL等传统的关系型数据库进行数据读取,会导致响应时间变慢,影响用户体验。而使用Redis可以将热点数据存储在内存中,快速响应用户请求,从而提升系统的性能。

  2. 分布式锁

    在分布式系统中,为了避免多个客户端同时修改同一个资源而导致的数据不一致问题,需要使用分布式锁来保证一次只有一个客户端能够访问共享资源。Redis提供了分布式锁的实现方案,可以使用Redis的SETNX命令实现一个分布式锁。当一个客户端想要获取锁时,可以使用SETNX命令尝试将一个指定的键值对设置为1,如果设置成功,说明获取锁成功,否则表示锁被其他客户端占用。

  3. 计数器

    Redis还可以用作计数器,例如网站的访问量统计,每次有用户访问时,将访问量加1,通过Redis INCR命令可以快速实现计数器的功能。Redis还提供了EXPIRE命令,可以设置键值对的过期时间,可以用于设置访问量在一定时间内生效。

  4. 消息队列

    Redis的发布/订阅功能可以用作消息队列,发布者将消息发布到指定的频道,订阅者可以订阅该频道,从而接收到消息。这种方式非常适用于异步任务的处理,例如用户上传头像后,需要对图片进行压缩和裁剪,这些操作可能需要较长时间,可以将任务发布到Redis的消息队列中,由订阅者异步处理。

  5. 地理位置

    Redis还支持地理位置的存储和检索,可以存储经度和纬度,通过GeoHash算法对地理位置进行编码,实现快速的距离计算和位置检索。这种方式非常适合于LBS应用的实现,例如附近的人功能。

  6. 实时排行榜

    Redis提供了有序集合的支持,可以将数据按照指定的顺序存储,例如按照分数从高到低排列。可以使用Redis的ZADD命令将数据添加到有序集合中,使用ZREVRANGE命令可以获取按照指定顺序排列的前N个元素。这种方式非常适合实现实时排行榜,例如游戏中的积分排名,可以将每个玩家的积分存储在Redis的有序集合中,按照积分从高到低排列,从而实现实时排行榜的功能。

注意:Redis实现消息队列和MQ实现消息队列的区别?

redis是一个高性能key-value数据库,支持消息推送功能,可以当作轻量级队列服务器来使用。

redis消息推送多用于实时性要求高,并不保证可靠;mq保证可靠但有延迟

下面通过几点来区分redis和mq:

  1. 可靠性redis:没有机制保证消息的可靠性,发布一条消息没有对应的订阅者,消息会丢失不会存在内存中mq:具有消息确认机制,发布一条消息没有消费该队列,这条消息会一直存在队列中,直到有消费者消费该消息,保证消息的可靠性

  2. 实时性redis实时性高,是高效的缓存服务器,所有数据都存在内存中,所以具有更高的实时性

  3. 消费者负载均衡mq可以被多个消费者同时监控消费,因确认机制每条消息只能消费一次,可以根据消费者能力调整负载redis发布订阅模式,一个消息可以被多个消费者订阅,消息到达时会将消息发送给每一个订阅者,是一种消息的广播形式,本身不做消费者负载均衡,因此消费效率存在瓶颈

  4. 持久性redis:redis持久化是针对整个redis缓存,可将整个redis缓存作为磁盘备份,以防异常导致数据丢失mq:每条消息可选择持久化,更灵活

  5. 队列监控redis:没有后台监控mq:实现后台监控,可在平台上查看详细情况

  6. 性能发布消息数据较小时,redis性能高于mq。读数据无论数据大小,redis都高于mq

4. 使用redis缓存在高并发下可能出现的问题?

  1. 缓存穿透

    在实际开发中,添加缓存工具的目的,减少对数据库的访问次数,提高访问效率。

      
      肯定会出现Redis中不存在的缓存数据。例如:访问id=-1的数据。可能出现绕过redis依然频繁访问数据库的情况,称为缓存穿透,多出现在数据库查询为null的情况不被缓存时。
      ​
      解决方案:
      ​
          1. 设置有效时间:如果查询出来为null数据,把null数据依然放入到redis缓存中,同时设置这个key的有效时间比正常有效时间更短一些。
      ​
          2. 布隆过滤器(Bloom Filter):是一种数据结构,用于快速检查一个元素是否属于某个集合中。它可以快速判断一个元素是否在一个大型集合中,且判断速度很快且不占用太多内存空间。
      ​
              布隆过滤器的主要原理是使用一组哈希函数,将元素映射成一组位数组中的索引位置。当要检查一个元素是否在集合中时,将该元素进行哈希处理,然后查看哈希值对应的位数组的值是否为1。如果哈希值对应的位数组的值都为1,那么这个元素可能在集合中,否则这个元素肯定不在集合中。
      ​
              由于哈希函数的映射可能会发生冲突,因此布隆过滤器可能会出现误判,即把不在集合中的元素判断为在集合中。但是,布隆过滤器不会漏判,即不会把在集合中的元素判断为不在集合中。
  2. 缓存击穿

    实际开发中,考虑redis所在服务器中内存压力,都会设置key的有效时间。一定会出现键值对过期的情况。如果正好key过期了,此时出现大量并发访问,这些访问都会去访问数据库,这种情况称为缓存击穿。

      
          解决方案:
      ​
              1. 永久数据
      ​
              2. 加锁
  3. 缓存雪崩

    在一段时间内容,出现大量缓存数据失效,这段时间内容数据库的访问频率骤增,这种情况称为缓存雪崩。

      
          解决方案:自定义算法,例如:随机有效时间。让所有key尽量避开同一时间段。

5、项目相关面试题

1. 登录如何实现?jwt全称?jwt结构?token存储哪里?

1.1 登录如何实现?
  1. 用户发起登录请求访问服务端接口

  2. 服务端接收账号,密码,验证是否合法用户

  3. 验证是合法用户,服务端会生成一个 Token,返回给客户端

  4. 客户端接收Token, 并将其进行保存(Sesssion Storage 或 LocalStorage 或 Cookie)

  5. 再每次发起请求访问服务端时,会在请求头中携带Token

  6. 服务端接收到客户端的请求,判断请求头中是否携带Token及Token的合法性,只有 Token 验证通过才会返回请求的资源。

1.2 jwt全称?jwt结构?

jwt全称为JSON Web Token是一个开放标准,用 JSON 对象在各方之间安全地传输信息。此信息是经过数字签名的,因此可以验证和信任。

Header(头部)、Payload(有效载荷)、Signature(签名),用点(.)将三部分隔开便是 JWT 的结构,形如xxxxx.yyyyyy.zzzzz的字符串。

Header 部分是一个 JSON 对象,描述 JWT 的元数据

{

“alg”:”JS256” -> 加密方式

“typ”:”JWT” -> Token类型

}

Header部分内容会经过Base64进行编码处理

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用,也可以自定义字段。

iss (issuer):签发人

exp (expiration time):过期时间

sub (subject):主题

aud (audience):受众

nbf (Not Before):生效时间

iat (Issued At):签发时间

jti (JWT ID):编号

Payload部分内容会经过Base64进行编码处理

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)

按照指定的加密方式将 header经过base64编码处理的结果 拼接. 拼接payload经过base64编码处理的结果 和 secret 进行加密得到Signature签名部分。

最终:base64编码处理的结果 . payload经过base64编码处理的结果 . Signature签名

2. 项目多少张表?项目多少个人?

2.1 项目多少张表

小型项目:20-30张表左右,以实际的项目为准,没有固定的数量。

中型项目:60-80张表左右,一般不会超过100张表,以实际的项目为准,没有固定的数量。

2.2 项目多少个人

通常中大型规模的角色:

1、项目经理:

​ 一个合格的项目经理必须要有技术背景,一般团队的项目经理由非常有项目经验的RD担当,他的职责在于将目标转化为可量化可实现的项目计划,偏重于执行层面。项目经理主要负责对外合作、跨产品线和重点项目的推进,确保按时优质地完成全部工作内容,达成项目目标,并顺利上线。

2、产品经理:

​ 产品经理核心任务是针对用户需求提出解决方案,做好产品设计。在项目上线后,组织开发、测试、运营进行上线监控,并在项目稳定运营后移交产品运营。产品经理负责产品需求梳理,产品设计,文案等工作。根据产品需求,完成产品的策划和设计。

3、UI设计师:

​ 根据产品需求,对产品的整体美术风格、交互设计、界面结构、操作流程等做出ui设计。负责项目中各种交互界面、图标、LOGO、按钮等相关元素的设计与制作;能积极与开发商沟通,推进界面及交互设计的最终实现。

4、前端开发工程师:

​ 前端,iOS/Android开发工程师根据需求进行客户端软件的设计、开发和维护。与项目相关人员配合共同完成应用软件的开发设计工作。遵循软件开发流程,进行应用及人机界面软件模块的设计和实现。参与技术难题攻关、组织技术积累等工作。配合项目经理执行开发过程的技术管理工作。

5、服务端开发工程师

​ 根据产品的需求,进行服务器端功能的开发和维护。在产品开发过程中,配合APP/终端/测试团队,确保方案落地。分析和监控服务器运行状况,确保服务器可扩展性和稳定性。

6、测试工程师:

​ 制定测试产品的测试计划、方案。设计并执行测试用例,对产品进行功能,性能,安全等测试。实施高效的测试活动,并对测试结果进行分析,给出专业报告,与其他部门紧密协作,跟踪缺陷及推动及时修复。维护测试环境,进行测试环境的部署与调试。设计并且开发测试工具,对测试方法进行创新。

7、运维工程师:

​ 对服务器进行日常维护,确保网络连续正常运行。配合数据分析、开发人员进行相关数据统计、参数配置、系统测试及系统监控;研究运维相关技术,根据系统需求制定运维技术方案。

中等大小的项目,团队规模可能在10人左右,涵盖了上述角色(不是所有公司都有上述的所有角色)。

1个项目经理,1产品经理,2个测试,5个后端,3个前端。

3. 分布式锁用过吗,怎么用的,那些场景

在业务开发中,为了保证在多线程下处理共享数据的安全性,需要保证同一时刻只有一个线程能处理共享数据。

例如:防止商品的超卖等。

分布式锁实现:

  1. 基于关系型数据库:基于关系型数据库实现分布式锁,是依赖数据库的唯一性来实现资源锁定,比如主键和唯一索引等。

    在数据表定义中,我们对指定的字段做了唯一性约束,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么就可以认为操作成功的那个线程获得了该方法的锁,可以执行后面的业务逻辑。当方法执行完毕之后,想要释放锁的话,在数据库中删除对应的记录即可。

    基于数据库实现分布式锁操作简单,但是并不是一个可以落地的方案,有很多地方需要优化。

    • 存在单点故障风险

    • 超时无法失效

    • 不可重入

    • 无法实现阻塞

  2. redis实现:相比基于数据库实现分布式锁,缓存的性能更好,并且各种缓存组件也提供了多种集群方案,可以解决单点问题。

    SET 命令有个 NX PX 毫秒数,NX 不存在才生效,PX过期时间,为了避免客户端发生异常而无法释放锁。

4. 用过哪些web服务器,nginx了解过吗

  1. Tomcat

    Tomcat是一个基于Java的Web应用服务器。与其他虚拟机相比,它对于Java应用生成的Web页面处理更加专业。Tomcat是一个免费的Web服务器,特别适用于开发者进行测试和调试。

  2. Apache

    Apache是世界上用的最多的Web服务器,市场占有率达60%左右。

  3. WebLogic

    BEA WebLogic Server是一种多功能、基于标准的web应用服务器,为企业构建自己的应用提供了坚实的基础。

  4. Jetty

    开源的servlet容器,它为基于Java的web内容,例如JSP和servlet提供运行环境。

  5. Nginx

    Nginx是一款高性能的Web服务器,用于处理大流量网站的请求。它突出的特点是更加高效和灵活。在处理静态和动态内容时非常快速和可靠。此外,Nginx还可以扮演反向代理服务器的角色,帮助服务器分担负载。

5. 项目开发中遇到的难点

记住2-3个即可:【java】 java开发中 常遇到的各种难点 思路方案_java项目中遇到的最大困难,怎么解决的-CSDN博客

6. 密码加密采用什么算法

  1. MD5算法的Md5PasswordEncoder

    MD5 是一种 128 位的哈希函数,常用于数据完整性校验和数字签名等方面。它将任意长度的信息映射为一个 128 位的摘要值,输出的值通常表示为一个 32 位的十六进制数。MD5 的安全性已经被破解,因此现在不再被广泛使用。

  2. SHA 算法的ShaPasswordEncoder

    SHA 系列算法有多个版本,比较常见的有 SHA-1、SHA-256、SHA-384 和 SHA-512。它们都是将输入数据映射为一个固定长度的输出摘要值。SHA-1 生成的摘要长度为 160 位,而 SHA-256、SHA-384 和 SHA-512 分别生成 256 位、384 位和 512 位的摘要值。SHA-256 是目前应用最广泛的 SHA 算法之一,它的输出长度和安全性都比 MD5 更高。

  3. BCrypt算法的BCryptPasswordEncoder

    Bcrypt 是一种密码哈希函数,可以用于对密码进行加密和验证。它是一种慢速哈希函数,其设计目的是为了防止暴力破解攻击。Bcrypt 还可以通过增加 salt(随机值)和工作因子(算法运行次数)来进一步增强安全性。

    Bcrypt 算法的实现中,会将明文密码和 salt 进行混合,并经过多轮哈希计算,最终得到一个长度为 60 个字符的密文。这个密文包括算法标识符、salt、工作因子和哈希值等信息。在验证密码时,Bcrypt 会从密文中提取出 salt 和工作因子,然后使用相同的哈希算法和参数来计算输入密码的哈希值,最后比较计算出的哈希值和密文中的哈希值是否一致。

    由于 Bcrypt 算法的计算量比较大,因此它可以有效地防止暴力破解攻击。同时,使用不同的 salt 和工作因子可以让相同的密码在不同的计算中生成不同的哈希值,进一步增加了破解难度。

7. 第三方登录

  1. 什么是OAuth2.0:OAuth是一项协议,它为用户资源的授权提供了一个安全、开放而简易的标准,OAuth的授权不会使第三方触及到用户的账号信息(比如密码),因此OAuth是相对安全的。而OAuth2.0就是OAuth的延续,不过2.0更加关注客户端开发者的简易性。

    常见的第三方应用都支持第三方登录,比如:QQ、微信、微博、GitHub、Gitee等,要想申请第三方登录权限,就需要去到对应的平台

  2. 基本原理:

    当用户点击第三方登录时,会跳转到第三方登录SDK内部;用户输入第三方登录用户名或密码,有些第三方登录平台,可以直接调用已经登录的账号,例如:QQ;完成第三方平台登录的;登录完成后,第三方平台,或者SDK会回调我们的应用,在回调的信息里面,可以拿到用户在第三方平台的OpenId,以及昵称,头像等信息。

    (A)用户打开客户端以后,客户端要求用户给予授权。(B)用户同意给予客户端授权。(C)客户端使用上一步获得的授权,向认证服务器申请令牌。(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。(E)客户端使用令牌,向资源服务器申请获取资源。(F)资源服务器确认令牌无误,同意向客户端开放资源

8. 支付宝支付

参照官方文档:小程序文档 - 支付宝文档中心

9. 微信支付

参照官方文档:开发指引-APP支付 | 微信支付商户平台文档中心

双亲委派机制

双亲委派机制是Java中的一种类加载机制。它的工作原理是当一个类加载器收到加载请求时,它首先将请求委派给父类加载器去执行。如果父类加载器还存在其父类加载器,则进一步向上委派,直到请求最终到达顶层的启动类加载器。如果父类加载器可以完成加载任务,则成功返回;否则,委派给它的子加载器去加载。通过这种机制,Java类加载器形成了一个层次结构,并按照层次顺序加载类 双亲委派机制的优势有两点。首先,它能够游免类的重复加载。一旦一个类被父类加载器加载之后,就不会再被委派给子类进行加载。其次,它能够保护程序的安全性。通过限制自定义类加载器的行为,双亲委派机制可以防止恶意代码加载不安全的类。 另外,双亲委派机制还有沙箱安全机制的作用。沙箱安全机制通过限制代码的执行权限,防止恶意代码对系统造成损害。

回表查询

如果查询条件为普通索引(非聚簇索引),需要扫描两次B+树

第一次扫描先通过普通索引定位到聚簇索引的值

第二次扫描通过第一次扫描获得的聚簇索引的值定位到要查找的行记录数据

要弄明白回表,首先得了解 InnoDB 两大索引,即聚集索引 (clustered index)和普通索引(secondary index)。

聚集索引 (clustered index)InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有且只有一个聚集索引。

如果表定义了主键,则Primary Key 就是聚集索引;如果表没有定义主键,则第一个非空唯一索引(Not NULL Unique)列是聚集索引;否则,InnoDB会创建一个隐藏的row-id作为聚集索引;普通索引(secondary index)普通索引也叫二级索引,除聚簇索引外的索引都是普通索引,即非聚簇索引。

InnoDB的普通索引叶子节点存储的是主键(聚簇索引)的值,而MyISAM的普通索引存储的是记录指针。·

  • 25
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于elemntu面试题,引用提到这本面试手册主要包括了Java基础、Java集合、JVM、Spring、Spring Boot、Spring Cloud、Mysql、Redis、RabbitMQ、Dubbo、Netty、分布式及架构设计等方面的技术点。因此,elemntu面试题可能涉及这些方面的内容。 引用提到MyBatis适用于对性能要求较高或需求变化较多的项目,比如互联网项目。可能会有MyBatis的优缺点相关的面试题。 引用提到分页插件是MyBatis进行分页的一种实现方式,并提及了该插件的原理。因此,elemntu面试题可能会涉及MyBatis的分页操作和分页插件的原理。 综上所述,elemntu面试题可能包括Java相关的技术点、MyBatis的优缺点、以及MyBatis的分页操作和分页插件的原理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Java面试题大全(备战2021)](https://download.csdn.net/download/m0_37968982/13214673)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Mybatis常见面试题(10个必备面试题)](https://blog.csdn.net/feng8403000/article/details/122282095)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值