Java面试黄金宝典32

1. 什么是存储过程,触发器

 

存储过程

  • 定义:存储过程是一组为了完成特定功能而预先编译好的 SQL 语句集,它被存储在数据库中,用户可以通过指定存储过程的名称并传递相应参数(如果有)来执行它。存储过程可以包含逻辑判断、循环等流程控制语句,能有效减少网络通信量,提升执行效率。
  • 要点
    • 封装性:将复杂的业务逻辑封装起来,提高代码的复用性。
    • 性能:经过预编译和缓存,执行速度更快。
    • 安全性:可以设置用户对存储过程的执行权限,增强数据安全性。
  • 应用:在企业级应用中,常用于处理复杂的业务逻辑,如财务系统的报表生成、订单处理等。不同的数据库系统提供了各自的存储过程语言,例如 MySQL 的 DELIMITER 语法,SQL Server 的 T - SQL 等。
  • 代码示例(MySQL)

sql

-- 创建一个存储过程,用于查询客户数量
DELIMITER //
CREATE PROCEDURE GetCustomerCount()
BEGIN
    SELECT COUNT(*) FROM customers;
END //
DELIMITER ;

-- 调用存储过程
CALL GetCustomerCount();

触发器

  • 定义:触发器是一种特殊的存储过程,它会在某个表执行特定操作(如 INSERT、UPDATE、DELETE)之前或之后自动执行。触发器主要用于实现数据的完整性约束、日志记录等功能。
  • 要点
    • 自动执行:无需手动调用,会在相应的表操作触发时自动执行。
    • 关联表操作:与特定表的操作紧密绑定。
    • 数据一致性:有助于保证数据的完整性和一致性。
  • 应用:常用于审计追踪,比如记录用户对数据的修改操作;还可用于实现数据的级联更新和删除等。
  • 代码示例(MySQL)

sql

-- 创建一个触发器,在插入新客户记录后,向日志表插入一条记录
DELIMITER //
CREATE TRIGGER after_insert_customer
AFTER INSERT ON customers
FOR EACH ROW
BEGIN
    INSERT INTO customer_log (action, customer_id) VALUES ('INSERT', NEW.customer_id);
END //
DELIMITER ;

2. B + 树和 B 树的区别 插入节点怎么分裂

 

区别

  • 定义
    • B 树:是一种自平衡的多路搜索树,每个节点可以包含多个键和子节点,键和数据可以存储在所有节点中,查询操作可能在非叶子节点就结束。
    • B + 树:是 B 树的一种变体,它的所有数据都存储在叶子节点,非叶子节点只存储索引信息,所有查询都必须到达叶子节点,并且叶子节点之间有指针相连,便于进行范围查询。
  • 要点
    • B 树:键和数据存储在所有节点,查询可能提前结束。
    • B + 树:数据仅在叶子节点,范围查询效率高。
  • 应用:数据库索引大多采用 B + 树,因为它更适合范围查询和磁盘读写;B 树在文件系统等场景有一定的应用。

插入节点分裂

  • B 树:当向一个节点插入一个键时,如果该节点的键数量超过了最大限制,节点就会分裂成两个节点。中间的键会被提升到父节点,左右两部分分别形成新的子节点。
  • B + 树:插入时,如果叶子节点的键数量超过最大限制,叶子节点会分裂成两个节点,中间的键会被复制到父节点,同时调整叶子节点间的指针。
  • 代码示例:由于 B 树和 B + 树的实现较为复杂,通常由数据库系统内部完成,以下是一个简单的 Java 示例来模拟 B 树节点分裂的基本逻辑:

java

import java.util.ArrayList;
import java.util.List;

class BTreeNode {
    List<Integer> keys;
    List<BTreeNode> children;
    boolean isLeaf;

    public BTreeNode(boolean isLeaf) {
        this.isLeaf = isLeaf;
        keys = new ArrayList<>();
        children = new ArrayList<>();
    }
}

public class BTreeSplitExample {
    private static final int MIN_DEGREE = 3;

    public static void splitChild(BTreeNode parent, int index, BTreeNode child) {
        BTreeNode newNode = new BTreeNode(child.isLeaf);
        parent.children.add(index + 1, newNode);
        parent.keys.add(index, child.keys.get(MIN_DEGREE - 1));

        for (int i = 0; i < MIN_DEGREE - 1; i++) {
            newNode.keys.add(child.keys.get(i + MIN_DEGREE));
        }
        child.keys.subList(MIN_DEGREE - 1, child.keys.size()).clear();

        if (!child.isLeaf) {
            for (int i = 0; i < MIN_DEGREE; i++) {
                newNode.children.add(child.children.get(i + MIN_DEGREE));
            }
            child.children.subList(MIN_DEGREE, child.children.size()).clear();
        }
    }
}

3. 有人建议给每张表都建一个自增主键, 这样做有什么优点跟缺点

 

优点

  • 定义:自增主键是一个自动递增的整数,在每次插入新记录时,数据库会自动为其分配一个唯一的值。
  • 要点
    • 唯一性:能够确保每行记录都有一个唯一的标识。
    • 性能:索引效率较高,插入数据时无需额外计算主键值。
    • 简化设计:无需考虑业务逻辑来生成唯一键,降低设计复杂度。
  • 应用:在分布式系统中,可以结合数据库自增主键或使用分布式 ID 生成器。
  • 代码示例(MySQL)

sql

-- 创建一个带有自增主键的表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50),
    age INT
);

-- 插入记录,无需指定主键值
INSERT INTO users (name, age) VALUES ('John', 25);

缺点

  • 定义:自增主键依赖数据库自身的机制,可能存在一些局限性。
  • 要点
    • 可预测性:容易被猜到记录的顺序和数量,存在一定的安全隐患。
    • 数据迁移问题:不同数据库的自增主键机制可能不同,在数据迁移时可能会出现问题。
    • 并发插入性能:在高并发情况下,自增主键可能成为性能瓶颈。
  • 应用:对于一些对安全性要求较高的系统,不适合使用自增主键。

 

4. 如何理解 group by

 

  • 定义GROUP BY 是 SQL 中的一个子句,用于将查询结果按照一个或多个列进行分组。分组之后,可以对每个组应用聚合函数(如 SUM、AVG、COUNT 等)进行统计计算。
  • 要点
    • 分组依据:根据指定列的值将相同的记录分为一组。
    • 聚合函数:对每个组进行统计计算。
    • 筛选分组:可以结合 HAVING 子句对分组结果进行筛选。
  • 应用:在数据分析、报表生成等场景中广泛应用,还可以结合 ORDER BY 对分组结果进行排序。
  • 代码示例(SQL)

sql

-- 按照部门分组,统计每个部门的员工数量
SELECT department, COUNT(*) as employee_count
FROM employees
GROUP BY department;

 

5. mybatis 中 % 与 $ 的区别

 

  • 定义:在 MyBatis 中,# 和 $ 是两种不同的参数占位符。# 会将参数进行预编译处理,能有效防止 SQL 注入;$ 会直接将参数值替换到 SQL 语句中。
  • 要点
    • #:安全性高,适合传入普通参数。
    • $:存在 SQL 注入风险,但可用于动态传入表名、列名等。
  • 应用:在实际开发中,应尽量使用 #,只有在必要时才使用 $ 并做好参数验证。
  • 代码示例(MyBatis XML)

xml

<select id="getUserById" parameterType="int" resultType="User">
    -- 使用 # 占位符,预编译处理
    SELECT * FROM users WHERE id = #{id}
</select>

<select id="getUserByColumnName" parameterType="String" resultType="User">
    -- 使用 $ 占位符,直接替换参数值
    SELECT * FROM users ORDER BY ${columnName}
</select>

 

6. 一个成绩表求出有不及格科目的同学

 

  • 定义:假设成绩表 scores 包含 student_id 和 score 字段,通过查询找出成绩小于 60 分的学生。
  • 要点:使用 WHERE 子句筛选成绩小于 60 分的记录,使用 DISTINCT 去除重复的学生 ID。
  • 应用:可结合其他表进行多表查询,如关联学生信息表获取学生的详细信息。
  • 代码示例(SQL)

sql

-- 查询有不及格科目的学生 ID
SELECT DISTINCT student_id
FROM scores
WHERE score < 60;

 

7. 500 万数字排序, 内存只能容纳 5 万个, 如何排序, 如何优化?

 

排序方法

  • 定义:采用外部排序算法,如归并排序。将 500 万数字分成 100 个块,每次读入 5 万个数字到内存中进行排序,然后将排序好的块写回磁盘。最后进行多路归并操作,将所有有序块合并成一个有序序列。
  • 要点
    • 分块排序:将大数据分成小的数据块进行排序。
    • 归并操作:将多个有序块合并成一个有序序列。
  • 应用:可使用多线程并行处理每个块的排序,提高排序效率。
  • 代码示例:以下是一个简单的 Java 示例,模拟外部排序的基本过程:

java

import java.io.*;
import java.util.*;

public class ExternalSorting {
    private static final int CHUNK_SIZE = 50000;

    public static void externalSort(String inputFile, String outputFile) throws IOException {
        List<String> tempFiles = splitAndSort(inputFile);
        mergeFiles(tempFiles, outputFile);
        deleteTempFiles(tempFiles);
    }

    private static List<String> splitAndSort(String inputFile) throws IOException {
        List<String> tempFiles = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(inputFile))) {
            List<Integer> chunk = new ArrayList<>();
            String line;
            while ((line = reader.readLine()) != null) {
                chunk.add(Integer.parseInt(line));
                if (chunk.size() == CHUNK_SIZE) {
                    String tempFileName = createTempFile(chunk);
                    tempFiles.add(tempFileName);
                    chunk.clear();
                }
            }
            if (!chunk.isEmpty()) {
                String tempFileName = createTempFile(chunk);
                tempFiles.add(tempFileName);
            }
        }
        return tempFiles;
    }

    private static String createTempFile(List<Integer> chunk) throws IOException {
        Collections.sort(chunk);
        String tempFileName = UUID.randomUUID().toString();
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFileName))) {
            for (int num : chunk) {
                writer.write(String.valueOf(num));
                writer.newLine();
            }
        }
        return tempFileName;
    }

    private static void mergeFiles(List<String> tempFiles, String outputFile) throws IOException {
        PriorityQueue<FileEntry> pq = new PriorityQueue<>();
        List<BufferedReader> readers = new ArrayList<>();

        for (String tempFile : tempFiles) {
            BufferedReader reader = new BufferedReader(new FileReader(tempFile));
            String line = reader.readLine();
            if (line != null) {
                pq.add(new FileEntry(Integer.parseInt(line), reader));
            }
            readers.add(reader);
        }

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
            while (!pq.isEmpty()) {
                FileEntry entry = pq.poll();
                writer.write(String.valueOf(entry.value));
                writer.newLine();
                String nextLine = entry.reader.readLine();
                if (nextLine != null) {
                    pq.add(new FileEntry(Integer.parseInt(nextLine), entry.reader));
                }
            }
        }

        for (BufferedReader reader : readers) {
            reader.close();
        }
    }

    private static void deleteTempFiles(List<String> tempFiles) {
        for (String tempFile : tempFiles) {
            new File(tempFile).delete();
        }
    }

    static class FileEntry implements Comparable<FileEntry> {
        int value;
        BufferedReader reader;

        public FileEntry(int value, BufferedReader reader) {
            this.value = value;
            this.reader = reader;
        }

        @Override
        public int compareTo(FileEntry other) {
            return Integer.compare(this.value, other.value);
        }
    }

    public static void main(String[] args) throws IOException {
        externalSort("input.txt", "output.txt");
    }
}

优化

  • 定义:通过减少磁盘 I/O 次数和提高内存利用率来优化排序过程。
  • 要点
    • 增加内存:使用更大的内存缓冲区,减少磁盘读写次数。
    • 并行处理:使用多线程或多进程并行进行排序和归并操作。
    • 优化归并算法:采用更高效的多路归并算法。

 

8. 平时怎么写数据库的模糊查询

 

  • 定义:数据库模糊查询通过通配符来实现,常见的通配符有 %(匹配任意数量的任意字符)和 _(匹配单个任意字符)。
  • 要点
    • LIKE 关键字:用于指定模糊查询条件。
    • 通配符位置:通配符的位置会影响查询范围和性能。
  • 应用:在 MySQL 中,可以使用 REGEXP 进行正则表达式模糊查询。
  • 代码示例(SQL)

sql

-- 查询以 'abc' 开头的产品记录
SELECT * FROM products WHERE product_name LIKE 'abc%';
-- 查询包含 'abc' 的产品记录
SELECT * FROM products WHERE product_name LIKE '%abc%';

 

9. 数据库里有 10000000 条用户信息, 需要给每位用户发送信息(必须发送成功) , 要求节省内存,具体如何实现

 

  • 定义:采用分批处理的方式,每次从数据库中取出一定数量的用户信息,处理完后释放内存,再取下一批数据,确保每条信息都能成功发送。
  • 要点
    • 分页查询:使用 LIMIT 和 OFFSET 进行分页查询。
    • 事务处理:确保每条信息发送成功,可使用数据库事务。
    • 资源释放:及时释放数据库连接和内存资源。
  • 应用:可使用消息队列异步处理信息发送,提高系统的吞吐量。
  • 代码示例(Java + JDBC)

java

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class UserMessageSender {
    private static final int BATCH_SIZE = 1000;

    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "root";
        String password = "password";
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            int offset = 0;
            while (true) {
                String sql = "SELECT user_id, email FROM users LIMIT " + BATCH_SIZE + " OFFSET " + offset;
                try (Statement stmt = conn.createStatement();
                     ResultSet rs = stmt.executeQuery(sql)) {
                    if (!rs.next()) {
                        break;
                    }
                    do {
                        int userId = rs.getInt("user_id");
                        String email = rs.getString("email");
                        // 发送信息逻辑
                        sendMessage(email);
                    } while (rs.next());
                }
                offset += BATCH_SIZE;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void sendMessage(String email) {
        // 实现信息发送逻辑
        System.out.println("Sending message to: " + email);
    }
}

 

10. 如何进行数据库设计与优化

 

数据库设计

  • 定义:根据业务需求,设计数据库的结构,包括表、字段、关系等,遵循数据库设计的范式,以确保数据的一致性和完整性。
  • 要点
    • 需求分析:明确业务需求和数据流程。
    • 概念设计:绘制 E - R 图,确定实体和关系。
    • 逻辑设计:将 E - R 图转换为关系模型。
    • 物理设计:选择合适的数据库管理系统和存储引擎,设计表结构和索引。
  • 应用:考虑数据库的扩展性和性能,为未来业务发展预留空间。

数据库优化

  • 定义:通过优化数据库结构、查询语句、索引等方式,提高数据库的性能和响应速度。
  • 要点
    • 索引优化:合理创建索引,避免过多或不必要的索引。
    • 查询优化:优化 SQL 语句,避免全表扫描和复杂子查询。
    • 表结构优化:合理设计表结构,避免数据冗余。
    • 数据库配置优化:调整数据库的参数配置,如内存分配、缓存大小等。
  • 应用:定期进行数据库性能监测和分析,根据监测结果进行优化调整。

 友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读

https://download.csdn.net/download/ylfhpy/90562306

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值