面试题总结

1、项目中的调优具体看哪些字段?>< between会走索引吗?

具体看id(决定查询几次数据库)、type字段、key(使用的索引)、rows(查询的行数)、extra(不要出现UsingFilesort),结果值从好到坏依次是:system > const > eq_ref > ref > range > index(索引全表扫描) > ALL。><between会走索引这种范围扫描索引扫描比全表扫描要好。

2、单例模式简单介绍怎么实现在哪加锁?为什么这样加?volatile是什么?怎么加?为什么加?为什么要双重检查?

单例模式分为饿汉式和懒汉式
饿汉式单例模式:在类加载的时候就创建实例(使用final修饰),这种方式线程安全
懒汉式单例模式:在需要的时候创建实例,这种方式可以节约资源,但是可能会出现线程安全问题。
双重检查锁定单例模式:在懒汉式的基础上,加上双重检查锁来保证线程安全。提高性能
volatile:加在变量前,volatile 关键字只能保证可见性和顺序性,而不能保证原子性,它确保对于变量的读取和写入操作不会被编译器优化(防止指令重排),而是直接从主内存中读取和写入。

3、synchronized和ReentrantLock区别?

①、ReentrantLock显示获得锁和释放锁,Synchronized隐式释放和获得
②、api级别 JVM
③、ReentrantLock默认非公平锁,通过 new ReentrantLock(true)实现公平锁 非公平锁
④、可以与condition绑定来唤醒特定的线程 只有notify和notifyall
⑤、接口 关键字
⑥、出现异常不会主动释放锁在finally中使用unlock释放锁 会释放锁

4、cas、乐观锁和悲观锁

类似于乐观锁机制 ,CAS机制中使用了三个基本操作数:内存地址和预期值和要修改的值,当要更新值时会和预期值进行比较,相同就替换。
缺点:CPU开销较大,高并发的情况下拿不到预期值会一直自旋,不能保证原子性,会有ABA问题(就好比去银行取钱一台机器上取钱100-50机器卡住(待执行)去其他机器上执行成功100-50,这时候女友转了50 50+50 最先的那台机器又恢复了执行成功100-50)解决方案:添加一个版本号。
乐观锁:顾名思义就是很乐观认为别人不会去修改数据,当版本号与预期不一致时会回滚数据。适合高并发情况下读取数据。
悲观锁:就是很悲观,认为别人会乱改数据,在每次操作时都会上锁,适用于高并发情况下的增删改。

5、spring ioc和aop理解

IOC:控制反转,将创建对象的事情交由Spring去处理,我们直接调用spring提供的对象即可,它帮我们解决了循环依赖,如果我们要在Bean的初始化前后增加一些操作可以实现BeanPostProcessor,如果想在实例化之前增加一些操作可以实现BeanFactoryPostProcessor。依赖注入DI:使用bean对象的Set和带参数的构造器方法对属性进行设置。
aop:面向切面编程 将非核心的业务代码进行横向抽取,在初始化对象之后利用BeanPostProcessor帮我们去利用JDK生成一个代理对象JdkDynamicAopProxy、JDK提供的方法—>Proxy.newProxyInstance在需要的地方进行切入,例如日志,分布式事务。底层原理是动态代理,如果是接口采用JDK动态代理,如果是类采用CGLIB的方式。
补充版:
Beanfactory作为整个Spring最为核心的接口,也是顶层的IOC容器,在SpringIOC容器中使用的是它的子类DefaultListableBeanFactory这个类继承和实现许多的类,从面向对象的角度来说这个类拥有它父类的所有方法,作为一个IOC容器它可以管理Bean的生命周期,一个Bean的普通生命周期分为五个阶段实例化、属性注入、初始化、使用、销毁,如果我们想要在Bean的实例化之前对创建的对象进行修改属性的修改就可以去实现BeanFactoryPostProcessor来对这个对象进行属性修改,例如我们在高并发场景下Spring默认帮我们生成的Bean对象是单例的并且它的成员变量是可变的,那么这个对象是线程不安全的所有我们要将这个对象在实例化之前改为多例的来确保线程安全,在我们为一个对象完成属性注入之后需要将它的属性值作为静态变量供其他类来调用则可以让该类实现InitializingBean重写afterPropertiesSet来为静态变量赋值,如果一个类既实现了InitializingBean又有初始化方法,那么按照底层代码的执行顺序,初始化方法会覆盖InitializingBean中相同的操作,当我们需要对所有Bean对象初始化前后添加一些操作时就可以去编写一个类去实现BeanPostProcessor重写postProcessBeforeInitialization、postProcessAfterInitialization来完成初始化之前和之后的操作。其中AOP的思想就是在Bean的初始化完成之后利用默认的JDK代理创建一个代理对象然后对目标方法进行增强。在我们的项目中就自定义了一个注解+AOP的思想来对我们的目标方法进行增强,帮我们解决了循环依赖采用三个ConcurrentHashMap来存储不同的对象。

6、Spring怎么解决循环依赖

在这里插入图片描述
三级缓存:singletonFactories 存放可以生成bean的工厂
二级缓存:earlySingletonObject 早期暴露出来的bean对象,即bean的生命周期还没有结束(属性还没有填充完)
一级缓存:singletonObjects 存放已经经历完整生命周期的bean对象
构造器注入不能解决循环依赖的原因:
bean的创建过程是先实例化在初始化(在进行bean 的实例化的过程中不必赋值)将实例化好的对象暴露出去,供其他对象调用。然而使用构造器完成对象的初始化既要给属性赋值又要创建对象,就会陷入死循环。
二级缓存可以解决循环依赖。
解决:
引入一个缓存池,当我们需要创建AService的实例时,首先会通过java的反射创建出来一个原始的AService(刚刚new出来的没有设置任何属性),此时把AService存入缓存池中。接下来就需要给AService的属性设置值同时处理AService的依赖BService,去创建BService时发现BService依赖AService,那么此时先从缓存池中取出AService先用着,然后继续BService创建的后续流程,直到BService创建完成以后,将之赋值给AService,此时AService和BService就都创建完成。

7、删除ArrayList中的偶数

使用 Stream 流的 filter() 方法来筛选出所有奇数元素,即满足条件 num % 2 != 0 的元素。然后使用 collect() 方法将筛选后的结果收集到一个新的 ArrayList 中,完成删除偶数的操作。

8、为什么重写equals方法必须重写HashCode

因为hashCode方法与equals方法在哈希表(如HashMap、HashSet等)中一起使用,用于确定对象的存储位置。哈希表使用对象的hashCode方法来确定对象在表中的存储位置。如果两个对象相等(使用equals方法判断),那么它们的hashCode方法应该返回相同的值。哈希码在不同的对象上可能是相同的,因此可能会导致哈希表中对象的存储位置错误。

9、HashMap 的扩容机制

JDK1.7之前HashMap 由数组加链表主成当数据量较大时,效率不是很高,所有1.8采用了数组加链表加红黑树的结构,当数组的长度小于64,链表的长度大于8时会继续扩容数组,当数组的长度大于64,并且链表的长度大于8时,链表会转变为红黑树提高效率。

10、tcp三次握手和四次挥手

确保数据的可靠传递,三次握手:第一次客户端发送一个数据包里面包含自己的编号(SYN)请求连接服务器,第二次:服务器同意连接发送数据包给客户端包含自己的编号(SYN+ACK),第三次客户端拿着自己SYN+ACK去去连接服务器。
四次挥手:第一次客户端请求断开连接。第二次服务器端同意断开连接,服务器会等待确认确保数据都已发送。第三次,服务器向客户端发送请求断开连接,此时服务器会等待客户端确认。第四次,客户端发送确认断开连接。

11、HashMap的get方法

当发生Hash冲突时(即不同的key具有相同的哈希值),在桶中链表或红黑树进行线性搜索来找到对应的值。

12、创建线程的方式

1、继承Thread类重写run方法.start作用:①启动当前线程 ②调用当前线程的 run()
2、实现Runnable接口实现run方法(无),将对象放入Thread的构造器中
3、实现callable接口的call方法(有返回值有异常),将对象放入futureTask构造器中创建FutrueTask对象再将次对象放入Thread的构造器中创建Thread对象,如果需要返回值可以通过FutrueTask对象调用get方法获取call方法中的返回值结果。
4、从线程池中取出线程。

13、线程池的创建方式

1、newFixedThreadPool(3) 创建固定数量的线程池适合执行长期任务。允许队列长度为Integer.MAX_VALUE可能堆积大量请求导致OOM
2、SingleThreadExecutor() 单例线程池任何时候都只有一个线程工作,确保任务的顺序性使用。允许队列长度为Integer.MAX_VALUE可能堆积大量请求导致OOM
3、newCachedThreadExecutor() 可扩展线程池,适用于短时异步任务。允许创建线程数量为Integer.MAX_VALUE可能会创建大量线程导致OOM.
4、newScheduledThreadPool() 定时任务线程池。

14、自定义线程池的好处

可以控制线程的数量、任务队列的大小和类型(ArrayBlockingQueue和LinkedBlockingQueue)、以及拒绝策略(默认抛出异常、直接拒绝、抛弃等待时间最长的(保证实时性)、哪里来的回哪去(能最大限度处理我们的业务)),以避免资源耗的浪费提高性能。在项目中我们在异步编排的地方使用了自定义的线程池,我们可以根据我们的业务需求去控制我们的核心线程数量、最大线程数量、阻塞队列的长度以及拒绝策略。

15、JUC的工具类

CyclicBarrier:所有资源到齐之后去做某件事,资源之间相互等待
CountDownLatch:其他线程完成之后让一个线程去做某事 (班长关灯)
SemaPhore:信号量,用于同时访问线程的数量,资源有限,资源只用一会 (停车场)
ForkJoin:将大任务拆分为小任务进行执行。

16、异步编排的应用场景和好处。

在微服务场景下使用openFeige调用远程接口时,使用异步编排可以提高效率,避免串行化执行。同时可以保证线程之间的依赖关系和执行顺序,最后使用CompletableFuture.allOf().join()方法执行。supplyAsync()开启有返回值的异步线程,使用有返回值线程对象调用thenAcceptAsync()使用其返回值开启新的异步线程、使用有返回值线程对象调用thenApplyAsync()使用其返回值开启新的有返回值的异步线程返回值通过get阻塞方法进行获取。runAsync()开启没有返回值的异步线程,whenComplete相当于then由调用的该异步线程的线程执行,whenCompleteAsync()由当前线程执行。

17、线程的状态

新建状态(New):线程对象被创建,但尚未启动。
就绪状态(Runnable):线程对象已经启动,并准备执行。
阻塞状态(Blocked):线程对象正在等待某个资源,例如锁或文件描述符,以便继续执行。
等待状态(Wait):线程对象正在等待,但可以继续执行,直到被中断或被唤醒。
终止状态(Terminated):线程对象已经执行完毕,但尚未被垃圾回收。

18、mysql的事务隔离级别、mysql的锁是怎么实现的?

读未提交、读已提交、可重复读、序列化;行锁和表锁、乐观锁和悲观锁。

19、AQS的底层数据结构

AQS 的底层数据结构主要由两个部分组成:Counter 和 WaitQueue。counter 为零表示没有线程等待直接执行同步方法,不为零则取出WaitQueue 中的线程执行,可以实现高效的线程同步机制,同时也可以避免死锁和性能问题。

20、redis的持久化机制

RDB和AOF
RDB:具有快速恢复的特性。当redis宕机会丢掉一定时间段内的数据。
AOF:通过追加日志的方式保存redis数据。如果出现宕机通过配置会丢弃前一秒的数据。

21、RabbitMQ怎么保证消息的可靠性,怎么实现削峰填谷。

消息确认机制、消息重试机制,消息持久化。将消息存放在队列中,让消费者缓慢的处理。

22、拦截器和过滤器区别

a、实现原理不同:
拦截器基于反射机制(动态代理) 过滤器基于函数回调(doFilter())
b、使用的范围不同:
过滤器是在Servlet规范中定义的依赖于tomcat只能在web应用中使用。
拦截器:spring组件由Spring管理不依赖容器可以单独使用。
c、触发时机不同
过滤器是在请求进入容器之后,进入Servlet之前进行预处理。拦截器是在请求进入servlet之后,进入controller之前进行预处理,对应方法有prehandle、posthandle、afterCompletion。
d、使用场景:
在我们项目中的应用:
过滤器:当访问特定页面时需要用户进行登录,在网关中添加全局过滤器实现GlobalFilter,如果没有登录则在response中设置状态码和对应的页面地址以及当前页面的路径,然后使用response调用setComplete进行跳转。
满足条件则直接交由下一个过滤器处理,chain.filter().
拦截器:当一个服务器要将一些关键信息传递给另外一个服务器时使用拦截器对其进行相应的处理(request中获取用户Id)。
过滤器主要用于对请求和响应对象进行处理或操作,如编码转换、内容过滤、认证授权等通常用于全局性的处理,可以在多个servlet之间共享数据。
拦截器:主要用于对业务方法进行拦截和处理,如日志记录、权限校验等,通常用于业务层面的控制。

23、缓存穿透、击穿、雪崩怎么处理?

缓存穿透:redis缓存没有,去数据库也没有,高并发情况下数据库压力过大。解决方案:直接把空对象也缓存了;缓存没有再通过布隆过滤器(将关键字段进行存储,特点:有不一定有,没有一定没有)判断,没有就不去数据库,存在再去数据库查
缓存击穿:高并发情况下,一个热点Key失效或者不存在,导致大量请求直接访问数据库造成数据 库压力过大。解决方案:①设置缓存永不过期,或者较长过期时间 ②使用互斥锁来保证只有一个请求去访问数据库③限流:限制并发访问的请求数量
缓存雪崩:在某个时间点,缓存中大部分的数据同时失效或被清除,导致大量请求直接访问数据库或后端服务,从而造成数据库或后端服务压力过大。出现的原因:①缓存服务器宕机或重启②
缓存设置了相同的过期时间导致某个时间点同时失效
解决方案:①搭建集群防止单点故障②使用随机值来分散缓存失效时间③通过加锁或者队列来控制并发量

24、redis的淘汰策略

在redis内存达到最大限制时,根据一定的策略来选择要删除的键值对,以释放内存空间。可以通过redis.conf中的maxmemory参数来设置。
默认策略:返回错误
删除设置了过期时间且不经常使用的键
删除所有不常使用的键
1、定时删除:在设置键的过期时间的时候,创建一个定时器,时间到了就立即删除对应的键值
优点:对内存友好,可以立即释放过期键占用的内容
缺点:对cpu不友好,比如在集中时间范围内有大量键过期的话,那么会占用很大cpu来进行删除
2、惰性删除:过期的键不会立马删除,而是每次读取键值的时候去检查对应的键是否过期,如果过期的话则删除、如果没有过期则返回
优点:对cpu友好
缺点:对内存不友好
3、定时删除:每间隔一定的时间执行一次删除所有过期键的操作,并且可以设置删除操作执行的时长和频率来控制对cpu和内存的影响,一种折中的策略

25、redis集群模式(去中心化的)

将数据分散存储在多个节点上,提供高可用和扩展性,主要也是为了解决写操作的负载均衡。
这样部署的缺陷:①一致性问题②Key分布导致的系列问题(例如事务,批量查询等)
扩容步骤:①添加节点②设置节点主从关系③移动部分槽位给新的节点
缩容步骤:①数据迁移,将要删除的节点数据,移动到其他节点②删除主节点和从节点。

26、什么是redis的主从复制?

将一个redis服务器的数据复制到其他redis服务器的过程
意义:①数据备份提高数据的安全性和可靠性②读写分离将读操作分摊到多个从节点上,减轻主节点的读负载,提高系统的并发能力③故障恢复当主机宕机时可以通过从节点提供的数据进行快速恢复,保证系统的高可用性。

27、分布式锁

在分布式下,对共享资源进行互斥访问的一种处理机制。在我们的项目中我们对热门的商品进行了加锁,如果缓存中没有则在查询数据库之前进行加锁开始使用的是redisson进行加锁,因为它帮我们封装了加锁的实现过程可以让我们更加专注于业务实现,但是考虑到商品是查询的比较多所有我们又对其进行了优化使用本地锁加将双重判断的方式来提高查询性能,后面又基于AOP的思想将我们的代码进行封装,自定义了注解在需要加锁的地方加上此注解就可以完成加锁操作。对于分布式锁还有红锁算法.红锁算法的实现原理:①获取当前时间②在多个redis节点上使用相同的key和value来获取锁并设置过期时间。③统计获取锁成功的节点数量,根据①的时间计算获取锁花费判断锁是否过期④如果获得的锁是N/2+1个并在有效时间内,则获取锁成功否则失败,失败则释放所有的锁。

28、RabbitMQ的消息可靠投递

要确保消息的可靠投递首先要确保消息发送到了交换机可以使用消息确认模式、回退模式(处理不了直接退回信息)、备份交换机(将当前交换机处理不了的消息发送到备份交换机进行处理,备份交换机使用发布订阅模式发送给队列一个是做发送短信处理通知程序员处理,第二个作为储备发送不了的消息的队列)其次是队列进行持久化处理(确保消息不丢失)最后则是消费者使用手动确认的模式。
队列TTL和消息的TTL
队列的TTL可以定制化处理某个消息,无法宽泛应用。创建队列的时候设置队列的“x-message-ttl”属性
消息的TTL由于队列的特性即使是过期时间到了也无法立刻得到处理必须遵循先进先出的原则,无法精确的消费。
解决:采用插件实现延迟队列
项目中队列的应用:在我们的项目中为了让服务之间解耦我们采用了消息队列,当管理员上架/下架商品时,往上架/下架商品的队列中发送一个消息,es服务器监听到有消息就进行消费同时为了确保消息的可靠投递我们采用了自动确认的方式在规定次数内消费完则手动确认,否则通知相应的的管理人员来进行处理。

29、MySQL的MVCC

在这里插入图片描述
Mysql数据库中一种实现并发控制的机制只在InnoDB存储引擎的事务表才有效,它能够在同时进行读写操作时,保证数据库的一致性和隔离性。
核心思想:
为每个事务提供一个独立的数据视图,使得每个事务看的数据是一致的,并与其他数据相互隔离
实现机制:
InnoDB在每一行数据都增加三个隐藏字段,一个唯一行号,一个记录创建的版本号,一个记录回滚的版本号。
在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行中。保证每个事务操作的数据互不影响。

30、String 类型是否是线程安全的?用关键字final修饰StringBuffer之后还能修改吗?

String类型是不可变的,当对字符串修改时,会产生一个新的字符串,所以通常是线程安全的。final修饰一个变量表示该变量是一个常量,一旦被赋值之后,其值不能再被修改。使用final修饰StringBuffer时,引用不能再指向其他对象,但可以修改StringBuffer的内容。

31、sleep(0)有什么作用

将当前线程或进程暂停一段极短的时间,以便于给其他线程或进程执行的机会。尽管sleep(0)的参数是0,即暂停时间为0秒,但实际上它会导致当前线程或进程主动让出处理器并进入等待状态,从而让其他处于就绪状态的线程或进程得以执行。

32、nacos怎么做隔离操作

服务隔离:通过创建不同的服务组来实现服务隔离
命名空间隔离:需要在nacos配置中心和服务发现中心的配置文件中指定所使用的命名空间。

33、Having的使用场景

HAVING子句只能用于SELECT语句中,而不能在UPDATE或DELETE语句中使用。同时,HAVING子句一般与GROUP BY子句一起使用,因为HAVING子句是对分组结果进行筛选的。

34、聚簇索引和非聚簇索引

innoDB索引类型:BTree、hash、聚簇索引、非聚簇索引
聚簇索引:按照索引键值进行排序并存储在数据页中的索引类型(主键)
非聚簇索引:不按照索引键值进行排序的索引类型。

35、Hash类型的底层数据结构、Hash类型可以有哪些操作?

底层数据结构主要分为两种:
静态哈希表:在创建时需要确定哈希表的大小,当哈希表被填满时,需要重新计算哈希表的大小并扩展哈希表,时间复杂度为o(1).
动态哈希表:在创建时不确定哈希表的大小,在插入时动态调整大小,优点:不重新计算分配内存的情况下增加哈希表的大小。时间复杂度o(n)每次插入元素都需要重新计算哈希值。
①插入键值对②删除③查询④更新⑤哈希函数⑥冲突解决⑦扩容⑧哈希表大小调整

36、SpringBoot自动装配的原理

自动装配理解:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能,如redis。
SpringBoot启动的时候通过@SpringBootApplication中的@EnableAutoConfiguration注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中。找到META-INF/spring.factories文件中的所有配置类,并对其加载,并不是所有的配置类都加载只有当@ConditionalOnXXX 中的所有条件都满足,该类才会生效。这些配置类都是以AutoConfiguration结尾来了命名的,它实际上就是一个javaConfig形式的IOC容器配置类,通过以Properties结尾命名的类取得在全局配置文件中配置的属性,比如server.port。
自定义Start
1、创建XXX-spring-boot-start 项目
2、添加相关依赖
3、编写配置文件
4、创建参数类使用@ConfigurationProperties(prefix)引入配置文件或者使用@Value引入配置文件中的配置
5、创建配置类使用@EableConfigurationProperties(XXX.class)引入参数类并使用@Configuration标记为一个配置类。
6、定义Bean
7、resources 包下创建META-INF添加Spring.factories文件将类配置类添加进去
8、打包放入仓库
自动装配详解

37、http和Https的区别

http(基于tcp)数据传输过程不进行加密不安全 Https加密安全
Http传输数据快Https慢

38、Map插入时间复杂度

如果插入的元素在Map最顶层,即根点,则时间复杂度为0(1)
其他地方则为0(log n).

39、bean的生命周期、作用域

默认情况下分为五个阶段:
存在BeanFactoryPostProcess时需要实现在实例化对象之前用于修改属性值。例如默认的单例改为多例,如果是配置文件则可以修改参数信息。
①利用反射调用构造器,或者是通过工厂方式创建Bean对象
②给Bean对象的属性注入值
③调用初始化方法,进行初始化,init-Method初始化方法。
④使用
⑤IOC容器关闭,销毁Bean对象
BeanPostProcessor对所有被spring管理的Bean都有效,在调用初始化方法前后执行。
加入Bean的后置处理器后分为七个阶段
在初始化前后执行Bean的后置处理器的PostProcessBeforeInitialization和。。。after。。。
作用域:singleton、prototype、request、session
注入方式:setter()方式、构造器
如果一个对象实现了InitializingBean和配置了init-method
InitializingBean
需要去重写afterPropertiesSet代表在属性注入完成之后进行
init-method自定义一个初始化方法

40、 SpringMVC的工作流程

在这里插入图片描述
①用户发送请求到前端控制器DispatcherServlet
②DispatcherServlet收到请求调用HandlerMapping处理器映射器
③处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispathcherServlet
④DispatcherServlet调用handlerAdapter处理器适配器
⑤处理器适配器调用具体的后端处理器Controller
⑥Controller执行完返回具体的ModelAndView
⑦处理器适配器将后端控制器返回的结果返回给dispatcherServlet
⑧DispatcherServlet将ModelAndView传给ViewReslover视图解析器
⑨viewReslover解析后返回具体的view
DispatcherServlet根据View进行渲染视图
响应用户

41、redis数据类型的应用

string:setNX(sentIfAbsent)当这个键不存在时才创建,可以做分布式锁使用用于解决缓存击穿、防止订单重复提交(保证幂等性)。
List:双向链表,在项目中我们使用其来作为热点商品的缓存。(boundlistOps、lpush、rpop)
set:无序去重,可以作收藏、关注一类的功能
Hash:用于储存对象,项目中用于存储用户的预下单信息
zset:用于作热点商品的点击排序。opsForZSet().incrementScore(),当数量达到一定次数再去更新数据库。

42、MySQL索引的最左前缀如何优化orderBy语句

1、排序字段和索引顺序要一致、带头大哥不能断
2、要使用过滤条件,给过滤字段也创建索引
3、排序字段的排序规则要一致。
4、查询字段要在索引之中避免产生回表导致filesort.
5、选择过滤条件好的作为带头大哥

43、JVM内存模型中、为什么要区分新生代和老年代,对于新生代,为什么又要区分Eden区和survial区

1、主流的垃圾回收器都使用了分代回收算法
2、对于朝生夕死的对象我们就存放在新生区、年龄达到一定次数的对象放到老年区
3、细分新生代保证读写内存的连续性、当Eden区满时会把有效对象复制到s0、s0 将对象复制到s1,空的始终是to区
Eden区通过复制算法保证读写连续性

44、什么情况下索引会失效

1、对字段进行计算、函数导致索引失效
2、like以%开头的索引
3、使用不等于
4、is not null可能失效
5、类型转换导致失效

44、优化数据库

1、SQL优化
2、减少函数的使用
3、索引优化
4、加入缓存
5、分库分表

45、数据库的三范式

第一范式:单表字段拆分到不可拆分为止,有主键、具有原子性,字段不可分割
第二范式:每一行数据唯一、多表关联
第三范式:表之间关联主键依赖,单行数据中保留唯一主键,建立外键关联表。

46、同步有几种实现方法

非堵塞:volatile、cas及atomic系列实现
阻塞:wait、sleep、synchronize、lock、countDownLatch、Cyclicbarrier、semaphore、blockingQueue

47、tcp和Udp区别

tcp 面向连接的协议使用三次握手四次挥手来确保数据的可靠性和顺序性,传输效率低
Udp 无连接的协议,无需确认,数据不可靠可能会出现丢包现象,传输效率高。

48、Nacos作用

①服务注册与发现
②配置中心
③服务路由和负载均衡
④服务健康检测

49、mybatis方法允许重载么

mybatis 方法不支持重载, 方法名称作为方法的唯一标识符, 与形参列表无关如果重复声明相同方法名, 不同形参列表, 那么启动会报错。

50、怎么查日志,怎么查最近1000条日志

tail -f -n 1000

51、为什么不建议使用Executors来创建线程池?

当使用executors创建线程池时,它里面使用的队列时LinkedBlockingQueue,是一个无界阻塞队列,任务过多的话就会不断的添加到队列之中,最终可能内存耗尽,导致OOM.

52、线程池的几种状态

Running、Shutdown(线程处于关闭状态,不在接受新的任务,但是会把队列中的任务处理完)、Stop(不接受新任务也不继续处理队列中的任务、正在运行的线程也会被中断)、TIDYING(线程池中没有正在运行的线程时自动装变为当前状态)、terminated

53、ThreadLocal的应用场景、底层实现

将数据缓存在线程中该线程可以在任意时刻任意方法中获取缓存中的数据,底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的Value为需要缓存的值。如果在线程池中使用Threadlocal会造成内存泄露,因为当threadLocal对象使用完之后,应该把设置的key、value进行回收,但线程池中的线程不会回收,需要手动调用Threadlocal的remove方法,手动清除entry对象。

54、Tomcat中为什么要自定义类加载器(WebAppclassloader)

一个类只能被一个类加载器所加载只能加载一次。
这样Tomcat中每个应用就可以使用自己的类加载器去加载自己的类,从而达到应用之间的类隔离,不会出现冲突。

55、varchar与char有什么区别?

①定长和变长
char长度固定、当char插入的长度小于定义的长度时,使用空格填充。varchar小于定义的长度时,还是按实际的长度存储。
因为其长度固定,char的存取速度要快但是要付出空间的代价
②容量不同
char最多存放255个字符和编码无关
varchar最多存放65535个字符,和编码有关。

56、synchronized和volatile区别

①synchronized可以使用在变量、方法、类级别。volatile只能在变量级别
②保证变量修改可见性和原子性 保证修改可见不能保证原子性
③变量可以被编译期优化 不可以优化
④可能造成线程阻塞 不会造成线程阻塞
⑤锁定当前变量只能当前线程访问,其他线程阻塞 本质是告诉JVM当前变量在寄存器中的值不确定,需要主存中读取

57、@Autowired 和 @Resource 的区别是什么?

@Autowired:属于 Spring 内置的注解默认的注入方式为byType,当一个接口存在多个实现类时,默认情况下它自己不知道选择哪一个。这种情况下,注入方式会变为 byName(根据名称进行匹配),这个名称通常就是类名(首字母小写)。@Qualifier 注解来显式指定名称而不是依赖变量的名称。
@Resource属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType。有两个比较重要且日常开发常用的属性:name(名称)、type(类型),指定 name 属性则注入方式为byName,如果仅指定type属性则注入方式为byType。

58、Bean的作用域

singleton、prototype、(request、session、application/global-session、websocket)仅web应用可用

59、Bean是线程安全的吗?

Bean是否线程安全取决于作用域和状态;
prototype 作用域下,每次获取都会创建一个新的 bean 实例,不存在资源竞争问题,所以不存在线程安全问题。
singleton 作用域下,IoC 容器中只有唯一的 bean 实例,可能会存在资源竞争问题(取决于 Bean 是否有状态)。如果这个 bean 是有状态的话,那就存在线程安全问题(有状态 Bean 是指包含可变的成员变量的对象)。大部分 Bean 实际都是无状态(没有定义可变的成员变量)的(比如 Dao、Service),这种情况下, Bean 是线程安全的。

60、AOP专业术语

在这里插入图片描述

61、什么是事务属性呢?

隔离级别、传播行为、回滚规则、是否只读、事务超时
在这里插入图片描述
默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。
事务详解

62、@Transactional 的使用注意事项总结

①@Transactional 注解只有作用到 public 方法上事务才生效,不推荐在接口上使用;
②避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效;
③正确的设置 @Transactional 的 rollbackFor 和 propagation 属性,否则事务可能会回滚失败;
④被 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
⑤底层使用的数据库必须支持事务机制,否则不生效;

63、主从复制的原理

在这里插入图片描述
①master将数据改变记录到二进制文件中
②当slave上执行 Start slave命令之后,slave会创建一个IO线程来连接master,请求master中的二进制文件
③master创建一个log dump线程,用于发送二进制的内容
④IO线程接受二进制文件放入中继日志(reply log中)
⑤slave开启SQL线程,读取relay log日志并解析。

64、Innodb是如何实现事务的

Innodb通过Buffer Pool,LogBuffer,Redo Log,Undo Log来实现事务,以⼀个update语句为例:

  1. Innodb在收到⼀个update语句后,会先根据条件找到数据所在的⻚,并将该⻚缓存在Buffer Pool中
  2. 执⾏update语句,修改Buffer Pool中的数据,也就是内存中的数据
  3. 针对update语句⽣成⼀个RedoLog对象,并存⼊LogBuffer中
  4. 针对update语句⽣成undolog⽇志,⽤于事务回滚
  5. 如果事务提交,那么则把RedoLog对象进⾏持久化,后续还有其他机制将Buffer Pool中所修改的数
    据⻚持久化到磁盘中
  6. 如果事务回滚,则利⽤undolog⽇志进⾏回滚

65、

在这里插入图片描述

66、守护线程

在这里插入图片描述

67、ThreadLocal内存泄露问题,以及如何避免

在这里插入图片描述

68、线程池中线程复用的原理

在这里插入图片描述

69、Atomic原子类原理是什么?

AtomicInteger 类利用 CAS (Compare and Swap) + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

70、keepalive对nginx存在的意义

Keepalive是一种长连接(也称为持久连接)机制,它允许客户端与服务器之间保持连接,并在不需要服务器时继续保持连接,这样做的好处减少网络传输的开销,并提高网络性能。在keepalive连接中,客户端和服务端之间的数据传输是异步的,这意味着它们可以在不等待对方响应的情况下发送数据,keepalive还具有超时机制,当客户端或服务器在一段时间内没有向对方发送或接收数据时,keepalive会自动关闭连接。

71、springcloud的注解

@EnableDiscoveryClient、@EnableFeignClient

72、spring的事务传播机制说一下

事务传播机制是一种将事务从一个事务管理器(TransactionManager)传播到另一个事务管理器(TransactionManager)的技术。
事务传播机制是指在数据库中,事务在不同的操作之间如何传播和影响的规则和机制。事务传播机制主要包括以下几种:

  1. REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认的传播行为。

  2. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。

  3. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  4. REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。

  5. NOT_SUPPORTED:以非事务的方式执行操作。如果当前存在事务,则将当前事务挂起。

  6. NEVER:以非事务的方式执行操作。如果当前存在事务,则抛出异常。

  7. NESTED:如果当前存在事务,则在当前事务的嵌套事务中执行。如果当前没有事务,则创建一个新的事务。

事务传播机制可以灵活地控制事务的行为,使得多个操作可以组成一个原子性的操作单元,保证数据的一致性和完整性。

73、ElasticSearch中添加黑名单和白名单

1、创建一个文本文件,将要添加的扩展词逐行写入该文件。
2、将该文件放置在ElasticSearch配置目录下的config文件夹中。
3、修改IKAnalyzer.cfg.xml
4、重启es
在这里插入图片描述

74、nacos注册中心的原理

注册:服务提供者在启动时,会将自己的服务信息(例如IP地址、端口号、服务名)注册到Nacos注册中心。这样,服务消费者就可以通过注册中心获取到可用的服务列表。

发现:服务消费者在启动时,会向Nacos注册中心发送服务发现的请求。注册中心会返回可用的服务列表给消费者,消费者可以根据自己的需求选择合适的服务进行调用。

监听:Nacos注册中心支持服务实例的动态注册和注销。当服务提供者启动或关闭时,会通知注册中心进行相应的操作。同时,服务消费者可以通过监听注册中心的变化,实时获取到服务实例的变化情况。

健康检查:Nacos注册中心会定期向服务实例发送心跳检测请求,以确保服务的可用性。如果某个服务实例长时间未响应心跳请求,注册中心会将其标记为不可用,从而不再将其返回给消费者。

总结来说,Nacos注册中心的原理是通过服务提供者将自己的服务信息注册到注册中心,服务消费者从注册中心获取可用的服务列表,并通过监听注册中心的变化来实时获取服务实例的变化情况。这种机制实现了服务的动态发现和管理。

75、Feign的工作原理

定义接口:首先,你需要定义一个接口来描述要调用的远程服务的方法,包括请求的URL、请求方法、请求参数、请求体等信息。这个接口可以使用注解来标记请求的相关信息,例如使用@RequestMapping来指定URL和请求方法。

创建代理对象:在应用启动时,Feign会根据接口的定义,动态创建一个代理对象。这个代理对象实现了你定义的接口,并且内部封装了发送HTTP请求的逻辑。

发起请求:当你调用代理对象的方法时,Feign会根据方法的注解和参数,构造出一个HTTP请求。然后,它会使用底层的HTTP客户端(默认是使用的是HttpClient)发送这个请求到指定的URL。

处理响应:当收到远程服务的响应后,Feign会将响应内容转换成你定义的返回类型,并将其返回给调用方。

Feign的工作原理基于动态代理和注解解析。通过定义接口和注解来描述远程服务的调用,Feign能够自动生成相应的HTTP请求,并将响应转换成指定的返回类型。这样,你可以像调用本地方法一样调用远程服务,无需手动处理HTTP请求和响应。

76、服务熔断和降级

服务熔断和服务降级是在分布式系统中常用的容错机制,用于处理服务之间的故障或性能问题。它们的目的都是提高系统的可用性和稳定性,防止故障在整个系统中的扩散。

服务熔断(Circuit Breaker):服务熔断是一种自动的故障保护机制,用于防止故障服务对整个系统的影响。当某个服务出现故障或响应时间超过预设阈值时,服务熔断器会自动切断对该服务的调用,并在一段时间内不再尝试调用该服务。这样可以避免对故障服务的连续调用,减轻了系统的负载和响应时间。
服务熔断器通常有三个状态:关闭(Closed)、打开(Open)和半开(Half-Open)。在关闭状态下,所有的请求会正常通过;当错误率超过一定阈值时,熔断器会切换到打开状态,此时所有的请求都会被快速失败;在一段时间后,熔断器会切换到半开状态,允许少量的请求通过,如果这些请求成功,则熔断器会切换回关闭状态,否则继续保持打开状态。

服务降级(Fallback):服务降级是一种在系统负载过高或出现故障时,通过牺牲部分功能来保证核心功能的可用性。当系统出现异常情况时,服务降级会将一些非核心的或者计算量较大的功能暂时替换为简化的实现或者静态数据。这样可以减少系统的复杂度和资源消耗,提高系统的可用性。

77、网络中为什么传输的对象是Json而不是xml

在网络传输中,使用JSON(JavaScript Object Notation)而不是XML(eXtensible Markup Language)作为数据格式的主要原因有以下几点:

  1. 可读性:JSON采用了一种简洁的键值对结构,易于阅读和理解。相比之下,XML使用了一种复杂的标记语言,包含大量的标签和嵌套结构,使得数据的表达更为冗长和繁琐。

  2. 数据量小:JSON相对于XML来说,具有更小的数据体积。JSON使用了更简洁的语法和数据结构,不需要像XML那样使用大量的标签和冗余的元数据。这使得在网络传输中,JSON可以更快地传输数据,减少网络带宽的使用。

  3. 解析速度快:由于JSON的结构相对简单,解析起来更加高效。相比之下,XML的解析需要更多的计算资源和时间。这对于网络传输和数据处理来说是非常重要的,特别是在大规模的分布式系统中。

  4. 跨平台支持:JSON在各种编程语言和平台上都有很好的支持。几乎所有的主流编程语言都提供了JSON的解析和序列化库,使得开发人员可以方便地处理JSON数据。而XML的支持则相对较少,需要额外的库或工具来处理。

  5. 易于与Web API集成:在Web开发中,JSON是一种常见的数据格式,广泛应用于Web API的设计和实现。许多Web框架和服务都支持JSON作为默认的数据格式,使得与Web API的交互更加方便和简洁。

综上所述,JSON相对于XML来说,在网络传输中更加适用。它具有可读性高、数据量小、解析速度快、跨平台支持以及与Web API集成等优势,使得它成为了广泛使用的数据格式。当然,在某些特定的场景下,XML仍然有其优势,比如需要处理复杂的文档结构或需要保留更多的元数据。但总体来说,JSON更适合在网络传输中使用。

78、怎么去调用别人的api接口

1、理解API文档:首先,你需要仔细阅读和理解提供方提供的API文档。API文档通常包含了API的使用说明、请求和响应的数据格式、授权方式、可用的端点(Endpoints)等信息。理解API的功能和限制对于正确地调用API至关重要。

2、获取API密钥或访问令牌:有些API需要进行身份验证才能使用,通常需要获取API密钥或访问令牌。这些认证信息可以在API提供方的网站上注册并获取,或者通过其他认证方式获取。

3、构建请求:根据API文档中提供的信息,构建API请求。请求通常包括HTTP方法(如GET、POST、PUT等)、请求URL、请求头、请求参数等。确保按照API文档的要求正确地构建请求。

4、发送请求:使用合适的HTTP客户端库(如Python的requests库、JavaScript的Fetch API等)发送HTTP请求。将请求发送到API的URL,并包含必要的请求头和参数。

5、处理响应:接收到API的响应后,根据API文档中的说明进行处理。响应通常包括HTTP状态码、响应头和响应体。根据响应的数据格式(如JSON、XML等),解析响应体并提取所需的数据。

6、错误处理:在处理API响应时,需要注意处理可能出现的错误。根据API文档中的错误代码和消息,判断响应是否成功,并根据需要采取相应的错误处理措施。

7、重试和限流:在调用API时,可能会遇到一些网络问题或者API本身的限流策略。在这种情况下,需要合理地进行重试和限流处理,以确保调用的稳定性和可靠性。

8、安全性考虑:在使用别人的API时,需要注意安全性问题。确保使用HTTPS协议进行通信,以保护数据的安全性。另外,避免在API请求中包含敏感信息,如密码、密钥等。

79、MySQL中索引的类型

1、按数据结构来分类:B+tree索引、Hash索引、Full-text索引
2、按物理存储分类:聚簇索引(主键索引)、二级索引(辅助索引)
3、按字段特性分类:主键索引、唯一索引、普通索引、前缀索引
4、按字段个数分类:单列索引、联合索引

80、 Linux查看文件内容的命令

1、cat命令:用于连接文件并打印到标准输出设备上。例如,cat filename可以查看文件的内容。

2、less命令:用于查看文件内容,并提供滚动和搜索功能。例如,less filename可以在终端中逐页查看文件。

3、more命令:类似于less命令,也可以用于查看文件内容并提供滚动功能。例如,more filename可以在终端中逐页查看文件。

4、head命令:用于显示文件的开头几行,默认显示前10行。例如,head filename可以查看文件的前10行内容。

5、tail命令:用于显示文件的末尾几行,默认显示最后10行。例如,tail filename可以查看文件的最后10行内容。

6、grep命令:用于在文件中搜索指定的模式,并显示匹配的行。例如,grep “pattern” filename可以查找文件中包含指定模式的行。

7、awk命令:用于以列为单位处理文本文件的内容。例如,awk ‘{print $1}’ filename可以打印文件的第一列内容。

8、sed命令:用于对文本文件进行流式编辑。例如,sed ‘s/old/new/g’ filename可以将文件中的所有"old"替换为"new"。

81、union和union all有什么区别?

UNION操作符会去除重复的行,而UNION ALL操作符会保留所有的行,包括重复的行。需要合并结果集并保留所有的行,包括重复的行,可以使用UNION ALL操作符。由于UNION操作符需要对结果集进行去重操作,因此在性能上可能会比UNION ALL操作符慢一些。

82、程序变慢怎么去排查?

检查硬件资源:确保计算机的CPU、内存和磁盘空间等硬件资源充足,没有被其他程序占用。

检查网络连接:如果程序涉及网络操作,检查网络连接是否正常,网络延迟是否过高。

检查代码逻辑:仔细检查程序的代码逻辑,查找可能导致性能问题的部分。可以使用调试工具来跟踪代码执行过程,找出耗时较长的部分。

检查数据库操作:如果程序涉及数据库操作,检查数据库的性能是否正常。可以通过优化查询语句、创建索引等方式来提升数据库性能。

检查第三方库和插件:如果程序使用了第三方库或插件,检查其版本是否最新,是否存在已知的性能问题。

进行性能测试:使用性能测试工具对程序进行测试,模拟多种负载情况,观察程序在不同负载下的性能表现,找出性能瓶颈。

进行优化:根据排查结果,对程序进行优化。可以采取一些常见的优化措施,如减少IO操作、使用缓存、并行化处理等。

监控和调优:在程序运行过程中,使用监控工具对程序的性能进行实时监控,及时发现并解决性能问题。

83、lock类常用的方法

lock():获取锁。如果锁已经被其他线程获取,则当前线程会被阻塞,直到获取到锁为止。

tryLock():尝试获取锁。如果锁已经被其他线程获取,则当前线程不会被阻塞,而是立即返回false。

unlock():释放锁。将锁的状态设置为可用,允许其他线程获取锁。

newCondition():创建一个与该锁关联的条件对象。条件对象可以用于实现更复杂的线程同步机制,例如等待/通知模式。

lockInterruptibly():获取锁,但允许在获取锁的过程中被中断。如果当前线程在获取锁的过程中被中断,则会抛出InterruptedException异常。

tryLock(long time, TimeUnit unit):在指定的时间内尝试获取锁。如果在指定的时间内未能获取到锁,则返回false。

getHoldCount():获取当前线程持有该锁的次数。如果当前线程未持有该锁,则返回0。

getQueueLength():获取正在等待获取该锁的线程数量。

isHeldByCurrentThread():判断当前线程是否持有该锁。

isLocked():判断锁是否被任何线程持有

84、分布式ID的特性

• 唯一性:确保生成的ID是全网唯一的。
• 有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
• 高可用性:确保任何时候都能正确的生成ID。
• 带时间:ID里面包含时间,一眼扫过去就知道哪天的交易。

85、服务分层

服务分层是一种常见的软件架构设计模式,它将应用程序的不同功能和责任划分为不同的层次,以实现代码的模块化、可维护性和可扩展性。

通常,服务分层可以分为以下几个层次:

  1. 表现层(Presentation Layer):负责与用户交互,接收用户的请求并展示数据。常见的表现层包括Web界面、移动应用界面等。

  2. 应用层(Application Layer):负责处理业务逻辑,协调各个领域模型之间的交互。应用层通常包含一些服务和应用服务,用于处理具体的业务需求。

  3. 领域层(Domain Layer):包含业务领域的核心逻辑和规则。领域层是应用程序的核心,它包含了实体、值对象、聚合根等领域模型,以及领域服务和领域事件等。

  4. 基础设施层(Infrastructure Layer):提供与外部系统的交互和数据存储等基础设施支持。基础设施层包括数据库访问、消息队列、缓存、文件系统等。

通过将不同的功能和责任划分到不同的层次,服务分层可以实现以下好处:

  1. 模块化和可维护性:不同层次的代码可以独立开发和维护,使得系统更易于理解和修改。

  2. 可扩展性:通过定义清晰的接口和契约,不同层次的组件可以相对独立地进行扩展和替换。

  3. 可测试性:不同层次的代码可以进行单元测试和集成测试,以确保系统的质量和稳定性。

总之,服务分层是一种有效的软件架构设计模式,可以帮助开发人员构建可维护、可扩展和高质量的应用程序。

86、redis的哨兵机制

Redis的哨兵机制是Redis提供的一种高可用性解决方案,用于监控和管理Redis主从复制架构中的故障转移和自动故障恢复。

在Redis的哨兵机制中,有一个或多个哨兵进程运行在独立的服务器上,它们通过定期向Redis服务器发送心跳检测来监控Redis的状态。当哨兵检测到主节点(master)宕机或不可达时,它会根据预定义的规则和策略,自动选举一个从节点(slave)作为新的主节点,并将其他从节点切换到新的主节点上。

哨兵机制的主要功能包括:

  1. 监控:哨兵定期向Redis服务器发送PING命令,检测服务器的状态,包括主节点和从节点的可用性。

  2. 故障检测:当哨兵检测到主节点不可达时,它会将主节点标记为下线,并开始进行故障转移的流程。

  3. 故障转移:哨兵会选举一个从节点作为新的主节点,并将其他从节点切换到新的主节点上。它会发送相应的命令,使得客户端可以自动切换到新的主节点。

  4. 自动恢复:当主节点重新上线时,哨兵会将其重新加入到主从复制架构中,并将其作为从节点连接到新的主节点。

通过使用Redis的哨兵机制,可以实现Redis的高可用性和故障恢复能力。它可以自动监控和管理Redis集群的状态,减少人工干预的需求,提高系统的可靠性和稳定性。

需要注意的是,哨兵机制并不是完全无故障的,它仍然可能存在单点故障的问题。为了进一步提高可用性,可以使用Redis Cluster来替代哨兵机制,它提供了分布式的高可用解决方案。

87、BIO,NIO,AIO分别是什么?

BIO,NIO,AIO是三种不同的I/O模型,用于描述计算机系统中的输入输出操作。

  1. BIO(Blocking I/O):阻塞I/O模型,也称为同步I/O模型。在BIO模型中,当应用程序发起一个I/O操作时,它会一直阻塞直到操作完成。这意味着应用程序在等待I/O操作完成期间无法执行其他任务。

  2. NIO(Non-blocking I/O):非阻塞I/O模型,也称为同步非阻塞I/O模型。在NIO模型中,应用程序可以发起一个I/O操作,然后继续执行其他任务,而不需要等待操作完成。应用程序可以通过轮询来检查I/O操作是否完成,从而实现非阻塞的效果。

  3. AIO(Asynchronous I/O):异步I/O模型,也称为事件驱动I/O模型。在AIO模型中,应用程序发起一个I/O操作后,不需要等待操作完成,而是继续执行其他任务。当操作完成时,操作系统会通知应用程序,从而实现异步的效果。AIO模型通常使用回调函数来处理I/O操作的结果。

这些I/O模型在不同的场景下有不同的适用性,选择合适的模型可以提高系统的性能和响应能力。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力奋斗的JAVA小余

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值