ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

整理面经刷题

2021-11-30 12:02:29  阅读:93  来源: 互联网

标签:结点 索引 redis 链表 整理 面经刷题 数据 加载


面经刷题

虾皮一面面经

进程、线程、协程

进程就是系统资源调度的基本单位,线程是CPU资源调度的基本单位,一个程序至少有一个进程,一个进程至少有一个线程,协程是更轻量级的线程,运行在线程之上,完全由用户程序控制,不被操作系统内核管理,以用户态运行,因此不会像线程的上下文切换一样消耗资源

三次握手

三次握手的过程客户端向服务器发送syn请求来请求建立连接,然后服务器收到syn请求后返回syn和ack连接请求和确认请求,这两步就可以确定客户端可以发送消息成功,服务器接收消息成功,然后第三步客户端返回ack请求给服务器,第三步即可以确定客户端收发消息成功,服务器收发消息成功

select、poll、epoll

select、poll、epoll是IO多路复用的三种调用方式,其中select和poll的时间复杂度是O(n),因为这两种调用方式没有办法知道哪个流发生IO事件,只知道会发生IO事件,因此会无差别轮询所有流,找出需要读写数据的流操作,而select和poll之间最大的区别在于poll以链表的方式存储流,因此没有最大连接数,而epoll的时间复杂度是O(1),因为epoll是以事件驱动的,哪个流发生了IO事件会进行通知,不会无差别轮询。IO多路复用机制可以监视多个描述符,当描述符(读写就绪)准备就绪时会通知程序进行相应的读写操作。select、poll、epoll本质上是同步IO,因为读写就绪时程序进行读写操作的时候是阻塞的,而异步IO是不需要程序进行读写的,会通过回调的方式将数据从内核返回给用户

多态

多态指的是一个行为有多种实现方式,在java中多态就是同一个接口可以有多个不同的类实现,每个类的具体实现都不同,多态有三个实现的要素,继承,重写,父类引用指向子类对象,主要的优点是可扩展性,可替换性,更灵活

深拷贝与浅拷贝

深拷贝与浅拷贝出现在引用数据类型中,浅拷贝只会复制对象的指针,而不会复制对象本身,当被复制对象发生改变时,浅拷贝出来的对象也会随之改变,新旧对象是共享一块内存的,而深拷贝是将对象本身复制一份

缓存雪崩、缓存击穿、缓存穿透

穿透:缓存null,布隆过滤器
击穿:永久key,高峰期之前刷新key,加大热key过期时长
雪崩:永久key,设置key错峰过期

虾皮二面面经

HashMap为什么线程不安全

hashmap在jdk1.7的时候主要是因为扩容函数的链表使用的是头插法,在多线程的时候导致链表死环,丢失数据
在jdk1.8的时候是因为putVal方法中多线程的时候会导致数据覆盖

HashMap扩容过程

hashmap会先判断是否需要扩容,如果旧容量大于等于hashmap最大容量则不扩容并把扩容阈值设置为Integer.MAX_VALUE,如果需要扩容则会将新容量和新扩容阈值乘以2,并开始扩容。
扩容会先遍历旧表,如果旧表的桶有元素且没有后续结点的,直接赋值该元素到新表的原下标,如果存在后续结点,判断是红黑树还是链表,如果是红黑树就进行红黑树的分割迁移,如果是链表的就进行链表的上下分割迁移。在jdk1.7中hashmap扩容通过transfer函数进行数据迁移,且需要链表每个结点进行rehash操作,jdk1.8不需要rehash,效率会高很多,是通过分割链表来实现,分成高下标和低下标链表,让每个链表结点与旧表容量按位与,如果为0说明属于低下标链表,如果为1说明属于高下标链表,最后将高下标链表和低下标链表分别放入新表的两个位置即完成数据迁移

如果把hashmap改成线程安全的,你会在哪一步进行操作?

安全的hashmap主要有三种方法,第一种是使用Hashtable,第二种是使用Collections.synchronizedMap方法进行包装让整个表上锁,第三种是使用JUC下的ConcurrentHashMap。
前两种方式都是在读写数据时对整个表上锁,每次只能有一个线程操作表
ConcurrentHashMap在jdk1.7的时候使用的是分段锁+ReentrantLock+HashEntry+链表的方式实现,默认Segment 数组数量是16,也就是并发度为16,可以同时有16个线程对ConcurrentHashMap进行操作,每一段之间的操作不会相互影响。
put操作会先定位segment进行尝试上锁,如果失败则自旋,如果超出自旋最大值会阻塞线程获取,在进入MapEntry插入数据
get操作进行两次hash,第一次hash定位segment段位置,第二次hash定位MapEntry位置,由于MapEntry通过volatile修饰保证数据的可见性,因此get操作不需要加锁
在Jdk1.8的时候使用synchronized+CAS代替了segment分段锁,因为synchronized进行了锁升级的优化,MapEntry的结点也用链表+红黑树代替了链表结构,增加了查询的效率
整体过程和HashMap的过程相似,插入空桶的时候不上锁,当桶值为空时使用CAS插入数据,否则如果桶存在数据,则上synchronized锁进行插入结点

CAS的弊端?空耗的是什么资源?

CAS机制
CAS的弊端是只能对一个变量进行操作,保证一个变量的原子性,但是无法保证几个变量或一整段代码的原子性,而且自旋时会对CPU资源进行空耗,有可能一直自旋空耗CPU,且存在ABA问题

hashmap为什么要引入红黑树?为什么不是直接数组+红黑树?

因为红黑树的查询时间复杂度为O(logn),而链表的时间复杂度为O(n),因此在桶的hash碰撞次数较高的情况下,链表的长度很长,查询数据的效率就会变得很低,这时引入红黑树的查询效率就会比链表的查询高得多
HashMap是当桶的结点元素大于8时进行树化,树化后结点小于6时退化成链表。因为红黑树的维护,树化过程,空间是需要成本的,TreeNodes占用空间是普通Nodes的两倍,而且在前几个元素查询中它的平均时间甚至比链表还长,因此需要空间成本和时间成本之间进行取舍,进行转化。hash碰撞次数越多,结点越多,红黑树的优势才越大,通过泊松分布的分析,在hash算法良好的时候,碰撞导致结点大于8的概率是非常低的,这时候用到红黑树的概率也非常低。

如何利用反射创建一个对象?

通过获取class类对象来进行反射,例如类.class,Class.forName来获取class类对象,其中包含各种该类的信息,可以通过class对象newInstance方法通过无参构造创建一个对象,通过set方法进行注入,又或者通过class对象获取类的有参构造器进行创建

抽象类和接口

抽象类设计的目的为了代码复用,当多个类有相同的方法具体实现时,这时可以派生出一个抽象类,多个类同时继承该抽象类,实现代码复用,例子AbstractMap抽象类中实现了一些共用的方法,有多个类实现该抽象类进行代码复用,HashMap则继承了AbstractMap复用了其中或重写了一部分方法,而接口的设计目的是为了进行代码的设计规范,只约束方法的存在,不约束方法的具体实现。
抽象类是可以存在具体方法和抽象方法,接口在jdk8之前只能存在抽象方法,在jdk8之后可以设计默认方法和静态方法。
抽象列只能单继承,接口可以多实现。

类加载的过程

类的生命周期分为7个阶段,加载,验证,准备,解析,初始化,使用,卸载。而其中加载,验证,准备,解析,初始化即为类的加载过程。
加载主要过程是,将Class文件转换为二进制字节流,并将这个字节流的静态存储转换为方法区中运行时的数据结构,并在内存中生成一个代表这个类的java Class对象。
而验证,准备,解析这三个过程叫连接,主要的功能是将java的二进制代码合并到JVM的运行状态中。
验证主要的功能是为了验证类信息被当做代码运行到JVM时是否符合规范且安全无害。
准备的主要功能是为类变量分配方法区的内存并赋予初始值。
解析的主要功能是为了将虚拟机中常量池的符号引用(常量名)转换为直接引用(地址)
初始化的主要功能是执行一个clinit方法,而clinit方法主要是自动收集所有类变量的赋值动作和静态代码块的合并执行。

介绍一下双亲委派模型?都有哪些类加载器?什么时候破坏双亲委派机制?

双亲委派模型就是对类加载的过程的一个规范,主要总结就是向上委托,向下加载。当一个类需要被类加载器加载的时候,会一直往父类加载器委托直到启动类加载器,然后开始往下执行,在启动类加载器找不到该类,则继续向下找扩展类加载器,应用程序加载器,自定义加载器等等。
这个模型的好处是对类的加载有了优先级,不会对一些核心的类进行覆盖,比如Object类是由启动类加载器进行加载的,如果自己写一个Object类全限定名是一样的,那么即使编译成功,自己写的Object类也不会被加载。
有启动类加载器,是核心的类加载器,比如rt.jar包下的类由启动类加载器加载,还有扩展类加载器是加载ext目录下的jar包类,可以进行扩展,还有应用程序类加载器和自定义类加载器, 应用程序类加载器是默认的加载器,如果没有自定义的类加载器的话则使用应用程序类加载器,主要是加载用户自己写的类。
历史上有三次比较大的破坏双亲委派机制的事件。
第一次是对以前用户自定义类加载器的兼容,因为双亲委派机制是从jdk1.2才出现的,在此之前有很多用户有自己的自定义类加载器,因此需要对其进行兼容,而loadClass方法的覆盖是没有办法被避免的,因此添加了一个新的findClass方法,并提倡用户去重写findClass方法去解决这个问题。
第二次是因为基础类型需要调用回用户的代码,因为双亲委派机制是从上往下执行,越基础的类型总是被越上层的类加载器加载,而基础类型总是被用户继承和调用的,但是如果存在基础类型需要调用用户代码的情况就会破坏双亲委派机制。这时的解决方案是添加一个线程上下文加载器,它可以让父类加载器请求子类加载器去完成类加载,但是这破幻了双亲委派机制。
第三次是因为代码热替换,模块热部署的问题,这个情况下会制定规则让不同的类由不同的类加载器进行,会破坏双亲委派机制。

介绍一下redis

redis是一个高性能的key-value的非关系型数据库,是将数据存储在内存之中,有平均10w的每秒的读写效率,且每一个操作都是原子性的,有string,hash,list,set,zset等5个基础数据类型和geospatial,hyperloglog,bitmaps等3个特殊的数据类型,支持持久化,事务,集群,key过期等功能

redis为什么可以抗住高并发?

因为redis是将数据存储在内存中的,对比于mysql存储在硬盘中,redis的抗并发效果会高得多

为什么redis使用单线程性能会优于多线程?

redis的单线程是易于维护和编程的,而且redis是存储在内存中的数据库,因此redis的性能瓶颈并不是CPU,而是内存和网络,而且如果是多线程,高并发情况下CPU之间的上下文切换和死锁问题反而有可能会导致性能降低

线程切换产生的开销主要体现在哪里?

线程的上下文切换是需要系统从用户态转换到内核态,而产生最主要的性能开销是CPU寄存器需要保存和加载,主要体现在高并发的情况下会慢串行

既然是单线程,那怎么监听大量的客户端连接呢?

因为redis是基于reactor模式开发的文件事件处理器,而由于文件事件处理器是单线程方式运行的,所以redis是单线程的模型。redis通过IO多路复用程序监听大量客户端的连接,而IO多路复用程序不需要redis创建额外的线程来监听客户端的大量连接,降低了资源的消耗。所以虽然redis是单线程的方式运行,但是IO多路复用程序是可以监听多个socket。
文件事件处理器主要包含四个部分:多个socket连接(客户端连接),IO多路复用程序(支持多个客户端连接),文件事件分派器(将不同socket关联到相应的事件处理器),事件处理器(连接应答处理器,命令请求处理器,命令回复处理器),其中连接应答处理器处理客户端到redis的连接请求,命令请求处理器处理客户端到redis的写请求,命令回复处理器处理客户端到redis的读请求

redis的数据类型

redis的五大数据类型实现原理
redis有5个基本数据类型,3个特殊数据类型。
有String,list,hash,set,zset等5种基本数据类型,而其中的数据结构各不相同,String使用的是SDS简单动态字符串,list中可以使用压缩列表ziplist和双端链表LinkedList,hash可以使用压缩列表ziplist和哈希表Hashtable,set可以使用整数集合intset和哈希表Hashtable,zset可以使用压缩列表ziplist和跳表skiplist
String是二进制安全的,可以存放整数,图片,视频等内容,
list可以做一个简单的消息队列,
hash是一个个键值对,可以用来存储一些对象,
set是无序不重复的,且是用字典实现,因此查找快,可以实现数据的全局去重,或者集合之间的交集并集差集等操作,
zset是一个有序带权重的集合,可以做一些统计或排序
有hyperloglog,bitmap,geospatial等3个特殊的数据类型
hyperloglog用于基数的统计,只存储数据计算的基数,不存储数据本身,可以实现网站uv的统计
bitmap是位图,可以用于实现用户签到,用户活跃等统计

zset的底层实现?为什么使用跳表?跳表的结构?

压缩列表或跳表。因为有序集合的链表如果从头开始遍历查找一个元素时间复杂度是O(n),跳表相当于用空间换时间,在原始链表的基础上增加1层或多层的索引链表,并且每一层索引链表都会最终向下指向同一个位置上的原始链表,时间复杂度为O(logn),从最高层链表一直往低层查询,直到查询到原始链表的数据。

redis过期策略

redis的过期策略有两个,一个是懒汉式删除,一个是定期删除,懒汉式删除指的是即使key已经过期了,但是只在key被取出来的时候才会对key进行过期检查执行删除,这样对CPU是最友好的,但是有可能会堆积很多过期key在内存中没有被删除,第二个是定期删除,定期删除指的是在一定时间内取出一定数量的key进行删除,redis也会控制删除时间和删除频率减少删除对CPU的影响,定期删除对内存更友好,redis使用的是定期删除+懒汉式删除结合

redis淘汰策略

redis目前淘汰策略有8个
volatile-lru 设置过期时间的数据集中选最近最少使用的数据淘汰
volatile-ttl 设置过期时间的数据集中选最接近过期时间的数据淘汰
volatile-random 设置过期时间的数据集中随机选择数据淘汰
volatile-lfu 设置过期时间的数据集中选使用次数最少的数据淘汰
allkeys-lru 所有数据集中选最近最少使用的数据淘汰
allkeys-random 所有数据集随机选择数据淘汰
allkeys-lfu 所有数据集中选使用次数最少的数据淘汰
no-eviction 不做淘汰,新数据写入报错

mysql的ACID是什么

ACID指的是事务的四个基本要素,原子性,一致性,隔离性,持久性,
原子性指的是一个事务中所有的操作都是一起成功或一起失败的,事务是不可分割的一个单位,
一致性指的是在事务执行前后,数据库是保持一致性和完整性的,不能因事务执行而破坏,不能说表之前存在外键约束,但事务导致数据造成约束失效,
隔离性指的是事务与事务之间是存在隔离的,事务之间不能相互影响,不会因为事务交叉执行而导致数据不一致
持久性指的是事务执行的操作在数据库中是永久的,不会因为外部因素到数据丢失

mysql保证事务隔离性的方式

事务有四个隔离级别,不提交读,提交读,可重复读,串行化,而提交读和可重复读使用mvcc进行不同级别的事务隔离,串行化则是对读写都进行上锁解决并发问题
mvcc指的是多版本并发控制,如果使用mvcc,表中的每一行数据都会维护两列事务id来作为版本号,一列是创建版本,一列是删除版本,而每个事务都会创建一致性视图来和版本号进行对比,对不同的数据有不同可读性,提交读是每个语句维护一个一致性视图,可重复读是每个事务维护一个一致性视图,这就称为快照读,无需上锁,读取的数据时可见版本
而有另外一种对数据进行修改插入读取的方式称为当前读,当前读指的是事务在进行update,insert,delete或select对当前实时数据的操作,并进行上锁,读取的数据时最新版本

mysql实现事务提交和回滚的两个文件

一个是redolog重做日志用于保证事务提交,保证已提交事务的持久性,一个是undolog回滚日志用于保证事务的回滚和mvcc版本回滚,保证未提交事务的原子性,两个日志都是在操作数据之前记录,先写到日志缓冲区,然后按照同步规则再将缓冲区数据同步到磁盘日志文件中进行持久化保存

B树与B+树

B树就是平衡树的意思,从二叉树过渡到B树主要跟硬盘读取效率有关。因为二叉树每一个结点只能存储一对键值,则意味着查询的时候每次都需要进行一次硬盘的IO,如果二叉树结点很多,就意味着查找一次数据就需要多次硬盘IO,效率很低,因此为了让查找数据的效率更快,减少硬盘IO的次数,因此引入B树
B树的一个结点可以存储多对键值,这就让整棵树变得更矮更胖,让读取硬盘的次数变少,数据查询的效率也会高很多,
而B+树则是对B树的进一步优化,B+树分为两种结点,一种是非叶子结点,只存储键,不存储值,一种是叶子结点,存储键和值,与B树不同,非叶子结点只存储键意味着一个结点可以容纳更多键,比B树更矮更胖,进一步减少了硬盘IO的次数,查询的效率也会更高,同时叶子结点之间通过双向链表连接,结点中的数据以单向链表连接,这样比B树更加适合范围查询,可以通过指针找到上下叶子结点的数据

mysql对B+树做了哪些优化

聚簇索引实现是B+树的优化,innodb使用的聚簇索引就是将数据放在B+树中,键是主键,值就是表中的所有数据,以主键作为B+树索引的键,而非聚簇索引则是myisam在使用,区别就是在叶子结点中的值不存储数据,变成了数据地址

聚簇索引和非聚簇索引

innodb使用聚簇索引,myisam使用非聚簇索引,
聚簇索引中主索引的非叶子结点存储的是主键,通过主键来直接找到表中所有数据,辅助索引的叶子结点则是存储主键,假如通过非主键的索引查询,则需要通过辅助索引查询出对应的主键,再使用主键在主索引中查询出表中的数据,
而非聚簇索引的主索引和辅助索引的叶子结点都是存储数据地址,都需要回表进行查询。
innodb这样的好处是1、如果通过主键查询数据则效率会更高,2、如果发生了行移动,聚簇索引中的辅助索引则不需要改变,只需要改变主索引中的值即可

spring采用了哪些设计模式

有单例模式,工厂模式,代理模式,模板模式,装饰器模式,观察者模式,适配器模式等等
单例模式在ioc管理bean中使用, bean有5个作用域,默认的就是使用单例模式来创建对象,
工厂模式也是在ioc中使用,主要是使用工厂模式通过beanFactory和ApplicationContext来创建bean,
模板模式比如jdbcTemplate就是一个模板类,简单来说就是定义一个算法的骨架,然后实现模板方法,模板方法让实现的子类可以在不改变整体骨架的情况下重定义一些算法的步骤
代理模式是aop的功能实现方式
观察者模式就是实现spring的事件驱动模型的方式,就是对象之间存在依赖关系,当被观察对象改变时,需要通知所有的观察对象,可以解耦代码
适配器模式就是将一个接口转换成客户的目标接口,让不兼容的类可以一起工作,spring中的aop通知和springmvc都用到适配器模式,mvc中的通过handlerMapping解析出handle后需要通过handlerAdapter适配器找到目标控制器执行业务
装饰器模式就是继承的一种更优雅的实现方式,也符合开闭原则(对扩展开放,对修改关闭),在不修改原代码的情况下动态扩展,比如spring在配置DataSource时动态切换数据源

spring创建和管理bean有几种方式

spring创建bean有
xml,
注解如@Component @Service @Import等,
配置类@Bean等,
实现ImportSelector接口,
手动通过DefaultListableBeanFactory创建。

spring管理bean有构造器注入,setter注入,field注解注入三种方式
构造器注入可以保证依赖不为空,完全初始化,保证依赖不可变

bean的作用域

有5个作用域,
singleton,单例模式只创建一个bean实例,
prototype,每次请求都创建一个新的bean实例,
request,每次请求都创建一个新的bean实例,该bean只在当前httpRequest域有效
session,每次请求都创建一个新的bean实例,该bean只在当前httpSession域有效
global-session,全局session作用域,仅在portlet中有意义

计算机网络

HTTP1.0、HTTP1.1 和 HTTP2.0 的区别

MyBatis

#{}和${}的区别是什么

${}是静态文本替换,括号里的值是什么就在sql语句中就是什么,ORDERBY时需要用到
#{}是预编译处理的,MyBatis会先将#{}替换成?,用preparedStatement的set方法按序给sql中的问号赋值,#{}可以有效防止sql注入,提高安全性

JVM

CMS和G1

1、CMS是面向老年代的收集器,需要配合新生代收集器一起使用,而G1是面向Java堆的收集器

2、两者的堆内存布局不同,CMS采用的是新生代和老年代的内存布局,而G1是基于Region的内存布局

3、两者基于的垃圾收集算法不同,CMS是基于标记清除算法的,会产生内存碎片,需要在FullGC时进行合并整理,而G1是整体来说是基于标记整理算法,Region之间是基于标记复制算法,意味着不会产生内存碎片

4、两者设计目标不同,CMS的目标是用户体验,即以最低延迟为目标,而G1是用户可自定义最大延迟时间,在最大延迟时间内获得尽可能高的吞吐量,在期望的停顿时间内可以获得最高的收益

5、两者的并发可达性算法不同,CMS是增量更新算法,记录插入黑色对象引用的对象,G1是原始快照算法,记录删除灰色对象引用的对象

6、两者维护对跨区域引用维护的记忆集用到的内存不一样,CMS只需要维护一个从老年代到新生代的卡表即可,而G1则需要每个Region区域都需要维护一个卡表,记忆集就需要占至少20%的堆内存容量

Parallel Scavenge和ParNew

1、Scavenge收集器有内存布局的自适应调节策略,不需要人工调节新生代和老年代的大小,Eden和Survivor的比例,会根据运行情况动态调整这些参数以提供最合适的停顿时间或最大的吞吐量,ParNew则没有

2、Scavenge的特点是关注点不同,它的目标是为了达到可控制的吞吐量

Mysql

Innodb和MyIsam区别

1、Innodb支持事务,MyIsam不支持事务
2、Innodb支持外键,MyIsam不支持
3、Innodb支持行级锁,MyIsam只支持表级锁
4、Innodb支持MVCC,MyIsam不支持
5、Innodb支持崩溃后安全恢复(redolog),MyIsam不支持

索引结构

B+树和 Hash

索引分类

1、主键索引
2、唯一索引
3、普通索引
4、复合索引
5、全文索引

适合创建索引的情况

1、主键
2、频繁被作为查询条件的字段
3、选择度高的字段
4、与其他表关联的字段,比如外键关系
5、排序的字段,B+树是排序存储的,可加速查找
6、多使用复合索引,减小索引文件的大小,降低索引总数,使用速度也高于多个单列索引(选择度更高),将选择度高的放在更左侧

不适合创建索引的情况

1、表数据太少
2、频繁更新的表
3、不作为查询的字段

存在索引但不使用的情况

1、以%开头的LIKE查询
2、数据类型出现隐式转换(字符常量没有用’ '单引号)
3、复合索引中不符合最左前缀原则
4、MySQL估计使用索引比全表扫描效率更低
5、用or分割的条件,or前有索引,or后没有索引(因为or后没有索引,最后还是需要进行全表扫描)

Explain性能分析

1、开启慢查询日志,并指定几秒以上算慢查询
2、手动EXPLAIN进行分析

  • type属性
    ALL全表扫描(select *)
    index索引全扫描(select 索引)
    range索引范围扫描
    ref非唯一索引扫描或唯一索引的前缀扫描(有可能查出多条数据)
    eq_ref唯一索引扫描(多表连接中只能查出一条数据)
    const/system单表中最多有有个匹配行
    null不用访问表或者索引
  • possible_keys属性
    可能使用的索引
  • key属性
    使用哪个索引
  • rows属性
    显示需要扫描的行数

常见慢查询优化:
1、索引没有起作用
2、优化数据库结构(比如字段很多的表,某些字段的使用频率低,可以进行水平切分)
3、分解关联查询(多个join查询多表可以分解成多次单个表查询)
4、优化分页(可以先查询出主键的id,再通过id进行范围查询,只插id后的limit值)

秒杀

1、动静分离(高性能)
2、热点缓存
3、减库存方式 预扣库存(一致性)
4、流量削峰,增加下单复杂度,消息队列排队,限流(高可用)

标签:结点,索引,redis,链表,整理,面经刷题,数据,加载
来源: https://blog.csdn.net/aimerary/article/details/119901222

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有