欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > 名人名企 > Java面试——场景题

Java面试——场景题

2024/10/23 19:26:37 来源:https://blog.csdn.net/qq_64064246/article/details/142762574  浏览:    关键词:Java面试——场景题

1.如何分批处理数据?

1.使用LIMIT和OFFSET子句: 这是最常用的分批查询方法。例如,你可以使用以下SQL语句来分批查询数据:

SELECT * FROM your_table LIMIT 1000 OFFSET 0;

分批查询到的数据在后端进行处理,达到分批处理数据的效果。

2.使用多线程的方式: 如果你需要用多线程分批处理数据,并且数据所在表的主键id是递增的,可以使用取模的方式进行分批查询。例如:

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;public class DatabaseUtils {// 数据库连接信息private static final String URL = "jdbc:mysql://localhost:3306/your_database";private static final String USER = "your_username";private static final String PASSWORD = "your_password";// 获取数据库连接public static Connection getConnection() throws SQLException {return DriverManager.getConnection(URL, USER, PASSWORD);}// 异步查询数据库的方法//第一个参数表示偏移量,表示当前已经查询到的数据id//第二个参数表示从当前偏移量开始,查询多少条数据public static CompletableFuture<List<String>> queryBatchAsync(int offset, int limit) {// 使用CompletableFuture.supplyAsync来异步执行数据库查询return CompletableFuture.supplyAsync(() -> {List<String> results = new ArrayList<>();try (Connection conn = getConnection();PreparedStatement stmt = conn.prepareStatement("SELECT id, data FROM your_table LIMIT ? OFFSET ?")) {// 设置查询的LIMIT和OFFSETstmt.setInt(1, limit);stmt.setInt(2, offset);// 执行查询try (ResultSet rs = stmt.executeQuery()) {// 遍历结果集,将结果添加到列表中while (rs.next()) {results.add(rs.getString("id") + ": " + rs.getString("data"));}}} catch (SQLException e) {// 如果发生异常,抛出运行时异常throw new RuntimeException(e);}// 返回查询结果return results;});}
}

       这个类只是负责连接数据库,以及一个异步查询数据库的方法。注意这个方法的返回结果是CompletableFuture<List<String>>,返回一个异步任务,异步任务中的返回结果是根据偏移量和批量查询条数的查询结果,封装成一个list集合。注意数据库中的id应该是自增的

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class MultiThreadedBatchProcessing {public static void main(String[] args) {// 假设我们有1000条记录需要处理,每批处理100条记录int totalRecords = 1000;int batchSize = 100;// 创建一个有10个线程的线程池ExecutorService executor = Executors.newFixedThreadPool(10);// 创建一个CompletableFuture数组来存储每个批次的异步任务CompletableFuture<?>[] futures = new CompletableFuture[10];// 循环创建并启动每个批次的异步查询任务for (int i = 0; i < totalRecords; i += batchSize) {int offset = i; // 计算当前批次的起始位置int limit = batchSize; // 每批处理的记录数// 启动异步查询任务futures[i / batchSize] = DatabaseUtils.queryBatchAsync(offset, limit).thenAccept(batchResult -> {// 处理每个批次的结果for (String record : batchResult) {System.out.println(record);}});}// 使用CompletableFuture.allOf等待所有批次的任务完成CompletableFuture.allOf(futures).thenRun(() -> {// 所有批次处理完成后,关闭线程池System.out.println("All batches have been processed.");executor.shutdown();}).exceptionally(e -> {// 如果发生异常,打印错误信息,并尝试紧急关闭线程池System.err.println("An error occurred: " + e.getMessage());executor.shutdownNow();return null;});}
}

追问:若多线程分批查询过程中有数据插入或者删除,则数据缺漏,如何解决问题?


        使用事务保证数据一致性: 可以通过事务来确保数据的一致性。在事务中执行查询、插入或删除操作,如果中途发生错误,可以通过回滚操作来撤销所有已执行的步骤,确保数据的完整性。这样可以避免因并发操作导致的数据不一致问题。


追问:多线程共享事务存在问题,不合适,有其他方式吗?

  1. 消息队列和异步重试:在执行更新数据库和删除缓存的操作时,可以使用消息队列和异步重试机制。这样,即使某个操作失败,也可以通过消息队列进行补偿操作,确保数据的最终一致性。
  2. 分布式锁:在高并发场景下,可以使用分布式锁来保证同一时间只有一个线程能修改特定的数据行。这可以通过在应用程序层面采用分布式锁、Redis等中间件实现锁机制来完成


2.大数据外部排序

场景:

假设我们有一个包含1亿条记录的大型数据文件,这些记录存储在磁盘上,而我们的计算机内存有限,例如只能一次性处理10万条记录。

  1. 初始划分(生成初始归并段)

    • 我们从磁盘中读取10万条记录到内存中。
    • 在内存中使用常规的归并排序算法对这10万条记录进行排序,然后将排好序的这10万条记录写回磁盘,形成一个初始归并段。
    • 重复这个过程,直到整个1亿条记录都被划分成1000个初始归并段(100000000÷100000 = 1000),每个归并段包含10万条已经排好序的记录。
  2. 多路归并过程

    • 我们开始进行多路归并。假设我们采用二路归并(每次合并两个归并段)。
    • 我们从磁盘中读取两个初始归并段(每个10万条记录)到内存中的不同缓冲区。
    • 然后比较这两个缓冲区中的第一条记录,将较小的那条记录写入到一个新的临时文件(在磁盘上)。
    • 接着继续比较这两个缓冲区中的下一条记录,不断重复这个过程,直到其中一个缓冲区中的记录全部被写入到临时文件。
    • 然后将另一个缓冲区中剩余的记录也写入到临时文件,这样就完成了一次二路归并,得到了一个包含20万条记录的新归并段。
    • 重复这个二路归并过程,不断合并归并段。例如,经过一系列的二路归并,我们可以将1000个初始归并段逐步合并成500个、250个等,最终合并成一个完整的有序文件。
  3. 优化(采用多路归并)

    • 为了提高效率,我们可以采用多路归并(如五路归并)。在这种情况下,我们从磁盘中同时读取五个初始归并段到内存中的不同缓冲区。
    • 然后比较这五个缓冲区中的第一条记录,将最小的那条记录写入到一个新的临时文件。
    • 不断重复这个过程,直到这五个归并段中的所有记录都被合并到新的临时文件中,这样就完成了一次五路归并。多路归并可以减少归并的趟数,从而减少磁盘I/O的次数,提高外部排序的效率。

3.大数据中找到最大的前十条

问题:MySQL中如果是非常大的数据量,比如说要从几千万条数据中,找到最大的前十条。

Java后端开多个进程,每个进程负责一部分数据,找出这部分数据中的最大的十条数据,然后再从每个线程中的最大十条数据中找出最大的十条,就是所有数据中最大的十条数据。

注意,由于MySQL没用top(SqlServer中的)方法,所以采用 order by + limit 实现找出最大几条数据。


4.用户登录的表设计

问题:一个用户登录页面,涉及不同用户,不同用户有多个不同权限。应该设计多少张表?

        设计用户登录和权限管理系统的数据库时,通常需要考虑以下几个核心实体:用户、角色、权限,以及它们之间的关系。以下是这些实体和关系的简要说明:

  1. 用户(Users):存储用户的基本信息,如用户名、密码、邮箱等。

  2. 角色(Roles):定义不同的角色,每个角色代表一组权限。

  3. 权限(Permissions):定义具体的操作权限,如读取、写入、删除等。

  4. 用户-角色关系(User-Role):一个用户可以有多个角色,一个角色可以被多个用户拥有,这是一个多对多的关系。

  5. 角色-权限关系(Role-Permission):一个角色可以有多个权限,一个权限可以被多个角色拥有,这也是一个多对多的关系。

基于这些实体和关系,通常需要设计以下几张表:

  1. Users 表:存储用户信息。

    • 用户ID
    • 用户名
    • 密码(通常是加密存储)
    • 邮箱
    • 其他个人信息
  2. Roles 表:存储角色信息。

    • 角色ID
    • 角色名称
    • 角色描述
  3. Permissions 表:存储权限信息。

    • 权限ID
    • 权限名称
    • 权限描述
  4. User_Roles 表(用户-角色关系表):存储用户和角色的多对多关系。

    • 用户ID
    • 角色ID
  5. Role_Permissions 表(角色-权限关系表):存储角色和权限的多对多关系。

    • 角色ID
    • 权限ID

这样设计的好处是:

  • 灵活性:可以灵活地为用户分配角色,为角色分配权限,而不需要修改用户信息或角色信息。
  • 扩展性:当需要添加新的角色或权限时,不需要修改现有的用户信息,只需添加新的角色或权限记录即可。
  • 安全性:通过控制角色的权限,可以方便地管理不同用户的访问控制,而不需要直接操作用户数据。

这种设计模式通常被称为角色基于访问控制(RBAC),是实现用户权限管理的一种常见和有效的方法。


5.从大量单词中找到出现频率最高的单词

        问题:有1gb大小的文件,文件中每行是一个单词,每个单词大小不超过16kb,现在内存大小只有1mb,请问怎么找出出现频率最高的100个单词。

一、分块处理

  • 文件分块
    • 由于内存只有1MB,而文件有1GB,无法一次性将整个文件读入内存进行处理。可以将1GB的文件按照一定的大小进行分块,例如每次读取1MB(考虑到内存还要用于其他操作,可能要读取稍小于1MB的数据块)。
    • 假设我们每次读取900KB的数据块。
  • 单词计数(针对每个块)
    • 对于每个数据块,使用一个哈希表。由于每个单词大小不超过16KB,在900KB的数据块中能容纳相当数量的单词。
    • 当读取完一个数据块后,将这个数据块中的单词计数结果保存到磁盘上的临时文件中。

二、合并计数结果

  • 读取临时文件
    • 读取之前保存的各个临时文件中的单词计数结果。由于这些临时文件的大小相对较小,可以逐个读取并合并。
    • 再次使用一个哈希表来合并这些计数结果。例如,如果在一个临时文件中单词“apple”出现了5次,在另一个临时文件中出现了3次,合并后“apple”的计数就是8次。

注意这里map要统计所有的单词出现频率,因此key不能存储整个单词。具体如何实现等我再研究一下。

  • 找出高频词
    • 在合并后的哈希表中,找出出现频率最高的100个单词。可以通过对哈希表中的值(即单词出现的频率)进行排序,然后取前100个对应的单词。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com