面试常见知识点梳理

常见 奔涌吧 ~

jvm

内存分布区域

​ 堆 TLAB(Thread Local Allocation Buffer)

​ 栈 /本地方法栈(私有)

​ 程序计数器(私有)

​ 方法区 (1.8 后元数据区 metaspace https://www.cnblogs.com/duanxz/p/3520829.html)

线程 1.8默认 1m 并且不是占用jvm内存

jvm运行内存

​ 老年代 标记清除算法: (1:3)

​ 新生代 eden survivor to survivor form. 复制算法 (8:1:1)

​ 永久代 (jdk8移除)

垃圾回收和算法

​ 如何确定垃圾

​ 引用计数法 (循环引用问题)

​ 可达性分析 (gc roots 栈变量、静态变量)

​ 标记清除

​ 标记复制

​ 标记整理

​ 分代回收

垃圾回收器

​ serial

​ parnew prallel sc (dfs bfs)

​ cms 四个过程

​ 初始标记(stw 直接到达),

​ 并发标记(预清理),

​ 重新标记(stw 修正并发期间变动的, 三色标记法(黑灰白) write barrier+set (cart table) ),

​ 并发清理

​ g1 改进:

​ 基于标记-整理不产生碎片

​ 精确控制停顿时间

​ 区域划分和优先级列表

ok—>怎么调优?

观察 jstat -gcutil pid 1000 , 确认是否需要调优(10s一次young 不管 10min一次full 不管)

无非就两方(考虑吞吐和响应) fullgc较长 fullgc(young gc)频繁

减少young gc , full gc (将老年代数量减少,扩大老年代 或者 gc时间)

考虑使用的垃圾回收器

考虑 堆总大小

考虑 堆老年代新生代比例

ok->怎么定位问题?

高cpu top -h 找到pid , 再找出线程id (转化为16进制) jstack (如果是死循环,多dump几次找出 一直执行的方法)

端口号占用: lsof

引用类型

​ 强 软 弱 虚

类加载

​ 加载

​ 链接 (准备(实例化)、验证、引用)

​ 初始化

​ 使用

​ 卸载

类加载机制层级

双亲委派 (破坏 驱动加载、 tomcat web项目)

吞吐 低延迟

invokedynamic

并发

内存模型

充分利用cpu资源

锁 (有序 原子 可见)

​ sync

​ 优化

​ cas 自旋锁(悲观锁/乐观锁)

​ 偏向锁

​ 轻量级锁

​ 重量级锁

​ volatile (有序性 (内存屏障)、可见性(总线锁 /缓存锁 )) jmm模型。

->happen before

  1. 单线程顺序 serial if
  2. volatile
  3. 传递性
  4. thread 启动 中断 终结
  5. 对象创建 hb finalize

编程方式锁 juc

​ cas + volatile

​ aqs 同步阻塞队列 condition

​ 共享锁/独占锁 (读写锁)

​ 公平锁/非公平锁

​ 可重入锁

锁优化

​ jvm级别 锁粗化、子旋、消除

​ 编程。减少锁粒度、锁持有时间 、分离

线程生命周期

new -> runnable->running ->blocked->dead

Thread启动

start 当前进程 , start0另外线程

简单总结: 就是在虚拟机中启动个线程 (屏蔽了平台无关) ,分配内存,线程id, 设置状态。 然后回调java中的run方法

线程基本操作

上下文切换

是指某一时间点 CPU 寄存器和程序计数器的内容。

线程池(策略)

ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

Coresize 判断

队列判断

最大线程判断

拒绝策略->

shutdown和shutdown now

java.util.concurrent.ThreadPoolExecutor#shutdownNow , 注意这个方法并不是强制退出哈, 它和 shutdown区别如下

  • 只是它会对运行中的线程设置中断标记
  • 还有不再执行等待队列的任务,返回未执行任务

拒绝策略

丢弃

抛出异常(默认)

丢弃最老

由提交者线程执行

第三方

Dubbo 打印堆z信息 dump线程。抛出异常

Netty

Netty中的实现很像JDK中的CallerRunsPolicy,舍不得丢弃任务。不同的是,CallerRunsPolicy是直接在调用者线程执行的任务。而 Netty是新建了一个线程来处理的。所以,Netty的实现相较于调用者执行策略的使用面就可以扩展到支持高效率高性能的场景了。但是也要注意一点,Netty的实现里,在创建线程时未做任何的判断约束,也就是说只要系统还有资源就会创建新的线程来处理,直到new不出新的线程了,才会抛创建线程失败的异常

activemq

activeMq中的策略属于最大努力执行任务型,当触发拒绝策略时,在尝试一分钟的时间重新将任务塞进任务队列,当一分钟超时还没成功时,就抛出异常

https://blog.csdn.net/huangjinjin520/article/details/103047363

阻塞队列

Chm

https://zhuanlan.zhihu.com/p/114508758?utm_source=wechat_session&utm_medium=social&utm_oi=557574927448846336

Aqs

​ AQS 围绕两个队列,提供了四大场景,分别是:

获得锁、释放锁、条件队列的阻塞,条件队列的唤醒,分别对应着 AQS 架构图中的四种颜色的线的走向。

​ 同步队列 + 条件队列,同步队列管理着获取不到锁的线程的排队和释放,条件队列是在一定场景下,对同步队列的补充,比如获得锁的线程从空队列中拿数据,肯定是拿不到数据的,这时候条件队列就会管理该线程,使该线程阻塞;

节点

线程调度算法。分片 抢占式

cpu密集n+1, io密集2n+1

java语言

集合类

List

扩容机制1.5

HashMap

https://juejin.im/post/5d5d25e9f265da03f66dc517

1.7->1.8

String hashCode equal重写

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

第一,31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了31呢?请看第二个原因。

第二、31可以被 JVM 优化,31 * i = (i << 5) - i

问题

  • (1)多线程扩容,引起的死循环问题 (1.8尾插法)
  • (2)多线程put的时候可能导致元素丢失
  • (3)put非null元素后get出来的却是null

2.你一般用什么作为HashMap的key?

一般用Integer、String这种不可变类当HashMap当key,而且String最为常用。

  • (1)因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。
  • (2)因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的,这些类已经很规范的覆写了hashCode()以及equals()方法。

树化

链表大于等于8 且大小大于64

6 且在Resize的时候

LinkedHashMap:

了解基本原理、哪两种有序(返回有序、插入有序) 、如何用它实现LRU(ture)

https://zhuanlan.zhihu.com/p/62322204

继承HashMap+双向链表

// Callbacks to allow LinkedHashMap post-actions
void afterNodeAccess(Node<K,V> p) { }
void afterNodeInsertion(boolean evict) { }
void afterNodeRemoval(Node<K,V> p) { }

TreeMap

​ 红黑树实现 (一致性哈希 )

https://blog.51cloud.win/2018/10/05/Hash%E4%B8%80%E8%87%B4%E6%80%A7%E7%AE%97%E6%B3%95%E4%B8%8EJava%E7%9A%84%E7%AE%80%E5%8D%95%E5%AE%9E%E7%8E%B0/

NavigableMap

字节码

对象

​ 对象头

​ 数据占用内存:

​ 1 BYTE = 8 bit

​ 2BYTE = 1 chara

​ 4BYTE = 1Int. Float

​ 8Byte = long double

语言特征

​ 接口和抽象类

spring

ioc

bean生命周期

​ load (bean defined)

​ BFPP

​ 实例化 接口回调

​ 属性注入

​ 初始化 ()AwareMethods BPP invoke(init)

细节

​ 循环依赖

​ 作用域

容器启动过程

​ 预处理

​ 实例化 refresh (获取beanFactory)

​ 初始化 配置相关回调(Aware), 内建依赖, 注册相关监听器,环境信息

​ BFPP注册/调用

​ BPP注册

​ initMessageSource

​ 注册事件播放器, 配合listener使用(Initialize event multicaster for this context.)

​ on refresh 子接口

​ 监听

​ Bean初始化

aop

实现原理

​ 代理 (静态agent和动态)

事务 (隔离级别)

​ 和mysql比多了个defualt

传播级别

request。 当前有事务使用,没有新建 (一起回滚)

Request_new。新建事务

nest。嵌套事务 (内回滚不影响外部)

support 不必也可以

Non_suport 不支持事务,有事务会挂起

Never 有事务抛出异常

Mandatory 没有事务抛出异常

实战

​ 日志(mq,写es) , 重复调用问题 (分布式锁)

mvc

​ servlet 启动流程

​ 八大组件

rpc

Dubbo

http://dubbo.apache.org/zh-cn/docs/dev/design.html

jsf2.0 (plugin)

image-20200512202119181

应用层

代理 (proxy)

​ jdk 、javassist、byte buddy

https://zhuanlan.zhihu.com/p/87393183

调用链 (tracing)

​ callGraph pFinder zipkin

缓存 (cache)

​ cache2k caffeine guava

配置 (config)

​ jsf registry zk consul

集群层

​ 节点选择(动态分组)

​ 区域感知 、全连

​ 集群策略(调用策略)

​ fallover 、failfast、 broadcast

​ 服务发现

​ jsf registry zk consul

​ 负载(路由)

​ roundRobin、random、consistentHash

协议层

​ 协议

​ jsf2 jsf1 grpc

​ 序列化协议

​ fat、pb、msgpack

https://tech.meituan.com/2015/02/26/serialization-vs-deserialization.html

​ 压缩

​ lz4 、lzma、 snnappy

传输层

​ netty apcheCXF resteasy

网络模型 (nio)

​ 微服务管理: 服务发现、负载路由、限流、链路追踪、熔断

流量控制

  • 节点选择,包括选择建立连接、负载选择
  • 流量达到预期之后,如何限制
  • 超预期后快速释放、恢复

mq

谢耦、异步、削峰

缺点: 可用性

复杂度

一致性问题

Rocketmq

Kafka

redis

数据结构(重点)

​ string

​ embstr(sds)

​ raw

​ hash

​ ziplist

​ rehash过程: http://redisbook.com/preview/dict/incremental_rehashing.html

​ list

​ ziplist

​ quicklist(多个ziplist + linkedlist)

​ set

​ zset (类似TreeMap)

​ 跳跃表

主从同步 (细节、一致性问题 raft)

​ sential

​ 集群 一致性哈希() 哈希槽(Gossip)

分布式锁

单节点:

由于超时时间导致锁被多 Client 同时获取:

异步的主从复制 & Master 宕机,导致锁丢失:

集群问题:

未解决执行时间大于过期时间问题

RedLock性能及崩溃恢复的相关解决方法

  1. 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。

  2. 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。

  3. 节点C重启后,客户端2锁住了C, D, E,获取锁成功。

    延迟重启(delayed restarts)的概念。也就是说,一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)。这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

基于故障转移实现的redis主从无法真正实现Redlock

  1. 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。

  2. 节点C崩溃,并且崩溃前锁未同步到slave节点锁,slave节点升级为master节点,这时该master节点并没有加锁。

  3. 这时客户端2锁住了C, D, E,获取锁成功。

    这个失败的原因是因为从redis立刻升级为主redis,如果能够过TTL时间再升级为主redis(延迟升级)后,或者立刻升级为主redis的节点只有过TTL的时间后再执行获取锁的任务,就能成功产生互斥效果;是不是这样就能实现基于redis主从的Redlock了

    最后,Martin得出了如下的结论:

    • 如果是为了效率(efficiency)而使用分布式锁,允许锁的偶尔失效,那么使用单Redis节点的锁方案就足够了,简单而且效率高。Redlock则是个过重的实现(heavyweight)。
    • 如果是为了正确性(correctness)在很严肃的场合使用分布式锁,那么不要使用Redlock。它不是建立在异步模型上的一个足够强的算法,它对于系统模型的假设中包含很多危险的成分(对于timing)。而且,它没有一个机制能够提供fencing token。那应该使用什么技术呢?Martin认为,应该考虑类似Zookeeper的方案,或者支持事务的数据库。

    参考:https://www.jianshu.com/p/2afeae8cda89

原理

高性能?

单线程 (线程切换)、内存操作、nio

内存管理 回收 、 持久化机制

实战问题

数据一致性问题(db和mysql)

​ 方法一、 先更新db 再更新redis(同步更新、异步监听变化 失败都发mq重试)

​ 方法二、先更新redis 再db

​ a->删除 (b->查询不存在,写redis) a->写db (解决方法 延迟双删)

雪崩 : 查mysql入队列、预更新、随机过期

穿透 : 缓存null值(黑名单) , 过滤器(布隆 误判率 存在不一定存在)

es

实现原理

​ 倒排索引

角色

​ Master 、Node 、协调 、预处理 、部落节点(多集群客户端)

写入流程

ETL

mysql

Myisam 无事务、全文哈希

innodb 事务、索引存储结构

mysql统计

https://blog.csdn.net/u012952442/article/details/48294849

索引

​ btree

​ InnoDB 底层存储结构为 B+树, B 树的每个节点对应 innodb 的一个 page,page 大小是固定的,

一般设为 16k。其中非叶子节点只有键值,叶子节点包含完成数据。

事务(ACID)

​ 原子性(Atomicity)

​ 一致性(Consistency)

​ 隔离性(Isolation)

​ 永久性(Durability)

​ 事务隔离级别

读未提交 脏读

​ 读已提交 | 不可重复读

​ 可重复读 |幻读

​ 串行

加锁分析

https://tech.meituan.com/2014/08/20/innodb-lock.html

在RR的隔离级别下,Innodb使用MVCC和next-key locks解决幻读,MVCC解决的是普通读(快照读)的幻读,next-key locks解决的是当前读(select from update)情况下的幻读。

唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。(区分度高的列作为索引)

为经常需要排序、分组和联合操作的字段建立索引

为常作为查询条件的字段建立索引。

限制索引的数目

尽量使用前缀来索引(String类型)

删除不再使用或者很少使用的索引

最左前缀匹配原则,非常重要的原则。

范式(冗余)

第一范式**(1st NF** -列都是不可再分)

第二范式**(2nd NF**-每个表只描述一件事情)

第三范式**(3rd NF**- 不存在对非主键列的传递依赖**)**

Mysql 日志

https://www.cnblogs.com/wy123/p/8365234.html

调优: explain

todo 服务结构: 客户端 、服务端 、引擎

todo 执行过程:

Tcp->缓存->解析->优化->执行->返回(缓存)

Netty

通信方式 nio 多路复用 事件机制

线程模型

Reactor 单线程模型

image-20200311221758454

Reactor 多线程模型

image-20200311221743601

主从 Reactor 多线程模型

image-20200311221735709

Acceptor 线程池仅仅只用于客户端的登陆、握手和安全 认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负 责后续的 IO 操作。

协议(多协议支持)

序列化协议(多协议支持 默认Google Protobuf)

Protocol Buffer 的序列化 & 反序列化简单 & 速度快的原因是:

  1. 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等)
  2. 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成

Protocol Buffer 的数据压缩效果好(即序列化后的数据量体积小)的原因是:

  1. a. 采用了独特的编码方式,如 Varint、Zigzag 编码方式等等
  2. b. 采用 T - L - V 的数据存储方式:减少了分隔符的使用 & 数据存储得紧凑

零拷贝

  • 直接内存
  • 组合buff 批量操作
  • transfer文件

内存池

随着 JVM 虚拟机和 JIT 即时编译技术的发展,对象的分配和回收是个非常轻量级的工作。但是对于缓 冲区 Buffer,情况却稍有不同,特别是对于堆外直接内存的分配和回收,是一件耗时的操作。为了尽 量重用缓冲区,Netty 提供了基于内存池的缓冲区重用机制。

无锁设计、线程绑定(ChannelPipeline handle编辑码)

Zk

角色

​ leader follower observer

节点

​ (临时节点、持久节点、顺序节点)

watch机制(xxx)

leader选举

数据同步

ZAB 协议: 崩溃恢复(选主+数据同步)/原子广播(类 2PC 事务,超过半数commit)

ZAB 协议

ZAB 协议是为分布式协调服务 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。在 ZooKeeper 中,主要依赖 ZAB 协议来实现分布式数据一致性,基于该协议,ZooKeeper 实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。

  • 读取时:客户端连接 zk 的任一节点,节点直接拿出自己对应的数据返回,这时该节点扮演 Observer 角色;
  • 写入时:客户端的任一提交都会由 Leader 去广播给所有的节点,有半数以上的节点写入成功即视为写入成功;

ZAB 的所有动作都是节点们通过协议同步的。在 ZAB 协议的事务编号 Zxid 设计中, Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器,针对客户端每一个事务请求,计数器加 1;而高 32 位则代表 Leader 周期 epoch 的编号,每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的 ZXID ,并从中读取 epoch 值,然后加 1,以此作为新的 epoch,并将低 32 位从 0 开始计数。

epoch 可以理解为当前集群所处的年代或者周期,每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代,leader 变更之后,都会在前一个年代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。

ZAB 协议有两种模式,崩溃恢复(选主+数据同步)和消息广播(事务操作)。任何时候都需要保证只有一个主进程负责进行事务操作,而如果主进程崩溃了,就需要迅速选举出一个新的主进程。主进程的选举机制与事务操作机制是紧密相关的。

选主:

Leader 选举过程,本质就是 广播优先级消息 的过程,选出 数据最新的服务节点,选出优先级最高的服务节点,基本步骤:

  • 各个服务器节点,广播自己的优先级标识 (sid,zxid)

  • 服务器节点收到其他广播消息后,跟自己的优先级(zxid)对比,自己优先级低,则变更当前节点投票的优先级(sid,zxid) ,并广播变更后的结果

  • 当任意一个服务器节点收到的投票数,超过了法定数量(quorum),则,升级为 Leader,并广播结果。

  • zookeeper 不是为高可用性设计的

  • zookeeper 的选举过程速度很慢

zookeeper 的选举流程通常耗时 30 到 120 秒,期间 zookeeper 由于没有master,都是不可用的。

  • zookeeper 的性能是有限的

典型的 zookeeper 的 tps(transaction peer secondes) 大概是一万多

  • zookeeper 无法进行有效的权限控制

  • zookeeper 并不保证读取的是最新数据

对于 Zookeeper 来说,它实现了A可用性(非高可用)、P分区容错性、C中的写入强一致性,丧失的是C中的读取一致性。

CP

应用

配置中心

Leader选举(Kafka 、hbase 、dubbo等)

分布式锁

​ 惊群效应

​ 利用有序节点来实现分布式锁

服务发现

不足缺陷

Cp 不是ap

Eureka 典型的 AP,作为分布式场景下的服务发现的产品较为合适,服务发现场景的可用性优先级较高,一致性并不是特别致命。其次 CA 类型的场景 Consu(Consul 通过 WAN 的 Gossip 协议,完成跨数据中心的同步)l,也能提供较高的可用性,并能 k-v store 服务保证一致性。 而Zookeeper,Etcd则是CP类型 牺牲可用性,在服务发现场景并没太大优势;

Web (网络协议协议 )

Tcp

滑动窗口

  • 发送窗口和可用窗口
  • ack回复

三次握手

ack=1 syn=xxxxx

ack=1 syn=syn+1

ack=1 syn=syn+1

四次挥手

等待数据最后传输(多一次原因)

image-20200311231328378

http

http协议 (应用)

URL

MIME Type

文本文件:text/html,text/plain,text/css,application/xhtml+xml,application/xml 图片文件:image/jpeg,image/gif,image/png.

视频文件:video/mpeg,video/quicktime

Accept: 表示客户端希望接受的数据类型,即告诉服务器我需要什么媒体类型的数据,此时 服务器应该根据 Accept 请求头生产指定媒体类型的数据
Content-Type: 表示发送端发送的实体数据类型,比如我们应该写过类似的: resposne.setContentType(“application/json;charset=utf-8”)的代码,表示服务端返回的数据 格式是 json。

状态码

200:一切正常
301:永久重定向
404:请求资源不存在
500:服务端内部错误

GET:一般是用于客户端发送一个 URI 地址去获取服务端的资源(一般用于查询操作),Get 不支持的传输数据有限制,具体限制由浏览器决定 POST:一般用户客户端传输一个实体给到服务端,让服务端去保存(一般用于创建操作) PUT:向服务器发送数据,一般用于更新数据的操作

DELETE:客户端发起一个 Delete 请求要求服务端把某个数据删除(一般用于删除操作) HEAD:获得报文首部、OPTIONS:询问支持的方法、TRACE:追踪路径、CONNECT:用隧 道协议连接代理

Restful

  1. 随着服务化架构的普及,http 协议的使用频率越来越高

  2. 很多人在错误的使用 http 协议定义接口,比如各种各样的命名,什么 getUserInfoById,

    deleteById 之类的、有状态和无状态请求混用。

  3. 对于 http 协议本身提供的规则并没有很好的利用

所以,为了更好的解决这些问题,干脆就定义一套规则,这套规则并没有引入新的东西,无 非就是对 http 协议本身的使用做了一些约束,比如说

  1. REST 是面向资源,每一个 URI 代表一个资源

  2. 强调无状态化,服务器端不能存储来自某个客户的某个请求中的信息,并在该客户的其他

    请求中使用

  3. 强调 URL 暴露资源时,不要在 URI 中出现动词

  4. 合理的利用 http 状态码、请求方法。

http 协议的完整组成

请求报文

image-20200311231202717

响应报文

image-20200311231212671

扩展

长链接

压缩/分割传输

http协议特点

无状态

cookie

session (JSessionId)

http 1.1 和 2.0

https 安全传输协议

ssl层

对称加密(容易被截取,怎么传输key)

非对称(公钥/密钥) md5加密

通过非对称加密,证书 —>获取对称密钥

证书

服务端 申请

image-20200311231837291

客户端 浏览器安装

image-20200311231855078

全过程

image-20200311232326908

websocket

nginx

计算机基础

linux

大数据相关

HDFS

https://matt33.com/2018/07/15/hdfs-architecture-learn/

spark

HBASE

https://juejin.im/entry/5c29811851882565a1577740

Row key倒序

预分区

写入优化

1500GB

3台

20region

其他

简历写法

https://mp.weixin.qq.com/s/YfanOHAqITJLUQUJL-L3KA


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:面试常见知识点梳理

字数:5.9k

本文作者:zhengyumin

发布时间:2020-05-10, 23:46:06

最后更新:2020-07-02, 20:49:19

原始链接:http://zyumin.github.io/2020/05/10/%E9%9D%A2%E8%AF%95%E5%B8%B8%E8%A7%81%E7%9F%A5%E8%AF%86%E7%82%B9%E6%A2%B3%E7%90%86/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。