【面试总结】八股①

目录

    • 数据库
        • 缓存穿透是什么
        • 常见的sql调优方法有哪些
        • 使用表的别名为什么能优化查询性能
        • MySQL事务特性是哪些
        • 事务隔离级别有哪些
    • Java基础
        • StringBuffer和StringBuilder的区别
        • String直接引号新建和new String新建的区别
        • Java中继承和实现的各种关系
        • HashTable和HashMap区别
    • 消息队列
    • Redis
    • 大数据相关
        • 给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。
        • 有10个文件,每个文件1G,每个文件的每一行都存放的是用户的query,每个文件的query都可能重复。如何按照query的频度排序?
        • Hadoop的备份机制,请问怎么做到数据保持一致?
    • 计算机常识
        • 缓冲击穿是什么
        • 布隆过滤器是什么
        • 进程间通信的方式有几种
        • TCP和UDP区别

数据库

缓存穿透是什么

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。解决缓存穿透的方法包括使用布隆过滤器,将可能访问的 key 存储在布隆过滤器中,不存在的 key 则直接被过滤掉,以及设置热点数据永远不过期等。

常见的sql调优方法有哪些

SQL调优的技巧和策略可以优化数据库查询性能,提高查询速度和效率。以下是一些常见的SQL调优技巧:

  • 使用索引:使用索引可以大大提高查询速度,可以通过为表创建索引来优化查询性能。
  • 避免使用having字句:having字句会在检索出所有记录之后才对结果集进行过滤,而使用WHERE字句可以在聚合前筛选记录,减少开销。
  • 使用表的别名:当在SQL语句中连接多个表时,使用表的别名并把别名前缀于每个列名上,可以优化查询性能。
  • 调整where子句中的连接顺序:DBMS一般采用自下而上的顺序解析where子句,根据这个原理,表连接最好写在其他where条件之前,可以过滤掉最大数量记录。
  • 将多条SQL语句压缩到一句SQL中:每次执行SQL的时候都要建立网络连接、进行权限校验、进行SQL语句的查询优化、发送执行结果,这个过程是非常耗时的,因此应该尽量避免过多的执行SQL语句,能够压缩到一句SQL执行的语句就不要用多条来执行。
  • 使用增量查询:使用SELECT name, age FROM user WHERE id > #{lastId} LIMIT 100;查询比上次id大的100条记录。
  • 高效分页:可以使用SELECT id, name, age FROM user LIMIT 10000, 20;查询10020条记录,然后丢弃前面10000条记录,或者使用SELECT id, name, age FROM user WHERE id > 10000 LIMIT 20;找到上次分页最大id。

使用表的别名为什么能优化查询性能

  • 表别名能够简化SQL语句,提高可读性。
  • 表别名能够缓解查询优化器在执行查询时所需的开销。在复杂的查询中,如果没有使用别名,优化器可能需要执行更多的工作来确定如何最有效地执行查询。通过使用别名,可以为优化器提供更多的信息,帮助它更有效地生成查询执行计划。
  • 表别名可以提高查询性能。在某些数据库系统(如MySQL)中,使用别名可以将表连接顺序提前确定,从而减少服务器需要解析查询表中字段及其相关值所需的时间,提高查询速度。

MySQL事务特性是哪些

MySQL事务具有以下四个特性:

  • 原子性(Atomicity):事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
  • 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
  • 隔离性(Isolation):当多个用户并发访问数据库时,比如同时操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。关于事务的隔离性数据库提供了多种隔离级别,详见《MySQL事务隔离级别和实现原理》。
  • 持久性(Durability):一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

这些特性确保了数据库的数据一致性和完整性,以及事务的可重复性和可靠性。

事务隔离级别有哪些

事务隔离级别有四种:

  • Serializable (串行化):这是最严格的隔离级别,事务串行执行,资源消耗最大。
  • Repeatable Read (重复读):保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
  • Read Committed (提交读):大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。
  • Read Uncommitted (未提交读):事务中的修改,即使没有提交,其他事务也可以看得到,会导致“脏读”、“幻读”和“不可重复读取”。

这四种隔离级别是逐渐降低的,更高的隔离级别可以提供更多的数据一致性保证,但同时也会牺牲更多的性能。选择哪个隔离级别通常需要根据具体的应用需求和性能要求来决定。

Java基础

StringBuffer和StringBuilder的区别

  StringBuffer和StringBuilder都是Java中用来处理字符串的类,它们的主要区别在于是否线程安全。StringBuffer是线程安全的,而StringBuilder不是。这意味着在多线程环境中,使用StringBuffer更加安全,因为它可以防止数据被并发修改。另一方面,由于StringBuilder不是线程安全的,所以它的性能通常会比StringBuffer更好。以下是StringBuffer和StringBuilder之间其他一些重要的区别:

  • 缓冲区大小:StringBuffer的缓冲区大小是固定的,而StringBuilder的缓冲区大小则是动态的。
  • 效率:对于大量的字符串操作,StringBuilder比StringBuffer更有效率,因为StringBuffer的所有方法都是同步的,这会在多线程环境下增加额外的开销。
  • 适用场景:如果程序是多线程的,或者你需要频繁地修改字符串,那么使用StringBuffer更好。如果你只是进行单线程的字符串操作,或者需要频繁地创建和修改字符串,那么使用StringBuilder会更高效。

String直接引号新建和new String新建的区别

  使用双引号定义字符串和使用new关键字创建字符串对象有一些区别,主要表现在内存分配和字符串池的使用上。

  • 内存分配:当你使用双引号定义字符串时,如 String s = “abc”,Java会在字符串缓冲池中查找是否有一个存储着相同内容的字符串的对象,如果存在,那么Java就会返回该对象,如果不存在,就会在堆内存中创建一个新的字符串对象,并将该对象放入字符串缓冲池中。而当你使用new关键字创建字符串对象时,如 String s = new String(“abc”),Java会在堆内存中创建一个新的字符串对象,不论字符串缓冲池中是否存在相同内容的字符串对象。
  • 字符串池的使用:使用双引号定义字符串时,Java会先在字符串池中查找是否存在相同的内容,如果存在就直接返回地址,如果不存在则在堆中创建一个新的对象,然后在字符串池中添加这个新的对象。使用new关键字创建字符串时,每次都会在堆内存中创建一个新的对象。
      总的来说,使用双引号创建字符串更有效率,因为它能共享相同内容的对象。而使用new关键字创建的每个字符串对象都会占用独立的内存空间。因此,建议在实际编程中尽可能使用双引号来定义字符串。

Java中继承和实现的各种关系

  在 Java 中,一个类只能继承一个类,这被称为单继承。但是,Java支持多重继承,即一个类可以实现多个接口,一个类可以实现任意数量的接口。
  接口可以继承接口,抽象类可以实现接口,但不可以继承接口。接口可以继承多个接口public interface C extends A, B {}。抽象类可以实现(implements)一个或多个接口public abstract class MyClass implements A, B {}。抽象类不能继承一个具体的类,但可以继承一个抽象类或实现一个接口public abstract class MyClass extends AbstractClass implements A {}
  抽象类中可以对方法进行实现。从Java 8开始,接口也可以定义默认方法和静态方法。抽象类可以实现方法,但这些方法只能被抽象类本身和其非抽象子类使用。

// 定义一个接口,其中包含默认方法和静态方法  
public interface MyInterface {  
    // 默认方法  
    default String defaultMethod() {  
        return "This is a default method in the interface.";  
    }  
      
    // 静态方法  
    static String staticMethod() {  
        return "This is a static method in the interface.";  
    }  
}  
  
// 实现接口的类  
public class MyClass implements MyInterface {  
    public static void main(String[] args) {  
        // 调用接口的默认方法  
        System.out.println(MyInterface.defaultMethod());  
          
        // 调用接口的静态方法  
        System.out.println(MyInterface.staticMethod());  
    }  
}

// 定义一个抽象类,其中包含一个抽象方法和一个具体方法  
public abstract class MyAbstractClass {  
    // 抽象方法  
    public abstract void abstractMethod();  
      
    // 具体方法  
    public void concreteMethod() {  
        System.out.println("This is a concrete method in the abstract class.");  
    }  
}  
  
// 实现抽象类的类  
public class MyClass extends MyAbstractClass {  
    // 实现抽象方法  
    @Override  
    public void abstractMethod() {  
        System.out.println("This is the implementation of the abstract method in the subclass.");  
    }  
}

HashTable和HashMap区别

HashTable和HashMap都用于在Java中实现Map接口,但它们之间有一些区别。HashTable是线程安全的,而HashMap不是。这意味着HashTable在多线程环境下是安全的,而HashMap在多线程环境下可能会出现错误的结果。

  • HashTable不允许null键和null值,而HashMap允许。
  • HashTable中的元素是无序的,而HashMap中的元素是按照插入顺序排列的。
  • HashTable的使用比HashMap慢,因为线程安全需要在插入、删除、查找等操作中进行额外的同步开销。

总的来说,如果不需要考虑线程安全,建议使用HashMap,因为它比HashTable更快。如果需要线程安全,可以考虑使用ConcurrentHashMap,它是Java中最常用的线程安全的Map实现。想要线程安全的HashMap有三种方法:(1)ConcurrentHashMap ; (2) HashTable ; (3) Collections.synchronizedHashMap()方法

消息队列

Redis

大数据相关

给定 a、b 两个文件,各存放 50 亿个 URL,每个 URL 各占 64B,内存限制是 4G。请找出 a、b 两个文件共同的 URL。

  每个 URL 占 64B,那么 50 亿个 URL占用的空间大小约为 320GB。5,000,000,000 * 64B ≈ 5GB * 64 = 320GB。由于内存大小只有 4G,因此,不可能一次性把所有 URL 加载到内存中处理。
  对于这种类型的题目,一般采用分治策略,即:把一个文件中的 URL 按照某个特征划分为多个小文件,使得每个小文件大小不超过 4G,这样就可以把这个小文件读到内存中进行处理了。
  思路如下:
  首先遍历文件 a,对遍历到的 URL 求 hash(URL) % 1000,根据计算结果把遍历到的 URL 存储到 a0, a1, a2, …, a999,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b0, b1, b2, …, b999 中。
  这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a0 对应 b0, …, a999 对应 b999,不对应的小文件不可能有相同的 URL。那么接下来,只需要求出这 1000 对小文件中相同的 URL 就好了。
  接着遍历 ai ( i∈[0,999]),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
  方法总结:分而治之,进行哈希取余;对每个子文件进行 HashSet 统计。

有10个文件,每个文件1G,每个文件的每一行都存放的是用户的query,每个文件的query都可能重复。如何按照query的频度排序?

  • 方案1: 顺序读取10个文件,按照hash(query)%10的结果将query写入到另外10个文件(记为)中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。 找一台内存在2G左右的机器,依次对用 hash_map(query, query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的 query 和对应的 query_cout 输出到文件中。这样得到了10个排好序的文件(记为)。对这10个文件进行归并排序(内排序与外排序相结合)。
  • 方案2: 一般query的总量是有限的,只是重复的次数比较多而已,可能对于所有的query,一次性就可以加入到内存了。这样,我们就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。
  • 方案3: 与方案1类似,但在做完hash,分成多个文件后,可以交给多个文件来处理,采用分布式的架构来处理(比如MapReduce),最后再进行合并。

Hadoop的备份机制,请问怎么做到数据保持一致?

  Hadoop备份机制:该方案利用Hadoop自身的Flover措施(通过配置dfs.name.dir),NameNode可以将元数据信息保存到多个目录。通常的做法,选择一个本地目录、一个远程目录(通过NFS进行共享),当NameNode发生故障时,可以启动备用机器的NameNode,加载远程目录中的元数据信息,提供服务。
  Hadoop中有个重要的工具就是HDFS分布式文件系统,那么在这种分布式环境下的数据一致性是如何保证呢? HDFS中,存储的文件将会被分成若干的大小一致的block(最后一个block的大小可能较小)分布式地存储在不同的机器上,那么就必须有一个角色来对这些数据进行管理,也就是NameNode节点,而存储这些block的结点我们称为DataNode,NameNode是用来管理这些元数据的。
  下面讲一个例子,在客户端上传文件时,NameNode如何保证数据的一致性。客户端上传文件时,NameNode首先往 edits log 文件中记录元数据的操作日志。与此同时,NameNode将会在磁盘做一份持久化处理( fsimage文件):他跟内存中的数据是对应的,如何保证和内存中的数据的一致性呢?在edits logs满之前对内存和fsimage的数据做同步(实际上只需要合并edits logs 和 fsimage 上的数据即可,然后 edits logs 上的数据即可清除) 而当 edits logs满之后,文件的上传不能中断,所以将会往一个新的文件edits.new上写数据,而老的edits logs的合并操作将由 secondNameNode 来完成,即所谓的 checkpoint 操作。
  那么什么时候checkpoint呢? 一般由两种限制,一个是edits logs的大小限制,即 fs.checkpoint.size 配置,一个是指定时间,即fs.checkpoint.period配置 当然根据规定,大小的限制是优先的,规定edits文件一旦超过阈值,则不管是否达到最大时间间隔,都会强制checkpoint。

计算机常识

缓冲击穿是什么

缓冲击穿是电容器的故障现象之一,指的是电容器在运行过程中,由于长时间超过额定电压,导致电容器内部绝缘材料性能下降,电容器内部出现击穿短路的现象。为了避免这种情况发生,电容器通常会采用串联电抗器或放电电阻等保护装置,以限制电流和电压的幅值,避免电容器因过电压而损坏。

布隆过滤器是什么

布隆过滤器(Bloom Filter)是一种很长的二进制向量和一系列随机映射函数。它用于检索一个元素是否在一个集合中。布隆过滤器的主要优点是空间效率和查询时间都比一般的算法要好的多。它的主要缺点是有一定的误识别率和删除困难。

进程间通信的方式有几种

进程间通信的八种方式:

  • 管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。它分为无管道单工通信方式和有管道单工通信方式两种。此外,无名管道(pipe)也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  • 高级管道(pipe):高级管道是一种半双工的通信方式,数据只能单向流动,但是它可以在不同进程间传递。它分为发送端和接收端两个部分,通过将发送端和接收端连接起来实现通信。
  • 信号(signal):信号是一种比较复杂的通信方式,它允许一个进程向另一个进程发送一个信号,通知该进程某个事件已经发生。接收到信号的进程可以根据信号的类型进行处理。
  • 信号量(semaphore):信号量是一个计数器,可以用来控制多个线程对共享资源的访问,因此主要用于进程间或线程的同步。
  • 消息队列(message queue):消息队列是一种消息的链表,存放在内核中并由消息队列标识符标识。它克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点,可以进行大量数据的传输。
  • 套接字(socket):套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
  • 共享内存(shared memory):共享内存是一种高效的进程间通信方式。多个进程可以通过共享内存来读写同一块物理内存空间,从而实现高效的通信。共享内存必须在具有亲缘关系的进程间使用。
  • 消息信箱(message box):消息信箱是一种可以进行跨进程通信的数据结构。一个进程可以发送消息到消息信箱,而另一个进程可以从该消息信箱中接收消息。

TCP和UDP区别

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol)是两种互联网协议,它们都用于在网络上传输数据,但是它们之间有一些显著的区别,如下:

  • 连接方式:TCP是面向连接的协议,每次通信前需要建立连接,数据传输完后需要释放连接;UDP则是无连接的协议,不需要建立和释放连接,直接发送数据包。
  • 可靠性:TCP是可靠的协议,能够保证数据传输的可靠性。当数据丢失或损坏时,TCP会自动重传丢失的数据,保证数据的完整性;UDP则是不可靠的协议,数据包在传输过程中有可能丢失、重复或损坏,不保证数据传输的可靠性。
  • 传输速度:UDP比TCP传输速度更快,因为UDP没有TCP的重传机制、流量控制机制和拥塞控制机制,减少了额外的开销。
  • 数据包大小:TCP数据包大小不限,可以根据网络状况动态调整;UDP的数据包大小有限制,最大只能达到64K。
  • 应用场景:TCP适用于对数据可靠性要求较高的场景,如文件传输、邮件发送等;UDP适用于对实时性要求较高的场景,如音频、视频等实时传输。

你可能感兴趣的:(Java相关,#,面试八股,面试,oracle,数据库)