资讯详情

[万字]java后端研发岗秋招常见面经总结

今年秋招从提前批开始到现在offer到目前为止,我采访了30多家企业,录音总结出来,文末有信息礼包,可以开奖!

spring

Spring一个核心功能是IOC,就是将Bean初始化加载到容器中,Bean它是如何加载到容器的,可以使用Spring注释或Spring XML配置方式。

依靠注入及其原理来控制反转

IoC称为控制反转,DI称为依赖注入。控制反转是将程序代码直接控制的传统对象的调用权交给容器,通过容器组装和管理对象组件。"控制反转"它是对组件对象控制权的转移,从程序代码本身转移到外部容器,由容器创建象之间的依赖关系。底层是对象工厂,首先配置xml文件,配置创建对象,然后创建工厂类别。

依赖注入的基本原则是,应用组件不应负责寻找资源或其他依赖的合作对象。配置对象的工作应由容器负责,搜索资源的逻辑应从应用组件的代码中提取并移交给容器。即将底层类作为参数传输到上层类,实现上层类对下层类"控制"。

可通过接口依赖注入,setter实现方法注入(设值注入)、构造器注入和接口注入,Spring支持setter注入和注入结构器通常使用注入结构器来注入必要的依赖关系。setter注入是更好的选择,setter注入需要提供无参结构或无参静态工厂的方法来创建对象。

Spring中自动装配的方法有哪些?

  • no:不自动组装,手动设置Bean的依赖关系。
  • byName:根据Bean自动组装名称。
  • byType:根据Bean自动组装的类型。
  • constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean如果参数类型与构造器相同,可以自动组装,否则会导致错误。
  • autodetect:若有默认结构器,则通过constructor自动组装,否则使用byType的方式进行自动装配。

spring aop

AOP它可以包装与业务相关但由业务模块共同调整的逻辑或责任,以减少系统的重复代码,减少模块之间的耦合、可扩展性和可维护性。

springboot的有点:

Spring Boot 快速发展的优势,特别适合构建微服务系统,包装各种常用套件

Spring 为 Java 程序提供全面的基础设施支持,包括许多非常实用的功能,如 Spring JDBC、Spring AOP、Spring ORM、Spring Test 这些模块的出现大大缩短了应用程序的开发时间,提高了应用程序开发的效率。

Spring Boot 本质上是 Spring 框架的延伸和扩展是为了简化 Spring 框架的初始架的初始构建和开发过程 Spring 在应用程序中 XML 配置,开发更快更高效 Spring 提供更有力的支持。

Spring aop它是通过动态代理实现的,有两种方法:JDK是基于反射机制,生成一个实现代理接口的匿名类,然后重写方法,实现方法的增强。它生成类的速度很快,但是运行时因为是基于反射,调用后续的类操作会很慢.

而且他只能针对接口编程.

CGLIB是基于继承机制,继承代理,所以方法不应声明为final,然后重写父类方法来增强类的作用。它的底部是基于asm第三方框架是代理对象class加载文件,修改字节码生成子类。生成类速度慢,但后续执行类操作速度快。可用于类别和接口.

Spring事务机制包括声明事务和编程事务。

编程事务管理:Transaction Template手动管理事务

声明事务管理:我们不需要处理从复杂的事务处理中解脱出来、获取连接、关闭连接、事务提交、回滚、异常处理等操作,Spring会帮我们处理的。

声明式事务管理使用 AOP 实现的本质是在实施目标方法之前和之后进行拦截。在实施目标方法之前添加或创建事务,并根据实际情况选择提交或回滚。

声明事务优势:无需在业务逻辑代码中编写事务相关代码,只需配置配置文件或使用注释(@Transaction),这种方法没有侵入性。

声明事务的缺点:声明事务的最细粒度作用于方法。如果代码块也有事务需求,则只能灵活地将代码块转换为方法。

Bean的生命周期

实例化 Instantiation属性赋值 Populate初始化 Initialization销毁 Destruction

依赖循环

循环依赖实际上是循环引用,即两个或两个以上bean相互持有,最终形成闭环。例如,A依赖于B,B依赖于C,C又依赖于A。类似于死

Spring中循环依赖场景有:

(1)构造器的循环依赖

(2)field属性的循环依赖,即setter循环依赖注入。(三级缓存可以解决)

Spring事实上,循环依赖的理论依据是Java基于引用传递,当我们得到对象的引用时,对象field或者属性可以延迟设置。

为避免循环依赖,在Spring中创建bean原则不同bean创建完成就会创建bean的ObjectFactory一旦下一个被提前曝光并添加到缓存中bean创建时候需要依赖上一个bean则直接使用ObjectFactory 。

计算机网络

应用层的任务是通过应用程序之间的交互完成特定的网络应用程序。应用层协议定义了应用程序之间的通信和交互规则。http,dns,smtp。

加密解密、转换翻译、压缩解压缩等表示层、信息的语法语义及其关联。

会话层:在不同机器上的用户之间建立和管理会话。

tcp,udp。端口号

网络层:在 在计算机网络中通信的两台计算机可能通过多个数据链接或多个通信子网络。网络层的任务是选择合适的网络路由和交换点, 确保数据及时传输。 在发送数据时,网络层传输运输层生成的报纸段或用户数据包装成分组和包。

数据链路层:通常称为链路层。两台主机之间的数据传输总是在一段一段的链路上传输,这需要一个特殊的链路层协议。 在两个相邻节点之间传输数据时,数据链路层将网络层交付 IP 数据报组装成帧,在两个相邻节点之间的链路上传输帧。物理层(physical layer)其功能是实现相邻计算机节点之间比特流的透明传输,尽可能屏蔽特定传输介质与物理设备的差异

tcp:面向连接,可靠,字节流。udp:无连接,不可靠,报文。

TCP 协议如何确保可靠传输

应用数据被分割成 TCP 最适合发送的数据块。

TCP 编号发送的每个包,接收方对数据包进行排序,并将有序数据传输到应用层。

校验和: TCP 它将保持第一部分和数据的检查和。这是一个端到端的检查和检测数据在传输过程中的任何变化。如果接收到段的检查和错误,TCP 本报文段将被丢弃并不确认。

TCP 重复数据将弃重复数据。

流量控制: TCP 连接的每一方都有固定大小的缓冲空间,TCP接收端只允许发送端发送接收端缓冲区可接受的数据。当接收方没有时间处理发送方的数据时,可以提示发送方降低发送速度,防止包丢失。TCP 流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)

拥塞控制: 当网络拥塞时,减少数据发送。1.慢开始2.拥塞控制3.快速重复4.快速恢复

ARQ协议: 也是为了实现可靠的传输,其基本原理是停止发送,等待对方确认。在收到确认后发送下一个组。

超时重传: 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。

http状态码

1** 信息,服务器收到请求,请求继续执行操作。** 成功,成功接收和处理操作。** 需要进一步的操作来完成要求。** 客户端错误,请求包括语法错误或无法完成请求。** 在处理请求的过程中,服务器出现错误。

TIME_WAIT与CLOSE_WAIT状态

TIME_WAIT对于爬虫服务器来说,它是主动关闭连接方的状态"客户端",完成任务后,他会主动关闭连接进入TIME_WAIT然后保持这个状态2MSL(max segment lifetime)时间过后,回收资源完全关闭。

防止上次连接中的包在迷路后再次出现,影响新连接

CLOSE_WAIT形成被动关闭连接。根据TCP状态机,服务器端收到客户端发送的信息FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但是,如果服务器端不执行close(),不能由CLOSE_WAIT迁移到LAST_ACK,系统中会有很多CLOSE_WAIT状态连接。此时,系统可能忙于处理读写操作,但没有收到FIN连接,进行close。此时,recv/read已收到FIN的连接socket,会返回0。

数据结构

DFS深度优先考虑搜索。以深度为准则,先走到最后,直到达到目标。否则,如果你没有达到目标,也没有办法走,那么回到最后一步,走其他路。

BFS广度优先搜索。广度优先搜索的目的是在面对十字路口时写下所有岔路口,然后选择其中一个进入,然后记录其分路情况,然后返回到另一个岔路口,重复此操作。

操作系统

进程和线程

流程与线程的主要区别在于它们是不同的操作系统资源管理模式。该过程有独立的地址空间,在一个过程崩溃后,在保护模式下不会影响其他过程,而线程只是一个过程中不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间。线程死亡等于整个过程死亡,因此多过程程序比多线程程序更强大,但在过程切换中,资源消耗更大,效率更差。但是,对于一些要求,同时进行和执行共享某些变量的并发操作,只能用线程,不能用进程。

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

进程是分配资源的基本单位;线程是系统调度和分派的基本单位。

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

①需要频繁创建销毁的优先用线程(进程的创建和销毁开销过大)  这种原则最常见的应用就是Web服务器了,来一个连接建立一个线程,断了就销毁线程,要是用进程,创建和销毁的代价是很难承受的

②需要进行大量计算的优先使用线程(CPU频繁切换)  所谓大量计算,当然就是要耗费很多CPU,切换频繁了,这种情况下线程是最合适的。这种原则最常见的是图像处理、算法处理。

③强相关的处理用线程,弱相关的处理用进程  什么叫强相关、弱相关?理论上很难定义,给个简单的例子就明白了。  一般的Server需要完成如下任务:消息收发、消息处理。"消息收发"和"消息处理"就是弱相关的任务,而"消息处理"里面可能又分为"消息解码"、"业务处理",这两个任务相对来说相关性就要强多了。因此"消息收发"和"消息处理"可以分进程设计,"消息解码"、"业务处理"可以分线程设计。  当然这种划分方式不是一成不变的,也可以根据实际情况进行调整。

④可能要扩展到多机分布的用进程,多核分布的用线程

⑤都满足需求的情况下,用你最熟悉、最拿手的方式

进程间通信方式

管道,是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

有名管道,是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

消息队列,是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

共享存储,就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

信号量,是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

套接字,套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

信号 ,是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

上下文,进程切换,为什么损耗那么大

进程切换分两步:1.切换页目录以使用新的地址空间。2.切换内核栈和硬件上下文。

1、线程上下文切换和进程上下问切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。

2、另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲等很多东西都会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

IO

BIO:同步阻塞,数据的读取写入必须阻塞在一个线程内等待其完成。

NIO:同步非阻塞,一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程处理。

(1)缓冲区 Buffer,在 NIO 中,所有数据都是用缓冲区处理的。

(2)通道 Channel,可以通过它读取和写入数据。通道与流的不同之处在于通道是双向的,而且通道可以用于读、写或者用于读写。同时Channel 是全双工的。

(3)多路复用器Selector,一个多用复用器 Selector 可以同时轮询多个 Channel。如果某个 Channel 上面有新的 TCP 连接接入、读和写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,进行后续的 I/O 操作。

AIO:AIO是一个有效请求一个线程

异步非阻塞,用户进程只需要发起一个IO操作便立即返回,等 IO 操作真正完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据处理就好了,不需要进行实际的 IO 读写操作,因为真正的 IO 操作已经由操作系统内核完成了。

什么情况下会发生死锁?解决死锁的策略有哪些?

银行家算法:

要设法保证系统动态分配资源后不进入不安全状态,以避免可能产生的死锁。即:每当进程提出资源请求且系统的资源能够满足该请求时,系统将判断如果满足此次资源请求,系统状态是否安全,如果判断结果为安全,则给该进程分配资源,否则不分配资源,申请资源的进程将阻塞。

内存管理的页面淘汰 算法

为提高内存利用率,解决内存供不应求的问题,更加合理的使用内存,人们创造了分页式内存抽象。同时有一个虚拟内存的概念,是指将内存中暂时不需要的部分写入硬盘,看上去硬盘扩展了内存的容量,所以叫做"虚拟"内存。分页式内存管理将物理内存分为等大的小块,每块大小通常为1K、2K、4K等,称为页帧;逻辑内存(使用虚拟内存技术扩大的内存,可认为其位于硬盘上)也被分为等大的小块,称为页;且页和页帧的大小一定是一样的,它是写入真实内存和写回硬盘最小单位。

Lru最近最少使用,为获得对最优算法的模拟,提出了LRU算法。由于当前时间之后需要用到哪些页无法提前获知,于是记录当前时间之前页面的使用情况,认为之前使用过的页面以后还会被用到。在置换时,将最近使用最少的页面换出内存。此种方法的开销比较大。

先进先出算法

FIFO算法的思想很简单,就是置换出当前已经待在内存里时间最长的那个页。FIFO算法的运行速度很快,不需要考虑其他的因素,需要的开销很少。但是正是由于没有考虑页面的重要性的问题,FIFO算法很容易将重要的页换出内存。

redis

Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

虽然文件事件处理器以单线程方式运行,但通过使用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与 Redis 服务器中其他同样以单线程方式运行的模块进行对接,这保持了 Redis 内部单线程设计的简单性。

,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。

虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了, 执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。

热点key

原因:用户消费的数据远大于生产的数据,请求分片集中,超过单 Server 的性能极限。

危害:1、流量集中,达到物理网卡上限。2、请求过多,缓存分片服务被打垮。3、DB 击穿,引起业务雪崩。

解决方案:客户端。在客户端设置全局字典(key和调用次数),每次调用Redis命令时,使用这个字典进行记录,维护成本较高。无法实现规模化运维统计。

代理层统计,提前预估,流量分析,redis自带的hotkey全局搜索方法

集群

主从复制

一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

Sentinel(哨兵)模式

第一种主从同步/复制的模式,当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预。通过发送命令,让 Redis 服务器返回监控其运行状态,包括主服务器和从服务器。当哨兵监测到 master 宕机,会自动将 slave 切换成 master ,然后通过通知其他的从服务器,修改配置文件,让它们切换主机;

Cluster 模式

Redis 的哨兵模式基本已经可以实现高可用,读写分离 ,但是在这种模式下每台 Redis 服务器都存储相同的数据,很浪费内存,所以在 redis3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念。Redis 集群有16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽。

脑裂

因为网络问题,导致redis master节点跟redis slave节点处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。同时有两个主节点,它们都能接收写请求。是客户端不知道应该往哪个主节点写入数据,结果就是不同的客户端会往不同的主节点上写入数据。进一步导致数据丢失。

解决:redis.conf 修改属性,通过活跃slave节点数和数据同步延迟时间来限制master节点的写入操作。原主库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据了。

缓存过期时间

因为内存是有限的,如果缓存中的所有数据都是一直保存的话,分分钟直接 Out of memory。还有就是业务需求,比如验证码。

Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。

惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除。

定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。

定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以 Redis 采用的是 定期删除+惰性式删除 。

内存淘汰机制:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰。。从已设置过期时间的数据集中挑选将要过期的数据淘汰。。从已设置过期时间的数据集中任意选择数据淘汰。。当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。。从数据集中任意选择数据淘汰。。禁止驱逐数据。。从已设置过期时间的数据集中挑选最不经常使用的数据淘汰。。当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key。

布隆过滤器

在java中,一个int类型占32个比特,现假如我们用int字节码的每一位表示一个数字的话,那么32个数字只需要一个int类型所占内存空间大小就够了,这样在大数据量的情况下会节省很多内存。

优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。 bitmap

当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。

Counting Bloom filter实际只是在标准Bloom filter的每一个位上都额外对应得增加了一个计数器,在插入元素时给对应的 k (k 为哈希函数个数)个 Counter 的值分别加 1,删除元素时给对应的 k 个 Counter 的值分别减 1。

1.读方: BF.EXISTS KEY element

如果想一次查询多个元素,可以使用bf.mexists命令。

BF.MADD批量添加

2.写方: BF.ADD KEY element

bf.reserve

错误率{error_rate}越小,所需的存储空间越大; 初始化设置的元素数量{capacity}越大,所需的存储空间越大,当然如果实际远多于预设时,准确率就会降低。RedisBloom官方默认的error_rate是 0.01,默认的capacity是 100。

Zset

当zset满足以下两个条件的时候,使用ziplist:

保存的元素少于128个

保存的所有元素大小都小于64字节

ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个节点保存元素的分值。并且压缩列表内的集合元素按分值从小到大的顺序进行排列,小的放置在靠近表头的位置,大的放置在靠近表尾的位置。

字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。

通过指针来共享相同元素的成员和分值,所以不会产生重复,造成内存的浪费。

并发

为什么要用到多线程

单线程模型中事件的执行是顺序执行的,下一个操作必须等待前一个操作完成后才能执行,而如果前一个操作非常耗时,就会影响下一个操作的效率。此时可以使用多线程进行异步调用。同时当CPU性能是处理任务的瓶颈时等特殊情况下,可以提高性能。

线程安全问题

在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。

中断线程:

interrupt(): 用于线程中断,该方法并不能直接中断线程,只会将线程的中断标志位改为true。它只会给线程发送一个中断状态,线程是否中断取决于线程内部对该中断信号做什么响应,若不处理该中断信号,线程就不会中断。

多线程的实现方式

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法

3.通过Callable和Future,三个接口实际上都是属于Executor框架,1.5,

4.通过线程池创建线程

Runnable的实现方式是实现其接口即可

Thread的实现方式是继承其类

Runnable接口支持多继承,但基本上用不到

Thread实现了Runnable接口并进行了扩展,而Thread和Runnable的实质是实现的关系,不是同类东西,所以Runnable或Thread本身没有可比性。

中断线程

interrupt中断线程。该方法并不能直接中断线程,只会将线程的中断标志位改为true。它只会给线程发送一个中断状态,线程是否中断取决于线程内部对该中断信号做什么响应,若不处理该中断信号,线程就不会中断。

需要在try,catch语句中。sleep中断或者抛出

多线程通信方式

通过synchronized,volatile关键字这种锁方式来实现线程间的通信。

while轮询,会浪费cpu资源

Object类的 wait() 和 notify() 方法。进入阻塞状态

管道通信,使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信

线程的几种状态

1)初始状态 2)可运行状态3)运行状态4)阻塞状态 5)死亡状态

等待和阻塞的区别?

最主要的区别就是释放锁所有权与否

Sleep,令阻塞,是Thread类的方法,在指定的时间内阻塞线程的执行。并不会失去对任何监视器(monitors)的所有权,不会释放锁,仅仅会让出cpu的执行权。

Wait方法,令线程等待。是Object的方法,他前提是当前线程已经获取了对象监视器monitor的所有权。会让出cpu的执行权,还会释放锁,并且进入wait set中

线程抢到了锁进了同步代码块,(由于某种业务需求)某些条件下Object.wait()了,就处于了等待状态。线程和其他线程抢锁没抢到,就处于阻塞状态了。

线程池

ThreadPoolExecutor

corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。

maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。

workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;

unit : keepAliveTime 参数的时间单位。

threadFactory : 用来实现创建线程的工厂接口

handler :饱和策略。

抛出异常来拒绝新任务的处理。调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。不处理新任务,直接丢弃掉。此策略将丢弃最早的未处理的任务请求。

创建线程是有代价的,不能每次要执行一个任务时就创建一个线程,但是也不能在任务非常多的时候,只有少量的线程在执行,这样任务是来不及处理的,而是应该创建合适的足够多的线程来及时的处理任务。随着任务数量的变化,当任务数明显很小时,原本创建的多余的线程就没有必要再存活着了,因为这时使用少量的线程就能够处理的过来了,所以说真正工作的线程的数量,是随着任务的变化而变化的。

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

对象头主要包括两部分数据:Mark Word、Klass Pointer。

Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

无锁,偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。

syn:1.修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得 当前对象实例的锁。2.修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例 ,进入同步代码前要获得 当前 class 的锁。3.修饰代码块 :指定加锁对象,对给定对象/类加锁。

volatile通过在变量的操作前后插入内存屏障的方式,保证了变量在并发场景下的可见性和有序性。是线程同步的轻量级实现, 性能更好。但只能用于变量而 syn可修饰方法以及代码块。

volatile能保证数据的可见性,但不能保证数据的原子性。syn两者都能。

volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

没有流水线技术前,如果同时两个指令过来执行 一个需要5秒,那么两个就需要10秒;有了流水线技术之后,可能就只要6秒。多指令同时执行时性能显著提升。

线程复用会产生脏数据。由于线程池会重用 Thread 对象 ,那么与 Thread 绑定的类的静态属性 ThreadLocal 变量也会被重用。

内存泄漏:绝大多数的静态threadLocal对象都不会被置为null。这样子的话,通过 stale entry 这种机制来清除Value 对象实例这条路是走不通的。

每次用完ThreadLocal,都要及时调用 remove() 方法去清理。

Renntranlock和AQS

Lock是接口, ReentrantLock是具体实现

1、synchronize 系java 内置关键字;而Lock 是一个类

2、synchronize 可以作用于变量、方法、代码块;而Lock 是显式地指定开始和结束位置

3、synchronize 不需要手动解锁,当线程抛出异常的时候,会自动释放锁;而Lock则需要手动释放,所以lock.unlock()需要放在finally 中去执行

4、性能方面,如果竞争不激烈的时候,synchronize 和Lock 的性能差不多,如果竞争激烈的时候,Lock 的效率会比synchronize 高

5、Lock 可以知道是否已经获得锁,synchronize 不能知道。Lock 扩展了一些其他功能如让等待的锁中断、知道是否获得锁等功能;Lock 可以提高效率。

6、synchronize 是悲观锁的实现,而Lock 则是乐观锁的实现,采用的CAS 的尝试机制

1、ReenTrantLock 可以中断锁的等待,提供了一些高级功能

2、多个线程在等待的时候,可以提供公平的锁;默认的是非公平锁,性能会比公平锁好一些;

3、ReenTrantLock 可以绑定多个锁条件

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。

内存溢出

jps:查看所有 Java 进程。jstat: 监视虚拟机各种运行状态信息。jinfo: 实时地查看和调整虚拟机各项参数。jmap:生成堆转储快照。jhat: 分析 heapdump 文件。jstack :生成虚拟机当前时刻的线程快照。JConsole:Java 监视与管理控制台

动态化线程池

简化线程池配置:线程池构造参数有8个,但是最核心的是3个:corePoolSize、maximumPoolSize,workQueue,它们最大程度地决定了线程池的任务分配和线程分配策略。考虑到在实际应用中我们获取并发性的场景主要是两种:(1)并行执行子任务,提高响应速度。这种情况下,应该使用同步队列,没有什么任务应该被缓存下来,而是应该立即执行。(2)并行执行大批次任务,提升吞吐量。这种情况下,应该使用有界队列,使用队列去缓冲大批量的任务,队列容量必须声明,防止任务无限制堆积。所以线程池只需要提供这三个关键参数的配置,并且提供两种队列的选择,就可以满足绝大多数的业务需求。

参数可动态修改:为了解决参数不好配,修改参数成本高等问题。在Java线程池留有高扩展性的基础上,封装线程池,允许线程池监听同步外部的消息,根据消息进行修改配置。将线程池的配置放置在平台侧,允许开发同学简单的查看、修改线程池配置。

增加线程池监控:对某事物缺乏状态的观测,就对其改进无从下手。在线程池执行任务的生命周期添加监控能力,帮助开发同学了解线程池状态。

线程阻塞的情况:

1、当线程执行Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;

2、当线程碰到一条wait()语句时,它会一直阻塞到接到通知(notify())、被中断或经过了指定毫秒时间为止(若制定了超时值的话)

3、线程阻塞与不同I/O的方式有多种。常见的一种方式是InputStream的read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间;

4、线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得synchronized语句必须的锁时阻塞)。

reactor和proactor

Netty提供了多个解码器,可以进行分包的操作,分别是,(回车换行分包,FrameDecoder(特殊分隔符分包)(固定长度报文来分包)自定义长度来分包)

reactor模式用于同步I/O,而Proactor运用于异步I/O操作。

同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪,而异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知(异步的特点就是通知)。

中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。

而在模式中,处理器--或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。

Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.

jvm

类加载过程

加载,验证,准备()解析,初始化

(1) 装载:

通过全类名获取定义此类的二进制字节流

将字节流所代表的静态存储结构转换为方法区的运行时数据结构

在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

(a)校验:检查载入Class文件数据的正确性;

(b)准备:

,这些内存都将在方法区中分配。

(c)解析:解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

(3) 初始化:初始化阶段是执行初始化方法 <clinit> ()方法的过程,是类加载的最后一步,

(1)如果一个类加载器接收到了类加载的请求,它自己不会先去加载,会把这个请求委托给父类加载器去执行。

(2)如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader

(3)如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常,这就是双亲委派模式

不想用:自定义加载器的话,需要继承 ClassLoader 。如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法

Java 对象的创建过程

Step1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的 符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必 须先执行相应的类加载过程。

2.4.2.2. Step2:分配内存

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成 后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配 方式有 指针碰撞和 空闲列表两种,选择哪种分配方式由堆是否规整决定,而堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

内存分配的两种方式:

指针碰撞:堆内存规整,用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可

空闲列表:不规则,虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很

频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保

证线程安全:

失败重试CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设 没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用配上失败重试的方式保证更新操作的原子性。为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存 时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用 上述的 CAS 进行内存分配

2.4.2.3. Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操 作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的 数据类型所对应的零值。

2.4.2.4. Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才 能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头 中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方 式。

2.4.2.5. Step5:执行 init 方法

在上面工作都完成之后,从虚拟机的视⻆来看,一个新的对象已经产生了,但从 Java 程序的视 ⻆来看,对象创建才刚开始, <init> 方法还没有执行,所有的字段都还为零。所以一般来说, 执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真 正可用的对象才算完全产生出来。

对象的访问定位有哪两种方式?

建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有使用句柄和直接指针两种:

1. 句柄如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中 存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信 息;

2. 直接指针如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类 型数据的相关信息,而reference 中存储的直接就是对象的地址。

这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄 地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直 接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

新生代和老生代的收集算法,比例,gc

标记清除,整理,复制

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用"复制算法",只需要付出少量存活对象的复制成本就可以完成收集。

在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用"标记-清理"或"标记-整理"算法来进行回收。

Eden区、From Survivor区(S0)、To Survivor区(S1)。默认8:1:1。

老年代和新生代是2:1

如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC

设置两个Survivor区最大的好处就是解决了碎片化,碎片化带来的风险是极大的,严重影响JAVA程序的性能。堆空间被散布的对象占据不连续的内存,最直接的结果就是,堆中没有足够大的连续内存空间,接下去如果程序需要给一个内存需求很大的对象分配内存

刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。S0和Eden被清空,然后下一轮S0与S1交换角色,如此循环往复。如果对象的复制次数达到16次,该对象就会被送到老年代中。整个过程中,永远有一个survivor space是空的,另一个非空的survivor space无碎片。

MinorGC:是指清理新生代

MajorGC:是指清理老年代(很多MajorGC是由MinorGC触发的)

FullGC:是指清理整个堆空间包括年轻代和永久代。调用System.gc()时,是建议JVM进行Full GC,只是建议,不是一定会发生。老年代空间不够时。方法区空间不够时。

jvm调优,gc优化

GC优化。确定目标、优化参数、验收结果。

请求高峰期发生GC,导致服务可用性下降。在执行垃圾回收时,Java应用程序中除了垃圾回收器线程之外其他所有线程都被挂起,意味着在此期间,用户正常工作的线程全部被暂停下来。1.39s

CMS的四个阶段:1.初始标记,该阶段进行可达性分析,标记GC ROOT能直接关联到的对象,所以很快。 2.并发标记,由前阶段标记过的绿色对象出发,所有可到达的对象都在本阶段中标记。 3. Remark重标记,暂停所有用户线程,重新扫描堆中的对象,进行可达性分析,标记活着的对象。

因为并发标记阶段是和用户线程并发执行的过程,所以该过程中可能有用户线程修改某些活跃对象的字段,指向了一个未标记过的对象。在并发标记开始时不可达,但是并行期间引用发生变化,变为对象可达,这个阶段需要重新标记出此类对象,防止在下一阶段被清理掉。特别需要注意一点,这个阶段是以新生代中对象为根来判断对象是否存活的。 4. 并发清理,进行并发的垃圾清理。

新生代GC和老年代的GC是各自分开独立进行的。由于跨代引用的存在,CMS在Remark阶段必须扫描整个堆,同时为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待Minor GC的发生。只是该阶段有时间限制,如果超时等不到Minor GC,Remark时新生代仍然有很多对象。新生代中对象的特点是"朝生夕灭",这样如果Remark前执行一次Minor GC,大部分对象就会被回收。我们的调优策略是,通过参数强制Remark前进行一次Minor GC,从而降低Remark阶段的时间。

单次执行时间>200ms的GC停顿消失

G1垃圾收集器:

目标减少swt时间,全年代回收器,取消物理分代,将整个堆空间分成一块一块相等的小内存,region,默认2048个,最大值,大小必须是2的倍数。5%给新生代。新生代和老年代由不同的region组成,不一定连续。可以设置gc停顿时间,每个region有一个回收价值(存活率,预计耗时等)组合一些有价值的region进行回收,region是动态变化的。

当新生代的region占比到60%就会就行垃圾回收,复制。年龄达到阈值,或者年龄大于50%以上的对象(比如1-5占50%,那6岁以上的进入),进入老年代。大对象存放在称为humongous的region,就行新生代或者老年代回收的时候顺带回收h。

默认老年代占比45%会垃圾回收(新生代和大对象也会回收),复制算法,可调。过程:初始标记(stw,gcroot能连到的标记),并发标记(遍历整个层,原始快照),最终标记(根据原始快照重新标记),混合回收(stw,可以分多次执行,先回收30,用户线程,再30个。默认分八次可改,避免停顿时间过长。)

GC ROOT

方法区中的静态属性,方法区的中的常量,虚拟机中的局部变量,本地方法栈,native修饰的方法指向的对象。

mysql

事务特性

原子性,2一致性(Consistency): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;

隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;

持久性(Durabilily): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

MySQL InnoDB 引擎使用  保证事务的,使用 ,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然来保证事务的

脏读:当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是"脏数据",依据"脏数据"所做的操作可能是不正确的。

 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。

读取未提交: 允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。读不会加任何锁。而写会加排他锁,并到事务结束之后释放。

读取已提交: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。会通过获取当前数据的快照,不加任何锁,也无视任何锁

: 是每次时。这就意味着,如果我们在事务A中执行多次的select,在每次select之间有其他事务了我们读取的数据并提交了,那就出现了

可重复读: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。一次事务中只在时生成版本,后续的查询都是在这个版本上进行,从而实现了

可串行化: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。select转化为select ... lock in share mode执行,即针对同一数据的所有读写都变成互斥的了,可靠性大大提高,并发性大大降低。

mvcc

MVCC是指多版本并发控制。MVCC是在并发访问数据库时,通过对数据进行多版本控制,避免因写锁而导致读操作的堵塞,从而很好的优化并发堵塞问题。

MVCC的两个实现核心是,通过undo log来保存多版本的数据,通过一致性视图来保存当前活跃的事务列表,将两者结合和制定一定的规则来判断当前可读数据。

为什么InnoDB要回表查询,那为什么不像MyISAM一样使用非聚簇索引直接就能把数据查出来?

只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。

将查询的字段放到联合索引里面进去

Innodb把表中所有数据都存放在主索引的叶子节点里,在往表里插入数据时,可能会导致主索引结构发生变化(分裂或合并的操作),也就导致了数据地址的变化,所以为什么要再回表一次确保拿到正确的数据。而myisam的做法使得B+树结构发生变化时,还需要同步更新其他的索引。

InnoDB二级索引存储主键值而不是存储行指针的优点与缺点  优点

减少了出现行移动或者数据页分裂时二级索引的维护工作(当数据需要更新的时候,二级索引不需要修改,只需要修改聚簇索引,一个表只能有一个聚簇索引,其他的都是二级索引,这样只需要修改聚簇索引就可以了,不需要重新构建二级索引)

缺点:

二级索引体积可能会变大,因为二级索引中存储了主键的信息。二级索引的访问需要两次索引查找。第一次通过查找 二级索引 找二级索引中叶子节点存储的 主键的值;第二次通过这个主键的值去 聚簇索引 中查找对应的行

聚簇索引和非

⾮聚簇索引:B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,⾸先按照 B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的 值为地址读取相应的数据记录。

聚簇索引:其数据⽂件本身就是索引⽂件。他的表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。⽽其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值⽽不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到key所 在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段 作为主键,这样会造成主索引频繁分裂。

,如果表中没有定义主键,InnoDB 会选择一个代替。如果没有这样的索引,InnoDB 会来作为聚簇索引。

如果我们使用哈希作为底层的数据结构,它对于处理范围查询或者排序性能会非常差,只能进行全表扫描并依次判断是否满足条件。使用 B+ 树其实能够保证数据按照键的顺序进行存储,也就是相邻的所有数据其实都是按照自然顺序排列的,使用哈希却无法达到这样的效果,因为哈希函数的目的就是让数据尽可能被分散到不同的桶中进行存储,我们总是要从根节点向下遍历子树查找满足条件的数据行,这个特点带来了大量的随机 I/O,也是 B 树最大的性能问题。

索引优缺点

第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 第二,可以大大加快数据的检索速度。 第三,可以加速表和表之间的连接。 第四,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

缺点:第一,创建索引和维护索引要耗费时间。 第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间。 第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

在经常需要搜索的列上,可以加快搜索的速度;在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构; 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度; 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的; 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间; 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。

1.对于在查询过程中很少使用或参考的列,不应该创建索引。2.对于那些只有很少数据值的列,不应该创建索引。  3.对于那些定义为image,text和bit数据类型的列,不应该创建索引。因为这些数据类型的数据列的数据量要么很大、要么很小,不利于使用索引。  4.当修改性能远大于检索性能,不应该建立索引。

索引失效

联合索引排序的原理:先对第一个字段进行排序,在第一个字段相同的情况下考虑第二个字段,然后在第二个字段相同的情况下

标签: swt131变送器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台