Java基础(十)

Java基础(十)

1.JDBC概述

  • JDBC为访问不同的数据库提供了统一 的接口,为使用者屏蔽了细节问题。
  • Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
  • JDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向着套接口编程即可,不同的数据库厂商,需要针对这套接口,提供不同实现。
  • JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中。

2. JDBC 程序编写步骤

  • 注册驱动–加载Driver类
  • 获取连接–得到Connection
  • 执行增删改查–发送SQL 给mysql执行
  • 释放资源–关闭相关连接

例子:

DROP TABLE IF EXISTS `actor`;
CREATE TABLE `actor` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL DEFAULT '',
  `sex` char(1) NOT NULL DEFAULT '女',
  `borndate` datetime DEFAULT NULL,
  `phone` varchar(12) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
public class Jdbc01 {
    public static void main(String[] args) throws SQLException {
        //1、注册驱动
        Driver driver = new Driver();
        //2、得到连接
        //(1)jdbc:mysql://规定好表示协议,通过jdbc的方式连接mysql
        //(2) localhost 主机 ,可以是ip地址
        //(3) 3306:表示mysql监听的端口
        //(4)demo 连接到mysql bdms的哪个数据库
        //(5) mysql的连接本质就是socket连接
        String url = "jdbc:mysql://localhost:3306/demo";
        //将用户名和密码放入到Properties 对象
        Properties properties = new Properties();
        //user  和 password 是规定好的键
        properties.setProperty("user","root");
       properties.setProperty("password","1123");
        Connection connect = driver.connect(url, properties);
        //3、执行sql
      //  String sql = "insert into actor values (null , '刘德华', '男' , '1970-11-11','110' )";
      //  String sql = "update actor set name = '周星驰'where id = 1";
        String sql = "delete from actor where id = 1";
       //statement 用于执行静态SQL语句并返回其生成结果的对象。
        Statement statement = connect.createStatement();
        int row = statement.executeUpdate(sql);//如果是dml语句,返回的就是影响的行数
        System.out.println(row > 0 ? "成功": "失败");
        //4、关闭连接资源
        statement.close();
        connect.close();
    }
}

3. java 连接sql的5种方式

 //方式一:
    public void connect01() throws SQLException {
        Driver driver = new Driver();//创建Driver对象
        String url = "jdbc:mysql://localhost:3306/demo";
        //将用户名和密码放入到Properties对象
        Properties properties = new Properties();
        properties.setProperty("user","root");//用户名
   properties.setProperty("password","1123");//密码
        Connection connection = driver.connect(url,properties);
        System.out.println(connection);
    }
    @Test
    //方式二:
    public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
        //使用反射加载Driver类 ,动态加载,加载更加灵活,减少依赖性
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
    Driver driver = (Driver)aClass.newInstance();
        String url = "jdbc:mysql://localhost:3306/demo";
        Properties properties = new Properties();
        properties.setProperty("user","root");//用户名      properties.setProperty("password","1123");//密码
        Connection connection = driver.connect(url,properties);
        System.out.println("方式二 "+ connection);
    }
//方式三:使用DriverManager替代 driver 进行统一管理
    @Test
    public void connect03() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
        //使用反射加载Driver
        Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver)aClass.newInstance();
        String url = "jdbc:mysql://localhost:3306/demo";
        String user = "root";
        String password = "1123";
        DriverManager.registerDriver(driver);//注册Driver驱动
        Connection connection = DriverManager.getConnection(url,user,password);
        System.out.println("第三种方式= "+connection);
    }
    //方式四:使用Class.forName自动完成注册,简化代码
    //这种方式获取连接是使用最多的,推荐使用
    @Test
  public void connect04() throws ClassNotFoundException, SQLException {
        //使用反射加载了Driver类
        //在加载Driver类时,完成注册
        /*
        源码 :1、静态代码块 ,在类加载时,会执行一次
               2、DriverManager.registerDriver(new Driver);
               3、因此注册driver的工作已经完成
               static{
               try{
               DriverManager.registerDriver(new Driver());
               }catch(SQLException var1){
               throw new RuntimeException("Can not register driver");
               }
               }
         */
      Class.forName("com.mysql.jdbc.Driver");
      String url = "jdbc:mysql://localhost:3306/demo";
      String user = "root";
      String password = "1123";
      Connection connection = DriverManager.getConnection(url,user,password);
      System.out.println("第四种方式= "+connection);
  }
  @Test
  //方式五,在方式四的基础上改进,添加配置文件,让连接mysql更灵活
  public void connect05() throws IOException, ClassNotFoundException, SQLException {
        //通过Properties 对象获取配置文件的信息
      Properties properties = new Properties();
      properties.load(new FileInputStream("src\\mysql.properties"));
      //获取相关的值
      String user = properties.getProperty("user");
      String password = properties.getProperty("password");
      String url = properties.getProperty("url");
      Class.forName("com.mysql.jdbc.Driver");//建议写上
      Connection connection = DriverManager.getConnection(url, user, password);
      System.out.println("方式五 "+ connection);
  }
#mysql.properties文件
user=root
password=1123
url=jdbc:mysql://localhost:3306/demo
driver=com.mysql.jdbc.Driver

4. ResultSet[结果集]

  • 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
  • ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
  • next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集。
public class ResultSet_ {
    public static void main(String[] args) throws IOException, SQLException, ClassNotFoundException {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\mysql.properties"));
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        String url = properties.getProperty("url");
        Class.forName("com.mysql.jdbc.Driver");//建议写上
        Connection connection = DriverManager.getConnection(url, user, password);
        Statement statement  = connection.createStatement();
        String sql = "select id , name ,sex ,borndate from actor ";
        ResultSet resultSet = statement.executeQuery(sql);
        //使用while取出数据
        while(resultSet.next()){//让光标向后移动,如果没有更多行,则返回false
            int id = resultSet.getInt(1);//获取该行的第一列
            String name= resultSet.getString(2);
            String sex = resultSet.getString(3);
            Date date = resultSet.getDate(4);
            System.out.println(id+"\t"+name+"\t"+sex+"\t"+date);
        }
        //关闭连接
        resultSet.close();
        statement.close();
        connection.close();
    }
    }

5. Statement

  • Statement对象用于执行静态SQL语句并返回其生成的结果的对象。
  • 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过Statement[存在SQL注入]、PreparedStatement[预处理]、CallableStatement[存储过程]
  • Statement对象执行SQL语句,存在SQL注入风险。
  • SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
  • 要防范SQL注入,只有用PreparedStatement(从Statement扩展而来)取代Statement就可以了。

6. PreparedStatement

  • PreparedStatement执行的SQL语句中的参数用(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数。setXxx()方法有两个参数,一个参数是设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值。
  • 调用executeQuery(),返回ResultSet对象
  • 调用executeUpdate():执行更新,包括增、删、修改。

7. JDBC API

DriverManager驱动管理 :
getConnection(url,user,pwd);
Collection接口:
createStatement创建Statement对象preparedStatement(sql)生成预处理对象
Statement接口:
executeUpdate(sql)执行dml语句,返回影响的行数。
executeQuery(sql)执行查询,返回ResultSet对象
execute(sql)执行任意的sql,返回布尔值
PrepareStatement接口:
executeUpdate()执行dml
executeQuery()执行查询,返回ResultSet
execute()执行任意sql,返回布尔
setXxx(占位符索引,占位符的值),解决SQL注入
setObject(占位符索引,占位符的值)
ResultSet(结果集):
next()向下移动一行,同时如果没有下一行,返回false
previous()向上移动一行,如果没有上一行,返回false
getXxx(列的索引|列名) 返回对应的值,接收类型是Xxx
getObject(列的索引|列名)返回对应的值,接收类型为Object

8. JDBCUtils

public class JDBCUtils {
    //定义相关的属性(4个),因为只需要一份,
    private static String user;
    private static String password;
    private static String url;
    private static String driver;
    static {
        Properties properties = new Properties();
        try {
            properties.load(new FileInputStream("src\\mysql.properties"));
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");

        } catch (IOException e) {
            //在实际开发中
            //1、将编译异常转换成运行异常
            //2、调用者可以选择捕获异常,也可以选择默认处理该异常
            throw new RuntimeException(e);
        }
    }
    //连接数据库
    public static Connection getConnection(){
        try {
            return DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 关闭资源:
     * 1、ResultSet 结果集
     * 2、Statement 或者PreparedStatement
     * 3、Connection
     * 4、如果需要关闭资源,就传入对象,否则传入Null
     */
    public static void close(ResultSet set , Statement statement,Connection connection){
        try {
            if(set!=null){
            set.close();
        }
        if (statement!=null){
        statement.close();
        }
        if (connection!=null) {
            connection.close();
        }
            } catch (SQLException e) {
            throw new RuntimeException();
            }
        }
    }
    @Test
   public void testDML(){
       Connection connection = null;
       String sql = "update actor set name = ? where id = ?";
       PreparedStatement preparedStatement=null;
       try {
           connection = JDBCUtils.getConnection();
           preparedStatement = connection.prepareStatement(sql);
           preparedStatement.setString(1,"周星驰");
           preparedStatement.setInt(2,3);
           preparedStatement.executeUpdate();
       } catch (SQLException e) {
           e.printStackTrace();
       }finally {        JDBCUtils.close(null,preparedStatement,connection);
       }
   }

9. 事务

基本介绍:

  • JDBC程序中当一个Connection对象创建时,默认情况下自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,不能回滚。
  • JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务。
  • 调用Connection的setAutoCommit(false)可以取消自动提交事务
  • 在所有SQL语句都执行后,调用commit()方法提交事务。
  • 在其中某个操作失败或出现异常时,调用rollback():方法回滚事务。

10. 批处理

  • 当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。

  • JDBC的批处理语句包括下面方法:

    addBatch():添加需要批处理的SQL语句或参数

    executeBatch()执行批处理语句。

    clearBatch():清空批处理包的语句

  • JDBC连接MySQL时,如果要使用批处理功能,在url中加参数 ?rewriteBatchedStatements=true

  • 批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,效率大大提高。

  • 例子:

      //传统方法,添加5000条数据到admin2
        @Test
        public void noBatch() throws Exception {
            Connection  connection = JDBCUtils.getConnection();
            String sql = "insert into admin2 values (null,?,?)";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            System.out.println("开始执行......");
            long start  = System.currentTimeMillis();
            for (int i = 0; i <5000 ; i++) {
                preparedStatement.setString(1,"jack"+i);
                preparedStatement.setString(2,"666");
                preparedStatement.executeUpdate();
            }
            long end  = System.currentTimeMillis();
            System.out.println("传统的方式耗时="+(end - start));//3726
            //关闭连接
            JDBCUtils.close(null,preparedStatement,connection);
        }
    批量处理:
     @Test
        public void batch() throws Exception {
            Connection connection = JDBCUtils.getConnection();
            String sql = "insert into admin2 values (null,?,?)";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            System.out.println("开始执行...");
            long start  = System.currentTimeMillis();
            for (int i = 0; i <5000 ; i++) {
                preparedStatement.setString(1,"jack"+i);
                preparedStatement.setString(2,"666");
                preparedStatement.addBatch();
                if((i + 1)%1000 == 0){
                    preparedStatement.executeBatch();
                    preparedStatement.clearBatch();
                }
            }
            long end  = System.currentTimeMillis();
            System.out.println("批量的方式耗时="+(end - start));//85
            //关闭连接
            JDBCUtils.close(null,preparedStatement,connection);
        }
    

11. 数据库连接池

基本介绍:

- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需要从"缓冲池"中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

传统获取Connection问题分析:

- 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Cnnection加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MYSQL崩溃。
- 解决传统开发中的数库连接问题,可以采用数据库连接池技术。

数据库连接池种类:

- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供。
- C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate、Spring)
- DBCP数据库连接池,速度相对c3p0较块,但不稳定。
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP数据库连接池,速度快。
- Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3P0、Proxool优于自一身的数据库连接池。

12. Apache-DBUtils

基本介绍:

- commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。

- DbUtils类:

- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增删改查、批处理

- 使用QueryRunner类实现查询

- ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式。

  ```java
  ArrayHandler:把结果集的第一行数据转换成对象数组。
  ArrayListHander:把结果集中的每一行数据都转换成一个数组,再存放到List中
  BeanListHandler:将结果集中的每一行元素封装到一个对应的JavaBean实例中,存放到List里
  ColumnListHandler:将结果集中某一列的数据存放到List中。
  KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存放到一个map里,其key为指定的key
  MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
  MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后存放到List
  ```

13. 正则表达式

就是用某种模式去匹配字符串的一个公式。

```java	
String content="字符串";
String regStr= "正则表达式";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
//java匹配默认为贪婪匹配,尽可能匹配多的
//java匹配中的非贪婪匹配:在限定符后面加?例如"\\d+?"
```

- 元字符-转义号 \\

  \\\符号 说明:使用正则表达式去检索某些特殊字符的时候,需要用到转义符号。

  在Java的正则表达式中,两个 \\\代表其他语句中的一个\

  需要用到转义符号的字符有以下: * + ( ) $ / \ ? [  ] ^ { }  

- 元字符-字符匹配符

  | 符号  | 说明                                               | 示例           | 解释                                                 |
  | ----- | -------------------------------------------------- | -------------- | ---------------------------------------------------- |
  | [   ] | 可接收的字符列表                                   | [efgh]         | e、f、g、h中的任意1字符                              |
  | [^ ]  | 不可接受的字符列表                                 | [^abc]         | 除a、b、c之外的任意1字符,包括数字和特殊符号         |
  | -     | 连字符                                             | A-Z            | 任意单个大写字母                                     |
  | .     | 匹配除\n以外的任何字符                             | a..b           | 以a开头,b结尾,中间包括2个任意字符的长度为4的字符串 |
  | \\\d  | 匹配单个数字字符                                   | \\\d{3}{\\\d}? | 包含3个或4个数字的字符串                             |
  | \\\D  | 匹配单个非数字字符,相当于[^0-9]                   | \\\D{\\\d}*    | 以单个非数字字符开头,后接任意个数字字符串           |
  | \\\w  | 匹配单个数字、大小写字母字符,相当于[0-9a-zA-Z]    | \\\d{3}\\\w{4} | 以3个数字字符开头的长度为7的数字字母字符串           |
  | \\\W  | 匹配单个非字符、大小写字母字符、相当于[^0-9a-zA-Z] | \\\W+\\\d{2}   | 以至少1个非数字字母字符开头,2个数字字符结尾的字符串 |
  | \\\s  | 匹配任何空白字符(空格,制表符等)                   |                |                                                      |
  | \\\S  | 匹配任何非空白字符,和\\\s刚好相反                 |                |                                                      |

- java正则表达式默认是区分字母大小写的 ,不区分大小写的方式

  (?i)abc 表示abc都不区分大小写

  a(?i)bc表示bc不区分大小写

  a((?i)b)c 表示只有b不区分大小写

  Patttern pat = Pattern.complile(regEx,Pattern.CASE_INSENSITIVE);

- 元字符-选择匹配符

  |      匹配"|"之前或之后的表达式 

- 元字符-限定符

  | 符号  | 含义                           | 示例        | 说明                                               | 匹配输入                |
  | ----- | ------------------------------ | ----------- | -------------------------------------------------- | ----------------------- |
  | *     | 指定字符重复0次或n次           | (abc)*      | 仅包含任意个abc的字符串,等效于\w*                  | abc、abcabcabc          |
  | +     | 指定字符重复1次或n次(至少一次) | m+(abc)*    | 以至少1个m开头,后接任意个abc的字符串              | m、mabc、mabcabc        |
  | ?     | 指定字符重复0次或1次(最多一次) | m+abc?      | 以至少1个m开头,后接ab或abc的字符串                | mab、mabc、mmmab        |
  | (n)   | 只能输入n个字符                | [abcd]{3}   | 由abcd中字母组成的任意长度为3的字符串              | abc、dbc、adc           |
  | {n,}  | 指定至少n个匹配                | [abcd]{3,}  | 由abcd中字母组成的任意长度不小于3的字符串          | aab、dbc、aaabdc        |
  | {n,m} | 指定至少n个但不多于m个匹配     | [abcd]{3,5} | 由abcd中字母组成的任意长度不小于3,不大于5的字符串 | abc、abcd、aaaaa、bcdab |

- 元字符-定位符

  | 符号 | 含义                   | 示例               | 说明                                                         | 匹配输入         |
  | ---- | ---------------------- | ------------------ | ------------------------------------------------------------ | ---------------- |
  | ^    | 指定起始字符           | \^[0-9]+[a-z]*      | 以至少1个数字开头,后接任意个小写字母的字符串                 | 123、6aa、555efd |
  | $    | 指定结束字符           | \^[0-9]\-[a-z]+$ | 以1个数字开头后接连字符"-",并以至少1个小写字母结尾的字符串   | 1-a              |
  | \\\b | 匹配目标字符串的边界   | han\\\b            | 这里说的字符串边界(后面)指的是子串间有空格,或者是目标字符串的结束位置 | sphan、nnhan     |
  | \\\B | 匹配目标字符串的非边界 | han\\\B            | 和\b的含义刚刚相反                                           | hanshunping      |

- 分组:

  | 常用分组构造形式 | 说明                                                         |
  | ---------------- | ------------------------------------------------------------ |
  | (pattern)        | 非命名捕获。捕获匹配的字符串,编号为零的第一个捕获的由整个正则表达式模式匹配的文本,其它捕获结果则根据左括号的顺序从1自动编号。 |
  | (?<name>pattern) | 命名捕获。将匹配的字符串捕获到一个组名或编号名称中。用于name的字符串不能包含任何标点符号,并且不能以数字开头,可以使用单引号替代尖括号,例如(?'name') |

- 特别分组

  | 常用分组构造形式 | 说明                                                         | 例如                                                         |
  | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
  | (?:pattern)      | 匹配pattern但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配,这对于用"or"字符(\|)组合模式部件的情况很有用。 | 'industr(?:y\|ies)'是比'industry\|industries'更经济的表达式  |
  | (?=pattern)      | 它是一个非捕获匹配                                           | 'Windows(?=95\|98\|NT\|2000)'匹配"Windows 2000"中的"Windows",但不匹配"Windows 3.1"中的"Windows" |
  | (?!pattern)      | 该表达式匹配不处于匹配pattern的字符的起始点的搜索字符串。他是一个非捕获匹配 | 'Windows(?!95\|98\|NT\|2000)'匹配"Windows 3.1中的"Windows",但不匹配"Windows 2000"中的"Windows" |

- Pattern类

  pattern对象是一个正则表达式对象。Pattern类没有公共构造方法,要创建一个Pattern对象,调用其公共静态方法,它返回一个Pattern对象,该方法接受一个正则表达式作为它的第一个参数.比如:Pattern r = Pattern.compile(pattern);

- Matcher类

  Matcher对象是输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher也没有公共的构造方法,需要调用Pattern对象的matcher方法来获得一个Matcher对象。

- PatternSyntaxException

  PatternSyntaxException是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

- 分组:可以用圆括号组成一个比较复杂的匹配模式,那么一个圆括号的部分可以看作一个子表达式/一个分组。

- 捕获:把正则表达式中/分组匹配的内容,保存到内存中以数字编号或显示命名的组里,方便后面引用,从左到右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推,组0代表的是整个正则式。

- 反向引用:圆括号的内容被捕获后,可以在这个括号后被使用,从而写出一个比较实用的匹配模式。这种引用既可以在正则表达式内部,也可以是在正则表达式外部,内部反向引用\\\分组号,外部反向引用$分组号。 

- 反向引用例子:要匹配两个连续的相同数字:(\\\d)\\\1

  要匹配五个连续的相同数字:(\\\d)\\\1{4}

  要匹配个位与千位相同,十位与百位相同的数 5225 、1551 (\\\d)(\\\d)\\\2\\\1	
  
  ​					
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值