0%

面试准备

记录自己准备面试的一些东西

  1. 你在使用Java jdk的时候,或者使用springboot框架的时候,见到过什么设计模式?请描述一下。

    • Spring中依赖注入的Bean实例默认是单例的。

      Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactory的getBean里。getBean的doGetBean方法调用getSingleton进行bean的创建。spring依赖注入时,使用了 双重判断加锁 的单例模式:

    • Spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现。
      事件机制的实现需要三个部分:事件源、事件、事件监听器。当事件触发时所有的监听器都会收到消息。

    • 策略模式。Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource 接口来访问底层资源。Resource接口是具体资源访问策略的抽象,也是所有资源访问类所实现的接口。Resource 接口本身没有提供访问任何底层资源的实现逻辑,针对不同的底层资源,Spring 将会提供不同的 Resource 实现类,不同的实现类负责不同的资源访问逻辑。

  2. Java有哪些集合类?比如用过HashMap吗?HashMap和HashTable以及ConcurrentHashMap的区别?

    • Java有List,Set,Map等集合类;
      List类型:List是有序的Collection,有ArrayList,Vector,LinkedList。

      • ArrayList,内部通过数组实现,允许对元素进行快速的访问;(但是当数组大小不满足时需要增加存储能力,就要将已有数组的数据复制到新的存储空间中)适合随机查找和遍历,不适合插入和删除。
      • Vector,支持线程同步,某一时刻只有一个线程能写Vector,由于实现同步的高花费,因此访问速度比Array list慢。
      • LinkedList,用链表存储数据,适合动态地插入和删除,随机访问和遍历速度较慢。

      HashSet:哈希表里面存放的是哈希值,HashSet存储元素的顺序并不是按照存入时的顺序,而是按照哈希值来存数据,取数据也是按照哈希值取得的。元素的哈希值是通过元素的hashcode方法来获取的。HashSet首先判断两个元素的哈希值,如果哈希值一样,接着就会比较equals方法,如果equals结果为true,就视为同一个元素。

    • HashMap 根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,具有很快的访问速度,但是遍历的顺序却是不确定的。 HashMap 最多只允许一条记录的键为 null,允许多条记录的值为 null。HashMap 非线程安全,即任一时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。 如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使HashMap 具有线程安全的能力 或者使用 ConcurrentHashMap 。

      ![image-20210228164402484](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210228164402484.png)

      ![image-20210228163815581](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210228163815581.png)

      数组 + 链表 +(链表元素超过8就会转化为红黑树) 红黑树(将时间复杂度从链表长度On降为OlogN)

    • ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。整个ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁。简单理解就是,ConcurrentHashMap 是一个 Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全。

    • hashmap扩容机制:

      1. 空参数的构造函数:实例化的HashMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16。
      2. 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让 [公式] 。(因此并不是我们手动指定了容量就一定不会触发扩容,超过阈值后一样会扩容!!)
      3. 如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。(容量和阈值都变为原来的2倍时,负载因子还是不变)

      ![image-20210228170514644](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210228170514644.png)

      ![image-20210228170627854](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210228170627854.png)

      ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。再具体到每个 Segment 内部,其实每个 Segment 很像之前介绍的 HashMap,不过它要保证线程安全。

    • Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。

    • HashMap为什么不是线程安全?

      HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入

      多个线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去,其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改

  3. 什么是反射?反射有哪些优点?又有哪些缺点?

    每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生 一个同名的 .class 文件,该文件内容保存着 Class 对象。类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。

    反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

    优点:在运行时判断任意一个对象所属的类、在运行时构造任意一个类的对象,在运行时判断任意一个类所具有的成员方法和变量、在运行时调用任意一个对象的方法。

    缺点:由于反射会额外消耗一定的系统资源,因此如果不需要动态地创建一个对象,那么就不需要用反射。另外,反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

  4. 你是用什么方案处理Java高并发问题的?可以详细讲讲吗?

    a.应用层面:读写分离、缓存、队列、集群、令牌、系统拆分、隔离、系统升级(可水平扩容方向)。

    讲一讲消息队列(MQ):

    解耦:A 系统发送数据到 BCD 三个系统,通过接口调用发送。如果 E 系统也要这个数据呢?那如果 C 系统现在不需要了呢?A 系统负责人几乎崩溃……A 系统跟其它各种乱七八糟的系统严重耦合,A 系统 产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。如果使用 MQ,A 系统产生一 条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。这样下来,A 系统 压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超 时等情况。

    就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。但 是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦。

    异步:A 系统接收一个请求,需要在自己本地写库,还需要在 BCD 三个系统写库,自己本地写库 要 3ms,BCD 三个系统分别写库要 300ms、450ms、200ms。最终请求总延时是 3 + 300 + 450 + 200 = 953ms,接近 1s,用户感觉搞个什么东西,慢死了慢死了。用户通过浏览器发起请求。如果使用 MQ,那么 A 系统连续发送 3 条消息到 MQ 队列中,假如耗时 5ms,A 系统从接受一个请求到返回响应给用户,总时长是 3 + 5 = 8ms。

    削峰:减少高峰时期对服务器压力。

    缺点:

    可用性降低,如果MQ挂了,系统就崩了。

    复杂度提高,如何保证消息没有重复消费,如何处理丢失情况,如何保证传递的顺序性。。。

    RabbitMQ处理消息丢失的情况:

    生产者丢失:使用confirm机制,MQ确认后才行。

    MQ丢失:持久化,收到消息后持久化到磁盘。

    消费者丢失:消费完才通知MQ。

    b.时间换空间:降低单次请求时间,这样在单位时间内系统并发就会提升。

    c.空间换时间:拉长整体处理业务时间,换取后台系统容量空间。

  5. 在做知识图谱系统中你遇到过最大的技术困难是什么?又是怎么解决的?

    消除歧义:包括步骤如下:

    S1:从数据集中分割出实体数据和中文地址数据;选取中文地址数据并创建地址树;

    S2:将中文地址数据分为多个层级;

    S3:分析层级对应的地址元素,将层级对应的地址元素的匹配模式设置为模糊匹配,将其余设置为精确匹配;

    S4:将待分析数据集中的中文地址数据按照地址元素的匹配模式与当前地址树进行匹配;

    S5:对中文地址数据进行编码,并根据编码值大小顺序创建新的地址树.

    S6:S4中匹配失败的地址元素存储于栈结构中,后续匹配成功时补充至地址树

  6. 你使用Redis做过什么?是怎么实现的?

    ![image-20210301145738414](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210301145738414.png)

    redis正是通过分数来为集合中的成员进行从小到大的排序,zset的成员是唯一的,但分数(score)却可以重复。

    对于网站里的每篇文章,使用一个散列来存储文章的标题、指向文章的网址、发布文章的用户、文章的发布时间、文章的投票数量等信息,键是文章的id。

    文章投票网站使用两个有序集合来有序地存储文章。第一个有序集合的成员为文章id,分值为文章的发布时间,按照文章发布时间由近到远进行排列;第二个有序集合的成员同样为文章id,分值为文章的评分。通过这两个有序集合,网站既可以按照文章发布的先后顺序来进行排列,也可以按照文章的评分高低来进行排列。

    为了防止用户对同一篇文章进行多次投票,网站需要为每篇文章记录一个已投票用户名单,使用集合来实现。

    redis的过期策略和内存淘汰机制:

    定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是 每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂 不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。 于是,惰性删除派上用场。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了 过期时间那么是否过期了?如果过期了此时就会删除。

    volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。

  7. 聊一下JVM?

    JVM是可运行Java代码的假象计算机,包括一套字节码指令集,一组寄存器,一个栈,一个垃圾回收,堆和一个存储方法域,JVM是运行在操作系统上的,与硬件没有直接的交互。

    栈:又称方法栈,线程私有的,线程执行方法是都会创建一个栈阵,用来存储局部变量表,操作栈,动态链接,方法 出口等信息.调用方法时执行入栈,方法返回式执行出栈。

    本地方法栈:与栈类似,也是用来保存执行方法的信息.执行Java方法是使用栈,执行Native方法时使用本地方法栈.

    堆:JVM内存管理最大的一块,对被线程共享,目的是存放对象的实例,几乎所有的对象实例都会放在这里,当堆没有可用空间时,会抛出OOM异常.根据对象的存活周期不同,JVM把对象进行分代管理,由垃圾回收器进行垃圾的回收管理。

    垃圾回收:

    • 新生代(堆的1/3),MinorGC。三个区 Eden(java新对象出生地),ServivorFrom(上一次GC幸存者,这一次GC被扫描者),ServivorTo区(保留一次GC中的幸存者)。采用复制算法:首先把Eden,ServivorFrom区域中存活的对象复制到ServivorTo区,同时把这些对象年龄加一;然后清空Eden和ServivorFrom中的对象;然后ServivorFrom和ServivorTo互换。
    • 老年代(堆的2/3),MajorGC。采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没 有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减 少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。

    什么时候触发FullGC,除了直接调用System.gc外,老年代空间不足也会引起fullGC,若执行fullGC还是不足,就会出现OutOfMemoryError。(让新生代多存活一些时间,不要创建过大的对象和数组)

  8. 谈一谈String、StringBuilder、StringBuffer?

    String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,这样不仅效率低下,而且大量浪费有限的内存空间。JDK中声明的时候String类就是final,内部的value子节数组也是final的。

    和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象

    StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。

    由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer 类。

  9. 重写与重载?

    • 重写Override,存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法,为了满足李氏替换条件,重写有两个原则:(可以使用@Override注解判断)
      1. 子类方法的访问权限必须大于等于父类方法
      2. 子类方法的返回类型必须是父类方法返回类型或其子类型。
    • 重载Overload,存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个 数、顺序至少有一个不同。 应该注意的是,返回值不同,其它都相同不算是重载。
  10. 类和对象?

    • 对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。
    • :类是一个模板,它描述一类对象的行为和状态。
  11. 主从复制?

    主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库,主数据库一般是准实时的业务数据库。

    • 做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
    • 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的评率,提高单个机器的I/O性能。
    • 读写分离,使数据库能支持更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。
    • 在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力;(主库写,从库读,降压)

    主从是对主操作数据,从会实时同步数据。反之对从操作,主不会同步数据,还有可能造成数据紊乱,导致主从失效。

    原理:

    1. 数据库有个bin-log二进制文件,记录了所有sql语句。

    2. 我们的目标就是把主数据库的bin-log文件的sql语句复制过来。

    3. 让其在从数据的relay-log重做日志文件中再执行一次这些sql语句即可。

    4. 1.binlog输出线程:每当有从库连接到主库的时候,主库都会创建一个线程然后发送binlog内容到从库。

      在从库里,当复制开始的时候,从库就会创建两个线程进行处理:

      2.从库I/O线程:当START SLAVE语句在从库开始执行之后,从库创建一个I/O线程,该线程连接到主库并请求主库发送binlog里面的更新记录到从库上。从库I/O线程读取主库的binlog输出线程发送的更新并拷贝这些更新到本地文件,其中包括relay log文件。

      3.从库的SQL线程:从库创建一个SQL线程,这个线程读取从库I/O线程写到relay log的更新事件并执行。

  12. 事务隔离级别?acid?

    • 未提交读(READ UMCOMMITTED)
      事务中的修改,即使没有提交,对其他事务也是可见的。
    • 提交读(READ COMMITTED)
      一个事务只能读取已经提交的事务所做的修改。
    • 可重复度(REPEATABLE READ)
      保证在同一事务中多次读取同样数据的结果是一样的。(默认)
    • 可串行化(SERIALIZABLE)
      强制事务串行执行。

    acid:原子性、一致性、隔离性、持久性

  13. 封锁类型?

    • 读写锁

      • 排它锁,写锁,X锁

      • 共享锁,读锁,S锁

      • 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。

        一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。

    • 意向锁

  1. Mybatis优缺点?

    优点:

    1. 与JDBC相比,减少了50%以上的代码量。

    2. MyBatis是最简单的持久化框架,小巧并且简单易学。

    3. MyBatis灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,便于统一管理和优化,可重用。

    4. 提供XML标签,支持编写动态SQL语句(XML中使用if, else)。

    5. 提供映射标签,支持对象与数据库的ORM字段关系映射(在XML中配置映射关系,也可以使用注解)。

    缺点:

    1. SQL语句的编写工作量较大,尤其是字段多、关联表多时,更是如此,对开发人员编写SQL语句的功底有一定要求。

    2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。

    Mybatis一级、二级缓存:

    一级:一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

    二级:二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存 储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置。

    对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear 掉并重新更新,如果开启了二级缓存,则只根据配置判断是否刷新。

  2. Hibernate???

  1. 封锁粒度

    MySQL 中提供了两种封锁粒度:行级锁以及表级锁。

    应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少, 发生锁争用的可能就越小,系统的并发程度就越高。但是加锁需要消耗资源,锁的各种操作(包括获取锁、释放锁、以及检查锁状态) 都会增加系统开销。因此封锁粒度越小,系统开销就越大。

  2. JAVA锁?乐观、悲观实现

    乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;悲观锁就刚好相反,觉得自 己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加 锁,只有自己把数据读完了,才允许别人修改那部分数据,或者反过来说,就是自己修改某条数 据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允 许其他用户访问那部分数据。

  3. Spring框架的优点?

    1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦 2.可以使用容易提供的众多服务,如事务管理,消息服务等 3.容器提供单例模式支持 4.容器提供了AOP技术,利用它很容易实现如权限拦截,运行期监控等功能

    • IOC控制反转,依赖注入
      当某个角色需要另外一个角色协助的时候,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在spring中创建被调用者的工作不再由调用者来完成,因此称为控制反转。创建被调用者的工作由spring来完成,然后注入调用者 。
    • AOP面向切面编程
      AOP从程序运行角度考虑程序的结构,提取业务处理过程的切面,oop是静态的抽象,aop是动态的抽象, 是对应用执行过程中的步骤进行抽象,从而获得步骤之间的逻辑划分。 权限模块、日志模块、事务模块。。。
  4. 谈一谈docker?

  5. SQl优化?

    1. 查询语句不使用select *;
    2. 尽量减少子查询,使用关联查询(left、right、inner join)代替;
    3. 减少使用IN或者NOT IN,使用EXISTS或者NOT EXIXTS代替;
    4. 尽量避免在where子句中使用 != 或 <>,否则将会放弃索引而进行全表扫描。
  6. 线程池?

  7. jenkins自动化部署?

    Jenkins是一款自包含的开源自动化服务,可用于自动执行与构建,测试和交付或部署软件有关的各种任务。

    关键就是构建触发器(访问url时自动构建项目)

  8. JVM的堆?

  9. ES?

    一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。

    为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。
    分片之所以重要,主要有两方面的原因:

    • 允许你水平分割/扩展你的内容容量
    • 允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量

    至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由Elasticsearch管理的,对于作为用户的你来说,这些都是透明的。

    在一个网络/云的环境里,失败随时都可能发生,在某个分片/节点不知怎么的就处于离线状态,或者由于任何原因消失了。这种情况下,有一个故障转移机制是非常有用并且是强烈推荐的。为此目的,Elasticsearch允许你创建分片的一份或多份拷贝,这些拷贝叫做复制分片,或者直接叫复制。复制之所以重要,主要有两方面的原因:

    • 在分片/节点失败的情况下,提供了高可用性。因为这个原因,注意到复制分片从不与原/主要(original/primary)分片置于同一节点上是非常重要的。
    • 扩展你的搜索量/吞吐量,因为搜索可以在所有的复制上并行运行

    总之,每个索引可以被分成多个分片。一个索引也可以被复制0次(意思是没有复制)或多次。一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态地改变复制数量,但是不能改变分片的数量。

    默认五个主分片和一个复制(共十个)

    倒排索引:

    倒排索引,描述了从单词到文档的关联关系。利用倒排索引,可以快速的通过单词找到和该单词关联的文档列表。倒排索引由两部分组成,一个是单词词典,一个是倒排列表。一个文档由许多的单词组成,我们交给ES的文档的越多,分析拆分后得到的单词就越多,这些单词被整理组织成一个集合,集合中的每个元素记录了单词本身的一些相关信息以及一个指定倒排列表的指针。通过单词,我们会在倒排列表中找到一个倒排项,该倒排项中,记录了出现过该单词的所有文档ID,以及单词在文档中的位置信息。也就是说,倒排列表,是倒排项的集合。倒排列表会以文件的形式,具体的存储在磁盘中的某个位置,这个文件,称之为倒排文件。实现结构:一种是哈希+链表,另一种是树。

  10. 索引?索引类型?

    索引是对数据库表中的一个或多个列的值进行排序的结构,有助于快速获取信息。

    1. 唯一索引:索引不能重复,允许空值
    2. 主键索引:唯一且不为空
    3. 组合索引:多字段的组合作为索引(最左匹配:最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配)
    4. 前缀索引:查询条件为若干字段时使用。

    聚簇索引和辅助索引:

    聚簇索引是按表的主键构造一颗B+树,叶子节点就保存了表的行记录数据 每个数据页通过一个双向链表连接 实际的数据页只能按照一个B+树进行排序,所以一张表只能拥有一个聚簇索引;辅助索引和聚簇索引结构相同,按索引列构建了一颗新的b+树, 区别是辅助索引的叶子节点不包含行记录的全部数据,而是包含了主键的值(不知道是否还有其他 信息),查到主键值后,再使用主键查聚簇索引。

    覆盖索引:SQL只需要通过索引就可以返回查询所需要的数据,而不必通过二级索引查到主键之后再去查询数据。

  11. hashcode是啥?

    Object的hashcode方法是本地方法,也就是用c或c++实现的,该方法直接返回对象的内存地址。

  12. 进程和线程的区别?多线程?几种实现多线程的方法?

    进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

    1. 继承Thread类
    2. 实现Runnable接口
    3. 实现Callable接口,通过Future Task包装器来创建Thread线程
  13. 类加载机制,双亲委派?

    类是在运行期间第一次使用时动态加载的,而不是编译时期一次性加载。因为如果 在编译时期一次性加载,那么会占用很多的内存。包括加载、验证、准备、解析、初始化。
    双亲委派:一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载。

  14. 网络七层模型以及五层模型?

    七层:物理层、数据链路层、网络层、运输层、会话层、表示层、应用层。

    五层:物理层(怎样在传输媒体上传输数据比特流)、数据链路层(为同一链路的主机提供数据传输服务)、网络层(为主机之间提供数据传输服务)、运输层(提供进程间的通用数据服务,TCP,UDP)、应用层(为特定应用程序提供数据传输服务,如HTTP)。

  15. 全局唯一id怎么实现?

    UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的 名字的标示比如文件的名字。

    数据库自增 id : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成 的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。

    利用 redis 生成 id : 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更 加复杂,可用性降低,编码更加复杂,增加了系统成本。

    Twitter的snowflake算法(64位整型) :1位标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以为0;41位时间戳部分,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年;10位节点部分,Twitter实现中使用前5位作为数据中心标识,后5位作为机器标识,可以部署1024个节点;12位序列号部分,支持同一毫秒内同一个节点可以生成4096个ID;

  16. HTTP,HTTPs?

    HTTP 有以下安全性问题:

    使用明文进行通信,内容可能会被窃听; 不验证通信方的身份,通信方的身份有可能遭遇伪装; 无法证明报文的完整性,报文有可能遭篡改。

    HTTPs 并不是新协议,而是让 HTTP 先和 SSL(Secure Sockets Layer)通信, 再由 SSL 和 TCP 通信,也就是说 HTTPs 使用了隧道进行通信。

    通过使用 SSL,HTTPs 具有了加密(防窃听)、认证(防伪装)和完整性保护 (防篡改)。

  17. 重排序和volatile、Synchronized?

    指令重排:处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。

    使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁

    1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他 线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。

    2)禁止进行指令重排序。

    Synchronized:是加锁方式同步,也就是说当如果一 个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待。

  18. == 与 equal?

    equal比较的是对象的地址,==比较的两个对象的引用(若为基本类型,则比较值)

  19. 重写equals还要重写hashcode?

    HashMap中比较key是这样的:先求出key的hashcode,比较值是否相同,再比较equal,看是否相同。若不重写equals,当比较equals时,只是看他们是否为同一对象(即内存地址的比较)

  20. 写一下单例模式?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Singleton {
    //私有的 静态的 本类属性
    private volatile static Singleton _instance;
    //私有化构造器
    private Singleton() {}
    public static Singleton getInstanceDC() {
    if (_instance == null) {//第一次检查
    synchronized (Singleton.class) {
    if (_instance == null) { //第二次检查 //线程1创建完对象后,线程会判断一次就不会创建对象了。解决了首次创建对象的唯一性问题。
    _instance = new Singleton();
    }
    }
    }
    return _instance;
    }
    }
  1. TCP和UDP?

    用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交 付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分, 只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。

    传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可 靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传 下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。

  2. TCP拥塞控制?

    ![image-20210302231749937](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210302231749937.png)

  3. 三次握手?四次挥手?

    ![image-20210302231553542](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210302231553542.png)

    ![image-20210302231627382](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210302231627382.png)

    ![image-20210302231646436](/Users/heqinyang/Library/Application Support/typora-user-images/image-20210302231646436.png)

  4. Cookie与Session

    Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据, 因此在考虑数据复杂性时首选 Session;

    Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;

    对于大型网站,如果用户所有的信息都存储在 Session 中,那么开销是非常大 的,因此不建议将所有的用户信息都存储到 Session 中。

  5. 线程安全了解吗?可以自己写一个线程安全的类吗?

    多线程中的三个核心概念:原子性(一个操作要么全部执行,要么全部不执行)、可见性(一个线程对共享变量的修改,其他线程能够立即看到)、顺序性(程序执行的顺序按照代码的先后顺序执行)。

    1. 保证原子性:使用锁(lock.lock();。。。。lock.unlock())或者同步方法。

      1
      2
      3
      4
      5
      6
      public void testLock () {
      synchronized (anyObject){
      int j = i;
      i = j + 1;
      }
      }
    2. 使用volatile保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。

    3. Java中可通过volatile在一定程序上保证顺序性,另外还可以通过synchronized和锁来保证顺序性。

  6. 读过java类库源码?讲一下???

  1. Linux I/O操作?

  1. SSO单点登录?

    在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统

    1. 用户访问app系统,app系统是需要登录的,但用户现在没有登录。

    2. 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。

    3. 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。

    4. SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。

    5. app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。

    6. 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。

  2. 秒杀遇到的问题以及解决方案?

  1. Java实现方法限流?

    接口限流实现 有一个API网关,出于对API接口的保护,需要建立一个流控功能,根据API名称, 每分钟最多只能请求指定的次数(如1000次),超过限制则这分钟内返回错误,但下一分钟又可以正常请求。

    首先:方法限流必须保证线程安全;其次:我们需要通过时间和调用频率来限制对象的使用,所以要用一个对象来存储每个apiName调用的时间和频率。如下我们用CacheValidate类来进行存储,我们采用isValidate方法判断调用次数是否超限:

  1. 缓存穿透和缓存雪崩???

    1. 缓存穿透

    一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

    如何避免?

    1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。

    2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

    1. 缓存雪崩

    当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

    如何避免?

    1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

    2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期

    3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

  2. Exception 与 Error包结构?

    java可抛出的结构分为三种类型:被检查的异常(CheckedException),运行时异常(RuntimeException),错误(Error)。

    • 运行时异常:java编译器不会检查它,当程序中可能出现这类异常时,如果既没有throws声明抛出他,也没有try-catch捕获他,还是会编译通过。如:类转换异常、数组越界、控制针。

    • 被检查的异常:java编译器会检查它,要么throws声明抛出他,要么try-catch捕获他,否则不会通过编译。如IOException、SQLEXCEPTION。

    • 错误:和运行时异常一样,编译器也不会对错误进行检查。当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。如内存泄漏、线程死亡等。

-------------本文结束感谢您的阅读-------------