如何诊断网站seo,网页qq登录保护怎么关,上海缘震网络科技有限公司,做网站好用的软件一、Java八股
1、ThreadLocal的底层原理是什么#xff1f;
ThreadLocal 在Java中用于提供线程局部变量#xff0c;这些变量在每个线程中都有独立的副本#xff0c;互不干扰。其底层原理可以简要描述如下#xff1a; 数据存储: 每个线程中都有一个 ThreadLocalMap 的实例
ThreadLocal 在Java中用于提供线程局部变量这些变量在每个线程中都有独立的副本互不干扰。其底层原理可以简要描述如下 数据存储: 每个线程中都有一个 ThreadLocalMap 的实例用于存储线程局部变量。键是ThreadLocal实例本身值是线程局部变量的副本。 初始化: 当线程第一次通过 get() 或 set() 方法访问 ThreadLocal 变量时会触发 ThreadLocal 的初始化流程为线程创建一个 ThreadLocalMap 实例。 获取值: 在调用 get() 方法时ThreadLocal 会使用当前线程中的 ThreadLocalMap并使用自身作为键来获取对应的值。 设值: 在调用 set() 方法时同样会使用当前线程的 ThreadLocalMap并使用 ThreadLocal 实例作为键来存储值。 防止内存泄漏: 由于使用了线程自身的 ThreadLocalMap因此有必要在不再需要线程局部变量时清理它们以防止潜在的内存泄漏。这通常是通过调用 ThreadLocal 的 remove() 方法来完成的。
2、双亲委派机制
双亲委派机制是一种强有力的安全和隔离机制但在某些情况下如果需要打破这种层级关系可以通过自定义类加载器并覆盖其加载类的方法来实现。双亲委派机制是Java中类加载器的一种工作机制它的主要流程如下 加载检查: 当一个类加载器需要加载一个类时它不会首先尝试自己加载这个类而是委派给它的父加载器。 递归委派: 这个委派过程是递归的最终会达到最顶层的类加载器如Bootstrap ClassLoader。 尝试加载: 如果父加载器能够加载这个类则使用父加载器的结果。如果父加载器无法加载这个类因为它没有找到这个类则调用子加载器自己的findClass方法尝试加载这个类。 安全性与隔离: 这种机制可以确保Java核心库的类型安全因为无论哪个类加载器最终加载了java核心库的类都是同一个类加载器Bootstrap确保了被信任的类不会被覆盖。 效率: 这种机制也提高了类加载的效率。因为如果一个类加载器已经加载过某个类那么它的所有子加载器都无需再次加载这个类。
3、线程池如何构造线程池7大参数拒绝策略有哪些
在Java中线程池可以通过java.util.concurrent.ExecutorService接口及其实现类来构造和管理。最常用的实现类是ThreadPoolExecutor。
线程池7大参数 corePoolSize: 线程池的基本大小即在没有任务需要执行时线程池的大小也就是线程池中基本的线程数。 maximumPoolSize: 线程池最大的大小即线程池中允许的最大线程数。 keepAliveTime: 当线程池中线程数量超过corePoolSize时多余的空闲线程能存活的最长时间。 unit: keepAliveTime的时间单位。 workQueue: 工作队列用于存储等待执行的任务。 threadFactory: 线程工厂用于创建新线程。 handler: 拒绝策略当任务太多来不及处理如何拒绝任务。
拒绝策略
常见的拒绝策略有以下几种
AbortPolicy: 直接抛出RejectedExecutionException异常。CallerRunsPolicy: 调用执行自己的线程运行任务。DiscardOldestPolicy: 放弃队列最前面的任务并执行当前任务。DiscardPolicy: 不处理直接丢弃。
4、多线程有了解吗异步编程如何实现
多线程是一种使得多个线程并发执行的编程技术通常用于提高程序的执行效率和响应速度。Java中的多线程可以通过Thread类或者实现Runnable接口来创建和管理。
异步编程是一种编程范式允许程序在等待某些操作完成的时候继续执行其他任务。在Java中异步编程可以通过Future、CompletableFuture或者使用Reactive编程库如Project Reactor来实现。异步编程的关键是将耗时的操作放在另一个线程或者任务中执行从而不阻塞当前线程提高程序的整体效率。
5、进程与线程的区别 定义: 进程: 是一个程序的执行实例具有独立的代码和数据空间进程间的通信需要特定的IPC方法。线程: 是进程内的一个执行单元负责当前进程中程序的执行。 资源分配和独立性: 进程: 拥有完全独立的地址空间进程间不会互相影响。线程: 同一进程内的线程共享地址空间一个线程崩溃会影响整个进程。 创建和管理: 进程: 创建和管理进程的开销较大。线程: 线程的创建和管理开销相对较小。 通信: 进程: 进程间通信IPC需要操作系统的介入效率较低。线程: 线程间可以直接通信效率更高。
6、结合操作系统说一下多线程并发为什么会对系统造成影响 资源竞争: 多个线程可能会同时访问共享资源导致资源竞争需要操作系统进行调度和管理。 上下文切换: 频繁的线程切换会导致大量的CPU时间被用于上下文切换降低系统效率。 死锁: 线程之间因资源竞争可能出现死锁的情况需要操作系统介入解决。 稳定性和安全性: 不合理的线程操作可能导致程序崩溃甚至影响操作系统的稳定性。
7、HashMap的数据结构和使用的注意点
HashMap在Java中是一种基于哈希表的Map接口实现其主要数据结构包括 数组: 存储元素的主体是一个数组。 链表: 当哈希冲突时元素会以链表的形式存储在数组的同一个索引位置。 红黑树: 当链表长度超过一定阈值时链表会转换为红黑树以提高查找效率。
使用HashMap的注意点 线程不安全: HashMap是线程不安全的多线程操作时需要注意同步。 初始容量和加载因子: 合理设置初始容量和加载因子可以提高HashMap的性能。 Key的哈希函数: Key对象的哈希函数需要合理设计以避免大量的哈希冲突。 Key的不可变性: 作为Key的对象最好是不可变的这样可以确保哈希值的一致性。 Null值: HashMap允许使用null作为Key和Value但最好避免这样做以防止出现歧义。 容量扩展: 当元素数量达到容量和加载因子的乘积时HashMap会进行扩容操作这是一个耗时的过程需要注意。 遍历效率: 如果HashMap的容量远大于实际存储的键值对数量遍历HashMap的效率会较低因为需要遍历很多空的桶。
8、HashMap中如何定位桶有100个key-value格式的数据要存在HashMap中如何使得效率最高
在HashMap中桶的定位是通过哈希算法来实现的。具体步骤如下 计算Key的HashCode: 调用Key对象的hashCode()方法计算出其哈希值。 扰动函数处理: 为了减少哈希碰撞将HashCode经过扰动函数处理充分混合HashCode的高位和低位。 计算索引位置: 使用处理过的HashCode值与数组长度减一进行位与操作计算出桶的位置。 index (n - 1) hash其中n是数组的长度hash是处理过的HashCode值。
为了使存储100个Key-Value对的效率最高可以考虑以下策略 设置合适的初始容量: 设置一个合适的初始容量以减少或消除扩容操作。考虑到加载因子默认为0.75为避免扩容初始容量应设置为至少134100 / 0.75。 设计良好的Key哈希函数: 确保Key的哈希函数设计得足够好能够均匀地分布哈希值减少哈希冲突。 避免使用可变对象作为Key: 使用不可变对象如字符串或整数作为Key以确保在HashMap的整个生命周期内Key的哈希值保持不变。
9、扰动函数的原理与作用为什么扰动函数能够减少hash冲突
原理
扰动函数是对哈希值进行一系列位操作的过程目的是为了更好地分散原始哈希码的位信息使得哈希值在桶数组中的分布更加均匀。在Java 8中HashMap的扰动函数主要通过hash 16实现即将32位的原始哈希码向右无符号移动16位。
作用
扰动函数的主要作用是为了减少哈希冲突提高HashMap的查找效率。通过将哈希码的高16位和低16位进行异或操作能够使得原本在低位分布不均匀的哈希码在数组中的分布更加均匀。
为什么能减少哈希冲突
扰动函数通过混合哈希码的高位和低位信息使得即使原始哈希码在低位上分布不均匀经过扰动函数处理后也能在数组中均匀分布从而减少了哈希冲突的可能性。这样即使在遇到较差的哈希函数时HashMap的性能也能得到一定程度的保障。
10、HashMap在rehash操作时需要重新对每一个值进行计算吗
在HashMap进行rehash操作时它需要重新计算每个存储在HashMap中的元素的桶位置。这是因为当HashMap的容量变化时通常是翻倍桶的数量也发生了变化这就需要重新计算每个元素在新数组中的位置。这个过程通常涉及到重新计算元素键的哈希值并根据新的数组大小重新定位元素。
11、接口和抽象类的区别
定义 接口Interface: 是一种完全抽象的结构只能定义方法的签名不能包含方法的实现。 抽象类Abstract Class: 是一种可能包含抽象方法没有实现的方法的类。
实现 接口: 一个类可以实现多个接口。 抽象类: 一个类只能继承一个抽象类。
方法 接口: Java 8之后接口可以包含默认方法有实现的方法和静态方法。但在此之前接口中的所有方法都必须是抽象的。 抽象类: 可以包含抽象方法和非抽象方法。
构造器 接口: 不能有构造器。 抽象类: 可以有构造器。
成员变量 接口: 只能定义常量public static final。 抽象类: 可以定义变量可以有成员变量。
访问修饰符 接口: 方法默认是public的。 抽象类: 方法可以有任意访问修饰符。
12、多态必须有继承和重写吗
多态的实现通常依赖于继承和方法重写。在面向对象编程中多态允许你将子类类型的对象赋值给父类类型的引用变量。这样你就可以使用父类的引用来调用在子类中重写的方法。 继承: 提供了一种机制允许子类继承父类的属性和方法。 重写Overriding: 在子类中提供了一种方法这种方法与在其父类中定义的方法具有相同的方法签名。
虽然多态的典型实现依赖于继承和重写但Java也支持接口实现的多态你可以有一个接口和多个实现该接口的类。在这种情况下多态是通过实现接口的不同类来实现的而不是通过继承和重写。
13、AQS对资源的两种共享方式以及具体的应用例子
AQSAbstractQueuedSynchronizer是Java中提供的一种用于构建锁和同步器的框架。AQS使用一个int成员变量来表示同步状态通过内置的FIFO队列来完成资源获取线程的排队工作。AQS支持两种资源共享方式
独占模式Exclusive:
在这种模式下每次只能有一个线程持有锁。AQS会将尝试获取锁的其他线程放入队列中。
应用例子: ReentrantLock是一个典型的独占模式的应用。它确保了每次只有一个线程能够执行被保护的代码区域。
共享模式Shared:
在这种模式下多个线程可以共同持有锁。具体有多少个线程可以共享锁取决于同步器的实
应用例子: Semaphore一个计数信号量允许多个线程同时访问。ReadWriteLock它有一个读锁和一个写锁读锁可以被多个读线程持有但写锁是独占的。CountDownLatch允许一个或多个线程等待其他线程完成操作。
14、Semaphore和CountDownLatch的区别
Semaphore信号量
作用: 主要用于控制对资源的访问线程数目。用途: 用于实现资源的限制访问常用于流量控制。灵活性: Semaphore可以控制同时访问资源的线程个数并提供了同步方法来释放或者获取访问权限。调用方法: acquire()和release()分别用于获取和释放访问权限。
CountDownLatch倒计时锁存器
作用: 允许一个或多个线程等待其他线程完成操作。用途: 常用于等待服务初始化完毕后再执行其他操作。灵活性: CountDownLatch的计数器在创建时就需要指定而且只能使用一次计数器达到0后无法重置。调用方法: countDown()和await()分别用于计数减一和等待计数到达0。
总的来说Semaphore更多的是用来控制对特定资源的访问线程数目而CountDownLatch用于一个线程等待多个线程完成操作后再执行。
15、 类加载的过程和机制
Java的类加载过程包括以下几个主要阶段
加载Loading
作用查找并加载类的二进制数据。过程通过一个类的全限定名来获取其定义的二进制字节流并将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。在内存中生成一个代表这个类的java.lang.Class对象作为方法区这个类的各种数据的访问入口。
验证Verification
作用确保Class文件的字节流包含的信息符合当前虚拟机的要求不会危害虚拟机自身的安全。过程包括文件格式验证、元数据验证、字节码验证、符号引用验证等。 准备Preparation
作用为类中定义的静态变量分配内存并设置默认初始值。注意这时候进行内存分配的仅包括类变量被static修饰的变量而不包括实例变量。
解析Resolution
作用将常量池内的符号引用替换为直接引用的过程。过程涉及到类或接口、字段、类方法、接口方法的解析。
初始化Initialization
作用对类进行初始化。过程包括执行类构造器clinit()方法的过程。此方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
类加载的机制包括
全盘负责当一个类加载器负责加载某个Class时该Class所依赖和引用的其他Class也将由这个类加载器负责载入除非显式使用另外一个类加载器来载入。父类委派这是一种组织类加载器的层次关系的常见方式。Java使用了父类委派机制来加载类即当一个类加载器接收到类加载的请求它首先不会尝试加载这个类而是把这个请求委派给父类加载器每一层都是如此因此所有的加载请求最终都应该传送到顶层的启动类加载器只有当父类加载器反馈自己无法完成这个加载请求时在它的搜索范围中没有找到所需的Class子加载器才会尝试自己去加载。
16、OOM 遇到情况和解决
OOM的常见情况 堆内存溢出Java Heap Space创建的对象过多超过了堆的最大限制。 解决方案增加堆内存大小或优化内存使用查找内存泄漏。 栈内存溢出Stack Overflow方法调用层次太深超出了栈的最大限制。 解决方案增加栈大小或优化递归调用。 方法区溢出常量池或类元数据信息太多超过了方法区的最大限制。 解决方案增加方法区的大小或减少运行时常量池的使用。 直接内存溢出使用NIO时申请的直接内存过大。 解决方案增加直接内存的大小。 线程创建过多创建的线程数目超过系统承载极限。 解决方案减少线程的创建使用线程池。
解决方法 分析堆转储文件使用工具如Eclipse Memory Analyzer分析堆转储文件查找内存泄漏或者大对象。 调整JVM参数根据具体情况调整JVM启动参数如-Xms, -Xmx, -Xss等。 代码优化优化代码逻辑减少内存占用避免内存泄漏。 资源清理确保使用完资源后进行适时的清理如关闭文件、数据库连接等。 使用Profiler工具使用Profiler工具对运行时的程序进行分析找出内存占用高或者线程活动异常的地方进行优化。
通过这些方法可以有效地避免或解决OOM问题确保程序的健壮性和稳定性。
17、怎么理解线程安全解决线程安全问题
线程安全理解
定义在多线程环境下多个线程对同一对象或资源进行操作时无论运行时操作系统如何调度这些线程程序都能正确执行即能够保证数据的正确性和一致性。实现要点 原子性操作要么全部完成要么全不完成不会停留在中间某个环节。可见性当一个线程修改了对象的状态其他线程能够立即看到这种变化。有序性操作执行的顺序按照代码的先后顺序执行。
解决线程安全问题的方法
加锁使用synchronized或ReentrantLock来为方法或代码块加锁确保同一时刻只有一个线程执行该段代码。使用线程安全类如使用ConcurrentHashMap代替HashMap使用StringBuffer代替StringBuilder等。使用volatile关键字确保变量的可见性禁止指令重排。使用不可变类如String、Integer等。使用局部变量局部变量是线程安全的因为它们存储在栈内存中属于线程私有的数据。使用ThreadLocal为每个线程提供单独一份存储空间实现线程间数据隔离
18、ConcurrentHashMap怎么保证线程安全、ConcurrentHashMap1.7和1.8的区别
ConcurrentHashMap的线程安全性
实现机制ConcurrentHashMap通过分段锁Segment的机制将数据分成一段一段存储然后给每一段数据配一把锁当一个线程占用锁访问其中一段数据时其他段的数据也能被其他线程访问。具体实现在JDK1.7中ConcurrentHashMap采用Segment数组和HashEntry数组结合的方式实现。在JDK1.8中放弃了Segment的概念直接采用Node数组链表红黑树的数据结构实现。
ConcurrentHashMap1.7和1.8的区别
Segment的使用 1.7中使用Segment来进行分段加锁。1.8中放弃了Segment采用了Node数组链表红黑树通过对Node的操作实现线程安全。锁的粒度 1.7中粒度较粗每个Segment持有一把锁。1.8中粒度更细使用synchronized对Node进行加锁大大提升了效率。数据结构 1.7中使用Segment数组HashEntry数组链表。1.8中使用Node数组链表红黑树。
19、分段锁是可重入的吗、你怎么理解可重入锁
分段锁的可重入性
分段锁的可重入性取决于其实现ConcurrentHashMap中的分段锁是可重入的因为它使用了ReentrantLock来实现锁的功能。
可重入锁理解
定义可重入锁也叫递归锁指的是同一线程外层函数获得锁后内层递归函数仍然能获取该锁的代码在同一个线程在外层方法获取锁的时候在进入内层方法会自动获取锁。特点简单来说线程可以进入任何一个它已经拥有的锁所同步着的代码块。好处可重入锁的最大作用是避免死锁
20、什么是公平锁和非公平锁、非公平锁吞吐量为什么比公平锁大
公平锁
定义公平锁是指多个线程按照请求锁的顺序来获取锁。特点 有序保证了执行顺序避免了饥饿现象。低吞吐量由于要在锁释放时进行线程调度会增加系统的开销导致吞吐量较低。应用场景适用于需要保证请求的执行顺序的场景。
非公平锁
定义非公平锁是指在释放锁之后所有等待锁的线程都有机会获取锁而不是按照请求锁的顺序。特点 无序不能保证执行顺序可能导致某些线程一直获取不到锁。高吞吐量因为线程有可能直接获得锁而不用排队等待减少了线程切换的开销。应用场景适用于对执行顺序没有严格要求追求高性能的场景。
非公平锁吞吐量为什么比公平锁大
非公平锁之所以有更高的吞吐量是因为它减少了线程切换的次数和等待的时间。在锁被释放时非公平锁允许新请求锁的线程直接获得锁而不是把锁分配给之前在队列中等待的线程。这样就省去了唤醒线程、线程切换等开销提高了执行效率。
21、JVM内存结构、JVM为什么把堆区进一步的划分
JVM内存结构
方法区存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等。堆区存储对象实例是垃圾收集器管理的主要区域。栈区存储局部变量值、方法调用的操作数、返回地址等随着方法的调用而动态增长和减小。程序计数器记录线程执行的字节码的行号指示器。本地方法栈为虚拟机使用到的Native方法服务。
为什么把堆区进一步划分
提高垃圾回收效率不同的对象有不同的生命周期通过将堆区进行划分可以根据不同区域的特点采用不同的垃圾回收算法。Eden区、Survivor区和老年代 Eden区存放新生对象。Survivor区存放从Eden区经过第一次垃圾回收后仍然存活的对象。老年代存放经过多次垃圾回收仍然存活的对象。减少全区垃圾回收的频率通过将新生对象和存活时间较长的对象分开存放可以减少对老年代的垃圾回收频率提高系统性能。
通过对堆区的划分JVM能够更精细地管理内存提升垃圾回收的效率从而提高整体的系统性能。
二、数据库八股 1、MySQL的存储引擎、B树和B树有哪些特征和不同点
MySQL的存储引擎
InnoDB支持事务处理提供了对数据库ACID的事务支持和行级锁定。MyISAM不支持事务表级锁定读取速度快但写入速度相对较慢。Memory所有的数据都在内存中速度极快但重启会丢失所有数据。其他NDB、Archive等。
B树
特点 所有关键字都出现在叶子节点的链表中而且是有序的。非叶子节点可以重复。比B树更加“矮胖”。优势 查询性能稳定。全部叶子节点构成一个有序链表便于范围查询。
B树
特点 关键字分布在整棵树中。任何一个关键字出现且只出现在一个节点中。关键字的排序是全局的。
B树和B树的不同点
层级B树比B树更加“矮胖”对于相同数量的关键字B树的查询性能更稳定。查询速度B树的查询性能较为稳定B树因为关键字不是全局有序所以查询性能不如B树。范围查询B树由于叶子节点形成了有序链表所以对范围查询非常友好而B树则需要进行多次检索。
2、在InnoDB下联合索引是如何创建的以及创建的数据结构是咋样的
创建方式
在MySQL中你可以通过CREATE INDEX或在CREATE TABLE时定义索引来创建联合索引。
CREATE INDEX index_name ON table_name (column1, column2, ...);数据结构
B树InnoDB使用B树作为索引的数据结构联合索引的每个索引项是索引列的值的组合。索引顺序数据是按照索引列的顺序存储的。
3、在InnoDB下每张表都一定有聚簇索引吗
聚簇索引将数据存储与索引放到了一起表中的数据按照每张表的主键构造一棵B树同时叶子节点中存放的就是整条记录的数据。
是否每张表都有
有主键的表如果表定义了主键InnoDB会使用这个主键作为聚簇索引。没有主键的表如果没有定义主键InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引InnoDB会生成一个隐藏的主键来作为聚簇索引。
所以每张InnoDB表都会有聚簇索引但这个聚簇索引可能不是由显示定义的主键来构建的。
4、覆盖索引
覆盖索引是指一个索引包含或覆盖了查询中需要的所有字段的情况。
特点
性能优化因为索引项通常比数据行小所有字段都在索引中时可以直接从索引中获取数据无需回表查询减少了磁盘I/O提高了查询效率。索引只扫描执行计划中会显示为“Using index”表示查询完全通过索引来完成。
应用场景
频繁查询的字段建议建立覆盖索引。需要注意的是覆盖索引会占用更多的存储空间。
5、Redis使用场景和数据类型
使用场景
缓存提高数据读取速度。会话存储例如存储用户的Session信息。排行榜使用Sorted Set实现。发布订阅系统如消息队列。计数器例如网站访问次数统计。
数据类型
String字符串。Hash哈希表。List列表。Set集合。Sorted Set有序集合。Bitmap位图。HyperLogLog用来做基数统计。
6、Redis中的分布式锁设计
设计原则
互斥性在任何时刻只有一个客户端持有锁。避免死锁即便有一个客户端在持有锁的期间崩溃没有主动解锁也需要保证后续其他客户端能够加锁。解锁安全只有加锁的客户端才能解锁。加锁和解锁是同一个客户端确保加锁和解锁的是同一个客户端。
实现方式
SETNXSETNX key value当且仅当key不存在时为key设置指定的值返回1key已存在什么也不做返回0。带超时的锁为了防止死锁锁需要有一个最大的生存时间。可以使用EXPIRE命令来为锁设置一个过期时间。安全的解锁需要判断锁是否属于自己可以通过GET命令获取锁的值客户端标识进行判断。解锁可以使用DEL命令。
注意事项
确保操作的原子性Redis的SET命令在2.6.12版本添加了选项可以确保设置值和设置过期时间的原子操作。SET key value EX seconds NX。延长锁的过期时间在锁即将过期的时候如果任务还没有执行完需要重新设置过期时间。Redlock算法Redis官方提出的分布式锁算法主要用于多实例的Redis环境中。
7、数据库与缓存一致性怎么设计的
基本原则
更新缓存策略先更新数据库再删除缓存。读取数据策略先读取缓存缓存没有再查询数据库并将数据更新到缓存。
实现方法
延时双删策略 第一次删除缓存。更新数据库。休眠一段时间比如100ms。第二次删除缓存。消息队列 数据更新后将更新的信息发送到消息队列。监听消息队列进行缓存更新。
8、分布式事务接触过吗原理是怎么样的
原理
分布式事务确保在分布式系统中事务的ACID属性仍然得以保持。
常见的实现方式有
两阶段提交2PC 第一阶段准备阶段事务协调者询问所有参与者是否准备好提交事务所有参与者返回结果。第二阶段提交/回滚阶段根据第一阶段的结果协调者发送提交或回滚的指令给所有参与者。三阶段提交 在两阶段提交的基础上增加了超时机制和CanCommit阶段。TCCTry Confirm Cancel Try阶段尝试执行业务。Confirm阶段确认执行业务。Cancel阶段取消执行业务。
9、为什么数据库与缓存一致性不采用分布式事务这种方案
性能影响分布式事务通常会带来较大的性能开销尤其是在高并发、大数据量的场景下。复杂性实现分布式事务需要解决网络延迟、节点宕机等问题实现复杂维护成本高。可用性降低分布式事务要求所有参与节点都正常工作一旦某个节点出问题可能会影响整个事务的完成。
在处理数据库与缓存一致性问题时通常追求的是高性能、高可用而分布式事务在这些方面有一定的劣势因此一般不会选用分布式事务来解决这类问题。相对来说采用延时双删、消息队列等策略更加轻量级更容易保证系统的高性能和高可用。
10、一般在什么情况下设计索引
在以下情况下通常需要考虑设计索引
频繁查询的字段对于查询操作非常频繁的字段设计索引可以显著提高查询效率。作为查询条件的字段经常作为WHERE子句中的条件的字段应当建立索引。排序、分组字段经常需要ORDER BY或GROUP BY的字段建立索引可以提高排序和分组的速度。主键和外键数据库自动为主键建立唯一索引外键也建议建立索引以提高关联查询的效率。更新不频繁读取频繁的字段因为索引的维护需要成本所以对于更新非常频繁的字段可能需要权衡是否建立索引。选择性高的字段字段的选择性越高不重复的值越多建立索引的效果越好。避免全表扫描对于大表建立索引可以避免查询时进行全表扫描。
11、SQL优化了解吗
选择合适的字段类型尽量选择最合适的数据类型避免不必要的空间浪费。使用索引合理使用索引特别是在查询条件中的字段。**避免SELECT * **只查询需要的字段避免不必要的数据传输。减少JOIN操作JOIN操作会增加查询复杂度尽量减少JOIN操作或者优化JOIN的顺序。使用LIMIT当只需要查询部分数据时使用LIMIT限制结果集的大小。优化WHERE子句避免在WHERE子句中使用函数或计算这会导致索引失效。适当使用分区对于非常大的表考虑使用分区技术。避免大事务操作大事务会锁定很多资源影响系统性能。SQL语句优化对复杂的SQL语句进行优化考虑执行计划。
12、Redis为什么使用跳表而不是用B树跳表和B树的区别
插入和删除效率高跳表的插入和删除操作平均时间复杂度为O(logn)而且实现相对简单。范围查询简单跳表支持范围查询且实现较为简单。内存占用虽然跳表的内存占用比B树高因为需要存储多级索引但在内存不是瓶颈的场景下跳表的高效和简单实现更加重要。实现简单跳表的数据结构和算法相对简单易于实现和维护。锁的粒度小跳表锁的粒度小适合高并发的场景。
与B树相比跳表在一些场景下提供了更好的性能尤其是在内存数据库和高并发环境下且实现相对更为简单。这是Redis选择使用跳表而不是B树的主要原因。
三、项目八股 1、Netty的三大重要组件 Channel代表一个网络套接字或者组件用于处理数据的传输。 NioSocketChannel用于TCP网络IO。NioDatagramChannel用于UDP网络IO。其他类型如用于文件传输的FileChannel等。 EventLoop用于处理Channel的I/O操作。 一个EventLoop可以被分配给一个或多个Channel。对于每个Channel来说它只会注册在一个EventLoop上且其生命周期内只会被一个线程处理这样就避免了多线程之间的竞争。 ChannelPipeline持有一系列ChannelHandler实例用于处理入站和出站I/O事件。 ChannelHandler处理I/O事件的接口可以自定义处理逻辑。ChannelPipeline实现了Interceptor模式可以通过它来实现网络通信中的编码、解码、安全认证等功能。
2、采用的是JDK的序列化机制底层序列化机制原理
JDK序列化机制是Java提供的一种将对象转换为字节流的方法也可以从字节流中恢复对象。其主要用于网络传输、本地存储等。
原理 对象序列化将对象的状态信息转换为可以存储或传输的形式。 使用ObjectOutputStream写出对象。需要序列化的对象必须实现Serializable接口。支持递归序列化对象的字段是对象时这些对象也会被序列化。 对象反序列化从序列化状态恢复为对象。 使用ObjectInputStream读取对象。类的定义需要可用且serialVersionUID需要匹配。
底层原理
流协议序列化流包含了类的元数据、类的名称、字段名称及值等信息。对象引用共享相同对象在序列化过程中只会被写入一次之后只会写入引用避免了循环引用问题。使用writeObject和readObject方法自定义序列化逻辑如果类中存在这两个方法JDK序列化机制会调用它们来进行序列化和反序列化。
3、SpringSession是用来干嘛的
集中式会话管理在分布式系统中多个应用实例共享用户的会话信息。Restful API兼容适用于无状态的Restful服务。多浏览器会话一个用户可以在多个浏览器或者标签页中保持登录状态。持久化会话存储将会话信息存储在外部存储中如Redis、数据库等。提供并发控制防止会话在并发修改时产生冲突。
通过使用Spring Session可以在分布式环境下提供更加强大、灵活的会话管理能力。
四、计算机网路八股
1、说一下计算机网络中的四次挥手以及各个状态
TCP协议为了保证数据可靠传输采用了三次握手和四次挥手的机制。四次挥手主要是为了断开一个TCP连接其过程如下
客户端发送FIN报文客户端进入FIN_WAIT_1状态通知服务端它已经没有数据要发送了希望关闭连接。服务端接收到FIN报文发送ACK报文服务端进入CLOSE_WAIT状态它告诉客户端已经收到了关闭请求。客户端收到ACK报文后进入FIN_WAIT_2状态。服务端发送FIN报文当服务端也没有数据要发送时它会发送FIN报文给客户端进入LAST_ACK状态等待客户端的最后一个ACK报文。客户端发送ACK报文客户端进入TIME_WAIT状态发送最后一个ACK报文给服务端。服务端收到ACK报文后连接关闭进入CLOSED状态。客户端会等待一段时间2MSL即最大报文段生存时间的两倍确保服务端收到ACK报文然后进入CLOSED状态。
2、读文件每行一个IP统计topK出现的IP
import heapqdef top_k_ips(filename, k):ip_count {}with open(filename, r) as f:for line in f:ip line.strip()if ip in ip_count:ip_count[ip] 1else:ip_count[ip] 1# 使用最小堆找出出现次数最多的top K个IPmin_heap []for ip, count in ip_count.items():if len(min_heap) k:heapq.heappush(min_heap, (count, ip))elif count min_heap[0][0]:heapq.heappop(min_heap)heapq.heappush(min_heap, (count, ip))# 输出结果while min_heap:count, ip heapq.heappop(min_heap)print(fIP: {ip}, Count: {count})# 测试
top_k_ips(ips.txt, 3)上述代码中我们使用了Python的heapq模块来维护一个最小堆从而高效地找到出现次数最多的top K个IP地址。
3、两个线程交替打印奇偶数字
import threadingdef print_odd_even(max_num):num 1odd_even_lock threading.Lock()def print_odd():nonlocal numwhile num max_num:with odd_even_lock:if num % 2 1:print(Odd:, num)num 1odd_even_lock.notify()else:odd_even_lock.wait()def print_even():nonlocal numwhile num max_num:with odd_even_lock:if num % 2 0:print(Even:, num)num 1odd_even_lock.notify()else:odd_even_lock.wait()odd_thread threading.Thread(targetprint_odd)even_thread threading.Thread(targetprint_even)odd_thread.start()even_thread.start()odd_thread.join()even_thread.join()print_odd_even(10)在这个例子中我们使用了一个锁threading.Lock和Python的with语句来简化线程同步的逻辑。两个线程“交替”获取锁根据当前数的奇偶性来决定打印并递增数值。odd_even_lock.notify()用于唤醒等待在锁上的另一个线程而odd_even_lock.wait()则使当前线程等待直到另一个线程调用notify()。