Java笔记(核心类、正则表达式、爬虫)

Java核心类

一.jdk17 API帮助文档下载

https://www.oracle.com/java/technologies/javase-jdk17-doc-downloads.html

二.Math类

  1. Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。image-20221210171102045

注意方法前面都有static修饰(全都是静态方法),意味着可以不用创建对象就直接使用,直接Math.方法就可以

1.求绝对值abs(absolute)
2.求立方根(cube root)

image-20221210172045733

3.ceil

向上取整 例如输入7.2,返回8.0

注意-12.34返回的是-12,不是-11(纯数学问题)

4.floor

向下取整 例如输入7.2,返回7.0

5.pow

求次方函数

6.Math.round

是四舍五入 12.34返回12 12.54返回13

  • 快捷键 ctrl+n,输入类名,右上角选择all places可以查看类的源码
7.Math.max

函数返回两个数的最大值 Math.max(20,30),返回30

8.Math.min

与之类似

9.pow

函数 如果第二个参数是正的0.0到1.0之间的数,那么就是开根号;如果第二个参数是负数,那么就是负根号,例如pow(2,-2)返回0.25

10.sqrt(Square Root )

是开根号的意思 Math.sqrt(4)返回2.0,注意返回的是算术平方根,也就是非负数

11.cbrt(Cubic Root)

是开立方根 Math.cbrt(8)返回2.0

12.random

返回 [0.0,1.0)的数,且小数点后面有很多位,如果要获取整数那么需要用到floor函数向下取整Math.floor(Math.random()*100+1),返回值为 [1.0,101)的整数,结果是100.0, 99.0, 98.0

//判断一个数是不是质数
public static void main(String[] args) {
        System.out.println(isPrime(997));
    }

    public static boolean isPrime(int number){
        int count = 0;
        for (int i = 2; i <= Math.sqrt(number); i++) {
            count++;
            if (number%i==0){
                return false;
            }

        }
        System.out.println(count);
        return true;

    }
//计算六合数(与水仙花数类似)
public static void main(String[] args) {
        int count = 0;
        for (int i = 100000; i < 999999; i++) {
            //获取个位十位和百位
            int ge = i%10;
            int shi = i/10%10;
            int bai = i/100%10;
            int qian = i/1000%10;
            int wan = i/10000%10;
            int shiwan = i/100000%10;
            //开始判断是否为水仙花数

            int sum = (int) (Math.pow(ge,6)+Math.pow(shi,6)+Math.pow(bai,6)+Math.pow(qian,6)+Math.pow(wan,6)+Math.pow(shiwan,6));
            if (sum == i){
                count++;
                System.out.println(i);
            }

        }
        System.out.println(count);
    }
//548834
//1

三.System类

image-20221211152037947

image-20221211152358766

1.exit

终止程序 System.exit(0)是表示当前虚拟机正常终止;

​ System.exit(1)是表示当前虚拟机异常终止;

2.currentTimeMillis

单位:毫秒,从时间原点出发,到现在终止,我国在东八区,所以操作系统中获取的时间原点是1970年1月1号早上八点

3.arraycopy

arrcopy括号中的参数

  1. 参数一:数据源,要copy的数据从那个数组来
  2. 参数二:从数据源数组的第几个数据开始拷贝
  3. 参数三:目的地,要把数据拷贝到哪个数组
  4. 参数四:目的地数组的索引,从第几个开始被拷贝上去
  5. 参数五:拷贝的数据的个数
 int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
        int[] arr2 = new int[10];
        System.arraycopy(arr1,0,arr2,0,10);
        for (int i = 0; i < arr2.length; i++) {
            System.out.print(arr2[i]+" ");

        }
//打印数组快捷键 arr2.fori

注意事项:

  1. 如果拷贝数组和被拷贝数组都是基本数据类型,那么要保证两者上的数据类型一致,否则程序运行时候会报错,拷贝失败
  2. 拷贝时要注意数组的长度,数组索引下标越界也会报错
  3. 如果数据源数组和目的地数组都是引用数据类型,那么子类类型可以复制给父类image-20221211162709080
  • 如上图所示:arr2为person类,是student类的子类,如果想把student类copy给父类,需要加强制转换说明(student)大变小需要强转,小变大不需要

image-20221211163103054

三.RunTime类

  • RunTime表示当前虚拟机的运行环境

image-20221211163343133

1.getRuntime 获得Runtime的对象

注意创建对象的方法,要用Runtime.getRuntime不能直接new一个Runtime对象,因为一台电脑只能有一个运行环境,多个runtime对象是没有意义的

		Runtime r1 = Runtime.getRuntime();
        Runtime r2 = Runtime.getRuntime();
        System.out.println(r1=r2);
//结果返回true
2.exit 停止虚拟机
//两种方法
Runtime r1 = Runtime.getRuntime();
r1.exit(0);

Runtime.getRuntime().exit(0);
3.availableProcessors 获得CPU的线程数
System.out.println(Runtime.getRuntime().availableProcessors());
//输出 16
4.maxMemory 获得总内存大小

单位是byte 字节

Runtime.getRuntime().maxMemory().sout
    //键盘输入以上字符 结果如下
   System.out.println(Runtime.getRuntime().maxMemory());
//结果 4223664128
//上式除以一个1024 = KB 再除以一个1024 = 兆 结果为4028,也就是4个G
5.totalMemory 虚拟机已经获取的总内存大小

单位是byte 字节

System.out.println(Runtime.getRuntime().totalMemory()/1024/1024 );
6.freeMemory 剩余内存
System.out.println(Runtime.getRuntime().freeMemory()/1024/1024 );
7.exec 运行cmd
Runtime.getRuntime().exec("notepad");//打开记事本
Runtime.getRuntime().exec("shutdown -s -t +时间单位为秒");
Runtime.getRuntime().exec("shutdown -a");//取消关机命令

image-20221211203807708

四.object类

  • Object类是JAVA中的顶级父类,所有类都直接或间接地继承Object类。
  • Object类中的方法可以被所有的子类访问,所以我们要学习Object类中的一些方法、
  • Object类中的方法是没有成员变量的,所以其方法没有代参的构造方法,都是空参构造方法,因为在JAVA中,继承的概念,子类的共性才会被抽取到父类中,但是在JAVA中,没有一个属性是所有类的共性,所以在Object类中没有成员变量
  • 在继承的概念中,对于任意一个类,都会有一个隐藏的super()访问父类的方法,这个方法访问的是父类的无参构造方法,而不是有参构造方法,原因就是顶级父类Object类没有成员变量,所以不需要访问有参构造方法
  • OBject类中有11个成员方法
1.toString 打印字符串
//没有写extends Object 但是默认继承顶级父类
public class Student {
    private int age ;
    private String name;
    public Student() {
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }
//这就是在子类中重写的toString
    public String toString() {
        return "Student{age = " + age + ", name = " + name + "}";
    }
}
//测试类
Student stu = new Student(23,"zhangsan");
System.out.println(r1 == r2);
//结果 Student{age = 23, name = zhangsan}
  • 默认情况下,object类中的toString方法(注意OBject类中的toString)返回的是地址值,所以默认情况下打印一个对象打印的就是地址值,但是地址值对于我们来说没什么意义,要想打印对象的属性,需要重写子类的toString类
  • 父类的toString方法,返回的是 包名+类名+@+对象的地址值,这不是我们想要看到的,所以我们需要在子类中重写toString方法,改写return语句,输出我们想要对象属性的形式
  • 在子类中重写toString方法来拼接属性 是为了规范输出子类对象的形式
  • 注意调用重写方法有两种
    • 第一种:alt+insert
    • 第二种:右键ptg to JavaBean(插件)
2.equals 比较两个对象是否相等
//student类在上文
Student stu1 = new Student();
Student stu2 = new Student();

//测试类中
boolean result = stu1.eauals(stu2)
System.out.println(result);//结果为 false  
  • 上述代码结果返回false原因:Student类的对象stu1调用了equals方法,但是Student类中并没有成员方法equals,所以程序调用的是父类Object中的equals方法,而该方法比较的是对象的地址值,但我们要比较的是对象的内容属性是否相同,所以解决办法就是在子类Student中重写equals方法
  • 重写父类中的equals方法需要在子类中按 alt + insert 选择equals() and hashcode(),生成之后 删除hashcode,因为是地址值,没有意义,那么现在重写的就是比较属性内容的equals方法
 @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

image-20221211215935864

  • 仔细看上述代码和注解,分析 equals的调用者和父类,以及输出的两个false的原因,输出结果一样,但是原因不一样
3.clone 对象克隆
  • 把A对象的属性值完全拷贝给B对象

image-20221211221147537

  • 把在二区的角色克隆到三区
  • clone方法是定义在Object下的,有protected修饰,那么只能在本包和其他包的子类中使用,所以要想使用clone,需要重写
  • 所有类都是Object类的子类,但是不一定属于Java.lang包,protected修饰的语法中其他包的子类指的是在类中调用自己类的方法,clone()是java.lang包下Object类的方法,而测试类是新包中的方法,所以不能直接创建对象并调用,虽然所有类都是Object类的子类,但是调用时候只能调用自己类的方法,这就是protected修饰的语法的意思
  • 如果一个接口里面没有任何的抽象方法,那么这个接口就是一个标记型接口,那么下面的cloneable接口就表示一旦实现了接口,那么对象就可以被克隆一旦没有实现那么对象就不可以被克隆
//注意在此之前要让本类先去实现一个cloneable接口,这个借口没有任何的抽象方法 
//即 public class Student implements Cloneable{
}
@Override
    protected Object clone() throws CloneNotSupportedException {
        //调用父类中的clone方法
        //相当于让JAVA帮我们克隆一个对象,并把克隆之后的对象返回出去
        return super.clone();
    }
  • 测试代码如下
Student u1 = new Student(23, "zadhwidhwe");
      //Object u2 = u1.clone();
		Student u2 = (Student) u1.clone();
//注意此处的强转,因为只new了Student类型的u1,u2其实是Object类型的,所以大变小要强转,把克隆的u2变成Student类型的变量
      	System.out.println(u1);
        System.out.println(u2);

//结果
Student{age = 23, name = zadhwidhw e}
Student{age = 23, name = zadhwidhwe}
  • 方法细节

    • 1.重写Object中的克隆方法
    • 2.让javaBean类实现Cloneable接口
    • 3.创建原对象并调用 clone方法
  • 堆内存

    • 基本数据类型:存储的是真实的值
    • 引用数据类型:存储的是另一个空间的地址值
  • 第一种克隆方式:浅克隆、浅拷贝

    • 对于下面的数组:两个数组指向的是同一个地址,只要有一个对象对其操作使其发生改变,另一个也会改变

image-20221212214306008

  • 第二种克隆方式:深克隆、深拷贝
    • 对于下面的数组:会重新创建一个新的数组并具有新的地址值,会把原来数组的数据拷贝下来

image-20221212214629897

image-20221212215318551

image-20221212215837884

image-20221212222455911

五.Objects类

image-20221212223704274

  • 为了防止上图情况:学生u1被赋值为空,那么比较的话,程序会报错,所以还需要改成下面的格式,但是有点麻烦

image-20221212223902249

  • 方法如下

image-20221212223959393

1.equals

image-20221212224342782

  • ctr+B进入equals源码,先判断是不是a和b是不是同一个对象,如果是,那么直接return false,如果不是,在做下面的判断,判断a是不是空,如果是,直接false,如果a不是,就会调用对象里面的equals方法进行比较

image-20221212224711455

2.isNull
3.nonNull

image-20221212224839656

image-20221212225031917

六.BigInteger类

image-20221216223113936

1. BigInteger(int num , Random rnd)
  • 括号内(次方数,随机数函数对象)
  • 注意范围是双闭区间
  • 函数获取这个范围内的一个整数
//第一种创建对象方式
BigInteger db1 = new BigInteger(4,new Random());
//第二种创建对象方式
Random r = new Random();
BigInteger db1 = new BigInteger(4,r);
2. BigInteger(String val)
  • 获取指定的大数据
  • 也就是说这个方法可以把一个超出long范围的数赋值给一个变量
  • long变量的范围*-9223372036854775808到9223372036854775807*
  • 注意这个方法的括号里不能是ABC,abc或者1.1(小数),字符串必须是整数
  • 注意加双引号,因为是字符串
BigInteger db1 = new BigInteger(“99999999999999999999999999999999999999999999999”);
3. BigInteger(String val,int Radix)
  • 获取指定进制的大整数
  • 注意括号里的进制是表示括号左边的那个数是该进制,打印在控制台上的数是被自动转换成10进制打印出来
  • 注意加双引号,因为是字符串
BigInteger bd2 = new BigInteger("1000",2);
System.out.println(bd2);
//打印结果是8
BigInteger bd2 = new BigInteger("123",2);
System.out.println(bd2);
//报错:因为二进制里面没有2和3
4.BigInteger.valueOf(long类型整数)
  • 括号里的数范围较小,符合long类型范围
  • 这个方法和上面第二种方法类似,但是有不同,这个方法把-16到16做了优化,提前创建好了BigInteger对象,如果多次获取不会创建新的,也就是虽然对象名字不一样,但是不一样名字的对象的地址是一样的,这一点可以用System.out.println(bdn==bdm)验证,因为sout比较的是地址的返回结果
  • 一旦超出16,结果就是fals e
        BigInteger bd4 = BigInteger.valueOf(16);
        BigInteger bd5 = BigInteger.valueOf(16);
        System.out.println(bd4==bd5);//结果为true
        BigInteger bd6 = BigInteger.valueOf(17);
        BigInteger bd7 = BigInteger.valueOf(17);
        System.out.println(bd6==bd7);//结果为false

image-20221216230520707

image-20221216230739290

  • BigInteger是一个个对象,对象不能直接相加减乘除,所以要想实现运算,必须用上面的方法

  • 有余数的除法的方法,整数部分后面跟的是一位小数,这个方法返回的是小数数组,数组[0]存的是整数部分,数组[1]存的是小数部分,整个结果的长度是2字节

        BigInteger bigInteger1 = BigInteger.valueOf(10);
        BigInteger bigInteger2 = BigInteger.valueOf(3);        
		BigInteger[] bigIntegers = bigInteger1.divideAndRemainder(bigInteger2);
        System.out.println(bigIntegers[0]);//结果是3
        System.out.println(bigIntegers[1]);//结果是1
  • 注意次方函数括号里面只是次方值,这个方法需要对象来调用
BigInteger bigInteger1 = BigInteger.valueOf(10);
System.out.println(bigInteger1.pow(2));//结果是100 
  • 底层存储方式:大的数据先转换成二进制补码,从左往右以三十二位一组,拆成n组,再去转换成十进制,放到数组里面

image-20221216233829439

image-20221216233949664

image-20221216234030707

七.BidgDecimal类

image-20221217111610220

在这里插入图片描述

  • 乘法直接按照正常乘就好

image-20221217111853556

  • 遇到小数二进制位非常多的情况下,只能舍弃数据,但比如火箭,银行,他们必须精确,所以JAVA提供bigDecimal类

  • 创建double类型数字(括号里):这种方法已经不精确,所以不推荐

  • 创建字符串型(括号里,精确)

  • image-20221217112652430

  • 直接sout(小数)也会不精确

  • image-20221217112559747

  • 该类的方法与integer类似

image-20221217114151470

  • divide方法 第一行的方法只能算除的净的否则报错,除不尽只能用第二种,并且规定保留位数
 BigDecimal b3 = bd1.divide(bd2,2,Round half up)//第三个参数是四舍五入的意思
     //bd1 = 10.0
     //bd2 = 3.0

image-20221217115130182

image-20221217115052340

image-20221217115544379

  • 把小数的每个字符单独遍历,转化成对应的ASCII码存储到数组里

image-20221217115852688

八.正则表达式

(8.1)案例

  • charAt:用来实现检索特定索引下的字符的String实例的方法
//要求:6位及20位以内;0不能开头;必须全部都是数字
public static void main(String[] args) {

        String qq = "1234567890";
        System.out.println(checkQQ(qq));
    }
    public static boolean checkQQ(String qq){
        int len = qq.length();
        if (len<6||len>20){
            return false;
        }
        if (qq.startsWith("0")){
            return false;
        }
        for (int i = 0; i < qq.length(); i++) {
            char c = qq.charAt(i);
            if (c<'0'||c>'9'){
                return false;
            }
        }
        return true;
    }
//结果 false
  • 核心思想

    • 先把异常数据进行过滤,剩下的就是满足要求的数据了
  • 用正则表达式实现上面的功能

qq.matches("[1-9]\\d{5,19}")
    //做以打印
    System.out.println(qq.matches("[1-9]\\d{5,19}"));
  • matches方法里面是字符串类型的语句,所以要有双引号
    • [1-9] :表示开头的那个字符是1-9的数字
    • \ \ d :表示开头字符后面的字符全部都是数字
    • {5,19}:表示开头字符后面可以有5到19位数字

(8.2)作用

  • 作用一:检验字符串是否满足规则
  • 作用二:在一段文本中查找满足要求的内容

(8.3)表达式内容

image-20221217180237677

(1)字符类

image-20221217184442670

  • 注意只匹配一个字符

  • 正则表达式的方括号[]表示一个范围,字符串里出现的字符一定在这个范围之内,如果不在这个范围之内就会返回false

  • ^ 上尖括号,表示取反

  • 注意,取交集必须是两个&符号,如果不小心只写了一个,那么就单纯表示这个字符&,也被拉入判断的字符里面

"&".matches("[a-z&[m-p]]");
//表示 & 这个字符 是不是属于a-z,是不是&符号,是不是属于m-p
//结果为true
  • “[a-z&&[def]]”:这个正则表达式的意思是 只有d e f这三个字符满足条件

  • “[a-z&&[ ^def]]”:表示在a-z范围内把d e f挖去 剩下的范围满足条件

(2)预定义字符类
  • JAVA中两个\表示一个\(\ \ = \)

  • 要想在正则表达式中表示点.
    就要写成 \\.
    

image-20221217184429487

  • image-20221217185053871

  • image-20221217185105997

  • image-20221217185451248

  • image-20221217185542645

以上的正则表达式只能校验单个字符

加上下面的数量词就可以实现检测多个字符

image-20221217190507714

  • image-20221217215859209

(8.4)练习

前言

  • 写正则表达式一定要拿着正确的数据从左到右依次去写
第一题:验证号码
//手机号码
public static void main(String[] args) {
        String regex1 = "[1][3-9]\\d{9}";//制定正则表达式
    	String regex1 = "1[3-9]\\d{9}";//或
System.out.println("18703523333".matches(regex1));//true
System.out.println("11012011922".matches(regex1));//false
System.out.println("03523593555".matches(regex1));//false
System.out.println("11111111111".matches(regex1));//false

    }
  • 第一部分 [1] 表示第一位只能是1
  • 第二部分 [3-9] 表示第二位是3~9的数字
  • 第三部分 \ \ d{9} 表示后面的数字范围是0~9且有九个数字
//座机号码
 //座机号码 020-2324242 02122442 0712-3242434
        String regex2 = "[0]\\d{2,3}-?[1-9]{4,9}";
        System.out.println("020-2324242".matches(regex2));
        System.out.println("02122442".matches(regex2));

image-20221217225652412

//邮箱号码(重点!!!!!!!!!!!!!!!!!!!!!!!!!)
//邮箱号码举例:

image-20221217230429633

        String regex3 = "\\w+@[\\w&&[^_]]{2,6}(\\.[a-zA-Z]{2,3}){1,2}";
        System.out.println("dleie0009@pci.com.cn".matches(regex3));

image-20221217231215672

  • 注意上图3.1和3.3 一个是用[]括起来,一个是用()括起来

  • 插件大法:String regex = “ ”,在双引号中间右键鼠标(调用插件any-rule)自定生成各种类型正则表达式,再对其稍作修改即可

第二题:验证用户名和身份证号码
需求
1.请编写正则表达式验证用户名是否满足要求。
要求:大小写字母,数字,下划线一共4-16位
2.请编写正则表达式验证身份证号码是否满足要求。
简单要求:18位,前17位任意数字,最后一位可以是数字可以是大写或小写的x
复杂要求:按照身份证号码的格式严格要求。
1.
String regex5 = "\\w{4,16}";
System.out.println("zhangsan".matches(regex5));
//返回true
2.
String regex6 = "[1-9]\\d{16}([0-9]|X|x)";
String regex6 = "[1-9]\\d{16}(\\d|X|x)";
String regex7 = "[1-9]\\d{16}[\\dXx]";

System.out.println("14020120011010000X".matches(regex6));
//true
//注意运用或者需要加括号()
//如果不加括号,那么第一个竖杠就会以左边所有内容为第一个条件
  • 注意[ \ \ d X x]表示括号里面的出现一个就好

  • image-20221217233952587

  • 注意上图的忽略大小写的正则表达式,i一定是小写

    • 上面三个输出语句全是true
  • 只忽略bc,不忽略a,那么只需要把(?i)挪到a的后面,这样就只对括号后面的内容生效image-20221217234209724

  • 只忽略bimage-20221217234316222

  • 那么上题的身份证就可以改为

    • String regex6 = "[1-9]\\d{16}(\\d|(?i)x)";
      
  • [12]表示1或者2

  • [123Xx]表示1或者2或者3或者X或者x,选其一

  • IDEA快捷键:ctrl+shift+上下箭头 可以上下移动整行

  • ​ + 加号表示前面紧挨着加号的部分至少出现一次

image-20221217235738468

image-20221217235829092

(8.5)爬虫

(一)爬取普通信息
  • 正则表达式作用二:查找满足要求的内容
练习一:爬取本地信息
    public static void main(String[] args) {

        String str="Java自从95年间世以米,经历了很多版木,目前企业中用的最多的是Java8和3aval1,"+
                "因为这两个是长期支持版木,下一个长期支持版木是]ava17,相信在未米不久Java17也会逐渐登上历史舞台";
        Pattern p = Pattern.compile("Java\\d{0,2}");
        //调用compile方法获取Pattern正则表达式的对象,此方法里面写的是正则表达式
        //m:文本匹配器的对象
        //str:大串(要被查找的段落)
        //p:规则(正则)
        //m要在str中找到符合规则的小串
        Matcher m = p.matcher(str);
        //正则表达式对象点上(调用)matcher方法,和str字符串进行匹配,并创建文本匹配器的对象
        //拿着文本匹配器从头开始读取,寻找是否有满足规则的字串
        //如果没有,方法返回false
        //如果有,返回true,并且在底层记录子串的起始索引+1
        //0,4(这是本母串中开头的从“J”到“自”的索引)
        boolean b = m.find();
        
        
        //方法底层会根据find记录的字符串进行截取
        //subString(起始索引,结束索引);包头不包尾,包左不包右
        //(0,4)但不包括4索引,所以正好就把JAVA四个字符截取下来
        //并返回截取的子串	
        String s1 = m.group();
        System.out.println(s1);
        
        //.........
        //上述内容只能获取一次,再想获取需要多些更多的对象和代码


    }
  • IDEA快捷键 ctrl+alt+m可以把选中的文本自动生成一个方法
//循环改进

image-20221218003033990

练习二:爬取网络信息

image-20221218003417056

练习三:爬取多个信息

image-20221218003917889

  • 注意上图的正则表达式写的时候由于是获取只要满足四个正则表达式之一的就可以,所以要用到|

  • String regex = "(电话)|(邮箱)|(座机)|(热线)"
    
(二)有条件爬取信息
练习
有如下文本,请按照要求爬取数据。
Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本,下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台

需求1:爬取版本号为8,11,17的Java文本,但是只要Java,不显示版本号。
需求2:爬取版本号为8,11,17的Java文本。正确爬取结果为:Java8  Java11  Java17  Java17
需求3:爬取除了版本号为8,11,17的Java文本,
//需求一
public static void main(String[] args) {
        String s = "Java自从95年问世以来,经历了很多版本,目前企业中用的最多的是Java8和Java11,因为这两个是长期支持版本," +
                "下一个长期支持版本是Java17,相信在未来不久Java17也会逐渐登上历史舞台";

        //1.定义正则表达式
        //?理解为前面的数据JAVA,是一个占位符
        //=表示在JAVA后面要跟随的数据
        //但是在获取的时候只获取前半部分
        String regex1 = "Java(?=8|11|17)";

        //创建正则表达式的对象
        Pattern c = Pattern.compile(regex1);

        //获取文本匹配器对象
        Matcher m = c.matcher(s);

        //循环(用m一个一个地去读取)
        //如果读到,返回true,并记录起始索引和结束索引+1
        while (m.find()) {
            //按照find记录的索引截取字符串
            System.out.println(m.group());
        }


    }
//结果
Java
Java
Java
Java
//注意这句
String regex1 = "Java(?=8|11|17)";
他会首先找到有8或11或17跟随的Java,没有数字跟随的Java则不会要,所以其实文本中有5个Java,但是没有截取第一个,因为第一个没有数字跟随
//需求二
String regex2 = "Java(?:8|11|17)";
//结果
Java8
Java11
Java17
Java17

image-20221218134036498

  • 注意上图里括号和占位符的作用
//需求三
String regex2 = "((?i)Java)(?!8|11|17)";
  • 问号后面加上感叹号。就表示不要后面的数字,只要java或其大小写

  • 总结

    • (?i)表示不区分大小写
    • (?=数据)表示截取跟随着要跟随的数据的字符,不截取跟随着的数据
    • (?:数据)表示截取前面字符跟随着要跟随的数据的字符,同时截取跟随着的数据,没有跟随数据的就不截取
    • (?!数据)表示只截取没有跟随数据的字符
(三)贪婪爬取和非贪婪爬取

image-20221218135948738

String regex1 = "ab+";
//以上为贪婪爬取 结果为abbbbbbbbbbbb
String regex2 = "ab+?"
//以上为非贪婪爬取  结果为ab

(8.6)正则表达式在字符串方法中的应用

image-20221218151855675

1.replaceAll
  • 方法返回一个结果,结果是替换后的字符串,而不是直接把原来的字符串做了改变
  • 细节:
    • 方法在底层跟之前一样也会创建文本解析器的对象
    • 然后从头开始去读取字符串的内容,只要有满足的,那么就用第二个参数去替换
public class zhengzebiaodashi4 {
    //"有一段字符串:小诗诗dqwefqwfqwfwg12312小丹丹dqwefqwfqwfwq12312小惠惠\n" +
      //      "要求1:把字符串中三个姓名之间的字母替换为Vs\n" +
        //    "要求2:把字符串中的三个姓名切割出来";
    public static void main(String[] args) {
        String s = "小诗诗dqwefqwfqwfwg12312小丹丹dqwefqwfqwfwq12312小惠惠";
        String result1 = s.replaceAll("[\\w&&[^_]]+", "vs");
        System.out.println(result1);
        
    }
   //结果 小诗诗vs小丹丹vs小惠惠
  • IDEA快捷键 :ctrl+b 查看源码
2.split
"要求2:把字符串中的三个姓名切割出来";
       String s2 = "小诗诗a小丹丹a小惠惠";
        String[] arr = s2.split("a");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
  • 方法会以括号里面的正则表达式为分割点,把字符串分隔开成一个个的子串,并在底层存放到数组里面,所以alt+enter分配对象,再用循环打印arr[i],就可以打出子串

(8.7)捕获分组和非捕获分组

image-20221218155020120

image-20221218155252033

  • 注意只看左括号,以左括号为基准
练习:捕获分组

image-20221218155455432

image-20221218165424774

  • \ \ 1 表示把第一组拿出来再用,第几组看第几个左括号,这里的第一组指的是正则表达式里面的第一组

image-20221218165655806

  • 要求出现一致的内容,就要用到 捕获分组
  • *表示出现0次或多次,出现也可以不出现也可以
  • 注意增删括号时候分组也会改变, \ \ n也会跟着改变

image-20221218170256459

image-20221218170346967

  • 上图结果 最后一个是false 因为这个字符串的首位不一样

image-20221218174507779

image-20221218174804516

  • 上图表示将重复内容只用一个字去代替替换

  • 正则表达式外面用$符号

  • 注意必须是上面的正则表达式写法,不然筛选不到

  • String regex = "(.)\\1+";
    \\1表示 跟前面第一个括号里的内容完全一致
    不能是String regex = "(.).+";
    这个意思是 第一个组后面的任意内容可以至少出现一次
    
  • 用的还是 replaceAll 只有这个方法里面的参数可以是正则表达式

练习:非捕获分组

image-20221218175650499

非捕获分组:是括号里面有 ?:!= ,这些符号外面也有括号,但是不能用\ \ 组号,因为此时程序根本不知道谁是第几组

image-20221218180054942

  • 所以在插件里面的非捕获分组也可以直接把他删掉,不影响

image-20221218180333881

  • 注意是不占组号,后面如果有括号,那么后面的括号依然有组号,只是费捕获分组没有组号(看后面的 \ \ 1变不变红)

image-20221218180456533

九.时间相关类

(一)JDK7以前时间相关类

Date类

image-20221219092955664

image-20221219093026393

  • get方法表示获取

  • set方法表示修改

  • 空参构造 会返回当前时间

    • Date d1 = new Date();
      
  • 代参构造 传入毫秒值参数后会返回距离时间原点的时间

    • Date d2 = new Date(0L);
      //结果
      Thu Jan 01 08:00:00 CST 1970
      注意返回的是东八区的时间
      
  • 四种方法介绍

public static void main(String[] args) {
        //1.创建对象表示一个时间
        Date d = new Date();
        System.out.println(d);//结果为Mon Dec 19 09:50:41 CST 2022


        //2.创建对象表示一个指定的时间
        //注意方法本身要传入long类型的数据,所以要在毫秒数后面加上L
        Date d1 = new Date(0L);
        //表示从时间原点开始,过了0毫秒的时间
        System.out.println(d1);//结果为Thu Jan 01 08:00:00 CST 1970


        //3.setTime 修改时间
        //1000毫秒等于一秒钟
        d1.setTime(1000L);
        System.out.println(d1);//结果为Thu Jan 01 08:00:01 CST 1970

        //所以再次打印时间的时候就比时间原点多一秒、

        //4.getTime获取当前时间的毫秒值
        long time = d1.getTime();
        System.out.println(time);//结果为1000
    }
  • 需求一:打印时间原点开始一年以后的时间
 public static void main(String[] args) {
        //1.创建一个对象,表示时间原点
        Date d2 = new Date(0L);

        //2.获取d2时间的毫秒值
        long time = d2.getTime();

        //3.在这个基础上,加一年的毫秒值
        time = time + 1000L*60*60*24*365;

        //4.把计算之后的时间毫秒值在设置回d2当中
        d2.setTime(time);

        //5.打印d2
        System.out.println(d2);

    }
//结果
Fri Jan 01 08:00:00 CST 1971
//可以看到时间比1970推后了一年
  • 思路:创建表示起始的时间、获取对应时间原点的毫秒值、在这个值的基础上加上要表示的时间到起始时间的毫秒值、用set方法设置变更后的时间、打印创建出的对象

  • 需求二:定义任意两个Date对象,比较一下哪个时间在前,哪个时间在后

public static void main(String[] args) {

        Random r = new Random();

        //创建两个时间对象,分别代表两个随机时间
        Date d1 = new Date(Math.abs(r.nextInt()));
        Date d2 = new Date(Math.abs(r.nextInt()));

        //不能直接if(d1>d2),因为这两个是时间对象,不是数据
        long time1 = d1.getTime();
        long time2 = d2.getTime();

        //打印时间
        System.out.println(d1);
        System.out.println(d2);

        //比较毫秒值
        if (time1>time2){
            System.out.println("第一个时间在后面,第二个时间在前面");
        }else if(time1<time2){
            System.out.println("第一个时间在前面,第二个时间在后面");
        }
    }
//结果
Thu Jan 22 19:55:43 CST 1970
Wed Jan 07 06:40:56 CST 1970
第一个时间在后面,第二个时间在前面

image-20221219102007314

SimpleDateFormat类

image-20221219102352139

  • 该类会创造不同格式的时间
  • 该类会把字符串形式的时间解析成毫秒对应的时间以便于进行运算

image-20221219102555776

  • 在SimpleDateFormat里传入想要达成的规范 注意中间有空格
  • 以下为API帮助文档里面的该类的内容规范
  • format是把时间格式化为字符串以便于打印

image-20221219102734571

//该类默认格式(空参构造方法) 
public static void main(String[] args) {


        SimpleDateFormat sdf = new SimpleDateFormat();
        Date d = new Date(0L);
        String str = sdf.format(d);
        System.out.println(str);

    }
//结果
1970/1/8 21:36
//说明这是SimpleDateFormat的默认格式
//指定格式(代参构造方法)
    public static void main(String[] args) {

        Date d1 = new Date(0L);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String str = sdf.format(d1);
        System.out.println(str);
        

    }
//结果
1970年01月01日 08:00:00
//在SimpleDateFormat里传入想要达成的规范 注意中间有空格
  • 解析字符串:可以把一个表示时间的字符串解析出来(让系统知道这是表示时间)
  • 有了毫秒值就可以对时间进行加减和判断,这就是解析的目的
  • parse是把字符串转变为时间让系统知道这个字符串表示一个时间
  • 注意SimpleDateFormat里面的参数是根据字符串的格式对照着写的
public static void main(String[] args) throws ParseException {
            //1.定义一个字符串表示时间
            String str = "2023-11-11 11:11:11";
            SimpleDateFormat sdf = new SimpleDateFormat("y-M-d HH:mm:ss");
    //注意SimpleDateFormat里面的参数是根据字符串的格式对照着写的
            Date date = sdf.parse(str);
            System.out.println(date);
            System.out.println(date.getTime());
    //打印时间的毫秒值(距离时间原点)

        }
//结果:
Sat Nov 11 11:11:11 CST 2023
1699672271000
/*假设,你初恋的出生年月日为:2000-11-11
请用字符串表示这个数据,并将其转换为:2000年11月11日*/
//思路:先解析,再格式化
public static void main(String[] args) throws ParseException {

            //1.获取字符串,并解析字符串,注意解析式的参数和字符串格式一致
            String str = "2000-11-11";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse(str);
            //2.重新格式化,注意要重新弄一个SimpleDateFormat,因为如果沿用上面的SimpleDateFormat,那么新日期的格式还是原来的格式
            SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日");
            String result = sdf2.format(date);
            System.out.println(result);

        }
//结果:
2000年11月11日
  • 上述代码步骤解释:

    • 先列出表示日期的字符串
    • 调用SimpleDateFormat 创建对象并进行有参构造,传入字符串日期的格式
    • 用刚刚创建的SimpleDateFormat的对象调用parse方法进行解析,获取字符串表示的时间,方法返回一个Date类型的对象,这个对象记录了字符串表示的时间
    • 这个时候就需要重新创建一个SimpleDateFormat对象,去格式化新的日期格式,传参传入新的日期格式
    • 用刚刚新创建的SimpleDateFormat的对象调用format方法,传参传入刚刚创建的日期对象,并返回一个字符串,打印字符串即可
  • 总结:字符串——日期——字符串

  • 练习

    • 注意字符串里面的日期的冒号必须是英文冒号
    • 字符串里什么时候有空格也要对应在SimpleDateFormat的参数里
       public static void main(String[] args) throws ParseException {

            /*需求:
秒杀活动:2023年11月11日0:0:0
开始时间:2023年11月11日0:10:0
小贾下单并付款的时间为:2023年11月11日0:01:00
小皮下单并付款的时间为:2023年11月11日0:11:0
用代码说明这两位同学有没有参加上秒杀活动?*/
            String str1 = "2023年11月11日 0:0:0";
            String str2 = "2023年11月11日 0:10:0";
            String str3 ="2023年11月11日 0:01:00";
            String str4 = "2023年11月11日 0:11:0";
            SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
            Date d1 = sdf1.parse(str1);
            Date d2 = sdf1.parse(str2);
            Date d3 = sdf1.parse(str3);
            Date d4 = sdf1.parse(str4);
            long time1 = d1.getTime();
            long time2 = d2.getTime();
            long time3 = d3.getTime();
            long time4 = d4.getTime();
            if (time3>=time1&&time3<=time2){
                System.out.println("小贾可以赶上");
            }else{
                System.out.println("小贾不可以赶上");
            }
            if (time4>=time1&&time4<=time2){
                System.out.println("小皮可以赶上");
            }else{
                System.out.println("小皮不可以赶上");
            }

        }
//结果:
小贾可以赶上
小皮不可以赶上

image-20221219120303125

calendar类

image-20221219143601438

  • calendar是一个抽象类,不能直接获取对象,需要通过他的getInstance获取其子类对象

image-20221219143716070

  • 字段:属性和成员变量的意思

image-20221219145606609

  • 查表法

image-20221219150042281

image-20221219150201359

  • 想要修改月份为12月,注意要写11

image-20221219150553187

  • 如果修改月份写了12,那么他不会报错,而是顺带把年份也往后调一个

image-20221219150707785

public static void main(String[] args) {
        //1.获取日历对象,利用其子类方法获取对象
        //细节:calendar是一个抽象类,不能直接new,而是通过一个静态方法得到子类对象
        //底层原理:
        //会根据系统的不同时区来获取不同的日历对象,默认显示当前时间
        //会把时间中的纪元,年,月,日 ,时,分,秒,星期,等等的都放到一个数组当中
        Calendar c = Calendar.getInstance();
        //2.修改一下日历代表的时间
        Date d = new Date(0L);
        c.setTime(d);
        c.set(Calendar.YEAR,2000);
        c.add(Calendar.YEAR,-1);
        int year = c.get(1);
        int month = c.get(2)+1;
        int date = c.get(5); 
        System.out.println(year+" "+month+" "+date);


    }

image-20221219151120676

(二)JDK8新增的时间相关类

image-20221219151709845

  • JDK7可以在原时间对象上修改时间,但无法避免多人修改时间时候产生的冲突
  • JDK8修改了上述冲突,即如果修改时间,那么就会产生一个新的对象记录修改后的时间,不会对原来对象记录的时间进行修改

image-20221219152109046

Date类

Zone时区
  • JAVA共定义了600多个时区
  • 但不用全部都背下来

image-20221219152246418

  • 常用的三个方法

image-20221219152416573

  • 快捷键:先点一下控制台 再 ctrl + f 可以在输出语句中寻找指定字符串
 public static void main(String[] args) {
        //获取所有时区的名称
        Set<String> zoneIds = ZoneId.getAvailableZoneIds();
        System.out.println(zoneIds);
     System.out.println(zoneIds.size());//结果为600,说明有600个时区
     
     //获取系统当前的默认时区
        ZoneId zoneId = ZoneId.systemDefault();
        System.out.println(zoneId);
     //结果为:Asia/Shanghai
     
      //获取指定的时区
        ZoneId zoneId1 = ZoneId.of("America/Cuiaba");
        System.out.println(zoneId1);
     //结果为:America/Cuiaba

    }
Instant时间戳

image-20221219154436302

//前两个方法
public static void main(String[] args) {
        //获取当前时间点Instance对象(标准时间:也就是说0时区时间)
        Instant now = Instant.now();
        System.out.println(now);//2022-12-19T08:10:45.114617200Z


        //根据(秒/毫秒/微秒)获取Instant对象
        Instant instant = Instant.ofEpochMilli(0L);//表示时间原点
        System.out.println(instant);//1970-01-01T00:00:00Z

        Instant instant1 = Instant.ofEpochSecond(1L);//表示时间原点过了1秒以后的时间
        System.out.println(instant1);//1970-01-01T00:00:01Z

        Instant instant2 = Instant.ofEpochSecond(1L, 1000000000);//表示时间原点过了1秒再加1后面9个0纳秒的时间
        System.out.println(instant2);//1970-01-01T00:00:02Z


    }
  • 注意:方法如果是静态方法,就要用类名去调用方法,如果不是静态的,就要用对象去调用
  • 指定时区方法
//首先用Instant.now()去获取时间的对象,再用时间的对象点上atZone(),这么做是因为now是静态方法,但是atZone不是静态方法
//atZone方法里面传的是ZoneId,而不是字符串,所以要用ZoneId调用of方法,再把时区名字放进去就好,相当于创建了一个时区对象,再把对象交给atZone方法
        //3.指定时区
        ZonedDateTime time = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));
        System.out.println(time);
//结果:2022-12-19T16:10:45.119618400+08:00[Asia/Shanghai]
//那么现在打印的就是标准的中国时间

  • isXxx判断
        Instant instant3 = Instant.ofEpochMilli(0L);
        Instant instant4 = Instant.ofEpochMilli(1000L);
        boolean result1 = instant3.isBefore(instant4);
		boolean result2 = instant3.isAfter(instant4);
        System.out.println(result1);//true
        System.out.println(result2);//false

image-20221219162139184

  • 加减时间
        //5.减少时间
        Instant instant5 = Instant.ofEpochSecond(1L);
        System.out.println(instant5);
//1970-01-01T00:00:01Z
        Instant instant6 = instant5.minusSeconds(-1);
        System.out.println(instant6);
//1970-01-01T00:00:02Z
//注意减负数就是加
ZoneDateTime

image-20221219163627168

image-20221219164849670

image-20221219164900793

DateTimeFormatter

image-20221219165942809

image-20221219170047432

ChronoUnit
  • 计算时间差

image-20221221090842463

        LocalDateTime of = LocalDateTime.of(2000, 11, 23, 0, 0, 0);
        LocalDateTime now = LocalDateTime.now();
        System.out.println("相差的天数   "+ ChronoUnit.DAYS.between(of,now));
//结果
相差的天数  8063

十.包装类

(一)基本方法和介绍
  • int Integer
  • char Character

image-20221221095049195

image-20221221095025664

  • valueof在底层的源码如下
    • 意思是底层会先遍历一个数组,这个数组名字是cache,在low和hige的范围内查找,如果传入的元素在这个范围内,就直接返回一个已经存在的对象,那么无论个这个数起什么名字,用==号判断,地址都是一样的(true),但一旦超出这个范围(-128~127),就会重新new一个对象,一旦用了new,不管传入的数据值是否相同,地址值都不同(这个设定可以防止浪费内存)
  • size=(high-low)+1原因,有个0

image-20221221095147762

image-20221221095548674

(二)拆箱和装箱
  • 想用包装类的数据进行计算时,不能直接相加减,而是先拆箱:把包装类变成基本数据类型,加减之后获取返回结果(这个结果也是基本数据类型),之后在进行装箱,把基本数据类型变回包装类
//以前的写法:包装类的计算    
Integer integer1 = Integer.valueOf(1);//创建两个包装类
        Integer integer2 = Integer.valueOf(2);
        int i = integer1.intValue()+ integer2.intValue();//变成基本数据类型
        Integer integer3 = new Integer(i);//再把结果变成包装类
        System.out.println(integer3);
//结果
3
  • 在JDK5的时候,提出了一种机制:叫做自动装箱和自动拆箱
    • 自动装箱:把基本数据类型自动变成对应的包装类
    • 自动拆箱:把包装类自动变成基本数据类型
  • 此时还会自动调用valueof方法得到一个Integer对象,只不过这个动作不需要手动完成
public class Main {
    public static void main(String[] args) {
         
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;
         
        System.out.println(c==d);
        System.out.println(e==f);
        System.out.println(c==(a+b));
        System.out.println(c.equals(a+b));
        System.out.println(g==(a+b));
        System.out.println(g.equals(a+b));
        System.out.println(g.equals(a+h));
    }
}
/*true
false
true
true
true
false
true*/
//上述代码反编译为
package com.mao.a_box;
 
import java.io.PrintStream;
 
public class Main
{
 
	public Main()
	{
	}
 
	public static void main(String args[])
	{
		Integer a = Integer.valueOf(1);
		Integer b = Integer.valueOf(2);
		Integer c = Integer.valueOf(3);
		Integer d = Integer.valueOf(3);
		Integer e = Integer.valueOf(321);
		Integer f = Integer.valueOf(321);
		Long g = Long.valueOf(3L);
		Long h = Long.valueOf(2L);
		System.out.println(c == d);
		System.out.println(e == f);
		System.out.println(c.intValue() == a.intValue() + b.intValue());
		System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
		System.out.println(g.longValue() == (long)(a.intValue() + b.intValue()));
		System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
		System.out.println(g.equals(Long.valueOf((long)a.intValue() + h.longValue())));
	}
}
  • 可以看到第一个和第二个输出结果没有什么疑问。第三句由于 a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。而对于c.equals(a+b)会先触发自动拆箱过程,再触发自动装箱过程,也就是说a+b,会先各自调用intValue方法,得到了加法运算后的数值之后,便调用Integer.valueOf方法,再进行equals比较。同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。

image-20221221103028069

(三)Integer成员方法

image-20221221103145313

        //1.把整数转成二进制,十六进制
        String s1 = Integer.toBinaryString(100);
        System.out.println(s1);//1100100

        //2.把整数转成八进制
        String s2 = Integer.toOctalString(100);
        System.out.println(s2);//144

        //3.把整数转成十六进制
        String s3 = Integer.toHexString(100);
        System.out.println(s3);//64

        //4.将字符串类型的整数转成int类型的整数
        //强类型语言:每种数据在JAVA中都有各自的数据类型
        //在计算的时候,如果不是同一种数据类型,是无法直接进行计算的
        int i11 = Integer.parseInt("123");
        System.out.println(i11+1);//124(说明这是字符串转换成整形之后再加一)
        //细节1:
        //在细节转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错
        //细节2:
        //八种包装类中,除了character都有对应的parseXxx方法,进行类型转换
        String str = "true";
        boolean b = Boolean.parseBoolean(str);//注意是大B,因boolean的包装类是Boolean
        System.out.println(b);//true
        //那么有了这个类型转换之后,就可以改写键盘录入的代码了

image-20221221105856922

  • 上图说明键盘录入以后,可以直接用包装类.parseXxx方法接收字符串,再用相应的包装类方法直接转换成对应的基本数据类型
综合练习

image-20221221111114269

    public static void main(String[] args) {
        //1.创建一个集合用来添加整数
        ArrayList<Integer> list = new ArrayList<>();
        //2.键盘录入数据添加到集合当中
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("请输入一个整数:");
            String numStr = scanner.nextLine();
            int num = Integer.parseInt(numStr);
            //过滤异常数据
            if (num<1||num>100){
                System.out.println("数据有误!");
            }else {
                list.add(num);
                int sum = getSum(list);
                //对sum进行判断
                if (sum>200){
                    System.out.println("集合中所有的数据和已经超过200!");
                    break;
                }
            }
        }
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i)+" ");
        }
    }
    private static int getSum(ArrayList<Integer> list) {
        int sum = 0;
        for (int i = 0; i < list.size(); i++) {
            //Integer num = list.get(i); 这里前面的int可以手动改成int因为JAVA可以自动拆箱
            Integer num = list.get(i);
            sum = sum + num;
        }
        return sum;
    }

image-20221221142957158

//思路,得到某数字字符串中的每一位数字,再用ASCII码表获取每一位字符得到ASCII表互相减得到数字 即-'0' 
    public static void main(String[] args) {

        //1.定义一个字符串
        String str = "123456789";
        //2.校验字符串
        //习惯:先学会把异常的数据进行过滤,剩下来的就是正常的数据
        if (!str.matches("[1-9]\\d{0,9}")) {
            //异常的数据(注意前面有取反)
            System.out.println("数据格式有误!");
        }else {
            System.out.println("格式正确!");
            int number = 0;
            for (int i = 0; i < str.length(); i++) {
                int c = str.charAt(i)-'0';
                System.out.print(c+" ");
                number = number*10+c;
                System.out.println(number);
            }

        }
//注意本题的巧妙之处:
        1.
        int c = str.charAt(i)-'0';
        利用ASCII码来进行获取数字。而不是单纯想着把charAt获取的字符直接变成int
        2.
        number = number*10+c;
        这样就可以把每次获取的数字配合上上一次循环获取的数字变成实际的数

image-20221221145544080

    public static void main(String[] args) {

        System.out.println(toBinary(100));
        System.out.println(Integer.toBinaryString(100));
    }

    public static String toBinary(int number){
        //不断除以余2,得到余数,一直商到0就截止
        //还需要把余数倒着拼接起来
        //定义一个StringBuilder用来拼接余数
        StringBuilder sb = new StringBuilder();

        //利用循环不断除以二获取余数
        while (true) {
            if (number==0){
                break;
            }
            //获取余数
            int remainder = number%2;
            //利用insert函数,前一位是要插入数字的索引位置,后一位是要插入的字符
            sb.insert(0,remainder);
            number = number/2;

        }
        return sb.toString();
    }
//结果:
1100100
1100100

image-20221221151722069

    public static void main(String[] args) throws ParseException {

        //JDK7
        //规则:只要对时间进行计算或判断,都需要先获取当前时间的毫秒值
        //1.计算自己出生年月的毫秒值
        String birthday = "2000年1月1日";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        Date date = sdf.parse(birthday);
        long birthdayTime = date.getTime();
        //2.获取当前时间的毫秒值
        long todayTime = System.currentTimeMillis();
        //3.计算间隔多少天
        long time = todayTime - birthdayTime;
        System.out.println(time/60/1000/60/24);

        //JDK8
        LocalDate ld1 = LocalDate.of(2000,1,1);
        //LocalDate是计算年月日但是LocalDateTime是计算包括时分秒在内的
        LocalDate now = LocalDate.now();//2022.12.21
        System.out.println(ChronoUnit.DAYS.between(ld1, now));

    }
//结果
8376
8376

image-20221221153051012

public static void main(String[] args) {
        //jdk7
        //简化
        //我们可以把时间设置为2000年3月1日
        Calendar c = Calendar.getInstance();
        c.set(2000,2,1);//月份的范围0~11
        //把日期往前减一天
        c.add(Calendar.DAY_OF_MONTH,-1);
        //看当前时间是28号还是29号
        int day = c.get(Calendar.DAY_OF_MONTH);
        System.out.println(day);

        //jdk8
        //设定时间为2000年3月1号
        LocalDate of = LocalDate.of(2000, 3, 1);//月份是正常的1~12
        //把时间往前减一天
        LocalDate localDate = of.minusDays(1);
        System.out.println(localDate);
        //获取这一天是一个月的几号
        int dayOfMonth = localDate.getDayOfMonth();
        System.out.println(dayOfMonth);
    }
        //jdk8还有一个方法直接判断是不是闰年
        System.out.println(localDate.isLeapYear());
//结果:
29
2000-02-29
29
    true

十一.字符串

image-20221221163846258

(一)String

  • java.lang.String类代表字符串,Java程序中的所有字符串文字(例如“abc”)都为此类的对象。
  • 字符串的内容是不会发生改变的,它的对象在创建后不能被更改
  • 字符串拼接会产生一个新的字符串
1.1 构造方法

image-20221221164831412

public static void main(String[] args) {

         //1.直接赋值方式获取一个字符串
        String s1 = "abc";
        System.out.println(s1);//abc

        //2.使用new的方式来获取一个字符串对象
        //传递一个字符数组,依照字符数组的内容再创建一个新的字符串对象
        //应用场景:因为字符串一旦创建就不可以改变,但是数组是可以改变的,所以就可以通过改变数组,再把数组赋值给字符串,来达到改变字符串的目的
        //比如要把 "abc"-->"Qbc" 就可以 {'a','b','c'} --> {'Q','b','c'}
        char[] chs = {'a','b','c','d'};
        String s4 = new String(chs);
        System.out.println(s4);//abcd

        //传递一个字节数组,根据字节数组的内容再创建一个新的字符串对象;
        byte[] bytes = {97,98,99,100};
        String s2 = new String(bytes);
        System.out.println(s2);//abcd
        //可以看到,字节数组赋值给字符串其实是先把数字通过ASCII码转为字符,再拼接起来赋值给字符串
        //应用场景:在网络中传输的数据其实都是字节信息,我们一般要把字节信息转换为字符串才能看懂信息

    }
1.1.1 内存原理

image-20230101113210511

  • 串池:存储创建出来的字符串
    • 下图的机制可以节约内存
    • 直接赋值得到的字符串会存储到串池中
    • 字符串在串池中存储的是地址

image-20230101113424561

  • 手动new出来的字符串
    • 每new一次就是开辟一个新的空间,s1和s2虽然字符串内容相同,但是地址值不同,所以new创建的字符串不会复用,如果相同的字符串比较多就比较浪费空间

image-20230104110047157

1.1.2 字符串的比较

image-20230104110350872

image-20230104110458189

image-20230104110609446

 public static void main(String[] args) {

        //1.假设现在键盘录入一个abc
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String s1 = sc.next();//abc 是new出来的
        //2.代码中再定义一个字符串abc;
        String s2 = "abc";
        //3.用==号比较,这两者能一样吗?
        System.out.println(s1 == s2);//false
        
        //结论:
        //以后想要比较字符串里面的内容,就要用String里面的方法
        
    }
1.1.3 练习
1.1.3.1 用户登录

image-20230104111833295

public static void main(String[] args) {
        //1.定义两个变量用来记录正确的用户名和密码
        String s1 = "zhangsan";
        String s2 = "123456";
        Scanner sc = new Scanner(System.in);
        //2.接受信息
        for (int i = 0; i < 3; i++) {

            System.out.println("请输入用户名:");
            String s3 = sc.next();
            System.out.println("请输入密码:");
            String s4 = sc.next();

            //3.比较
            if (s3.equals(s1)&&s4.equals(s2)){
                System.out.println("登录成功");
                break;
            }else {
                System.out.println("登录失败");
            }
        }
        
    }
1.1.3.2 遍历字符串

image-20230104164256092

image-20230104164525063

public static void main(String[] args) {

        //1.键盘录入一个字符串
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入一个字符串");
        String str = scanner.nextLine();
        //2.进行遍历
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            System.out.println(c);
        }
    }
1.1.3.3 统计字符个数

image-20230104165128892

public static void main(String[] args) {

        //1.键盘录入一个字符串
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入一个字符串");
        String str = scanner.nextLine();
        //2.进行遍历
        int bigcount = 0;
        int smallcount = 0;
        int numbercount = 0;

        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            if (c>='a'&&c<='z'){
                smallcount++;

            } else if (c>='A'&&c<='Z') {
                bigcount++;
            } else if (c>='0'&&c<='9') {
                numbercount++;
            }
        }
        System.out.println("大写有"+bigcount);
        System.out.println("小写有"+smallcount);
        System.out.println("数字有"+numbercount);

    }
1.1.3.4 拼接字符串

image-20230104171102805

//注意题目是要定义一个返回字符串的方法
public static void main(String[] args) {

    int[] arr = {1,2,3,4,5,6};
    System.out.println(toString(arr));


}
public static String toString(int[] arr){
    String result = "[";
    if (arr==null){
        return "";
    }else if (arr.length==0){
        return "[]";
    } else {
        for (int i = 0; i < arr.length; i++) {

            if (i<arr.length-1){
                result=result+arr[i]+",";

            }else {
                result=result+arr[i]+"]";
            }
        }
        return result;
    }


}
1.1.3.5 字符串反转

image-20230104172514527

  • 快捷键 arr.length().fori 是从i=0开始正着遍历
  • 快捷键 arr.length().forr 是从i=length开始倒着遍历
  • 快捷键 shift+F6 是批量修改单词
//方法:倒着获取字符串里面的每一个字符,再把每一个字符做拼接
public static void main(String[] args) {
    System.out.println(reverse("abc"));
}
public static String reverse(String arr){

    String str = "";//定义一个长度为0的字符串
    for (int i = arr.length()-1; i >= 0; i--) {
            char c = arr.charAt(i);
        str = str+c;//将字符串进行拼接
    }
    return str;//返回一个长度为0的字符串
}
1.1.3.6 金额转换

image-20230104173917065

public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int money;
    while (true) {
        System.out.println("请输入金额:");
        money = sc.nextInt();
        if(money>0&&money<99999999){
            break;
        }else if(money<=0){
            System.out.println("金额无效!");
        }
    }
    //定义一个变量表示钱的大写
    String moneyStr = "";

    //得到money的每一位数字,基础拼接
    while (true){
        int ge = money%10;//通过余数获取最后一位

        String getnumber = getnumber(ge);
        moneyStr = getnumber + moneyStr;//注意这里把拼接顺序翻一下,产生的效果大相径庭,把反着的字符串就正过来了
        money = money/10;//通过除以10获得整数部分获取剩余位数

        if (money == 0){
            break;
        }
    }

    //在前面补0,补齐7位
    int count = 7 - moneyStr.length();
    for (int i = 0; i < count; i++) {
        moneyStr = "零" + moneyStr;

    }
    //开始打印
    //插入单位:佰拾万仟佰拾元
    //定义拼接结果
    String result = "";
    //定义数组表示单位
    String[] arr = {"佰","拾","万","仟","佰","拾","元"};
    for (int i = 0; i < moneyStr.length(); i++) {
        result = result + moneyStr.charAt(i) + arr[i];

    }
    System.out.println(result);

}

public static String getnumber(int number){
    //定义数组,让数字和大写汉字产生对应关系
    String[] arr = {"零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};
    return arr[number];

}
//注意result = result + str.charAt(i) + arr[i];字符串拼接的时候一定要有自我的累加
1.1.3.7 手机号屏蔽

image-20230104230327046

public static void main(String[] args) {
    //1.获取一个手机号码
    String phoneNumber = "13112349468";

    //2.截取手机号码
    String start = phoneNumber.substring(0, 3);//包左不包右

    String end = phoneNumber.substring(7);//截取从包括数字代表的索引一直到末尾

    //3.拼接
    String str = start + "****" + end;
    System.out.println(str);


}
1.1.3.8 敏感词替换

image-20230104232724952

public static void main(String[] args) {

    //1.获取说的话
    String talk = "你玩的真好,以后不要再玩了,TMD,CNM";
    //2.定义一个敏感词库
    String[] arr = {"TMD","CNM","SB","MLGB"};
    //3.循环得到每一个敏感词,依次替换
    for (int i = 0; i < arr.length; i++) {
        talk = talk.replace(arr[i],"***");
    }
    //4.打印结果
    System.out.println(talk);
    
}

(二)StringBuilder

2.1 StringBuilder概述
  • StringBuilder可以看成是一个容器,创建之后容器里面的内容是可变的
  • 作用:提高字符串的操作效率

image-20230106211103310

2.2 构造方法

image-20230106211237050

2.3 成员方法

image-20230106211336871

public class StringBuilderDemo01 {
    public static void main(String[] args) {


        //1.创建对象
        StringBuilder sb = new StringBuilder("abc");

        //2.添加元素
        sb.append(1);
        sb.append(2.3);
        sb.append("三联");

        //3.reverse反转字符串
        //反转,跟字符串不一样,字符串一旦定义之后不可修改,而reverse直接操作容器
        sb.reverse();//联三3.21cba

        //打印:
        /*普及:
        * 因为StringBuilder是java已经写好的类
        * Java在底层对他做了特殊处理
        * 打印对象不是地址值而是属性值
        * */
        System.out.println(sb);

        //4.获取字符串长度
        System.out.println(sb.length());//9

        //5.toString
        //toString的作用就是把StringBuilder类型的对象sb变成String类型的字符串str,
        //sb只是一个容器,这个对象不是字符串,字符串相关的函数他不能直接用,比如subString等
        // 所以要把StringBuilder类型的sb用toString方法变成字符串类型的对象
        String s = sb.toString();
        System.out.println(s);//联三3.21cba

        //可用于直接将用户输入的转化为StringBiulder类型,给大家分享一下
        //StringBiulder s=new StringBuilder(sc.next());


    }
}
  • 注意:

    • toString的作用就是把StringBuilder类型的对象sb变成String类型的字符串str,sb只是一个容器,这个对象不是字符串,字符串相关的函数他不能直接用,比如subString等, 所以要把StringBuilder类型的sb用toString方法变成字符串类型的对象

    • 可用于直接将用户输入的转化为StringBiulder类型,给大家分享一下 StringBiulder s=new StringBuilder(sc.next());

2.4 练习
2.4.1 对称字符串

image-20230107220343243

public static void main(String[] args) {
    //键盘录入一个字符串
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入一个字符串:");
    String str = sc.next();
    //反转键盘录入的字符串,用StringBuilder里面的reverse方法
    //当然想用reverse方法需要先有一个StringBuilder对象
    //容器里面需要先有值
    StringBuilder sb = new StringBuilder();
    sb.append(str);

    //链式编程
    String s = new StringBuilder().append(str).reverse().toString();
    System.out.println(s);

    //比较,原来和反转以后的一样就是回文字符串
    if (str.equals(s)){
        System.out.println("是回文字符串");
    }else {
        System.out.println("不是回文字符串");
    }


    //StringBuilder的equals如果不进行重写那么默认比较地址,String的equals重写了
    if (sb.equals(sb.reverse())){
        System.out.println("是回文字符串");
    }else {
        System.out.println("不是回文字符串");
    }
    //打印结果一直是"是回文字符串",因为地址一样


}
  • 什么情况下会用到StringBuilder?

    • 1.字符串的拼接
    • 2.字符串的反转
  • StringBuilder的equals如果不进行重写那么默认比较地址,而String的equals重写了,详情看上方例子,靠下方的打印的一直是"是回文字符串",因为sb地址一样

2.4.2 拼接字符串

image-20230107231815820

public static void main(String[] args) {
    //定义数组
    int[] arr = {1,2,3};
    //调用方法把数组变成字符串
    String s = attyoString(arr);
    System.out.println(s);


}
public static String attyoString(int[] arr){

    StringBuilder sb = new StringBuilder();
    sb.append("[");
    for (int i = 0; i < arr.length; i++) {
        if (i==arr.length-1){
            sb.append(arr[i]);
        }else {
            sb.append(arr[i]+",");
        }
    }
    sb.append("]");
    return sb.toString();

}
//[1,2,3]

(三)StringJoiner

3.1 构造方法

image-20230107233926264

image-20230107233958883

3.2 成员方法

image-20230107234024574

import java.util.Scanner;
import java.util.StringJoiner;
import org.junit.Test;

public class StringBuilderDemo02 {
    @Test
    public void testStringBuilderDemo01(){
        //1.创建一个对象,并指定中间的间隔符号
        StringJoiner sj = new StringJoiner("---");
        //2.添加元素
        sj.add("aaa").add("bbb").add("ccc");
        //3.打印
        System.out.println(sj);//aaa---bbb---ccc

    }

    @Test
    public void testStringBuilderDemo02(){
        //1.创建一个对象,并指定中间的间隔符号
        StringJoiner sj = new StringJoiner(",","[","]");
        //2.添加元素
        sj.add("aaa").add("bbb").add("ccc");
        //3.打印
        System.out.println(sj);//[aaa,bbb,ccc]
    }

    @Test
    public void testStringBuilderDemo03(){
        //1.创建一个对象,并指定中间的间隔符号
        StringJoiner sj = new StringJoiner(", ","[","]");
        //2.添加元素
        sj.add("aaa").add("bbb").add("ccc");
        //3.获取长度(获取的是从开头到结尾所有字符的个数,包括左右括号,逗号空格)
        int len = sj.length();
        System.out.println(len);//15
    }

    @Test
    public void testStringBuilderDemo04(){
        //1.创建一个对象,并指定中间的间隔符号
        StringJoiner sj = new StringJoiner(", ","[","]");
        //2.添加元素
        sj.add("aaa").add("bbb").add("ccc");
        //3.toString变成字符串
        String str = sj.toString();
        System.out.println(str);//[aaa, bbb, ccc]
    }


}

(四)字符串原理

image-20230107235713302

image-20230107235759280

扩展底层原理3:字符串拼接原理

  • 等号右边没有变量

image-20230108000657315

  • 等号右边存在变量

image-20230108000854141

image-20230108110615282

image-20230108111216864

  • 扩展底层原理5:StringBuilder源码分析

image-20230108111544889

image-20230108111705373

  • 扩容之后还不够,容量以实际长度为准

image-20230108111741975

image-20230108112709885

(五)练习

5.1 转换罗马数字

image-20230108112913493

//运用查表法
@Test
public void test1(){

    String str = "13579";
    System.out.println(test2(str));
    StringBuilder sb = new StringBuilder();
    if (test2(str)){
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            int number = c-48;//0的asc是48,如果这里不减去48,那么当number代表的就是c的asc,而0-9的asc从48-57
            sb.append(change(number));
    }
        System.out.println(sb);//ⅠⅢⅤⅦⅨ
        
    }

}

public static String change(int number){

    String arr[] = {"","Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ"};
    return arr[number];

}

public static boolean test2(String str){
    //长度小于等于9
    if (str.length()>9){
        return false;
    }
    //只能是数字
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (c<'0'||c>'9'){
            return false;
        }

    }
    //只有当字符串全部判断完了再return true
    return true;

}
  • 新思路:switch
    • 下面的String str = switch(number)的写法是jdk12之后提出的

image-20230108120300675

  • 右箭头自带break语句,整个switch代表,如果传进来的char number是字符1,那么就把右箭头右面的罗马数字一赋值给str变量,但是这种写法注意在switch的花括号右边加上分号,最后把strreturn出去就好

  • default 可用于在上面所有 case 都不为真时执行一个任务。default case 中的 break 语句不是必需的。

5.2 调整字符串

image-20230108120739674

  • 代码实现
  • 方法一:截取字符串
package web.listener;

public class test {
    public static void main(String[] args) {
        //定义两个字符串
        String strA = "abcde";
        String strB = "cdeab";

        //调用写好的方法旋转字符串
        boolean check = check(strA, strB);
        System.out.println(check);//true
    }

    public static boolean check(String strA,String strB) {
        //循环的目的就是限制旋转次数 , 次数和A字符串长度一样
        for (int i = 0; i < strA.length(); i++) {
            strA = rotate(strA);
            if (strA.equals(strB)){
                return true;
            }
        }
        return false;
    }

    //方法作用:旋转字符串,把左侧的字符移动到右边去
    //形参:旋转前的字符串
    //返回值:旋转后的字符春
    public static String rotate(String str){
        //套路:
        //如果以后有调整字符串的要求
        //可以有两个办法:
        //1.用subString把字符串进行截取 , 左边的截取出来拼接到右边去
        //2.把字符串变成一个字符数组 , 然后调整数组里面的数据 , 最后再把数组变成字符串

        //这里我们选择截取思路
        //获取最左侧的字符
        char first = str.charAt(0);
        //获取剩余的字符
        String end = str.substring(1);

        return end + first;


    }
}
  • 方法二:变成字符数组

在这里插入图片描述

package web.listener;

public class test2 {
    public static void main(String[] args) {
        //定义两个字符串
        String strA = "abcde";
        String strB = "bcdea";

        //调用写好的方法旋转字符串
        boolean check = check(strA, strB);
        System.out.println(check);
    }

    public static boolean check(String strA,String strB) {
        //循环的目的就是限制旋转次数 , 次数和A字符串长度一样
        for (int i = 0; i < strA.length(); i++) {
            strA = rotate(strA);
            if (strA.equals(strB)){
                return true;
            }
        }
        return false;
    }

    //方法作用:旋转字符串,把左侧的字符移动到右边去
    //形参:旋转前的字符串
    //返回值:旋转后的字符春
    public static String rotate(String str){
        //套路:
        //如果以后有调整字符串的要求
        //可以有两个办法:
        //1.用subString把字符串进行截取 , 左边的截取出来拼接到右边去
        //2.把字符串变成一个字符数组 , 然后调整数组里面的数据 , 最后再把数组变成字符串


        //变成字符数组
        char[] arr = str.toCharArray();
        //拿到0索引上的字符
        char first = arr[0];
        //把剩余的字符依次往前挪一个位置, 就是把第i位上的字符挪到前面,注意这里i=1
        for (int i = 1; i < arr.length; i++) {
           arr[i-1] = arr[i];
        }
        //把0索引上的字符放到最后一个位置
        arr[arr.length-1] = first;
        //利用字符数组创建一个字符串对象
        String result = new String(arr);
        return result;

    }
}
  • 注意有个思路:可以倒着遍历
    的。**==
5.2 调整字符串

[外链图片转存中…(img-Kwqjt5eA-1734369307294)]

  • 代码实现
  • 方法一:截取字符串
package web.listener;

public class test {
    public static void main(String[] args) {
        //定义两个字符串
        String strA = "abcde";
        String strB = "cdeab";

        //调用写好的方法旋转字符串
        boolean check = check(strA, strB);
        System.out.println(check);//true
    }

    public static boolean check(String strA,String strB) {
        //循环的目的就是限制旋转次数 , 次数和A字符串长度一样
        for (int i = 0; i < strA.length(); i++) {
            strA = rotate(strA);
            if (strA.equals(strB)){
                return true;
            }
        }
        return false;
    }

    //方法作用:旋转字符串,把左侧的字符移动到右边去
    //形参:旋转前的字符串
    //返回值:旋转后的字符春
    public static String rotate(String str){
        //套路:
        //如果以后有调整字符串的要求
        //可以有两个办法:
        //1.用subString把字符串进行截取 , 左边的截取出来拼接到右边去
        //2.把字符串变成一个字符数组 , 然后调整数组里面的数据 , 最后再把数组变成字符串

        //这里我们选择截取思路
        //获取最左侧的字符
        char first = str.charAt(0);
        //获取剩余的字符
        String end = str.substring(1);

        return end + first;


    }
}
  • 方法二:变成字符数组

[外链图片转存中…(img-TSBaA3TN-1734369307294)]

package web.listener;

public class test2 {
    public static void main(String[] args) {
        //定义两个字符串
        String strA = "abcde";
        String strB = "bcdea";

        //调用写好的方法旋转字符串
        boolean check = check(strA, strB);
        System.out.println(check);
    }

    public static boolean check(String strA,String strB) {
        //循环的目的就是限制旋转次数 , 次数和A字符串长度一样
        for (int i = 0; i < strA.length(); i++) {
            strA = rotate(strA);
            if (strA.equals(strB)){
                return true;
            }
        }
        return false;
    }

    //方法作用:旋转字符串,把左侧的字符移动到右边去
    //形参:旋转前的字符串
    //返回值:旋转后的字符春
    public static String rotate(String str){
        //套路:
        //如果以后有调整字符串的要求
        //可以有两个办法:
        //1.用subString把字符串进行截取 , 左边的截取出来拼接到右边去
        //2.把字符串变成一个字符数组 , 然后调整数组里面的数据 , 最后再把数组变成字符串


        //变成字符数组
        char[] arr = str.toCharArray();
        //拿到0索引上的字符
        char first = arr[0];
        //把剩余的字符依次往前挪一个位置, 就是把第i位上的字符挪到前面,注意这里i=1
        for (int i = 1; i < arr.length; i++) {
           arr[i-1] = arr[i];
        }
        //把0索引上的字符放到最后一个位置
        arr[arr.length-1] = first;
        //利用字符数组创建一个字符串对象
        String result = new String(arr);
        return result;

    }
}
  • 注意有个思路:可以倒着遍历
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值