JAVA算法和数据结构

一、Arrays类

1.1 Arrays基本使用

我们先认识一下Arrays是干什么用的,Arrays是操作数组的工具类,它可以很方便的对数组中的元素进行遍历、拷贝、排序等操作。

下面我们用代码来演示一下:遍历、拷贝、排序等操作。需要用到的方法如下

public class ArraysTest1 {
    public static void main(String[] args) {
        // 1、public static String toString(类型[] arr): 返回数组的内容
        int[] arr = {10, 20, 30, 40, 50, 60};
        System.out.println(Arrays.toString(arr));
​
        // 2、public static 类型[] copyOfRange(类型[] arr, 起始索引, 结束索引) :拷贝数组(指定范围,包前不包后)
        int[] arr2 = Arrays.copyOfRange(arr, 1, 4);
        System.out.println(Arrays.toString(arr2));
​
        // 3、public static copyOf(类型[] arr, int newLength):拷贝数组,可以指定新数组的长度。
        int[] arr3 = Arrays.copyOf(arr, 10);
        System.out.println(Arrays.toString(arr3));
​
        // 4、public static setAll(double[] array, IntToDoubleFunction generator):把数组中的原数据改为新数据又存进去。
        double[] prices = {99.8, 128, 100};
        //                  0     1    2
        // 把所有的价格都打八折,然后又存进去。
        Arrays.setAll(prices, new IntToDoubleFunction() {
            @Override
            public double applyAsDouble(int value) {
                // value = 0  1  2
                return prices[value] * 0.8;
            }
        });
        System.out.println(Arrays.toString(prices));
​
        // 5、public static void sort(类型[] arr):对数组进行排序(默认是升序排序)
        Arrays.sort(prices);
        System.out.println(Arrays.toString(prices));
    }
}

1.2 Arrays操作对象数组

刚才我们使用Arrays操作数组时,数组中存储存储的元素是int类型、double类型,是可以直接排序的,而且默认是升序排列。

如果数组中存储的元素类型是自定义的对象,如何排序呢?接下来,我们就学习一下Arrays如何对对象数组进行排序。

首先我们要准备一个Student类,代码如下:

public class Student implements Comparable<Student>{
    private String name;
    private double height;
    private int age;
    
    public Student(String name, double height, int age) {
        this.name = name;
        this.height = height;
        this.age = age;
    }
​
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", height=" + height +
                ", age=" + age +
                '}';
    }
}
​

然后再写一个测试类,往数组中存储4个学生对象,代码如下。此时,运行代码你会发现是会报错的。

public class ArraysTest2 {
    public static void main(String[] args) {
        // 目标:掌握如何对数组中的对象进行排序。
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
​
        // 1、public static void sort(类型[] arr):对数组进行排序。
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

上面的代码为什么会报错呢?因为Arrays根本就不知道按照什么规则进行排序。为了让Arrays知道按照什么规则排序,我们有如下的两种办法。

  • 排序方式1:让Student类实现Comparable接口,同时重写compareTo方法。Arrays的sort方法底层会根据compareTo方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。代码如下:

public class Student implements Comparable<Student>{
    private String name;
    private double height;
    private int age;
    
    //...get、set、空参数构造方法、有参数构造方法...自己补全
​
    // 指定比较规则
    // this  o
    @Override
    public int compareTo(Student o) {
        // 约定1:认为左边对象 大于 右边对象 请您返回正整数
        // 约定2:认为左边对象 小于 右边对象 请您返回负整数
        // 约定3:认为左边对象 等于 右边对象 请您一定返回0
        /* if(this.age > o.age){
            return 1;
        }else if(this.age < o.age){
            return -1;
        }
        return 0;*/
​
        //上面的if语句,也可以简化为下面的一行代码
        return this.age - o.age; // 按照年龄升序排列
        // return o.age - this.age; // 按照年龄降序排列
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", height=" + height +
                ", age=" + age +
                '}';
    }
}
  • 排序方式2:在调用Arrays.sort(数组,Comparator比较器);时,除了传递数组之外,传递一个Comparator比较器对象。Arrays的sort方法底层会根据Comparator比较器对象的compare方法方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。代码如下

public class ArraysTest2 {
    public static void main(String[] args) {
        // 目标:掌握如何对数组中的对象进行排序。
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
​
        // 2、public static <T> void sort(T[] arr, Comparator<? super T> c)
        // 参数一:需要排序的数组
        // 参数二:Comparator比较器对象(用来制定对象的比较规则)
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                // 制定比较规则了:左边对象 o1   右边对象 o2
                // 约定1:认为左边对象 大于 右边对象 请您返回正整数
                // 约定2:认为左边对象 小于 右边对象 请您返回负整数
                // 约定3:认为左边对象 等于 右边对象 请您一定返回0
//                if(o1.getHeight() > o2.getHeight()){
//                    return 1;
//                }else if(o1.getHeight() < o2.getHeight()){
//                    return -1;
//                }
//                return 0; // 升序
                 return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
                // return Double.compare(o2.getHeight(), o1.getHeight()); // 降序
            }
        });
        System.out.println(Arrays.toString(students));
    }
}

二、Lambda表达式

JDK8新增的一种语法形式,叫做Lambda表达式。作用:用于简化匿名内部类代码的书写。

2.1 Lambda表达式基本使用

怎么去简化呢?Lamdba是有特有的格式的,按照下面的格式来编写Lamdba。

(被重写方法的形参列表) -> {
    被重写方法的方法体代码;
}

需要给说明一下的是,在使用Lambda表达式之前,必须先有一个接口,而且接口中只能有一个抽象方法。(注意:不能是抽象类,只能是接口)

像这样的接口,我们称之为函数式接口,只有基于函数式接口的匿名内部类才能被Lambda表达式简化。

public interface Swimming{
    void swim();
}

有了以上的Swimming接口之后,接下来才能再演示,使用Lambda表达式,简化匿名内部类书写。

public class LambdaTest1 {
    public static void main(String[] args) {
        // 目标:认识Lambda表达式.
        //1.创建一个Swimming接口的匿名内部类对象
		Swimming s = new Swimming(){
             @Override
             public void swim() {
                 System.out.println("学生快乐的游泳~~~~");
             }
         };
         s.swim();
		
        //2.使用Lambda表达式对Swimming接口的匿名内部类进行简化
        Swimming s1 = () -> {
              System.out.println("学生快乐的游泳~~~~");
        };
        s1.swim();
    }
}

好的,我们现在已经知道Lamdba表达式可以简化基于函数式接口的匿名内部类的书写。接下来,我们可以把刚才使用Arrays方法时的代码,使用Lambda表达式简化一下了。

public class LambdaTest2 {
    public static void main(String[] args) {
        // 目标:使用Lambda简化函数式接口。
        double[] prices = {99.8, 128, 100};
        //1.把所有元素*0.8: 先用匿名内部类写法
        Arrays.setAll(prices, new IntToDoubleFunction() {
            @Override
            public double applyAsDouble(int value) {
                // value = 0  1  2
                return prices[value] * 0.8;
            }
        });
        //2.把所有元素*0.8: 改用Lamdba表达式写法
        Arrays.setAll(prices, (int value) -> {
                return prices[value] * 0.8;
        });
​
        System.out.println(Arrays.toString(prices));
        System.out.println("-----------------------------------------------");
        
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
        //3.对数组中的元素按照年龄升序排列: 先用匿名内部类写法
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
            }
        });
        //4.对数组中的元素按照年龄升序排列: 改用Lambda写法
        Arrays.sort(students, (Student o1, Student o2) -> {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
        });
        System.out.println(Arrays.toString(students));
    }
}

2.2 Lambda表达式省略规则

Java觉得代码还不够简单,于是还提供了Lamdba表达式的几种简化写法。具体的简化规则如下

1.Lambda的标准格式
    (参数类型1 参数名1, 参数类型2 参数名2)->{
        ...方法体的代码...
        return 返回值;
    }
​
2.在标准格式的基础上()中的参数类型可以直接省略
    (参数名1, 参数名2)->{
        ...方法体的代码...
        return 返回值;
    }
    
3.如果{}总的语句只有一条语句,则{}可以省略、return关键字、以及最后的“;”都可以省略
    (参数名1, 参数名2)-> 结果
    
4.如果()里面只有一个参数,则()可以省略
    (参数名)->结果

接下来从匿名内部类开始、到Lambda标准格式、再到Lambda简化格式,一步一步来简化一下。

public class LambdaTest2 {
    public static void main(String[] args) {
        // 目标:使用Lambda简化函数式接口。
        double[] prices = {99.8, 128, 100};
        //1.对数组中的每一个元素*0.8: 匿名内部类写法
        Arrays.setAll(prices, new IntToDoubleFunction() {
            @Override
            public double applyAsDouble(int value) {
                // value = 0  1  2
                return prices[value] * 0.8;
            }
        });
        //2.需求:对数组中的每一个元素*0.8,使用Lambda表达式标准写法
        Arrays.setAll(prices, (int value) -> {
                return prices[value] * 0.8;
        });
        //3.使用Lambda表达式简化格式1——省略参数类型
        Arrays.setAll(prices, (value) -> {
            return prices[value] * 0.8;
        });
        //4.使用Lambda表达式简化格式2——省略()
        Arrays.setAll(prices, value -> {
            return prices[value] * 0.8;
        });
        //5.使用Lambda表达式简化格式3——省略{}
        Arrays.setAll(prices, value -> prices[value] * 0.8 );
​
        System.out.println(Arrays.toString(prices));
        
        System.out.println("------------------------------------
​
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
        
        //1.使用匿名内部类
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
            }
        });
        //2.使用Lambda表达式表达式——标准格式
        Arrays.sort(students, (Student o1, Student o2) -> {
                return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
        });
        //3.使用Lambda表达式表达式——省略参数类型
        Arrays.sort(students, ( o1,  o2) -> {
            return Double.compare(o1.getHeight(), o2.getHeight()); // 升序
        });
        //4.使用Lambda表达式表达式——省略{}
        Arrays.sort(students, ( o1,  o2) -> Double.compare(o1.getHeight(), o2.getHeight()));
​
​
        System.out.println(Arrays.toString(students));
    }
}

三、JDK8新特性(方法引用)

我们知道Lambda是用来简化匿名代码的书写格式的,而方法引用是用来进一步简化Lambda表达式的,它简化的更加过分。

到这里有小伙伴可能就想慰问Java爸爸了:“之前的代码挺好的呀!好不容易学会,你又来搞这些,把我都搞晕了。“ 说句大实话,确实有这样的问题,学习新的东西肯定会增加我们的学习成本,从心理上来说多少是有写抗拒的。但是从另一个角度想,一旦我们学会了,会大大简化我们的代码书写,提高我们编写代码的效率,而且这些新的语法都是有前提条件的,遇到的时候就简化得了。再退一步想,就算你没有学会,还是用以前的办法一点问题也没有。

3.1 静态方法引用

我们先学习静态方法的引用,还是用之前Arrays代码来做演示。现在准备好下面的代码

public class Test1 {
    public static void main(String[] args) {
        Student[] students = new Student[4];
        students[0] = new Student("蜘蛛精", 169.5, 23);
        students[1] = new Student("紫霞", 163.8, 26);
        students[2] = new Student("紫霞", 163.8, 26);
        students[3] = new Student("至尊宝", 167.5, 24);
​
        // 原始写法:对数组中的学生对象,按照年龄升序排序
        Arrays.sort(students, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge(); // 按照年龄升序排序
            }
        });
​
        // 使用Lambda简化后的形式
        Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge());
    }
}

现在,我想要把下图中Lambda表达式的方法体,用一个静态方法代替

准备另外一个类CompareByData类,用于封装Lambda表达式的方法体代码;

public class CompareByData {
    public static int compareByAge(Student o1, Student o2){
        return o1.getAge() - o2.getAge(); // 升序排序的规则
    }
}

现在我们就可以把Lambda表达式的方

//静态方法引用:类名::方法名
Arrays.sort(students, CompareByData::compareByAge);

法体代码,改为下面的样子

Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));

Java为了简化上面Lambda表达式的写法,利用方法引用可以改进为下面的样子。实际上就是用类名调用方法,但是把参数给省略了。这就是静态方法引用

 

3.2 实例方法引用

现在,我想要把下图中Lambda表达式的方法体,用一个实例方法代替。

在CompareByData类中,再添加一个实例方法,用于封装Lambda表达式的方法体

接下来,我们把Lambda表达式的方法体,改用对象调用方法

CompareByData compare = new CompareByData();
Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2)); // 降序

最后,再将Lambda表达式的方法体,直接改成方法引用写法。实际上就是用类名调用方法,但是省略的参数。这就是实例方法引用

CompareByData compare = new CompareByData();
Arrays.sort(students, compare::compareByAgeDesc); // 降序

3.2 特定类型的方法引用

Java约定:
    如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数作为方法的主调,    后面的所有参数都是作为该实例方法的入参时,则就可以使用特定类型的方法引用。
格式:
    类型::方法名
public class Test2 {
    public static void main(String[] args) {
        String[] names = {"boby", "angela", "Andy" ,"dlei", "caocao", "Babo", "jack", "Cici"};
        
        // 要求忽略首字符大小写进行排序。
        Arrays.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 制定比较规则。o1 = "Andy"  o2 = "angela"
                return o1.compareToIgnoreCase(o2);
            }
        });
        
        //lambda表达式写法
        Arrays.sort(names, ( o1,  o2) -> o1.compareToIgnoreCase(o2) );
        //特定类型的方法引用!
        Arrays.sort(names, String::compareToIgnoreCase);
​
        System.out.println(Arrays.toString(names));
    }
}

3.3 构造器引用

现在,我们准备一个JavaBean类,Car类

public class Car {
    private String name;
    private double price;
​
    public Car() {
​
    }
​
    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public double getPrice() {
        return price;
    }
​
    public void setPrice(double price) {
        this.price = price;
    }
​
    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

因为方法引用是基于Lamdba表达式简化的,所以也要按照Lamdba表达式的使用前提来用,需要一个函数式接口,接口中代码的返回值类型是Car类型

interface CreateCar{
    Car create(String name, double price);
}

最后,再准备一个测试类,在测试类中创建CreateCar接口的实现类对象,先用匿名内部类创建、再用Lambda表达式创建,最后改用方法引用创建。同学们只关注格式就可以,不要去想为什么(语法就是这么设计的)。

public class Test3 {
    public static void main(String[] args) {
        // 1、创建这个接口的匿名内部类对象。
        CreateCar cc1 = new CreateCar(){
            @Override
            public Car create(String name, double price) {
                return new Car(name, price);
            }
        };
        //2、使用匿名内部类改进
        CreateCar cc2 = (name,  price) -> new Car(name, price);
​
        //3、使用方法引用改进:构造器引用
        CreateCar cc3 = Car::new;
        
        //注意:以上是创建CreateCar接口实现类对象的几种形式而已,语法一步一步简化。
        
        //4、对象调用方法
        Car car = cc3.create("奔驰", 49.9);
        System.out.println(car);
    }
}

四、常见算法

1.1 认识算法

接下来,我们认识一下什么是算法。算法其实是解决某个实际问题的过程和方法。比如百度地图给你规划路径,计算最优路径的过程就需要用到算法。再比如你在抖音上刷视频时,它会根据你的喜好给你推荐你喜欢看的视频,这里也需要用到算法。

我们为什么要学习算法呢?主要目的是训练我们的编程思维,还有就是面试的时候,面试官也喜欢问一下算法的问题来考察你的技术水平。最后一点,学习算法是成为一个高级程序员的必经之路。

当然我们现在并不会学习非常复杂的算法,万丈高楼平地起,我们现在只需要学习几种常见的基础算法就可以了。而且Java语言本身就内置了一些基础算法给我们使用,实际上自己也不会去写这些算法。

1.2 冒泡排序

接下来,我们学习一种算法叫排序算法,它可以价格无序的整数,排列成从小到大的形式(升序),或者从大到小的形式(降序)

排序算法有很多种,我们这里只学习比较简单的两种,一种是冒泡排序,一种是选择排序。学习算法我们先要搞清楚算法的流程,然后再去“推敲“如何写代码。(注意,我这里用的次是推敲,也就是说算法这样的代码并不是一次成型的,是需要反复修改才能写好的)。

先来学习冒泡排序,先来介绍一下,冒泡排序的流程

冒泡排序核心思路:每次将相邻的两个元素继续比较
如下图所示:
   第一轮比较 3次
   第二轮比较 2次
   第三轮比较 1次

public class Test1 {
    public static void main(String[] args) {
        // 1、准备一个数组
        int[] arr = {5, 2, 3, 1};
​
        // 2、定义一个循环控制排几轮
        for (int i = 0; i < arr.length - 1; i++) {
            // i = 0  1  2           【5, 2, 3, 1】    次数
            // i = 0 第一轮            0   1   2         3
            // i = 1 第二轮            0   1             2
            // i = 2 第三轮            0                 1
​
            // 3、定义一个循环控制每轮比较几次。
            for (int j = 0; j < arr.length - i - 1; j++) {
                // 判断当前位置的元素值,是否大于后一个位置处的元素值,如果大则交换。
                if(arr[j] > arr[j+1]){
                    int temp = arr[j + 1];
                    arr[j + 1] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

1.2 选择排序

刚才我们学习了冒泡排序,接下来我们学习了另一种排序方法,叫做选择排序。按照我们刚才给大家介绍的算法的学习方式。先要搞清楚算法的流程,再去推敲代码怎么写。

所以我们先分析选择排序算法的流程:选择排序的核心思路是,每一轮选定一个固定的元素,和其他的每一个元素进行比较;经过几轮比较之后,每一个元素都能比较到了。

接下来,按照选择排序的流程编写代码

ublic class Test2 {
    public static void main(String[] args) {
        // 1、准备好一个数组
        int[] arr = {5, 1, 3, 2};
        //           0  1  2  3
​
        // 2、控制选择几轮
        for (int i = 0; i < arr.length - 1; i++) {
            // i = 0 第一轮    j = 1 2 3
            // i = 1 第二轮    j = 2 3
            // i = 2 第三轮    j = 3
            // 3、控制每轮选择几次。
            for (int j = i + 1; j < arr.length; j++) {
                // 判断当前位置是否大于后面位置处的元素值,若大于则交换。
                if(arr[i] > arr[j]){
                    int temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
}

1.3 查找算法

接下来,我们学习一个查找算法叫做二分查找。在学习二分查找之前,我们先来说一下基本查找,从基本查找的弊端,我们再引入二分查找,这样我们的学习也会更加丝滑一下。

先聊一聊基本查找:假设我们要查找的元素是81,如果是基本查找的话,只能从0索引开始一个一个往后找,但是如果元素比较多,你要查找的元素比较靠后的话,这样查找的此处就比较多。性能比较差。

再讲二分查找:二分查找的主要特点是,每次查找能排除一般元素,这样效率明显提高。但是二分查找要求比较苛刻,它要求元素必须是有序的,否则不能进行二分查找。

  • 二分查找的核心思路

第1步:先定义两个变量,分别记录开始索引(left)和结束索引(right)
第2步:计算中间位置的索引,mid = (left+right)/2;
第3步:每次查找中间mid位置的元素,和目标元素key进行比较
        如果中间位置元素比目标元素小,那就说明mid前面的元素都比目标元素小
            此时:left = mid+1
        如果中间位置元素比目标元素大,那说明mid后面的元素都比目标元素大
            此时:right = mid-1
        如果中间位置元素和目标元素相等,那说明mid就是我们要找的位置
            此时:把mid返回       
注意:一搬查找一次肯定是不够的,所以需要把第1步和第2步循环来做,只到left>end就结束,如果最后还没有找到目标元素,就返回-1.

/**
 * 目标:掌握二分查找算法。
 */
public class Test3 {
    public static void main(String[] args) {
        // 1、准备好一个数组。
        int[] arr = {7, 23, 79, 81, 103, 127, 131, 147};
​
        System.out.println(binarySearch(arr, 150));
​
        System.out.println(Arrays.binarySearch(arr, 81));
    }
​
    public static int binarySearch(int[] arr, int data){
        // 1、定义两个变量,一个站在左边位置,一个站在右边位置
        int left = 0;
        int right = arr.length - 1;
​
        // 2、定义一个循环控制折半。
        while (left <= right){
            // 3、每次折半,都算出中间位置处的索引
            int middle = (left + right) / 2;
            // 4、判断当前要找的元素值,与中间位置处的元素值的大小情况。
            if(data < arr[middle]){
                // 往左边找,截止位置(右边位置) = 中间位置 - 1
                right = middle - 1;
            }else if(data > arr[middle]){
                // 往右边找,起始位置(左边位置) = 中间位置 + 1
                left = middle + 1;
            }else {
                // 中间位置处的元素值,正好等于我们要找的元素值
                return middle;
            }
        }
        return -1; // -1特殊结果,就代表没有找到数据!数组中不存在该数据!
    }
}

五、正则表达式

正则表达式其实是由一些特殊的符号组成的,它代表的是某种规则。

正则表达式的作用1:用来校验字符串数据是否合法

正则表达式的作用2:可以从一段文本中查找满足要求的内容

5.1 正则表达式初体验

现在,我们就以QQ号码为例,来体验一下正则表达式的用法。注意:现在仅仅只是体验而已,我们还没有讲正则表达式的具体写法。

  • 不使用正则表达式,校验QQ号码代码是这样的

public static boolean checkQQ(String qq){
        // 1、判断qq号码是否为null
        if(qq == null || qq.startsWith("0") || qq.length() < 6 || qq.length() > 20){
            return false;
        }
​
        // 2、qq至少是不是null,不是以0开头的,满足6-20之间的长度。
        // 判断qq号码中是否都是数字。
        // qq = 2514ghd234
        for (int i = 0; i < qq.length(); i++) {
            // 根据索引提取当前位置处的字符。
            char ch = qq.charAt(i);
            // 判断ch记住的字符,如果不是数字,qq号码不合法。
            if(ch < '0' || ch > '9'){
                return false;
            }
        }
        // 3、说明qq号码肯定是合法
        return true;
    }
  • 用正则表达式代码是这样的

public static boolean checkQQ1(String qq){
    return qq != null && qq.matches("[1-9]\\d{5,19}");
}

5.2 正则表达式书写规则

这里需要用到一个方法叫matches(String regex)。这个方法时属于String类的方法。

这个方法是用来匹配一个字符串是否匹配正则表达式的规则,参数需要调用者传递一个正则表达式。但是正则表达式不能乱写,是有特定的规则的。

下面我们就学习一下,正则表达式的规则。从哪里学呢?在API中有一个类叫做Pattern,我们可以到API文档中搜索,关于正则表达式的规则,这个类都告诉我们了。

我们将这些规则,在代码中演示一下

/**
 * 目标:掌握正则表达式的书写规则
 */
public class RegexTest2 {
    public static void main(String[] args) {
        // 1、字符类(只能匹配单个字符)
        System.out.println("a".matches("[abc]"));    // [abc]只能匹配a、b、c
        System.out.println("e".matches("[abcd]")); // false
​
        System.out.println("d".matches("[^abc]"));   // [^abc] 不能是abc
        System.out.println("a".matches("[^abc]"));  // false
​
        System.out.println("b".matches("[a-zA-Z]")); // [a-zA-Z] 只能是a-z A-Z的字符
        System.out.println("2".matches("[a-zA-Z]")); // false
​
        System.out.println("k".matches("[a-z&&[^bc]]")); // : a到z,除了b和c
        System.out.println("b".matches("[a-z&&[^bc]]")); // false
​
        System.out.println("ab".matches("[a-zA-Z0-9]")); // false 注意:以上带 [内容] 的规则都只能用于匹配单个字符
​
        // 2、预定义字符(只能匹配单个字符)  .  \d  \D   \s  \S  \w  \W
        System.out.println("徐".matches(".")); // .可以匹配任意字符
        System.out.println("徐徐".matches(".")); // false
​
        // \转义
        System.out.println("\"");
        // \n \t
        System.out.println("3".matches("\\d"));  // \d: 0-9
        System.out.println("a".matches("\\d"));  //false
​
        System.out.println(" ".matches("\\s"));   // \s: 代表一个空白字符
        System.out.println("a".matches("\s")); // false
​
        System.out.println("a".matches("\\S"));  // \S: 代表一个非空白字符
        System.out.println(" ".matches("\\S")); // false
​
        System.out.println("a".matches("\\w"));  // \w: [a-zA-Z_0-9]
        System.out.println("_".matches("\\w")); // true
        System.out.println("徐".matches("\\w")); // false
​
        System.out.println("徐".matches("\\W"));  // [^\w]不能是a-zA-Z_0-9
        System.out.println("a".matches("\\W"));  // false
​
        System.out.println("23232".matches("\\d")); // false 注意:以上预定义字符都只能匹配单个字符。
​
        // 3、数量词: ?   *   +   {n}   {n, }  {n, m}
        System.out.println("a".matches("\\w?"));   // ? 代表0次或1次
        System.out.println("".matches("\\w?"));    // true
        System.out.println("abc".matches("\\w?")); // false
​
        System.out.println("abc12".matches("\\w*"));   // * 代表0次或多次
        System.out.println("".matches("\\w*"));        // true
        System.out.println("abc12张".matches("\\w*")); // false
​
        System.out.println("abc12".matches("\\w+"));   // + 代表1次或多次
        System.out.println("".matches("\\w+"));       // false
        System.out.println("abc12张".matches("\\w+")); // false
​
        System.out.println("a3c".matches("\\w{3}"));   // {3} 代表要正好是n次
        System.out.println("abcd".matches("\\w{3}"));  // false
        System.out.println("abcd".matches("\\w{3,}"));     // {3,} 代表是>=3次
        System.out.println("ab".matches("\\w{3,}"));     // false
        System.out.println("abcde徐".matches("\\w{3,}"));     // false
        System.out.println("abc232d".matches("\\w{3,9}"));     // {3, 9} 代表是  大于等于3次,小于等于9次
​
        // 4、其他几个常用的符号:(?i)忽略大小写 、 或:| 、  分组:()
        System.out.println("abc".matches("(?i)abc")); // true
        System.out.println("ABC".matches("(?i)abc")); // true
        System.out.println("aBc".matches("a((?i)b)c")); // true
        System.out.println("ABc".matches("a((?i)b)c")); // false
​
        // 需求1:要求要么是3个小写字母,要么是3个数字。
        System.out.println("abc".matches("[a-z]{3}|\\d{3}")); // true
        System.out.println("ABC".matches("[a-z]{3}|\\d{3}")); // false
        System.out.println("123".matches("[a-z]{3}|\\d{3}")); // true
        System.out.println("A12".matches("[a-z]{3}|\\d{3}")); // false
​
        // 需求2:必须是”我爱“开头,中间可以是至少一个”编程“,最后至少是1个”666“
        System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+"));
        System.out.println("我爱编程编程66666".matches("我爱(编程)+(666)+"));
    }
}

5.3 正则表达式应用案例

学习完正则表达式的规则之后,接下来我们再利用正则表达式,去校验几个实际案例。

  • 正则表达式校验手机号码

/**
 * 目标:校验用户输入的电话、邮箱、时间是否合法。
 */
public class RegexTest3 {
    public static void main(String[] args) {
        checkPhone();
    }
​
    public static void checkPhone(){
        while (true) {
            System.out.println("请您输入您的电话号码(手机|座机): ");
            Scanner sc = new Scanner(System.in);
            String phone = sc.nextLine();
            // 18676769999  010-3424242424 0104644535
            if(phone.matches("(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})")){
                System.out.println("您输入的号码格式正确~~~");
                break;
            }else {
                System.out.println("您输入的号码格式不正确~~~");
            }
        }
    }
}
使用正则表达式校验邮箱是否正确

public class RegexTest3 {
    public static void main(String[] args) {
        checkEmail();
    }
​
    public static void checkEmail(){
        while (true) {
            System.out.println("请您输入您的邮箱: ");
            Scanner sc = new Scanner(System.in);
            String email = sc.nextLine();
            /**
             * dlei0009@163.com
             * 25143242@qq.com
             * itheima@itcast.com.cn
             */
            if(email.matches("\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2}")){
                System.out.println("您输入的邮箱格式正确~~~");
                break;
            }else {
                System.out.println("您输入的邮箱格式不正确~~~");
            }
        }
    }
}
​

5.4 正则表达式信息爬取

正则表达式的第二个作用:在一段文本中查找满足要求的内容

我们还是通过一个案例给大家做演示:案例需求如下

/**
 * 目标:掌握使用正则表达式查找内容。
 */
public class RegexTest4 {
    public static void main(String[] args) {
        method1();
    }
​
    // 需求1:从以下内容中爬取出,手机,邮箱,座机、400电话等信息。
    public static void method1(){
        String data = " 来黑马程序员学习Java,\n" +
                "        电话:1866668888,18699997777\n" +
                "        或者联系邮箱:boniu@itcast.cn,\n" +
                "        座机电话:01036517895,010-98951256\n" +
                "        邮箱:bozai@itcast.cn,\n" +
                "        邮箱:dlei0009@163.com,\n" +
                "        热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
        // 1、定义爬取规则
        String regex = "(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})|(\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2})"
                + "|(400-?\\d{3,7}-?\\d{3,7})";
        // 2、把正则表达式封装成一个Pattern对象
        Pattern pattern = Pattern.compile(regex);
        // 3、通过pattern对象去获取查找内容的匹配器对象。
        Matcher matcher = pattern.matcher(data);
        // 4、定义一个循环开始爬取信息
        while (matcher.find()){
            String rs = matcher.group(); // 获取到了找到的内容了。
            System.out.println(rs);
        }
    }
}

5.5 正则表达式搜索、替换

接下来,我们学习一下正则表达式的另外两个功能,替换、分割的功能。需要注意的是这几个功能需要用到Stirng类中的方法。

/**
 * 目标:掌握使用正则表达式做搜索替换,内容分割。
 */
public class RegexTest5 {
    public static void main(String[] args) {
        // 1、public String replaceAll(String regex , String newStr):按照正则表达式匹配的内容进行替换
        // 需求1:请把下面字符串中的不是汉字的部分替换为 “-”
        String s1 = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
        System.out.println(s1.replaceAll("\\w+", "-"));
        
        // 需求2(拓展):某语音系统,收到一个口吃的人说的“我我我喜欢编编编编编编编编编编编编程程程!”,需要优化成“我喜欢编程!”。
        String s2 = "我我我喜欢编编编编编编编编编编编编程程程";
        System.out.println(s2.replaceAll("(.)\\1+", "$1"));
​
        // 2、public String[] split(String regex):按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
        // 需求1:请把下面字符串中的人名取出来,使用切割来做
        String s3 = "古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
        String[] names = s3.split("\\w+");
        System.out.println(Arrays.toString(names));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/408609.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

26.HarmonyOS App(JAVA)列表对话框

列表对话框的单选模式&#xff1a; //单选模式 // listDialog.setSingleSelectItems(new String[]{"第1个选项","第2个选项"},1);//单选 // listDialog.setOnSingleSelectListener(new IDialog.ClickedListener() { // Override …

互联网加竞赛 机器视觉opencv答题卡识别系统

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 答题卡识别系统 - opencv python 图像识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分…

C++中的左值和右值

目录 一. 左值和右值的概念 1. 左值 1.1 可修改的的左值 1.2 不可修改的左值 右值 二. 左值引用和右值引用 1. 左值引用 2. 右值引用 主要用途 1. 移动语义 2. 完美转发 2.1 引用折叠 2.2 std::forward 一. 左值和右值的概念 什么是左值和右值 1. 左值 左值是一个表示…

Unity3D 使用 Proto

一. 下载与安装 这里下载Google Protobuff下载 1. 源码用来编译CSharp 相关配置 2. win64 用于编译 proto 文件 二. 编译 1. 使用VS 打开 2. 点击最上面菜单栏 工具>NuGet 包管理器>管理解决方案的NuGet 管理包 版本一定要选择咱们一开始下载的对应版本否则不兼容&am…

使用免费的L53巧解Freenom域名失效问题

进入2月份以来&#xff0c;不少小伙伴纷纷收到Freenom提供的域名失效&#xff0c;状态由正常变成了Pending。 失效后&#xff0c;域名无法使用&#xff0c;免费的午餐没有了&#xff0c;而现在域名的价格也是水涨船高&#xff0c;真是XXX。很多做外贸的小伙伴表示 难 啊&#x…

树状数组与线段树<2>——线段树初步

这个系列终于更新了(主要因为树状数组初步比较成功) 话不多说&#xff0c;切入正题。 什么是线段树&#xff1f; 线段树是一种支持单点修改区间查询(树状数组也行) and 区间修改单点查询(树状数组不行) and 区间修改区间查询(树状数组更不行)的高级数据结构&#xff0c;相当…

Chiplet技术与汽车芯片(二)

目录 1.回顾 2.Chiplet的优势 2.1 提升芯片良率、降本增效 2.2 设计灵活&#xff0c;降低设计成本 2.3 标准实行&#xff0c;构建生态 3.Chiplet如何上车 1.回顾 上一篇&#xff0c;我们将来芯粒到底是什么东西&#xff0c;本篇我们来看芯粒技术的优势&#xff0c;以及它…

5.1 Ajax数据爬取之初介绍

目录 1. Ajax 数据介绍 2. Ajax 分析 2.1 Ajax 例子 2.2 Ajax 分析方法 &#xff08;1&#xff09;在网页页面右键&#xff0c;检查 &#xff08;2&#xff09;找到network&#xff0c;ctrl R刷新 &#xff08;3&#xff09;找 Ajax 数据包 &#xff08;4&#xff09;…

多线程相关(4)

线程安全-下 使用层面锁优化减少锁的时间&#xff1a;减少锁的粒度&#xff1a;锁粗化&#xff1a;使用读写锁&#xff1a;使用CAS&#xff1a; 系统层面锁优化自适应自旋锁锁消除锁升级偏向锁轻量级锁重量级锁 ThreadLocal原理ThreadLocal简介原理ThreadLocal内存泄漏 HashMap…

VMware使用虚拟机,开启时报错:无法连接虚拟设备 0:0,因为主机上没有相应的设备。——解决方法

检查虚拟机配置文件并确保物理设备已正确连接。 操作&#xff1a; 选中虚拟机&#xff0c;打开设置&#xff0c;点击CD/DVD。在连接处选择使用ISO镜像文件

fpga_硬件加速引擎

一 什么是硬件加速引擎 硬件加速引擎&#xff0c;也称硬件加速器&#xff0c;是一种采用专用加速芯片/模块替代cpu完成复杂耗时的大算力操作&#xff0c;其过程不需要或者仅需要少量cpu参与。 二 典型的硬件加速引擎 典型的硬件加速引擎有GPU&#xff0c;DSP&#xff0c;ISP&a…

【二分查找】【浮点数的二分查找】【二分答案查找】

文章目录 前言一、二分查找&#xff08;Binary Search&#xff09;二、浮点数的二分查找三、二分答案总结 前言 今天记录一下基础算法之二分查找 一、二分查找&#xff08;Binary Search&#xff09; 二分查找&#xff08;Binary Search&#xff09;是一种在有序数组中查找目…

1 Nacos数据持久化方式

Nacos 支持两种数据持久化方式&#xff0c;一种是利用内置的数据库&#xff0c;另一种是利用外置的数据源。 1、内置数据库支持 Nacos 默认内置了一些数据存储解决方案&#xff0c;如内嵌的 Derby 数据库。 这种内置方式主要用于轻量级或测试环境。 2、外置数据库支持 对于生…

【RN】学习使用 Reactive Native内置UI组件

简言 当把导航处理好后&#xff0c;就可以学习使用ui组件了&#xff08;两者没有先后关系&#xff0c;个人习惯&#xff09;。 在 Android 和 iOS 开发中&#xff0c;一个视图是 UI 的基本组成部分&#xff1a;屏幕上的一个小矩形元素、可用于显示文本、图像或响应用户输入。甚…

如何使用逻辑回归处理多标签问题?

逻辑回归处理多分类 1、背景描述2、One vs One3、One vs Rest4、从Sigmoid到Softmax的推导 1、背景描述 逻辑回归本身只能用于二分类问题&#xff0c;如果实际情况是多分类的&#xff0c;那么就需要对模型进行一些改动。下面介绍三种常用的将逻辑回归用于多分类的方法 2、One …

目标跟踪之KCF详解

High-Speed Tracking with Kernelized Correlation Filters 使用内核化相关滤波器进行高速跟踪 大多数现代跟踪器的核心组件是判别分类器&#xff0c;其任务是区分目标和周围环境。为了应对自然图像变化&#xff0c;此分类器通常使用平移和缩放的样本补丁进行训练。此类样本集…

用Python实现创建十二星座数据分析图表

下面小编提供的代码中&#xff0c;您已经将pie.render()注释掉&#xff0c;并使用了pie.render_to_file(十二星座.svg)来将饼状图渲染到一个名为十二星座.svg的文件中。这是一个正确的做法&#xff0c;如果您想在文件中保存图表而不是在浏览器中显示它。 成功创建图表&#xf…

嵌入式软件分层设计的思想分析

“嵌入式开发&#xff0c;点灯一路发” 那今天我们就以控制LED闪烁为例&#xff0c;来聊聊嵌入式软件分层: ——————————— | | | P1.1 |-----I<|--------------<| | | | P2.1 |-------------/ ---------…

MyBatis的⾼级映射及延迟加载

MyBatis的⾼级映射及延迟加载 一、多对一1.方式一&#xff1a;级联属性映射2.方式二&#xff1a;association3.方式三&#xff1a;分步查询 二、一对多1.方式一&#xff1a;collection2.方式二&#xff1a;分步查询 三、延迟加载&#xff08;懒加载&#xff09;1.分步查询的优点…

【C++】类和对象之拷贝构造函数篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 前言2. 传值传参和传引用传参3. 概念4. 特征 1. 前言 在前面学习了6个默认成员函数中的构造函数和析构函数 【C】构造函数和析构函数详解&#xff0c;接下来继续往后看拷…