零零碎碎的一些小知识点

Mybatis

ORM框架: 持久化框架, 对象关系映射框架

用于实现面向对象编程里 不同类型系统的数据 之间的转换

mappers 标签 . 加载有几种方式? (4种)

1.package

2.resource

3.url

4.mapperClass

优先级: package>resource>url>mapperClass

mybatis有三种执行器Executor

1.simple

2.reuse

3.batch

​ openSession实质上是对 执行器进行初始化

mybatis一级缓存 默认开启.

cacheEnabled=true

生产环境使用HashMap导致cpu100%占用问题.

hashmap单向链表, linkedmap双向链表.

HashMap的本质

JDK7

1.数组(采用一段连续的存储单元来储存数据插入慢,查询快)

2.链表(物理存储单元上一段非连续,非顺序的存储结构 插入快,查询慢)

头插法

算法: 哈希算法 (也叫散列)

把任意长度值(Key)通过散列算法变换成固定长度的key(地址)通过这个地址进行访问的数据结构,它通过把关键码值映射到表中的一个位置来访问记录,以加快查找的速度

哈希冲突(碰撞) > 链表

JDK8 数组/链表/红黑树 (尾插法)

红黑树的意义

为了解决链表过长查询效率过低的问题.

阈值 8 ,数组长度超过8会转换成红黑树,低于6又变为单向链表.

左中右. 红黑树会牺牲插入的性能.

String, StringBuffer,StringBuilder的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XrmgvedJ-1612419354250)(C:\Users\2020\AppData\Roaming\Typora\typora-user-images\image-20210201223512183.png)]

String底层是使用final关键字修饰的char数组来存储字符.因此是不可变的

StringBuffer是线程安全的, StringBuilder是线程不安全的.

开发中优先使用StringBuilder,性能相较于StringBuffer高.

接口和抽象类的区别

JDK8之前

​ 抽象类: 方法可以有抽象的,也可以有非抽象的, 有构造器

​ 接口:方法都是抽象,属性都是常量,默认public static final修饰.

JDK8之后

​ 接口里面可以有实现的方法,注意要在方法的声明上加入default或者static.

接口的好处是解耦. 高内聚低耦合.

List和Set的区别

list 有序可重复

Set无序不可重复

Collections 工具类

Collection 集合根接口

ArrayList和LinkedList的区别

ArrayList是数组,是连续的内存空间,方便寻址,但删除,插入慢. 因为需要发生数据迁移. (定位快)

LinkedList是双向链表,不是连续的内存空间,查找慢是需要通过指针一个个寻找,但删除插入快, 只要改变前后节点的指针指向.

SpringIOC的理解

使用方式:

完成2件事情:
1.解析
2.赋值
=================================================
1.配置文件的方式
<bean>
	<args></args>
</bean>

1.解析XML ===> Dom4j
2.调用相关方法实现注入.(反射)

=================================================
2.注解的方式
@Autowrie或者@Resource

1.解析 "类" .
	加入注解, 发现这个 "类" 是归Spring管理的. 将所有注解的类管理起来,
	获取到类的注解信息和属性的注释信息(反射).
2.赋值

HashSet的存储原理

​ 主要是从如何保证唯一性来说起.

/**
 * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
 * default initial capacity (16) and load factor (0.75).
 */
public HashSet() {
    map = new HashMap<>();
}

//底层其实是一个HashMap的key值来存储.

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}

1.为什么采用Hash算法,有什么优势?解决了什么问题?

唯一性.

​ 存储数据底层采用的是数组.但是如何判断数据是否唯一?(遍历但是效率低)

​ 所以使用Hash算法,通过计算存储对下个的hashcode,然后再跟数组长度-1 做位运算,得到我们要存储在数组的哪个下标下,如果此时计算的位置没有其他元素,直接存储. (hashcode).

​ 随着数据增多,可能会出现哈希碰撞的问题, 即不同的对象计算出来的hash值是相同的,这个时候就需要比较==> equals方法.

​ 如果equals相等,则不插入. 不相等则形成链表.

2.所谓的哈希表是什么?

​ 本质是一个数组, 而且数组的元素是链表. JDK8之后 优化为 红黑树结构 RB-TREE

ArrayList 与 Vector 的区别

ArrayList 线程不安全, 效率高, 常用

Vector 线程安全但是效率低. 用synchronized修饰

Hashtable & HashMap & ConcurrentHashMap

1.Hashtable : 线程安全的对象, 内部有上锁的控制synchronized

2.HashMap : 线程不安全的对象, 优点:效率高 . 缺点:多个线程同时操作一个hashmap就可能出现线程不安全,甚至出现死锁的情况

3.Collections.synchronizedMap(). 工具类提供了一个同步包装器的方法,来返回具有线程安全的集合对象.(性能依然有问题,实际上跟用HashTable没什么太大的区别)

4.ConcurrentHashMap (分段锁): 兼顾了线程安全和效率的问题. 将锁的粒度变小

​ HashTable锁了整段数据(用户操作的是不同的数据段,依然需要等待).解决方法: 把数据分段,执行分段锁(分离锁),核心把锁的范围变小, 这样出现并发冲突的概率就变小.

​ 在保存的时候,计算所存储的数据是属于哪一段,只锁当前这一段.

分段锁是JDK8之前的一种方案,JDK8之后做了优化.

在开发中如何选择???

1.优先选择HashMap, 如果不是多个线程访问同一个资源的情况下优先选择HashMap. (局部变量,不是全局变量)

2.如果是全局变量,在多个线程共享访问的情况下选择ConcurrentHashMap .

开发一个自己的stack 栈.

1.数组 2.先进后出 3.出栈 4.入栈

双向链表
class Node<T>{
    Node pre;
    Node next;
    T data;
}

IO流的分类及选择

1.分类:

​ 按方向分: 输入流, 输出流.

​ 按读取的单位分:字节流(二进制文件),字符流(文本文件)

字节流能搞定任何信息,只是解析中文可能会出现乱码,字符流只是字节流+编码

字节流的InputStream和OutputStream是一切的基础。实际总线中流动的只有字节流。需要对字节流做特殊解码才能得到字符流。Java中负责从字节流向字符流解码的桥梁是:

InputStreamReader
InputStreamWriter

​ 按处理的方式分(一个一个传输还是批量传输?):节点流,处理流.

比如,FileInputStream和BufferedInputStream(后者带有缓冲区的功能-byte[]) 本质是数组.

IO流的4大基类:InputStream,OutputStream, Reader,Writer

seriaIVersionUID的作用是什么? 版本号

当执行序列化时,我们写对象到磁盘中,会根据当前这个类的结构生成一个版本号.

当反序列号的时候,程序会比较磁盘中的序列化版本号ID跟当前的类结构生成的版本号是否一致? 如果一致则反序列化成功,否则失败.

​ 加上版本号 有助于我们类结构发生变化依然可以之前已经序列化的对象反序列化成功.

ObjectOutputStream.writeObject(stu);
//序列化
//根绝类结构生成序列化版本号.

XXX xxx = (xxx)ObjectInputStream.readObject();
//是否能强转成功?
//拿当前这个类的结构生成当前的版本号
//比较两个版本号, 如果相同则说明是同一个对象则反序列化成功.

所以一般会在类中加入版本号, 主要是为了新版本升级,兼容老版本

异常体系(为了保证健壮性)

常见的运行时异常: (此类易唱编译时没有提示做异常处理,通常此类异常的正确理解应该是"逻辑错误")

算数异常 ArithmeticException

空指针 NullPointerException

类型转换异常 ClassCastException

数组越界异常 IndexOutOfBoundsException

NumberFormateException(数字格式异常,转换失败)

常见的非运行时异常

IOException

SQLException

FileNotFoundException

NoSuchFileException

NoSuchMethodException

throw和throws的区别

throw 作用于方法内,用于主动抛出异常.

throws 作用于方法声明上,声明该方法有可能会抛出某些异常.

针对项目中,异常的处理方式,一般采用的是层层上抛,最终通过异常机制统一处理 (展示异常解面或者返回统一的json信息). 自定义异常一般都继承RunTimeException.

为什么框架的异常都是继承RuntimeException

因为框架是定义一系列的规则, 它不属于语法错误,而是属于逻辑错误.

创建线程的方式

继承Thread

实现Runable接口

实现Callable接口(可以获取线程执行之后的返回值…)

本质上来说创建线程的方式就是继承Thread,就算是线程池的内部也只是创建好了线程对象来执行任务.

实际后两种,更准确的理解是创建了一个可执行的任务,要采用多线程的方式执行,还需要通过创建Thread对象来执行.

实际开发中一般采用线程池的方式来完成Thread的创建,更好的管理线程资源

除了main方法以外还有GC后台线程

GC 垃圾回收机制 垃圾回收线程.

它是一个后台线程.

线程的生命周期

1.new新建状态

2.runnable就绪状态

3.blocked阻塞状态

4.waiting等待状态

5.timed waiting 超时等待

6.terminated 终止状态.

1.当进入到synchronized同步代码块或者同步方法时,且没有获取到锁,线程就进入了blocked状态,直到锁被释放,重新进入runnable状态.

2.当线程调用wait()或者join时,线程都会进入到waiting状态,当调用notify或者notifyAll的时候,或者join的线程执行结束后,就会进入runnable状态,.

3.当线程调用sleep(time),或者wait(time)时,进入timed waiting状态. 当休眠时间结束之后,或者调用notify或者notifyAll时就会重新进入runnable状态.

4.程序执行结束,线程进入terminated状态.

线程安全的理解

实现线程安全的方式有多种,其中在源码中常见的方式—> 采用synchronized关键字给代码块或者方法上锁. 比如StringBuffer, 已经在方法内部加锁了.

那么在开发中需要拼接字符串,使用StringBuilder还是StringBuffer?

看场景,如果在方法中使用,那么建议在方法中创建StringBuilder. 这个时候相当于是每一个线程独立的占有一个StringBuilder对象,不存在多线程共享一个资源的情况. 尽管StringBuilder不是线程安全的,但是依然可以放心使用.

什么时候需要考虑线程安全???

1.多个线程访问同一个资源.

2.资源是有状态的.比如特定情况下的字符串拼接,这个时候数据是会有变化的.

sleep和wait的区别

sleep是定义在Thread上的. 不会释放锁. 可以使用在任何代码块

wait是定义在Object类上的. 会释放锁. 只可以使用在同步方法/同步代码块

与wait配套的使用方法:

​ 唤醒: notify()(唤醒随机一个). notifyAll()(唤醒所有).

为什么wait要定义在Object中,而不是定义在Thread中

​ 在同步代码块中,需要的是对象锁来实现多线程的互斥效果. 也就是说 Java的锁是对象级别的而不是线程级别的.

为什么wait要写在同步代码块中

​ 避免CPU切换到其他线程. 而其他线程又提前执行了notify方法,那样就达不到预期(先wait再由其他线程来唤醒). 所以需要一个同步锁来保护.


双亲委派机制

针对一些Class,JDK是怎么分工的??谁来加载???

不同的类加载器ClassLoader

1.Java核心类, 这些Java运行的基础类,由一个名为BootstrapClassLoader加载器负责加载,这个类加载器被称为 “根加载器或者引导加载器”

注意:BootstrapClassLoader 不继承 ClassLoader. 而是由JVM内部实现. 所以通过java程序访问不到,得到的是NULL.

2.Java扩展类,是由ExtClassLoader负责加载,被称为 “扩展类加载器”.

3.项目中编写的自定义类是由AppClassLoader来负责加载. 被称为 “系统类加载器”

当一个文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。那么有人就有下面这种疑问了?

为什么要设计这种机制
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。


Ajax的工作原理

关键三要素: 异步交互,XmlHttpRequest对象, 回调函数.

​ 早期是用XML为主的传输数据格式,但现在更多的是用Json,因为XmlHttpRequest需求的内存较大.

JSP和servlet的区别

技术角度

JSP本质就是一个servlet

JSP的工作原理: JSP —>翻译 —> servlet(java) —> 编译 —> class

应用角度

JSP = HTML + Java

Servlet = Java+Html

JSP的特点是实现视图. Servlet的特点在于实现控制逻辑

servlet的生命周期

servlet是单实例的!!!

生命周期的流程:

创建对象 > 初始化 > service() >doXXX() > 销毁

创建对象的时机:

1.默认是第一次访问该Servlet的时候创建

2.也可以通过配置web.xml,来改变创建的时机 ,比如在容器启动的时候创建.

​ 例如DispatcherServlet (SpringMVC前端控制器)

​ 1

执行的次数:

对象创建只有一次,单例

初始化一次,销毁一次 (多次)

关于线程安全

构成线程不安全的三个因素:

1.多线程环境

2.多线程共享资源, 比如一个单例对象

3.这个单例对象是有状态的 (比如在Servlet方法中采用全局的变量,并且以该变量的运算结果作为下一步操作的判断依据)

Session和Cookie的区别

1.存储位置不同

Session: 服务端

Cookie: 客户端

2.存储格式不同

Session : value为对象,Object类型

Cookie: value为字符串,如果存储一个对象,这个时候需要将对象转换为Json类型

3.存储的数据大小不同

Session:受服务器内存的控制

Cookie: 一般来说最大为4K.

4.生命周期不同

Session:受服务器端控制,默认是30分钟. (当用户关闭浏览器,Session并不会消失)

Cookie: 受客户端控制,其实是客户端的一个文件.分两种情况

​ 1.默认是会话级的cookie,随着浏览器的关闭而消失. 例如保存sessionid的cookie

​ 2.非会话级cookie,通过设置有效期来控制,比如 “7天免登录” . setMaxAge

5.cookie的其他配置

HttpOnly=true 防止客户端XSS攻击 (跨站脚本攻击)

Path="/" 访问路径

domain="" 设置cookie的域名 (跨域情况下考虑的问题)

6.cookie和session之间的联系

http协议是一种无状态协议,服务器为了记住用户的状态,采用的是Session机制. 即 服务器会自动生成会话级的cookie来保存session的标识.

转发和重定向的区别

1.转发

发生在服务器内部的跳转, 所以对客户端来说至始至终都只是一次请求. 所以在此期间存放在request对象中的数据可以传递.

2.重定向

发生在客户端的跳转,是多次请求,这个时候如果要在多次请求之间传递数据,需要用session对象

谈谈三层架构

1.javaEE将企业级软件架构分为三个层次:

Web层: 负责与用户交互并对外界提供服务接口

业务逻辑层 : 实现业务逻辑模块

数据存取层: 将业务逻辑层处理的结果持久化,方便后续查询

2.每个层都有各自的框架

Web层: SpringMVC,Struts2,Struts1

业务逻辑层:spring

数据持久层:Hibernate,Mybatis,SpingDataJPA,SpringJDBC

MVC是对Web层做了进一步的划分

Model 模型 POJO

View 视图 HTML.JSP.Thymeleaf.

Controller 控制器 . Servlet,Controller

比如,SpringMVC分为两个控制器

DispatchServlet:前端控制器,由它接收客户端请求,再根据客户端请求的URL的特点,分发给对应的业务控制器.

JSP的9大内置对象

JSP无需创建,可以直接使用

request

response

config

application

session

exception

page

out

pageContext

JSP的4大域对象

ServletContext context域:只能在同一个web应用中使用 (全局的)

HttpSession session域 只能在同一个会话中使用

HttpServletRequest request域 只能在同一个请求中使用,转发有效,重定向无效.

PageContext page域 只能在当前jsp页面中使用

并发和并行的区别

并发:同一个CPU执行多个任务,按细分的时间片交替进行

并行:多个CPU上同时处理多个任务.

数据库设计的三大范式以及反范式

数据库三大范式

第一范式:列不可分,保证原子性

第二范式:要有主键,完全依赖于主键.

第三范式:不可存在传递依赖,不浪费空间.

第三范式主要是从空间的角度来考虑,避免产生冗余数据,浪费磁盘空间.

反范式设计:(反第三范式,追求查询的性能)

为什么会有反范式设计?

​ 1.提高查询效率(读多写少)

​ 2.保存历史快照信息

常见的聚合函数

count / sum / avg / max / min

左连接,右连接,内连接.

左连接,以左表为主

select a.×,b.× from a left join b on a.b_id = b.id;

右连接,以右表为主

right join

内连接:只列出两张表关联查询复合条件的记录.

inner join

如何解决SQL注入?

采用预处理对象, PreparedStatement对象而不是Statement.

​ 另外也可以提高执行效率, 因为是预编译执行.

SQL执行过程(语法检验 > 编译 > 执行)

Mybatis如何解决了SQL注入的问题???

​ 采用#.

Mybatis的# 和 ‘$’ 的差异?

​ # 可以解决SQL注入的问题, ? 号不能.

JDBC对事务的控制

connection.setAutoCommit(false);	//不自动提交事务

doxxx();
connection.commit();// 手动提交


事物的边界一般是放在业务层,因为业务层通常包含多个dao层的操作

事务的特点

ACID. 原子性,一致性,隔离性,持久性

原子性是基础,隔离性是手段,一致性是约束条件,持久性是目的.

原子性: 要么一起成功要么一起失败

一致性:数据库中的数据在事务操作前后都必须满足业务规则约束.

隔离性:一个事务的执行不能被其他事务干扰.(不同的隔离级别,互相干扰的程度不同)

持久性:事务一旦提交,结果便是永久的.即使发生宕机,也会依靠事务日志完成数据的持久化.

日志包括'回滚日志'和'重做日志'.

​ 当我们通过事务进行数据修改时,首先会将数据库变化的信息记录到重做日志中,然后再对数据库中的数据进行修改. 这样即便是数据库崩溃也可以通过重做日志进行数据恢复.

并发下事务会产生的问题

举个例子,事务A和事务B操纵的是同一个资源,事务A有若干个子事务,事务B也有若干个子事务,事务A和事务B在高并发的情况下,会出现各种各样的问题。“各种各样的问题”,总结一下主要就是五种:第一类丢失更新、第二类丢失更新、脏读、不可重复读、幻读。五种之中,第一类丢失更新、第二类丢失更新不重要,不讲了,讲一下脏读、不可重复读和幻读。

1、脏读

所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。

2、不可重复读

所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。

3、幻读

所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。

事务隔离级别

事务隔离级别,就是为了解决上面几种问题而诞生的。为什么要有事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大****,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。

事务隔离级别有4种,但是像Spring会提供给用户5种,来看一下:

1、DEFAULT

默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别

2、READ_UNCOMMITTED

读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用

3、READ_COMMITED

读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读

4、REPEATABLE_READ

重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决

5、SERLALIZABLE

串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

MySQL默认第三隔离级别。

Oracle默认第二隔离级别。

Synchronized和lock的区别

1.作用的位置不同

synchronized可以给方法,代码块加锁.

lock只能给代码块加锁.

2.锁的获取锁和释放机制不同

synchronized无需手动获取锁和释放锁,发生异常会自动解锁.不会出现死锁的现象.

lock需要自己加锁和释放锁,如lock()和unlock(). 如果忘记解锁就会出现死锁的现象.

所以一般会在finally中使用unlock()

//明确采用手工手段获取锁
lock.lock();

//明确采用手工的手段释放锁
lock.unlock();
synchronized修饰成员方法时,默认锁对象是当前对象. (对象锁)
如果synchronized修饰静态方式时,默认锁的对象是当前类的class对象. (类锁)
synchronized修饰代码块时可以自己设置锁对象,如:
synchronized(this);{
    //线程进入,自动获取锁

    //线程执行结束,自动释放锁
}

TCP和UDP的区别

首先,两者都是属于传输层的协议.

TCP提供可靠的传输协议,传输前需要建立连接,面向字节流,传输慢. > 打电话

UDP无法保证传输的可靠性,无需创建链接,以报文的方式传输,效率高. >发短信


什么是TCP的三次握手?四次挥手?

三次握手建立可靠连接.保证彼此都可以确认对方收到自己的消息.

三次握手:

第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

四次挥手:

第一步,当主机A的应用程序通知TCP数据已经发送完毕时,TCP向主机B发送一个带有FIN附加标记的报文段(FIN表示英文finish)。

第二步,主机B收到这个FIN报文段之后,并不立即用FIN报文段回复主机A,而是先向主机A发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)。

第三步,主机B的应用程序告诉TCP:我要彻底的关闭连接,TCP向主机A送一个FIN报文段。

第四步,主机A收到这个FIN报文段后,向主机B发送一个ACK表示连接彻底释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值