• 学习JDBC总结


    目录

    JDBC程序编写步骤

    获取数据库连接的5种方式

    第一种方式:静态加载,灵活性差,依赖性强

    第二种方式:使用反射机制

    第三种方式:使用DriverManager 替代 driver 进行统一管理

    第四种方式:使用Class.forName 自动完成注册驱动,简化代码

    一定要看

    第五种方式:改进方式4,添加配置文件

    ResultSet

    Statement

    什么是SQL注入?

    PreparedStatement

    注意

    封装JDBCUtils(第一代)

    事务

    批处理

    数据库连接池

    连接池

    C3P0

    Druid

    C3P0(配置文件)与Druid对比

    利用德鲁伊改进工具类

    Apache——DBUtils


    JDBC程序编写步骤

    1. 注册驱动-加载Driver类

    2. 获取连接-得到Connection

    3. 执行增删改查-发送SQL给mysql执行

    4. 释放资源-关闭相关连接

    获取数据库连接的5种方式

    第一种方式:静态加载,灵活性差,依赖性强

    1. Driver driver = new Driver(); // 创建driver对象
    2. String url = "jdbc:mysql://localhost:3306/cs_db02";
    3. //将 用户名和密码放入到Properties 对象
    4. Properties properties = new Properties();
    5. //说明 user 和 password 是规定好,后面的值根据实际情况写
    6. properties.setProperty("user", "root");// 用户
    7. properties.setProperty("password", "xxx");// 密码
    8. Connection connect = driver.connect(url, properties);
    9. System.out.println(connect);

    第二种方式:使用反射机制

    1. //使用反射加载Driver类,动态加载,更加的灵活,减少依赖性
    2. Class aClass = Class.forName("com.mysql.jdbc.Driver");
    3. Driver driver = (Driver) aClass.newInstance();
    4. String url = "jdbc:mysql://localhost:3306/cs_db02";
    5. //将 用户名和密码放入到Properties 对象
    6. Properties properties = new Properties();
    7. //说明 user 和 password 是规定好,后面的值根据实际情况写
    8. properties.setProperty("user", "root");// 用户
    9. properties.setProperty("password", "xxx");// 密码
    10. Connection connect = driver.connect(url, properties);
    11. System.out.println("方式2=" + connect);

    使用反射加载Driver类,动态加载,更加的灵活,减少依赖性

    第三种方式:使用DriverManager 替代 driver 进行统一管理

    1. //使用反射加载Driver
    2. Class aClass = Class.forName("com.mysql.jdbc.Driver");
    3. Driver driver = (Driver) aClass.newInstance();
    4. //创建url 和 user 和 password
    5. String url = "jdbc:mysql://localhost:3306/cs_db02";
    6. //将 用户名和密码放入到Properties 对象
    7. String user = "root";
    8. String password = "xxx";
    9. DriverManager.registerDriver(driver);//注册Driver驱动
    10. Connection connection = DriverManager.getConnection(url, user, password);
    11. System.out.println("方式3=" + connection);

    第四种方式:使用Class.forName 自动完成注册驱动,简化代码

    这种方式获取连接时使用最多的

    1. Class.forName("com.mysql.jdbc.Driver");
    2. //创建url 和 user 和 password
    3. String url = "jdbc:mysql://localhost:3306/cs_db02";
    4. String user = "root";
    5. String password = "xxx";
    6. Connection connection = DriverManager.getConnection(url, user, password);
    7. System.out.println("方式3=" + connection);

    一定要看

    //使用反射加载了 Driver类
    //在加载 Driver类时,完成注册
    /*
        源码:1. 静态代码块,在类加载时,会执行一次
        2. DriverManager.registerDriver(new Driver());
        3. 因此注册driver的用作已经完成
        static {
            try {
                DriverManager.registerDriver(new Driver());
            } catch (SQLException var1) {
                throw new RuntimeException("Can't register driver!");
            }
        }
     */

    Driver源码

    1. //
    2. // Source code recreated from a .class file by IntelliJ IDEA
    3. // (powered by FernFlower decompiler)
    4. //
    5. package com.mysql.jdbc;
    6. import java.sql.DriverManager;
    7. import java.sql.SQLException;
    8. public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    9. public Driver() throws SQLException {
    10. }
    11. static {
    12. try {
    13. DriverManager.registerDriver(new Driver());
    14. } catch (SQLException var1) {
    15. throw new RuntimeException("Can't register driver!");
    16. }
    17. }
    18. }

    不用写下面代码也可以执行

     

    Class.forName("com.mysql.jdbc.Driver");

     

    我使用的版本

    第五种方式:改进方式4,添加配置文件

    1. //通过Properties对象获取配置文件的信息
    2. Properties properties = new Properties();
    3. properties.load(new FileInputStream("src\\mysql.properties"));
    4. String user = properties.getProperty("user");
    5. String password = properties.getProperty("password");
    6. String driver = properties.getProperty("driver");
    7. String url = properties.getProperty("url");
    8. Class.forName(driver);
    9. Connection connection = DriverManager.getConnection(url, user, password);
    10. System.out.println("方式5=" + connection);

    配置文件位置

     

    1. user=root
    2. password=xxx
    3. url=jdbc:mysql://localhost:3306/cs_db02?rewriteBatchedStatements=true
    4. driver=com.mysql.jdbc.Driver

    ResultSet

     

    debug找到数据存储再ResultSet的位置

     

    实例

    1. //通过Properties对象获取配置文件的信息
    2. Properties properties = new Properties();
    3. properties.load(new FileInputStream("src\\mysql.properties"));
    4. String user = properties.getProperty("user");
    5. String password = properties.getProperty("password");
    6. String driver = properties.getProperty("driver");
    7. String url = properties.getProperty("url");
    8. //1. 注册驱动
    9. Class.forName(driver);
    10. //2. 得到连接
    11. Connection connection = DriverManager.getConnection(url, user, password);
    12. System.out.println("方式5=" + connection);
    13. //3. 得到Statement
    14. Statement statement = connection.createStatement();
    15. //4. 组织SQL
    16. String sql = "select id, name, sex, borndate from actor";
    17. //执行给定的SQL语句,该语句返回单个 ResultSet对象
    18. ResultSet resultSet = statement.executeQuery(sql);
    19. //5. 使用while取出数据
    20. while (resultSet.next()) {// 让光标向后移动,如果没有更多行,则返回false
    21. int id = resultSet.getInt(1);//获取改行改行的第1列数据
    22. String name = resultSet.getString(2);//获取到改行的第2列
    23. String sex = resultSet.getString(3);//获取到改行的第3列
    24. String date = resultSet.getString(4);//获取到改行的第4列
    25. System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
    26. }
    27. //6. 关闭连接
    28. resultSet.close();
    29. statement.close();
    30. connection.close();

    Statement

     

    什么是SQL注入?

    SQL 注入(SQL Injection) 是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。 主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。

    简单的案例

    1. -- SQL
    2. -- 输入用户名 为1' or
    3. -- 输入密码 为 or '1'='1
    4. SELECT *
    5. FROM admin
    6. WHERE `name` = '1' OR' AND pwd = 'OR '1'='1';

    输入这样的用户名和密码可以直接绕过检查,这是早期黑客使用的SQL注入。

    测试一下

     

    当然现在的技术早就预防了这种简单的SQL注入

    下一个知识点就是使用PreparedStatement预防SQL注入

    PreparedStatement

     

    类图

     

     使用PreparedStatement会对其预处理不用,最后和整个程序一起编译

     

    注意

    如果放进去还是会有SQL注入的,所以一定不要放

     

     

    封装JDBCUtils(第一代)

    将相同的步骤封装到一个类中,调用时直接使用其方法。

    1. public class JDBCUtils {
    2. //定义相关的属性(4个),因为只需要一份,因此,我们做出static
    3. private static String user; //用户名
    4. private static String password; //密码
    5. private static String url; //url
    6. private static String driver; //驱动名
    7. //在static代码块去初始化
    8. static {
    9. Properties properties = new Properties();
    10. try {
    11. properties.load(new FileInputStream("src\\mysql.properties"));
    12. //读取相关的属性
    13. user = properties.getProperty("user");
    14. password = properties.getProperty("password");
    15. url = properties.getProperty("url");
    16. driver = properties.getProperty("driver");
    17. } catch (IOException e) {
    18. //在实际开发中,我们可以这样处理
    19. //1. 将编译异常转成运行异常
    20. //2. 这里调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便
    21. //
    22. throw new RuntimeException(e);
    23. }
    24. }
    25. //连接数据库,返回Connection
    26. public static Connection getConnection() {
    27. try {
    28. return DriverManager.getConnection(url, user, password);
    29. } catch (SQLException e) {
    30. throw new RuntimeException(e);
    31. }
    32. }
    33. //关闭相关资源
    34. /*
    35. 1. ResultSet 结果集
    36. 2. Statement 或者 PreparedStatement
    37. 3. Connection
    38. 4. 如果需要关闭资源,就传入对象,否则传入空
    39. */
    40. public static void close(ResultSet set, Statement statement, Connection connection) {
    41. //判断是否为null
    42. try {
    43. if (set != null) {
    44. set.close();
    45. }
    46. if (statement != null) {
    47. statement.close();
    48. }
    49. if (connection != null) {
    50. connection.close();
    51. }
    52. } catch (SQLException e) {
    53. throw new RuntimeException(e);
    54. }
    55. }
    56. public static void close(ResultSet set) {
    57. //判断是否为null
    58. try {
    59. if (set != null) {
    60. set.close();
    61. }
    62. } catch (SQLException e) {
    63. throw new RuntimeException(e);
    64. }
    65. }
    66. public static void close(Statement statement) {
    67. //判断是否为null
    68. try {
    69. if (statement != null) {
    70. statement.close();
    71. }
    72. } catch (SQLException e) {
    73. throw new RuntimeException(e);
    74. }
    75. }
    76. public static void close(Connection connection) {
    77. //判断是否为null
    78. try {
    79. if (connection != null) {
    80. connection.close();
    81. }
    82. } catch (SQLException e) {
    83. throw new RuntimeException(e);
    84. }
    85. }
    86. }

    后面的案例中会直接使用工具类的方法来获取连接和关闭资源

    事务

    JDBC自动开启事务,也自动开启自动提交

    处理事务的案例

    1. @Test
    2. //事务来解决
    3. public void useTransaction() {
    4. //操作转账的事务
    5. //1.得到连接
    6. Connection connection = null;
    7. //2. 组织一个sql
    8. String sql = "update account set balance = balance - 100 where id = 1";
    9. String sql2 = "update account set balance = balance + 100 where id = 2";
    10. PreparedStatement preparedStatement = null;
    11. PreparedStatement preparedStatement1 = null;
    12. //3. 创建PreparedStatement 对象
    13. try {
    14. connection = JDBCUtils.getConnection();//在默认情况下,connection是默认自动提交
    15. //将connection 设置为不自动提交
    16. connection.setAutoCommit(false);
    17. preparedStatement = connection.prepareStatement(sql);
    18. //执行
    19. preparedStatement.executeUpdate();
    20. // int i = 1 / 0; // 抛出异常
    21. preparedStatement1 = connection.prepareStatement(sql2);
    22. preparedStatement1.executeUpdate();
    23. //这里提交事务
    24. connection.commit();
    25. } catch (Exception e) {
    26. //这里我们可以进行回滚,即撤消执行的SQL
    27. //默认回滚到事务开始的状态
    28. System.out.println("执行发生了异常,回滚撤消执行的SQL");
    29. try {
    30. connection.rollback();
    31. } catch (SQLException ex) {
    32. ex.printStackTrace();
    33. }
    34. e.printStackTrace();
    35. } finally {
    36. //关闭资源
    37. JDBCUtils.close(null,preparedStatement,connection);
    38. }
    39. }

    批处理

     

    效率对比

    不使用批处理,即一条一次执行

    1. //传统方法1,添加5000条数据到admin3
    2. @Test
    3. public void noBatch() throws Exception{
    4. Connection connection = JDBCUtils.getConnection();
    5. String sql = "insert into admin2 values (null, ?, ?)";
    6. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    7. System.out.println("开始执行了");
    8. long start = System.currentTimeMillis();
    9. for (int i = 0; i < 5000; i++) {
    10. preparedStatement.setString(1, "jack" + i);
    11. preparedStatement.setString(2, "666" + i);
    12. preparedStatement.executeUpdate();
    13. }
    14. long end = System.currentTimeMillis();
    15. System.out.println("传统方式 耗时=" + (end - start));//5956
    16. //关闭连接
    17. JDBCUtils.close(null,preparedStatement, connection);
    18. }

     

    使用批处理

    1. @Test
    2. //使用批量方式添加数据
    3. public void batth() throws Exception{
    4. Connection connection = JDBCUtils.getConnection();
    5. String sql = "insert into admin2 values (null, ?, ?)";
    6. PreparedStatement preparedStatement = connection.prepareStatement(sql);
    7. System.out.println("开始执行了");
    8. long start = System.currentTimeMillis();
    9. for (int i = 0; i < 5000; i++) {
    10. preparedStatement.setString(1, "jack" + i);
    11. preparedStatement.setString(2, "666" + i);
    12. //将SQL 语句加入到批处理包 -> 看源码
    13. /*
    14. //1. 第一次创建 ArrayList - elementData => Object[]
    15. //2. elementData => Object[] 就会存放我们预处理的sql语句
    16. //3. 当elementData满后,就按照1.5倍扩容
    17. //4. 当添加到指定的值后,会executeBatch
    18. //5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
    19. public void addBatch() throws SQLException {
    20. synchronized(this.checkClosed().getConnectionMutex()) {
    21. if (this.batchedArgs == null) {
    22. this.batchedArgs = new ArrayList();
    23. }
    24. for(int i = 0; i < this.parameterValues.length; ++i) {
    25. this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
    26. }
    27. this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
    28. }
    29. }
    30. */
    31. preparedStatement.addBatch();
    32. //当有一天条记录时,再批量执行
    33. if ((i + 1) % 1000 == 0) { //满1000条
    34. preparedStatement.executeBatch();
    35. //清空一把
    36. preparedStatement.clearBatch();
    37. }
    38. }
    39. long end = System.currentTimeMillis();
    40. System.out.println("批量方式 耗时=" + (end - start));//72
    41. //关闭连接
    42. JDBCUtils.close(null,preparedStatement, connection);
    43. }

     

    注意要想让批处理发挥作用需要再url里面加入

    ?rewriteBatchedStatements=true

    否则速度和没有使用批处理没两样

     批处理底层使用的是ArrayList,元素是SQL语句

     

     

    主要代码

     

    数据库连接池

    为什么要用连接池?传统方式会有什么影响?

     

    例如需要连接5000次数据库

    传统方式,即

    1. @Test
    2. //代码 连接mysql 5000次
    3. public static void main(String[] args) {
    4. //看看看连接-关闭 connection 会好用多久
    5. long start = System.currentTimeMillis();
    6. for (int i = 0; i < 5000; i++) {
    7. //使用传统jdbc方式及,得到廉连接
    8. Connection connection = JDBCUtils.getConnection();
    9. //做一些工作,比如得到PreparedStatement发送sql
    10. //.......
    11. //关闭
    12. JDBCUtils.close(null,null,connection);
    13. }
    14. long end = System.currentTimeMillis();
    15. System.out.println("传统方式5000次 耗时=" + (end - start));//传统方法耗时 5186
    16. }

     5000千次就使用了5秒左右,一个程序每天有大量的使用者使用,所以这中方式是低效的

    我们可以使用连接池的方式获取连接

     

    连接池

    C3P0

    1. //方式1:相关参数,在程序中指定user,url,password等
    2. @Test
    3. public void testC3P0_01() throws Exception {
    4. //1. 创建一个数据源对象
    5. ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    6. //2. 通过配置文件mysql.properties获取相关的信息
    7. Properties properties = new Properties();
    8. properties.load(new FileInputStream("src\\mysql.properties"));
    9. //读取相关的属性
    10. String user = properties.getProperty("user");
    11. String password = properties.getProperty("password");
    12. String url = properties.getProperty("url");
    13. String driver = properties.getProperty("driver");
    14. //给数据源 comboPooledDataSource 设置相关的参数
    15. //注意:连接管理是由 comboPooledDataSource 来管理
    16. comboPooledDataSource.setDriverClass(driver);
    17. comboPooledDataSource.setJdbcUrl(url);
    18. comboPooledDataSource.setUser(user);
    19. comboPooledDataSource.setPassword(password);
    20. //设置初始化连接数
    21. comboPooledDataSource.setInitialPoolSize(10);
    22. comboPooledDataSource.setMaxPoolSize(50);
    23. //测试连接池的效率,测试对mysql 5000次操作
    24. long start = System.currentTimeMillis();
    25. for (int i = 0; i < 5000; i++) {
    26. Connection connection = comboPooledDataSource.getConnection();//这个方法就是从 DataSource 接口实现
    27. // System.out.println("连接成功");
    28. connection.close();
    29. }
    30. long end = System.currentTimeMillis();
    31. //c3p0 5000次连接mysql 耗时=226
    32. System.out.println("c3p0 5000次连接mysql 耗时=" + (end - start));
    33. }

    查看类图

     

     

    方式1:相关参数,在程序中指定user,url,password。耗时:226ms

    第二种方式 使用配置文件模板来完成。耗时:400ms

    1. @Test
    2. //第二种方式 使用配置文件模板来完成
    3. //1. 将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
    4. //2. 该文件指定了连接数据库
    5. public void testc3p0_02() throws SQLException {
    6. ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("cs_student");
    7. //测试5000次连接mysql
    8. long start = System.currentTimeMillis();
    9. for (int i = 0; i < 500000; i++) {
    10. Connection connection = comboPooledDataSource.getConnection();
    11. // System.out.println("连接成功");
    12. connection.close();
    13. }
    14. long end = System.currentTimeMillis();
    15. //c3p0的第二种方式 耗时=400
    16. System.out.println("c3p0的第二种方式(500000) 耗时=" + (end - start));
    17. }

     

     

    Druid

    1. @Test
    2. public void testDruid() throws Exception {
    3. //1. 加入 Druid jar 包
    4. //2. 加入 配置文件 druid.properties,将文件文件宝贝到项目的src目录
    5. //3. 创建PreparedStatement,读取配置文件
    6. Properties properties = new Properties();
    7. properties.load(new FileInputStream("src\\druid.properties"));
    8. //4. 创建一个指定参数的数据库,Druid连接池
    9. DataSource dataSource =
    10. DruidDataSourceFactory.createDataSource(properties);
    11. long start = System.currentTimeMillis();
    12. for (int i = 0; i < 500000; i++) {
    13. Connection connection = dataSource.getConnection();
    14. // System.out.println("连结成功");
    15. connection.close();
    16. }
    17. //druid连接池 操作5000次 耗时=388
    18. long end = System.currentTimeMillis();
    19. System.out.println("druid连接池 操作500000次 耗时=" + (end - start));
    20. }

    C3P0(配置文件)与Druid对比

    数据量小的时候看不到差距,数据量大的时候很明显

    例如连接次数50万次

    C3P0:1109ms

     

    Druid:295ms

     

    利用德鲁伊改进工具类

    1. public class JDBCUtilsByDruid {
    2. private static DataSource ds;
    3. //在静态代码块完成 ds初始化
    4. static {
    5. Properties properties = new Properties();
    6. try {
    7. properties.load(new FileInputStream("src\\druid.properties"));
    8. ds = DruidDataSourceFactory.createDataSource(properties);
    9. } catch (Exception e) {
    10. e.printStackTrace();
    11. }
    12. }
    13. //潘泻getConnection方法
    14. public static Connection getConnection() throws SQLException {
    15. return ds.getConnection();
    16. }
    17. //关闭连接,老师再次强调:在数据库连接池技术中,close 不是真的断掉连接
    18. //而是把使用的Connection对下个放回连接池
    19. public static void close(ResultSet resultSet, Statement statement, Connection connection) {
    20. try {
    21. if (resultSet != null) {
    22. resultSet.close();
    23. }
    24. if (statement != null) {
    25. statement.close();
    26. }
    27. if (connection != null) {
    28. connection.close();
    29. }
    30. } catch (SQLException e) {
    31. throw new RuntimeException();
    32. }
    33. }
    34. }

    测试案例

    放心食用

    1. public class JDBCUtilsByDruid_USE {
    2. @Test
    3. public void testSelect() {
    4. System.out.println("使用 druid完成");
    5. //1.得到连接
    6. Connection connection = null;
    7. //2. 组织一个sql
    8. String sql = "select * from actor";
    9. PreparedStatement preparedStatement = null;
    10. ResultSet set = null;
    11. //3. 创建PreparedStatement 对象
    12. try {
    13. connection = JDBCUtilsByDruid.getConnection();
    14. System.out.println(connection.getClass());//class com.alibaba.druid.pool.DruidPooledConnection
    15. preparedStatement = connection.prepareStatement(sql);
    16. //执行
    17. ResultSet resultSet = preparedStatement.executeQuery();
    18. JDBCUtilsByDruid.close(set,preparedStatement,connection);
    19. while (resultSet.next()) {
    20. int id = resultSet.getInt("id");
    21. String name = resultSet.getString("name");
    22. String sex = resultSet.getString("sex");
    23. Date borndate = resultSet.getDate("borndate");
    24. String phone = resultSet.getString("phone");
    25. System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
    26. }
    27. } catch (SQLException e) {
    28. e.printStackTrace();
    29. } finally {
    30. //关闭资源
    31. JDBCUtilsByDruid.close(set,preparedStatement,connection);
    32. }
    33. }
    34. //使用老师的土方法来解决ResultSet =封装=> ArrayList
    35. @Test
    36. public ArrayList testSelectToArrrayList() {
    37. System.out.println("使用 druid完成");
    38. //1.得到连接
    39. Connection connection = null;
    40. //2. 组织一个sql
    41. String sql = "select * from actor";
    42. PreparedStatement preparedStatement = null;
    43. ResultSet set = null;
    44. ArrayList list = new ArrayList<>();//创建ArrayList对象,存储actor对象
    45. //3. 创建PreparedStatement 对象
    46. try {
    47. connection = JDBCUtilsByDruid.getConnection();
    48. System.out.println(connection.getClass());//class com.alibaba.druid.pool.DruidPooledConnection
    49. preparedStatement = connection.prepareStatement(sql);
    50. //执行
    51. ResultSet resultSet = preparedStatement.executeQuery();
    52. while (resultSet.next()) {
    53. int id = resultSet.getInt("id");
    54. String name = resultSet.getString("name");
    55. String sex = resultSet.getString("sex");
    56. Date borndate = resultSet.getDate("borndate");
    57. String phone = resultSet.getString("phone");
    58. // System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
    59. //把得到的resultset 的记录,封装到 Actor对象,放入到list集合
    60. list.add(new Actor(id, name, sex, borndate, phone));
    61. }
    62. System.out.println("list集合数据=" + list);
    63. for (Actor actor : list) {
    64. System.out.println("id=" + actor.getId() + "\tname=" + actor.getName());
    65. }
    66. } catch (SQLException e) {
    67. e.printStackTrace();
    68. } finally {
    69. //关闭资源
    70. JDBCUtilsByDruid.close(set,preparedStatement,connection);
    71. return list;
    72. }
    73. }
    74. }

    Apache——DBUtils

     

     

    如果关闭连接,Result就不能正常使用了,如何将获取的内容暂时保留或存储下来呢?马上就会解决

    DBUtils的原理 将数据提前存储到集合中

     

  • 相关阅读:
    运算符重载的三种实现方法
    解决Nginx unknown directive “stream”问题
    【数据结构】二叉搜索树
    第二次授课内容
    大龄码农的转型:总结免费升讯威在线客服系统的推广经验与成绩
    指针类型的意义
    glm模型询问批量csv glm_ask_batch_csv
    YOLO系列算法改进方法 | 目录一览表
    [Leetcode]9. 回文数
    30-Spark入门之Spark技术栈讲解、分区、系统架构、算子和任务提交方式
  • 原文地址:https://blog.csdn.net/qq_63918780/article/details/126206097