javaSE字符串学习笔记

news2024/11/24 19:26:52

API和API帮助文档

API

  • API(Application Programming Interface):应用程序编程接口
  • 简单理解:API酒啊别人已经写好的东西,我们不需要自己编写,直接使用即可。

API这个术语在编程圈中非常常见.我第一次接触API这个词语是在大一下。老师的要求很简单,让我写接口调用API。这个我也是实现了,但是有很多不理解的地方,API是什么,我写的是API,还是别人写的是api。到现在,还是很迷糊。

API就是一段函数、方法。是别人已经写好的,我们负责用就可以。用一段python代码举个例子

ans = fun()

我调用了fun() 函数,那么fun()就是API。

  • Java中的API

    指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可,我们可以通过帮助文档来学习这些API如何使用。

简单理解就是JDK给我们提供的API。

API帮助文档

目前,我们已经学习过两个API,分别是Scanner键盘输入的API和Random随机数的API.除此之外,JDK还提供了很多的API,好在这些api不需要刻意去记,都放在了一个文档中,API帮助文档:[Java参考文档].JDK_API_1_6_zh_CN.CHM

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这个里面汇集了Java已经定义好的各种包。

因为这个里面汇集了大量的Java包,我们想找到其中一个,那就是大海捞针了。因此我们需要搜索

搜索的方式:点击右上角的【显示】——【索引】——就会发现搜索框,搜索然后回车。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于这个界面,我们应该学会查看基础的信息

  • 看类所在的包

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    从上向下看,java.util 表示Random类定义在java.util 下,因此使用时需要导包,

    import java.util.Random;
    
  • 查看类的描述

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    第一行就是Random类的描述,此类的实例用于生成伪随机数流。这个类我们已经用过了,不假。

    下面还有一个版本JDK1.0表示在JDK1.0的时候就有Random这个类了。即所有JDK这个版本都可以使用这个类、

    如果出现一个JDK8的字样,那么这类只有在高于或等于JDK8的时候可以用,低于JDK8不能用。

  • 查看构造方法

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    既然导入了这个类,那我们肯定是使用这个类实现一些功能。那就得获取这个类的对象。因此构造方法决定了如何创建对象。这个时候出现了如下代码

    Random r = new Random();
    

    这是一个空参构造

  • 查看成员方法

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    方法名一样,但是参数不一样,重载。

    因此也就用了如下代码。

    res = r.nextInt(10);
    

练习

需求:按照帮助文档的使用步骤学习Scanner类的使用,并实现接收键盘录入一个小数,最后输出在
控制台

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些构造方法没有一个是空参的。我们实际上用的是第三个,

        Scanner sc = new Scanner(System.in);

此时括号里就不能是空的了,会报错。原因是没有空参构造。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

他要的是小数,直接看方法摘要。小数是float。找到了。

import java.util.Scanner;

public class ScannerDemo1 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个小数");
        double result = sc.nextDouble();
        System.out.println(result);
    }
}

老师用的是nextDouble()方法。但是我以为nextFloat()方法也是可以的。因此改动

        float result = sc.nextFloat();

在Java数据类型中float和double都是小数,题目中也没有明确。

字符串

String概述

String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!

String str1 = "Hello World!";
String str2 = "你好 世界!";

这里要注意两点,Java的字符串数字类型是String,还有一点字符串有双引号""括起来。

字符串的内容是不会改变的,他的对象在创建后不能被更改。

String name = "张三";
String classroom = "大数据";
System.out.println(name+classroom);

字符串拼接产生一个新的字符串。这段代码很好理解。

String name = "张三";
name = "李四";

对于这段代码,我们的思维肯定是一开始创建一个name的字符串并赋值张三,然后接着把name字符串的值改成李四。

但是在Java中对这段代码有更严谨的描述:一开始创建了一个字符串张三赋值给了name,随后有创建了一个字符串李四,赋值给了name,看上去name的值发生了改变,实际上字符串张三并没有发生改变。从而产生了两个字符串。因此字符串的内容不会发生改变。

创建String对象

创建String对象共有两种方式,第一种方式简单粗暴,直接赋值。第二种方式通过new关键字进行创建。

直接创建代码示例

String str1 = "Hello World!";

接下来是如何通过new创建。

方法名说明
public String()创建一个空白字符串对象,不含有任何内容
public String(char[] chs)根据字符数组的内容,来创建字符串对象
public String(byte[] bys)根据字节数组的内容,来创建字符串对象
`String s = “abc”;直接赋值的方式创建字符串对象,内容就是abc

以上是Java常用的构造方法。

代码示例

public class StringDome01 {
    public static void main(String[] args) {
        // 直接赋值方式
        String str1 = "abcd";
        System.out.println(str1);  // abcd

        // 空参构造
        String str2 = new String();
        System.out.println("@"+str2+"!");  // @!

        // 传递一个字符粗
        String str3 = new String("abcd");
        System.out.println(str3);  // abcd

        // 传递一个字符数组
        char[] chs = {'a', 'b', 'c', 'd'};
        String str4 = new String(chs);
        System.out.println(str4);  // abcd

        // 传递一个字节数组
        byte[] bytes = {97, 98, 99, 100};
        String str5 = new String(bytes);
        System.out.println(str5);  // abcd

    }
}

在日常开发中,用的更多的是直接赋值方式,简单快捷。

空参构造,可以看成创建一个空的字符粗。"@"+str2+"!" = @!,因为str2 = ""str2没有

在构造方法中传递一个字符串。这个和直接赋值方法是一样的,反而多了一步。这里讲究效率的话直接使用直接复制方法。

传递一个字符数组,这个就有意思了。字符串对象创建后就不能修改了,但是我们可以修改数组里面的内容。如果把字符串adcd改成efgh。可以更改字符串的内容。

public class StringDome02 {
    public static void main(String[] args) {
        // 传递一个字符数组
        char[] chs = {'a', 'b', 'c', 'd'};
        String str4 = new String(chs);
        System.out.println("字符串str4更改前");
        System.out.println(str4);  // abcd

        chs[0] = 'e';
        chs[1] = 'f';
        chs[2] = 'g';
        chs[3] = 'h';
        str4 = new String(chs);
        System.out.println("字符串str4更改后");
        System.out.println(str4);  // efgh
    }
}

传递字节数组,97,98,99,100这四个数字刚好对应abcd的ASCII码,他的转换流程是先查找ASCII码,再进行转换。在开发中多用于网络,网络的信息传递都是字节,因此需要转换成我们能看懂的字符串。

以上解除了五种字符串创建的方式,常用的是直接创建。但直接创建不是因为简单而出名,他还有其他学问。这里需要借助Java的内存模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

首先,回忆一下Java的内存模型。这是目前我们已经学习过的Java内存模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在多了一块串池,因此这部分用于存放字符串,但是只有直接创建的字符串才会放到串池里面。在JDK7以前,串池在方法区里面。JDK7以后,串池挪到了堆内存里面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以上是理论学习,接下来分析源码

先说直接赋值方式,代码如下

public class StringDome01 {
    public static void main(String[] args) {
        // 直接赋值方式
        String str1 = "abcd";
        String str2 = "abcd";
    }
}

他的内存如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

执行这段程序,那么main()方法会先进栈。开始从上到下执行代码。String str1 = "abcd";,在main方法里面开辟一块内存,内存名是str1,内存的数据类型是String。前面提到过String类型是字符串类型,字符串保持在串池里面。在串池里面创建时,首先会看一下串池里面有没有数据"abcd",第一次创建肯定没有,开辟一小块内存,存放数据"abcd",这块内存的地址是0x0011,随后返回到栈内存,得知在栈内存中str1保存的数据是串池里面内存地址是0x0011的数据。

接着执行String str2 = "abcd";。和上面步骤一样,在堆内存里面创建内存起名str2。因为是String类型,且还是直接赋值方式,会自动在串池里面找有没有与"abcd"相同的数据,如果有,那就返回"abcd"数据的内存地址。如果没有那就创建数据"abcd"然后返回。

正好在串池里面发现了数据"abcd",因此就返回了他的内存地址0x0011。从而在栈内存中str2保存的数据是串池里面内存地址是0x0011的数据。

当使用双引号直接赋值时,系统会检查该字符串在串池中是否存在。
不存在:创建新的
存在:复用

接下来看new创建的对象

代码如下

public class StringDome01 {
    public static void main(String[] args) {
    char[] chs = {'a', 'b', 'c', 'd'};
        String str1 = new String(chs);
        String str2 = new String(chs);
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于这张内存图,看着就复杂多了。

首先我们要知道,new出来的东西要放在堆内存里面。

执行第一行代码char[] chs = {'a', 'b', 'c', 'd'};在栈内存中开辟空间,起名chs并且在堆内存中也是开辟内存存放数组数据,随后返回堆内存中的数组地址0x0011给栈内存中的chs空间。

执行第二行代码String str1 = new String(chs);按照常例,在栈内存中开辟空间,这一次因为是new,所以String类型的数据还需要在堆内存里面开辟框架。这块内存里的内容就是字符数组的内容。随后还是返回堆内存里面的地址0x0022str1

执行第三行代码String str2 = new String(chs);一切照旧。虽然str2str1创建的内容是一样的,但是在堆内存里面并没有引用而是创建了两个"abcd"

以上就是直接创建和new创建的区别,从图片上就可以看出来。new创建使用的内存资源多。因此直接使用yyds!

字符串的比较

==号比较方式

  • 比较基本数据类型:比较的是具体的值
  • 比较引用数据类型:比较的是对象地址值

这么看比较难懂,通过代码来解释

public class StringDemo03 {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abc";
        String str3 = "def";
        System.out.println(str1==str2);  // true
        System.out.println(str1==str3);  // false

    }
}

先从代码表面看,str1str1的值都是一样的abc,因此是返回truestr1str3的值不一样,返回false。单纯这样理解只能半对。上面代码中创建字符串对象用的是直接复制方式,创建的字符串都放在串池里面。如果创建相同的字符串,是复用,那么肯定相同。

public class StringDemo03 {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = new String("abc");
        System.out.println(str1==str2);  // false
    }
}

看上去str1str1的值都是一样的abc,返回的结果却是false。直接引用方式创建的字符串放在串池里面,通过new关键字创建的字符串在堆内存里面。虽说这两个字符串都是相同的,但是一个保存在串池里面,一个在堆里面。他们的内存地址完全不同,因此返回false

public class StringDemo03 {
    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = new String("abc");
        System.out.println(str1==str2);  // false
    }
}

还是返回false,因为通过new出来的字符串在堆内存里面,而且不是相互引用。看上去两个字符串是相同的,但是他们地址不同。

.equals方法的作用

方法名描述返回类型
.equals()字符串比较,相同返回true,否则返回falseboolean
equalsIgnoreCase()忽略大小写的字符串比较boolean
  • .equals()

    public class StringDemo04 {
        public static void main(String[] args) {
            String str1 = "abc";
            String str2 = new String("abc");
            boolean res = str1.equals(str2);
            System.out.println(res);  // true
        }
    }
    
  • .equalsIgnoreCase()

    public class StringDemo04 {
        public static void main(String[] args) {
            String str1 = "abc";
            String str2 = new String("Abc");
            boolean res = str1.equalsIgnoreCase(str2);
            System.out.println(res);  // true
        }
    }
    
    

.equals()常用于密码验证,而.equalsIgnoreCase()常用于验证码验证

import java.util.Scanner;

public class StringDemo05 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str1 = sc.next();
        String str2 = "abc";
        boolean res = str1.equals(str2);
        System.out.println(str1 == str2);  // false
        System.out.println(res);  // true
    }
}

对于这段代码而言,看似String str1 = sc.next();是一个直接赋值,实际上是new,因此,str1 == str2返回false。

这里就需要看一下sc.next()的源码,选中next快捷键ctrl+B,跟进源码,一直找到与next相关的核心代码。

通过一系列的跟进操作之后,找到了核心代码

    public static String newString(byte[] val, int index, int len) {
        if (len == 0) {
            return "";
        }
        return new String(Arrays.copyOfRange(val, index, index + len),
                          LATIN1);
    }

说白了,还是new。

案例

用户登录

​ 已知用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录之后,给出相应的提示

import java.util.Scanner;

public class StringDemo06 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String rightUsername = "admin";
        String rightPassword = "abc000";

        for (int i = 0; i < 3; i++) {

            System.out.print("请输入用户名: ");
            String username = sc.next();
            System.out.print("请输入密码: ");
            String password = sc.next();

            if (rightUsername.equals(username) && rightPassword.equals(password)){
                System.out.print("用户登录成功");
                break;
            }else{
                if(i<3) System.out.println("用户名或密码有误!请重新输入,还有"+ (2 - i) + "次机会。");
                else System.out.println("用户登录失败," + username + "已锁定,请联系管理员");
            }
        }
    }
}

先可以进行一次成功的登录代码逻辑后,再加一个for循环就好。

字符串遍历

​ 键盘录入一个字符串,使用程序实现在控制台遍历该字符串

首先介绍两个方法

方法描述
public char charAt(int index)根据索引返回字符
public int length()返回此字符串的长度

在这里需要注意一下方法.length()的使用方式。

在数组中也有.length的使用,具体用法是arr.length,而在字符串中是str.length()。他们之间的区别在于小括号,是因为在数组中,.length是属性,而字符串中.length()是方法,数学没有小括号,方法需要加小括号。

import java.util.Scanner;

public class StringDemo07 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入字符: ");
        String str = sc.next();

        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            System.out.println(ch);
        }
    }
}

这个代码的核心是:

for (int i = 0; i < str.length(); i++) {
    char ch = str.charAt(i);  // 根据索引返回字符
    System.out.println(ch);
	}
}

str.charAt(i)是根据索引返回字符串,我们就知道Java的字符串也是有索引的,学过计算机的都知道,索引是从0开的,那么根据这个逻辑,就解释通了。

i < str.length()字符串长度-1就是最后一个 索引,所以这里设置成小于,也可以是小于等于i <= str.length()-1

字符串统计

键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)

import java.util.Scanner;

public class StringDemo08 {
    public static void main(String[] args) {
        int bigCount = 0;
        int smallCount = 0;
        int numberCount = 0;
        Scanner sc = new Scanner(System.in);

        // 输入字符串
        System.out.println("请输入字符串");
        String text = sc.next();  // HelloWorld!123

        // 遍历字符串进行统计
        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            // 统计
            if (ch >= 'a' && ch <= 'z') smallCount++;
            else if (ch >= 'A' && ch <= 'Z') bigCount++;
            else if (ch >= '0' && ch <= '9') numberCount++;
        }

        System.out.println("字符串统计结果如下:");
        System.out.println("大写有:" + bigCount + "个");  // 大写有:2个
        System.out.println("小写有:" + smallCount + "个");  // 小写有:8个
        System.out.println("数字有:" + numberCount + "个");  // 数字有:3个

    }
}

这段代码的核心部分是统计部分。这里需要中注意一点的是:char类型在参与运算的时候会升为int类型,因此比较的是ASCII码。

ch >= '0' && ch <= '9'不要写成ch >= 0 && ch <= 9',因为 String text = sc.next()输入的是字符串类型,因此123也就字符串

字符串拼接

​ 定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回,调用该方法,

​ 并在控制台输出结果。例如,数组为 int[] arr = {1,2,3}; ,执行方法后的输出结果为:[1, 2, 3]

public class StringDemo09 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        String result = arrToString(arr);
        System.out.println(result);
    }

    public static String arrToString(int[] arr) {
        if (arr == null) return "";
        else if (arr.length == 0)  return "[]";
        else  {
            String str = "[";
            for (int i = 0; i < arr.length; i++) {
                if (i == arr.length-1) str += arr[i];
                else str += arr[i] + ", ";
            }
            str += "]";
            return str;
        }
    }
}

题目中有要求用方法,Java的数组是 {1,2,3},需要转换成 [1,2,3]

else  {
    String str = "[";
    for (int i = 0; i < arr.length; i++) {
        if (i == arr.length-1) str += arr[i];
        else str += arr[i] + ", ";
    }
    str += "]";
    return str;

字符串初始是"[",后面用到了字符串加字符串的知识str += arr[i] + ", ",要注意最后一个元素后面不跟, 因此str += arr[i],后面还有一个"]"

字符串反转

定义一个方法,实现字符串反转。键盘录入一个字符串,调用该方法后,在控制台输出结果

​ 例如,键盘录入 abc,输出结果 cba

import java.util.Scanner;

public class StringDemo10 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.print("请输入字符串: ");
        String str = sc.next();  // hello
        String res = reverse(str);
        System.out.print(res);  // olleh
    }

    public static String reverse(String str){
        String res = "";
        for (int i = str.length()-1; i >= 0; i--) {
            res += str.charAt(i);
        }
        return res;
    }
}

字符串遍历的循环是“

for (int i = 0; i < str.length(); i++) {
    char ch = str.charAt(i);
    System.out.println(ch);
}

字符串反转

for (int i = str.length()-1; i >= 0; i--) {
    res += str.charAt(i);
}

金额转换

​ 把2135变成:零佰零拾零万贰仟壹佰叁拾伍元

​ 把789变成:零佰零拾零万零仟柒佰捌拾玖元

import java.util.Scanner;

public class StringDemo11 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int money, count;
        String[] unitArr = {"佰","拾","万","仟","佰","拾","元"};
        String capitalNumber= "", result = "";

        // 判断输入是否合法
        while (true) {
            System.out.println("请输入金额");
            money = sc.nextInt();
            // 合法输入
            if (money >= 0 && money <= 9999999) break;
            // 非法输入,重新输入
            else System.out.println("非法输入,重新输入");
        }

        while (money != 0) {
            int ge = money % 10;
            money /= 10;
            capitalNumber = getCapitalNumber(ge) + capitalNumber;
        }

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

        // 插入单位
        for (int i = 0; i < unitArr.length; i++) {
            result += capitalNumber.charAt(i) + unitArr[i];
        }
        System.out.println(result);
    }
    
    public static String getCapitalNumber(int num){
        String[] capitalNumberArr = {"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
        return capitalNumberArr[num];
    }

}

这道题的代码,我们需要认真分析一下。因为看到这道题的时候我也没有思路

先是定义了一个方法,getCapitalNumber得到这个数字的大写,例如getCapitalNumber(1)返回壹。

判断输入的内容有没有问题是一个很好的习惯,

while (money != 0) {
    int ge = money % 10;
    money /= 10;
    capitalNumber = getCapitalNumber(ge) + capitalNumber;
}

这部分的逻辑是数字的小写转大写。用到了数位拆分。取个位用取余%。比如12%10就是个商1余2,取余数,2被取出来了。12/10,是个1,1被取出来,接着取余是1.

capitalNumber = getCapitalNumber(ge) + capitalNumber;

每取出一位数就得到他的大写。然后保存到capitalNumber

capitalNumber变量的初始化是空的,在最开始。假设我们要进行拆分的数字是123、

那么第一次拆分下来的数字应该是3,叁;即"叁" + " ",得到的结果是"叁"

第二次拆分后是2,贰;即"贰" + "叁",得到的结果是"贰叁"

第三次拆分后是1,壹 ;即"壹" + "贰叁",得到的结果是"壹贰叁"

长度已经统一了,因此长度不够的话需要补位。然后是插入单位。

在这里有个细节。

capitalNumber += getCapitalNumber(ge);

在大部分情况下我们都会这样写,用的+=。这样等价于capitalNumber = capitalNumber + getCapitalNumber(ge) ;

那么第一次拆分下来的数字应该是3,叁;即" " + "叁",得到的结果是"叁"

第二次拆分后是2,贰;即"叁" + "贰",得到的结果是"叁贰"

第三次拆分后是1,壹 ;即"贰叁" + "壹",得到的结果是"叁贰壹"

数位拆分后的结果应该是反过来的,比如1234的数位拆分的结构是:4 3 2 1,得到的大写也应该是肆 叁 贰 壹 。这与我们需要的结果不符,需要的结果是壹 贰 叁 肆。如果需要壹 贰 叁 肆这种形式,那么就需要capitalNumber = getCapitalNumber(ge) + capitalNumber;

手机号屏蔽

需求:以字符串的形式从键盘接受一个手机号,将中间四位号码屏蔽

最终效果为:131****9468

方法名描述
substring(int beginIndex)返回一个新的字符串,它是此字符串的一个子字符串。
substring(int beginIndex, int endIndex)返回一个新字符串,它是此字符串的一个子字符串。
public class StringDemo12 {
    public static void main(String[] args) {
        String phoneNumber = "12345678910";
        String start = phoneNumber.substring(0, 3);  // 前三位
        String end = phoneNumber.substring(7);  // 后四位
        String result = start + "****" + end;
        System.out.println(result);
    }
}

字符串切片,和python的大同小异。

.substring(0, 3)从字符串的0索引开始到3索引结束,不包括3索引的字符。包左不包右,包头不包尾。

.substring(7)从字符串的7---------索引开始到字符串结束

敏感词替换

需求1:键盘录入一个 字符串,如果字符串中包含(TMD),则使用***替换

方法名描述
replace(char oldChar, char newChar)返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
public class StringDemo14 {
    public static void main(String[] args) {
        String talk = "后裔你玩什么啊,TMD";

        String result = talk.replace("TMD", "***");  // 替换

        System.out.println(talk);  // 后裔你玩什么啊,TMD
        System.out.println(result);  // 后裔你玩什么啊,***
    }
}

这里用到了字符串替换方法。replace()

import java.util.Scanner;

public class StringDemo15 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入要说的话");
        String talk = sc.next();  // 后裔你玩什么啊,TMD,GDX,ctmd,ZZ

        String[] sensitiveWordsArr = {"TMD","GDX","ctmd","ZZ","lj","FW","nt"};

        for (int i = 0; i < sensitiveWordsArr.length; i++) {
            talk = talk.replace(sensitiveWordsArr[i],"***");
        }

        System.out.println(talk);  // 后裔你玩什么啊,***,***,***,***
    }
}

如果有多个敏感词,那么做一个敏感词数组,遍历数组,一一替换。

身份证信息查看

身份证的每一位都是有固定的含义:

  • 1、2位:省份
  • 3、4位:城市
  • 5、6位:区县
  • 7-14位:出生年、月、日
  • 15、16位:所在地派出所
  • 17位:性别(奇数男性,偶数女性)
  • 18位:个人信息码(随机产生)

要求打印内容方式如下:

​ 人物信息为:

​ 出生年月日:XXXX年X月X日

​ 性别为:男/女

public class StringDemo13 {
    public static void main(String[] args) {
        String id = "321281202001011234";

        // 获取年月日7~14
        String year = id.substring(6, 10);
        String month = id.substring(10, 12);
        String day = id.substring(12, 14);

        System.out.println("人物信息为:");
        System.out.println("出生年月日:" + year + "年" + month + "月" + day + "日");

        // 性别
        char gender = id.charAt(16);
        int num = gender - 48;
        if(num % 2 == 0){
            System.out.println("性别为:女");
        }else {
            System.out.println("性别为:男");
        }

    }
}

简单理解就是对字符串的切片操作

StringBuilder

先看两段代码

public class StringDemo17 {
    public static void main(String[] args) {
        String s = "";
        for (int i = 0; i < 100000000; i++) {
            s = s + "abc";
        }
        System.out.println(s);
    }
}

这段代码,里面写了个循环,循环100000000次。程序不报错,也不会出来运行结果。

public class StringDemo18 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("");
        for (int i = 0; i < 100000000; i++) {
            sb.append("abc");
        }
        System.out.println(sb);
    }
}

循环同样的次数,这段代码达到了我的要求。

这是老师课程引入的代码,具体为什么,我也不知道。

StringBuilder概述

StringBuilder 可以看成是一个容器,创建之后里面的内容是可变的。

老师举了个例子,字符串拼接

public class StringDemo19 {
    public static void main(String[] args) {
        String s1 = "aaa";
        String s2 = "bbb";
        String s3 = "ccc";
        String s4 = "ddd";
        String s5 = "eee";
        String s6 = s1 + s2 + s3 + s4 + s5;
        System.out.println();  // aaabbbcccdddeee
    }
}

字符串拼接,这段代码的逻辑是先拼接s1 + s2拼接后是aaabbb,紧接着是aaabbb + s3 = aaabbbcccaaabbbccc + s4 = aaabbbcccdddaaabbbcccddd + s5 = aaabbbcccdddeeeaaabbbcccdddeee赋值给s6。

然而产生的aaabbbaaabbbccc aaabbbcccdddaaabbbcccdddeee这些都会单独开辟空间进行存放。其实这些都没用了,最后我们就关心s6的结果。

而StringBuilder就不一样,这是个容器。相当于箱子。完成字符串拼接时会直接吧这堆字符串(s1s2s3s4s5)放进箱子里,顺其自然也就有了aaabbbcccdddeee

StringBuilder使用

  • 构造方法

    方法名描述
    public StringBuilder()创建一个空白可变字符串对象,不含有任何内容
    public StringBuilder(String str)根据字符串的内容,来创建可变字符串对象
  • 成员方法

    方法名描述
    public StringBuilder append(任意类型)添加数据,并返回对象本身
    public StringBuilder reverse()反转容器中的内容
    public int length()返回长度(字符出现的个数)
    public String toString()`通过toString()就可以实现把StringBuilder 转换成String
import java.util.Random;

public class StringBuilderDemo01 {
    public static void main(String[] args) {
        // 创建对象
        StringBuilder sb = new StringBuilder();
        Random r = new Random();
        System.out.println(sb);
        System.out.println(r);  // java.util.Random@3b07d329
    }
}

打印一个对象,会返回对象的地址值,如打印随机数创建的对象r,则返回java.util.Random@3b07d329,但是打印sb并没有返回。

因为StringBuilder是Java已经写好的类,在底层做了一些处理,在打印对象的时候打印的是属性值而不是地址值。

import java.util.Random;

public class StringBuilderDemo01 {
    public static void main(String[] args) {
        // 创建对象
        StringBuilder sb = new StringBuilder("abc");
        Random r = new Random();
        System.out.println(sb);  // abc
        System.out.println(r);  // java.util.Random@3b07d329
    }
}

在使用含参构造时,会打印容器里的内容。

public class StringBuilderDemo02 {
    public static void main(String[] args) {
        // 创建对象
        StringBuilder sb = new StringBuilder("abc");

        //添加元素
        sb.append(1);
        sb.append(1.2);
        sb.append(true);
        sb.append('c');

        System.out.println(sb);  // abc11.2truec
    }
}

.append()添加元素,这个方法有很多的重载,可以添加不同类型变量的数据

public class StringBuilderDemo02 {
    public static void main(String[] args) {
        // 创建对象
        StringBuilder sb = new StringBuilder("abc");

        //反转
        sb.reverse();

        System.out.println(sb);  // cba
    }
}

字符串反转,在这里就会发现StringBuilder对象的好处,不需要String str = 因此也节省内存。

public class StringBuilderDemo02 {
    public static void main(String[] args) {
        // 创建对象
        StringBuilder sb = new StringBuilder("abc");

        //统计长度
        int len = sb.length();

        System.out.println(len);  // 3
    }
}

输出字符串的长度。

public class StringBuilderDemo03 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("aaa");

        sb.append("bbb");
        sb.append("ccc");
        sb.append("ddd");

        // 转换成String类型
        String str = sb.toString();

        String strNew = str.replace("aaa", "***");

        System.out.println(sb);  // aaabbbcccddd
        System.out.println(strNew);  // ***bbbcccddd
    }
}

把StringBuilder 类型的字符串转换成String类型的字符串,这样一来就可以用String的方法进行操作。以防String有的方法而StringBuilder 没有

链式编程

当我们在调用一个方法的时候,不需要用变量接收他的结果,可以继续调用其他方法。

import java.util.Scanner;

public class StringBuilderDemo04 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        int len = getString().substring(1).replace("a", "z").length();

        sb.append("aaa").append("bbb").append("ccc").append("ddd");
        String str = sb.toString();

        System.out.println(len);  // 2
        System.out.println(sb);  // aaabbbcccddd
        System.out.println(str);  // aaabbbcccddd
    }

    public static String getString(){
        Scanner sc = new Scanner(System.in);
        System.out.println("输入字符串");
        return sc.next();
    }
}

其中int len = getString().substring(1).replace("a", "z").length();sb.append("aaa").append("bbb").append("ccc").append("ddd");就是链式编程。链式编程的思想还是很常用的。

案例

对称字符串

需求:

​ 键盘接受一个字符串,程序判断出该字符串是否是对称字符串,并在控制台打印是或不是

  对称字符串:123321、111
  									
  非对称字符串:123123
import java.util.Scanner;

public class StringBuilderDemo对称字符串 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入字符串");
        String str = sc.next();
        String strReverse = new StringBuilder(str).reverse().toString();  // 创建对象,反转,转换
        if(str.equals(strReverse)) System.out.println(str + "是对称字符串");
        else System.out.println(str + "不是是对称字符串");
    }
}

这段代码的逻辑很简单。在这里我想复制一份我写错的代码,我一开始写的。

// 错误的
import java.util.Scanner;

public class StringBuilderDemo对称字符串 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入字符串");
        StringBuilder sb = new StringBuilder(sc.next());
        if(sb == sb.reverse()) System.out.println(sb + "是对称字符串");
        else System.out.println(sb + "不是是对称字符串");
    }
}

这段代码很简洁,我把链式编程运用到了极致。运行出来了,111是对称字符串3221是对称字符串。看了运行结果,不对,

if(sb == sb.reverse()) System.out.println(sb + "是对称字符串");

这就是个错误,这是Java不是python。

反转的时候用StringBuilder,对比的时候还是要回到String的.equals()方法。

拼接字符串

需求:定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回。

​ 调用该方法,并在控制台输出结果。

​ 例如:数组为int[] arr = {1,2,3};

​ 执行方法后的输出结果为:[1, 2, 3]

public class StringBuilderDemo字符串拼接 {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5};
        String str = arrTpString(arr);
        System.out.println(str);

    }

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

这道题很面熟的,做过。逻辑是一样的。只不过用的是StringBuilder。

StringBuilder的用途大部分是反转和字符串拼接,其他的操作StringBuilder也没有。

StringJoiner

  • StringJoiner跟StringBuilder一样,也可以看成是一个容器,创建之后里面的内容是可变的。
  • 作用:提高字符串的操作效率,而且代码编写特别简洁,但是目前市场上很少有人用。
  • JDK8出现的

看完了视频这个更方便,但是很少人用。

需求:定义一个方法,把 int 数组中的数据按照指定的格式拼接成一个字符串返回。

​ 调用该方法,并在控制台输出结果。

​ 例如:数组为int[] arr = {1,2,3};

​ 执行方法后的输出结果为:[1, 2, 3]

还是这道题,第三次见。

import java.util.StringJoiner;

public class StringJoinerDemo01 {
    public static void main(String[] args) {
        //1.定义数组
        int[] arr = {1,2,3};
        StringJoiner sj = new StringJoiner(",", "[", "]");

        for (int i = 0; i < arr.length; i++) {
            sj.add(arr[i]+"");
        }

        System.out.println(sj);
    }


}

代码变短了,当然这和没有写方法不是一回事。

  • 构造方法

    方法名描述
    public StringJoiner(间隔符号)创建StringJoiner对象,指定拼接时的间隔符号
    public StringJoiner(间隔符号, 开始符号, 结束符号)创建StringJoiner对象,指定拼接时的间隔符号,开始符号, 结束符号
import java.util.StringJoiner;

public class StringJoinerDemo02 {
    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner("-");
        sj.add("aaa").add("bbb").add("ccc");
        System.out.println(sj);  // aaa-bbb-ccc
    }
}

使用无参构造方法

import java.util.StringJoiner;

public class StringJoinerDemo03 {
    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner(",", "[", "]");
        sj.add("aaa").add("bbb").add("ccc");
        System.out.println(sj);  // [aaa,bbb,ccc]
    }
}

含参构造

字符串原理

字符串拼接的底层原理

字符串拼接分两种情况,先看第一种情况

public class StringDemo20 {
    public static void main(String[] args) {
        String str = "a" + "b" + "c";
        System.out.println(str);  // abc 
    }
}

拼接的时候没有变量,都是字符串。触发字符串优化机制。在编译的时候就已经是最终结果了。

这种情况是字符串的拼接过程中没有其余变量参与,在编译结束后,结果也就出来了。

首先我们会写Java文件,在写Java文件的时候会是这么一行语句: String str = "a" + "b" + "c";根据Java的执行逻辑,写完Java文件后,会进行编译。在编译完成之后 String str = "a" + "b" + "c";会变成 String str = "abc";然后直接输出。

public class StringDemo21 {
    public static void main(String[] args) {
        String str1 = "a";
        String str2 = str1 + "b";
        String str3 = str2 + "b";
        System.out.println(str3);  // abc
    }
}

在字符串拼接过程中有变量参与,这里还有分支

在JDK8之前,用的是StringBuilder拼接。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

熟悉的感觉

首先需要了解一下StringBuilder对象下的.toString()方法的源码,快捷键:CTRL+N

public static String newString(byte[] val, int index, int len) {
    if (len == 0) {
        return "";
    }
    return new String(Arrays.copyOfRange(val, index, index + len),
                      LATIN1);
}

String str1 = "a";会先在串池里面存入字符串a

String str2 = str1 + "b"; 串池里面存放"b"。在字符串拼接的时候有变量参与,那么就会创建StringBuilder对象。这句话等价于 new StringBuilder().append(str1).append("b").toString(),刚刚看过toString()方法的源码,即一次加会创建两个对象

String str3 = str2 + "b";同理,放到串池,创建第二个StringBuilder对象、创建第二个String对象。

每一行在拼接的时候都会创建一个StringBuilder,因此效率慢了。

在jdk8以后,字符串拼接迎来了有害,他会预估用数组字符串的长度,然后创建String对象 、

public class StringDemo22 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        String s4 = s1 + s2 + s3;
        System.out.println(s4);
    }
}

s1,s2,s3的字符串长度都是1,所以根据预估,就会创建一个长单是3的字符数组。再把字符数组转换成字符粗

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果是每一行字符粗拼接的时候都会有变量参与,那么效率还是会慢。

public class StringDemo21 {
    public static void main(String[] args) {
        String str1 = "a";
        String str2 = str1 + "b";
        String str3 = str2 + "b";
        System.out.println(str3);  // abc
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果有很多,那么数组也会多,还是占内存。

如果很多字符粗变量拼接,不要直接+,会在底层创建多个对象,浪费时间、浪费性能

StringBuilder提高效率原理图

public class StringBuilderDemo05 {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        sb.append("a");
        sb.append("b");
        sb.append("c");
        System.out.println(sb);
    }
}

看一下这段代码的内存图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

虽然这里的字符串abc,不是直接赋值的方式创建的,但是字符还是放在了串池。如果细看底层源码,肯定离不开String对象。

StringBuilder内容可变,所以把所有元素都可以放里面放

容器的话,水装多了会爆,但是StringBuilder不会爆

面试题

题1

下列代码的运行结果

public class StringDemo23 {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "ab";
        String s3 = s2 + "c";
        System.out.println(s1 == s3);
    }
}

肯定不一样了,==号比的是内存地址。而他们的地址值不一样。

字符串拼接的时候,如果有变量:

  • JDK8以前:系统底层会自动创建一个StringBuilder对象,然后再调用其append方法完成拼接。
    拼接后,再调用其toString方法转换为String类型,而toString方法的底层是直接new了一个字符串对象。
  • JDK8版本:系统会预估要字符串拼接之后的总大小,把要拼接的内容都放在数组中,此时也是产生一个新的字符串。
题2

读结果

public class StringDemo24 {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "a" + "b" + "c";
        System.out.println(s1 == s2);  // true
    }
}

这道题我以为是false。结果是true,他们的地址值一样。

因为是直接赋值,且在字符串拼接的时候没有变量参与。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

会先执行String s1 = "abc";,因此字符串"a""b""c"会在串池中创建,等到执行String s2 = "a" + "b" + "c";字符串"a""b""c"会进行复用。因此地址一样、

字符串在编译的时候会直接变成"abc"。复用

StringBuilder源码分析

在看源码之前,先了解一下StringBuilder的设计

在创建StringBuilder对象的时候,底层会创建一个16位的字节数组。默认容量是16,但是长度初始是0,因为没有东西。

  • 容量:最多能装多少;
  • 长度:实际上有多少;

接下来,分三种情况讨论:

  • 存放abc(存放内容不超过16):直接存放。此时长度为3,容量是16;
  • 存放二十六个英文字母(超过16):会触发默认的扩容机制原来长度(16)*2+2=34
  • 存放二十六个英文字母加零到九十个数字(扩容后还不够):以实际长度为准;
public class StringBuilderDemo06 {
    public static void main(String[] args) {
        StringBuilder sb1 = new StringBuilder();
        StringBuilder sb2= new StringBuilder();
        StringBuilder sb3 = new StringBuilder();
        StringBuilder sb4 = new StringBuilder();

        // StringBuilder的初始容量和长度
        System.out.println(sb1.capacity());  // 16
        System.out.println(sb1.length());  // 0

        // 存放内容不超过16
        sb2.append("abc");
        System.out.println(sb2.capacity());  // 16
        System.out.println(sb2.length());  // 3

        // 存放内容超过16
        sb3.append("abcdefghijklmnopqrstuvwxzy");
        System.out.println(sb3.capacity());  // 34
        System.out.println(sb3.length());  // 26

        // 扩容之后还不够
        sb4.append("abcdefghijklmnopqrstuvwxzy0123456789");
        System.out.println(sb4.capacity());  // 36
        System.out.println(sb4.length());  // 36

    }
}

上面的代码虽然有点乱,不影响。验证了StringBuilder的容器机制。在StringBuilder中有两个方法

  • .capacity():查看当前StringBuilder容器容量。
  • .length():查看当前StringBuilder容器长度

接下来学习源码

选中StringBuilder后CTRL+B,

    /**
     * Constructs a string builder with no characters in it and an
     * initial capacity of 16 characters.
     */
    @IntrinsicCandidate
    public StringBuilder() {
        super(16);
    }

这是一个空参构造,这里idea有个提示显示super(capacity:16),capacity是容量的意思。所以说,在进行构造的时候这个容量就已经定了。

按住CTRL不松,点击super进入

    /**
     * Creates an AbstractStringBuilder of the specified capacity.
     */
    AbstractStringBuilder(int capacity) {
        if (COMPACT_STRINGS) {
            value = new byte[capacity];
            coder = LATIN1;
        } else {
            value = StringUTF16.newBytesFor(capacity);
            coder = UTF16;
        }
    }

. AbstractStringBuilder()方法的参数是int capacity,其内容是 value = new byte[capacity];创建有个长度是capacity长的数组。那么super(16)就是创建一个长度是16的字符数组。

接下来,看appen方法,看参数是字符串的,因为append方法都很多重载。

    @Override
    @IntrinsicCandidate
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

ctrl点击append

    /**
     * Appends the specified string to this character sequence.
     * <p>
     * The characters of the {@code String} argument are appended, in
     * order, increasing the length of this sequence by the length of the
     * argument. If {@code str} is {@code null}, then the four
     * characters {@code "null"} are appended.
     * <p>
     * Let <i>n</i> be the length of this character sequence just prior to
     * execution of the {@code append} method. Then the character at
     * index <i>k</i> in the new character sequence is equal to the character
     * at index <i>k</i> in the old character sequence, if <i>k</i> is less
     * than <i>n</i>; otherwise, it is equal to the character at index
     * <i>k-n</i> in the argument {@code str}.
     *
     * @param   str   a string.
     * @return  a reference to this object.
     */
    public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
    }

会先判断字符串 是否是空的,null。如果是空,接着ctrl点击.appendNull()方法

   private AbstractStringBuilder appendNull() {
        ensureCapacityInternal(count + 4);
        int count = this.count;
        byte[] val = this.value;
        if (isLatin1()) {
            val[count++] = 'n';
            val[count++] = 'u';
            val[count++] = 'l';
            val[count++] = 'l';
        } else {
            count = StringUTF16.putCharsAt(val, count, 'n', 'u', 'l', 'l');
        }
        this.count = count;
        return this;
    }

可以看到是往val数组里面存放null。count表示已经存了几个字符。初始是0;

如果存放的字符串不是空的,那么先获取长度int len = str.length();

然后ensureCapacityInternal(count + len); 这里的count是字符串长度。

也可以证明一下count就是长度,快捷键从ctrl+f12,选择length方法

    /**
     * Returns the length (character count).
     *
     * @return  the length of the sequence of characters currently
     *          represented by this object
     */
    @Override
    public int length() {
        return count;
    }

返回count。ensureCapacityInternal(count + len); 方法里的参数count + len count默认是0,假设存放字符串"abc",那么len就是3。0+3 在idea里面有提示 ensureCapacityInternal(minimumcapacity: count + len); minimumcapacity是对count + 3的解释。最小容量是3.

CTRL点击ensureCapacityInternal方法

    /**
     * For positive values of {@code minimumCapacity}, this method
     * behaves like {@code ensureCapacity}, however it is never
     * synchronized.
     * If {@code minimumCapacity} is non positive due to numeric
     * overflow, this method throws {@code OutOfMemoryError}.
     */
    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        int oldCapacity = value.length >> coder;
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }
    }

这里有一个新的变量名oldCapacity老容量,默认容量16。if (minimumCapacity - oldCapacity > 0)这个if条件是判断要不要扩容。3-16=-13 -13<0条件为false,要存放的字符串长度减去默认容量16如果小于0,那么就不扩容。

假设存放字符串abcdefghijklmnopqrstuvwxzy超过了默认容量。因为26-16=1010>0所以要扩容。

if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }

按住CTRL选择newCapacity、 newCapacity(minimumCapacity),可知 minimumCapacity=26

    /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by the current length + 2 if
     * that suffices.
     * Will not return a capacity greater than
     * {@code (MAX_ARRAY_SIZE >> coder)} unless the given minimum capacity
     * is greater than that.
     *
     * @param  minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero or
     *         greater than (Integer.MAX_VALUE >> coder)
     */
    private int newCapacity(int minCapacity) {
        int oldLength = value.length;
        int newLength = minCapacity << coder;
        int growth = newLength - oldLength;
        int length = ArraysSupport.newLength(oldLength, growth, oldLength + (2 << coder));
        if (length == Integer.MAX_VALUE) {
            throw new OutOfMemoryError("Required length exceeds implementation limit");
        }
        return length >> coder;
    }


这个方法的参数是minCapacity是最小容量,把26传了进来。oldLength老的长度,那就是16;newLength是新的长度,等于minCapacity那就是26。growthnewLength - oldLength应该是需要新增的长度。

int length = ArraysSupport.newLength(oldLength, growth, oldLength + (2 << coder));.newLength()方法,传入了老的长度、新增长度以及 oldLength + (2 << coder)意思是老长度+2左移coder,coder针对中文,没有中文就是0;CTRL.newLength()方法

    /**
     * Computes a new array length given an array's current length, a minimum growth
     * amount, and a preferred growth amount. The computation is done in an overflow-safe
     * fashion.
     *
     * This method is used by objects that contain an array that might need to be grown
     * in order to fulfill some immediate need (the minimum growth amount) but would also
     * like to request more space (the preferred growth amount) in order to accommodate
     * potential future needs. The returned length is usually clamped at the soft maximum
     * length in order to avoid hitting the JVM implementation limit. However, the soft
     * maximum will be exceeded if the minimum growth amount requires it.
     *
     * If the preferred growth amount is less than the minimum growth amount, the
     * minimum growth amount is used as the preferred growth amount.
     *
     * The preferred length is determined by adding the preferred growth amount to the
     * current length. If the preferred length does not exceed the soft maximum length
     * (SOFT_MAX_ARRAY_LENGTH) then the preferred length is returned.
     *
     * If the preferred length exceeds the soft maximum, we use the minimum growth
     * amount. The minimum required length is determined by adding the minimum growth
     * amount to the current length. If the minimum required length exceeds Integer.MAX_VALUE,
     * then this method throws OutOfMemoryError. Otherwise, this method returns the greater of
     * the soft maximum or the minimum required length.
     *
     * Note that this method does not do any array allocation itself; it only does array
     * length growth computations. However, it will throw OutOfMemoryError as noted above.
     *
     * Note also that this method cannot detect the JVM's implementation limit, and it
     * may compute and return a length value up to and including Integer.MAX_VALUE that
     * might exceed the JVM's implementation limit. In that case, the caller will likely
     * attempt an array allocation with that length and encounter an OutOfMemoryError.
     * Of course, regardless of the length value returned from this method, the caller
     * may encounter OutOfMemoryError if there is insufficient heap to fulfill the request.
     *
     * @param oldLength   current length of the array (must be nonnegative)
     * @param minGrowth   minimum required growth amount (must be positive)
     * @param prefGrowth  preferred growth amount
     * @return the new array length
     * @throws OutOfMemoryError if the new length would exceed Integer.MAX_VALUE
     */
    public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
        // preconditions not checked because of inlining
        // assert oldLength >= 0
        // assert minGrowth > 0

        int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow
        if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
            return prefLength;
        } else {
            // put code cold in a separate method
            return hugeLength(oldLength, minGrowth);
        }
    }

newLength()方法有三个参数。int oldLength老长度16, int minGrowth新增长度10, int prefGrowth老长度+2得18

在这里CTRLmax的源码;

    /**
     * Returns the greater of two {@code int} values. That is, the
     * result is the argument closer to the value of
     * {@link Integer#MAX_VALUE}. If the arguments have the same value,
     * the result is that same value.
     *
     * @param   a   an argument.
     * @param   b   another argument.
     * @return  the larger of {@code a} and {@code b}.
     */
    @IntrinsicCandidate
    public static int max(int a, int b) {
        return (a >= b) ? a : b;
    }

这是一个三目运算符。a是新增长度10,b是老长度+2得18;因为是18大,所以max的return是18;

回到上一段代码中; int prefLength = oldLength + Math.max(minGrowth, prefGrowth);我们已经知道了 Math.max(minGrowth, prefGrowth);的结果是18,那么,oldLength是16,所以prefLength 就是16+18=34。也可以看作16*2+2=34;prefLength是34,现在新建的长度。

如果在三目运算符比较中,新增的要大,那么就是prefLength = coldLength + minGrowth 新建的长度。

跨度有点大: int length = ArraysSupport.newLength(oldLength, growth, oldLength + (2 << coder));因此计算出来需要扩容的长度。为了方便,我把代码复制一份

 private int newCapacity(int minCapacity) {
        int oldLength = value.length;
        int newLength = minCapacity << coder;
        int growth = newLength - oldLength;
        int length = ArraysSupport.newLength(oldLength, growth, oldLength + (2 << coder));
        if (length == Integer.MAX_VALUE) {
            throw new OutOfMemoryError("Required length exceeds implementation limit");
        }
        return length >> coder;
    }

下面还有一个if比较,这里也反映出来StringBuilder容器石油长度的 。存放的字符串不能超过int类型的大小 2147483647。超出长度,会报错。

现在是往回推

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        int oldCapacity = value.length >> coder;
        if (minimumCapacity - oldCapacity > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity) << coder);
        }
    }

Arrays.copyOf(value, newCapacity(minimumCapacity) << coder)方法。CTRL

    /**
     * Copies the specified array, truncating or padding with zeros (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain {@code (byte)0}.
     * Such indices will exist if and only if the specified length
     * is greater than that of the original array.
     *
     * @param original the array to be copied
     * @param newLength the length of the copy to be returned
     * @return a copy of the original array, truncated or padded with zeros
     *     to obtain the specified length
     * @throws NegativeArraySizeException if {@code newLength} is negative
     * @throws NullPointerException if {@code original} is null
     * @since 1.6
     */
    public static byte[] copyOf(byte[] original, int newLength) {
        byte[] copy = new byte[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }


这个方法有两个参数,original是原字节数组,newLength是需要扩容的长度。

byte[] copy = new byte[newLength];根据需要扩容的长度创建一个字节数组;

.arraycopy()方法根据方法名可以看出是复制数组,根据参数大致可以明白,把原来数组的内容复制到新的数组内容。

然后回到开始,

    public AbstractStringBuilder append(String str) {
        if (str == null) {
            return appendNull();
        }
        int len = str.length();
        ensureCapacityInternal(count + len);
        putStringAt(count, str);
        count += len;
        return this;
    }

在分析了一大圈之后 ensureCapacityInternal(count + len);就是给字节数组扩容。

putStringAt(count, str);添加字符串str到StringBuilder

· count += len;` 修改StringBuilder的长度

综合练习

调整字符串

给定两个字符串,A和B。A的旋转操作就是将A最左边的字符移动到最右边。
例如,若A=‘abcde’,在移动一次之后结果就是’bcdea’。如果在若干次调整操作之后,A能变成B,那么返回True。如果不能匹配成功,则返回false

public class StringTest01Case1 {
    public static void main(String[] args) {
        String strA = "abcde";
        String strB = "deabc";

        System.out.println(check(strA, strB));

    }

    // 检查
    public static boolean check(String strA, String strB) {
        for (int i = 0; i < strA.length(); i++) {
            strA = rotateStr(strA);
            if (strA.equals(strB)) return true;
        }
        return false;
    }

    // 字符串旋转方法
    public static String rotateStr(String str){
        // 拆分字符串,分为头部和尾部
        String headStr = str.substring(1);  
        char tailStr = str.charAt(0);
        return headStr + tailStr;  // 拼接
    }
}

首先要明白题目中字符串的旋转方式。一次旋转就是把字符串第一个字符放到最后,例如:"abcde --> bcdea"

Java中String对象内容不可改变。但是可以使用截取的方式,因此rotateStr()方法用来完成一次旋转。

check()方法用来检查,如果strA和strB相等,返回true,不等返回false。

字符串不可能一直旋转下去,他是有一个度的。拿字符串"abcde"来举例他的旋转过程。

第一次旋转:abcde --> bcdea
第二次旋转:bcdea --> cdeab
第三次旋转:cdeab --> deabc
第四次旋转:deabc --> eabcd
第五次旋转:eabcd --> abcde

根据旋转过程,字符串"abcde"通过5次旋转就会回来。而5也是字符串的长度。

每一次循环进行一次比较。

  // 字符串选择方法
    public static String rotateStr(String str){
        char[] arr = str.toCharArray();
        char end = arr[0];  // 先获取需要放到后面的字符
        // 剩下的字符往前移
        for (int i = 1; i < arr.length; i++) {
            arr[i-1] = arr[i];
        }
        arr[arr.length-1] = end;
        return new String(arr);
    }

这道题还有一种解法。数组

字符串是不可变的,但是可以用截取或者数组。

字符串打乱

键输入任意字符串,打乱里面的内容

import java.util.Random;
import java.util.Scanner;

public class StringTest02 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.next();

        // 字符串变数组
        char[] arr = str.toCharArray();

        String result = new String(disruptArr(arr));
        System.out.println(result);
    }

    // 打乱数组方法
    public static char[] disruptArr(char[] arr){
        Random r = new Random();
        for (int i = 0; i < arr.length; i++) {
            int indexR = r.nextInt(arr.length);  // 设定随机数范围,随机索引
            char temp = arr[indexR];
            arr[indexR] = arr[i];
            arr[i] = temp;
        }
        return arr;
    }
}

生成验证码

内容:可以是小写字母,也可以是大写字母,还可以是数字
规则:长度为5;内容中是四位字母,1位数字;其中数字只有1位,但是可以出现在任意的位置。

import java.util.Random;

public class StringTest03 {
    public static void main(String[] args) {
        Random r = new Random();
        // 创建两个数组,存放大小写字母数组和数字数组
        char[] alphabetArr = new char[52];  // 26+26
        char[] numberArr= {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
        char[] verificationCodeArr = new char[5];
        int capitalAlphabet = 65, minusculeAlphabet = 97;

        // 字母数组大写初始化
        for (int i = 0; i < 26; i++) {
            if (capitalAlphabet <= 90) {
                char c = (char) capitalAlphabet;
                alphabetArr[i] = c;
                capitalAlphabet ++;
            }
        }

        // 字母数组小写初始化
        for (int i = 26; i < alphabetArr.length; i++) {
            char c = (char) minusculeAlphabet;
            alphabetArr[i] = c;
            minusculeAlphabet++;
        }

        // 组成验证码
        for (int i = 0; i < verificationCodeArr.length; i++) {
            int indexR ;
            if (i<=3) {
                indexR = r.nextInt(alphabetArr.length);
                verificationCodeArr[i] = alphabetArr[indexR];
            } else {
                indexR = r.nextInt(numberArr.length);
                verificationCodeArr[i] = numberArr[indexR];
            }
        }

        String result = new String(disruptArr(verificationCodeArr));
        System.out.println(result);
    }

    public static char[] disruptArr(char[] arr){
        Random r = new Random();
        for (int i = 0; i < arr.length; i++) {
            int indexR = r.nextInt(arr.length);  // 设定随机数范围,随机索引
            char temp = arr[indexR];
            arr[indexR] = arr[i];
            arr[i] = temp;
        }
        return arr;
    }
}

借用了上道题打乱字符串的方法。先把验证码保存到字符数组里面,前四位字母,后一位数字,随后打乱。

这里有个常识,应该记忆一下

  • 大写字母的ASCII码(AZ)是6590;
  • 小写字母的ASCII码(az)是97122;

这道题经过搜索,还有改进。我是通过两个循环把52个字母存到数组里的,但是还有别的方法

public class Main {
    public static void main(String[] args) {
        char[] letters = new char[52];
        for (int i = 0; i < 26; i++) {
            letters[i] = (char) ('A' + i);
            letters[i + 26] = (char) ('a' + i);
        }

        for (char letter : letters) {
            System.out.print(letter + " ");
        }
    }
}

第二个for循环的语法有点看不懂,但是逻辑是清晰的。

数组元素相乘

给定两个以字符串形式表示的非负整数num1和num2,返回num1和num2的乘积,它们的乘积也表示为字符串形式。
注意:需要用已有的知识完成。

练习四

给你一个字符串s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。
单词是指仅由字母组成、不包含任何空格字符的最大子字符串。

示例1:输入:s = “Hello World” 输出:5
解释:最后一个单词是"wor1d”,长度为5。

示例2:输入:s = " fly me to the moon" 输出:4
解释:最后一个单词是“moon”,长度为4。

示例3:输入:s= “luffy is still joyboy” 输出:6
解释:最后一个单词是长度为6的"joyboy”。

总结

字符串操作在编程中很常用的。
// 字母数组小写初始化
for (int i = 26; i < alphabetArr.length; i++) {
char c = (char) minusculeAlphabet;
alphabetArr[i] = c;
minusculeAlphabet++;
}

    // 组成验证码
    for (int i = 0; i < verificationCodeArr.length; i++) {
        int indexR ;
        if (i<=3) {
            indexR = r.nextInt(alphabetArr.length);
            verificationCodeArr[i] = alphabetArr[indexR];
        } else {
            indexR = r.nextInt(numberArr.length);
            verificationCodeArr[i] = numberArr[indexR];
        }
    }

    String result = new String(disruptArr(verificationCodeArr));
    System.out.println(result);
}

public static char[] disruptArr(char[] arr){
    Random r = new Random();
    for (int i = 0; i < arr.length; i++) {
        int indexR = r.nextInt(arr.length);  // 设定随机数范围,随机索引
        char temp = arr[indexR];
        arr[indexR] = arr[i];
        arr[i] = temp;
    }
    return arr;
}

}


借用了上道题打乱字符串的方法。先把验证码保存到字符数组里面,前四位字母,后一位数字,随后打乱。

这里有个常识,应该记忆一下

* 大写字母的ASCII码(A~Z)是65~90;
* 小写字母的ASCII码(a~z)是97~122;

这道题经过搜索,还有改进。我是通过两个循环把52个字母存到数组里的,但是还有别的方法

```java
public class Main {
    public static void main(String[] args) {
        char[] letters = new char[52];
        for (int i = 0; i < 26; i++) {
            letters[i] = (char) ('A' + i);
            letters[i + 26] = (char) ('a' + i);
        }

        for (char letter : letters) {
            System.out.print(letter + " ");
        }
    }
}

第二个for循环的语法有点看不懂,但是逻辑是清晰的。

数组元素相乘

给定两个以字符串形式表示的非负整数num1和num2,返回num1和num2的乘积,它们的乘积也表示为字符串形式。
注意:需要用已有的知识完成。

练习四

给你一个字符串s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。
单词是指仅由字母组成、不包含任何空格字符的最大子字符串。

示例1:输入:s = “Hello World” 输出:5
解释:最后一个单词是"wor1d”,长度为5。

示例2:输入:s = " fly me to the moon" 输出:4
解释:最后一个单词是“moon”,长度为4。

示例3:输入:s= “luffy is still joyboy” 输出:6
解释:最后一个单词是长度为6的"joyboy”。

总结

字符串操作在编程中很常用的。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【会议征稿,IEEE出版】第三届机器人、人工智能与智能控制国际会议(RAIIC 2024,7月5-7)

第三届机器人、人工智能与智能控制国际会议&#xff08;RAIIC 2024&#xff09;将于2024年7月5-7日中国绵阳举行。 RAIIC 2024是汇聚业界和学术界的顶级论坛&#xff0c;会议将邀请国内外著名专家就以传播机器人、人工智能与智能控制领域的技术进步、研究成果和应用做专题报告…

vs工程添加属性表

一、简介 1、 vs工程属性表以&#xff08;.props&#xff09;为后缀 2、 作用&#xff1a;当多个工程需要配置很多相同的属性配置时方便同步&#xff0c;比如多个工程需要链接相同的头文件&#xff0c;库文件&#xff0c;输出路径&#xff0c;中间目录等 3、本章内容测试环境&a…

Web渗透-SSRF服务端请求伪造

SSRF&#xff08;Server-Side Request Forgery&#xff0c;服务器端请求伪造&#xff09;是一种由攻击者利用漏洞服务器发送恶意请求的攻击方式。SSRF漏洞通常出现在服务器端的web应用中&#xff0c;应用允许用户提供的输入被服务器用来发起请求&#xff0c;而没有对输入进行充…

数据结构:为什么说链表是顺序表的升级版(c语言实现)

前言&#xff1a; 我们在之前的几篇文章中详细的讲解了顺序表的特点&#xff0c;增删改查操作和动态顺序表的优点&#xff0c;并使用顺序表的底层结构实现了通讯录项目&#xff0c;似乎顺序表是一个非常完美的数据结构&#xff0c;它可以实现按照需求实现增删查改&#xff0c;对…

换电脑后导入git本地仓库记录

导入本地仓库tig记录 换了新电脑&#xff0c;将旧电脑的数据盘查到新的笔记本之后发现&#xff0c;使用pycharm 读取不到本地的git提交记录了&#xff0c;我没有将本地git上传到远程仓库的习惯&#xff0c;这可抓马了&#xff0c;硬盘插回去的话也太麻烦了。试了 vscode 提示设…

冲击2024年CSDN博客之星TOP1:CSDN文章质量分查询在哪里?

文章目录 一&#xff0c;2023年博客之星规则1&#xff0c;不高的入围门槛2&#xff0c;[CSDN博文质量分测评地址](https://www.csdn.net/qc) 二&#xff0c;高分秘籍1&#xff0c;要有目录2&#xff0c;文章长度要足够&#xff0c;我的经验是汉字加代码至少1000字。3&#xff0…

币旺BitonAI系统助力智能化交易 引领加密资产交易行业革新

随着加密货币市场的蓬勃发展&#xff0c;交易者们面临着市场波动、信息过载和日益激烈的竞争等多重挑战。在这样的背景下&#xff0c;智能化交易系统应运而生。AI技术的引入无疑为加密货币交易市场带来了一场革命性的变革。通过深度学习和大数据分析&#xff0c;加密货币交易成…

手机怎么自动切换ip地址

在数字化时代&#xff0c;网络IP地址不仅是设备在网络世界的标识&#xff0c;也是确保用户网络安全和数据隐私的关键因素。对于手机用户来说&#xff0c;在某些情境下可能需要自动切换IP地址&#xff0c;本文将为您介绍手机怎么自动切换IP地址。 随着网络技术的发展&#xff0c…

Claude 3.5 强势出击:解析最新AI模型的突破与应用

近年来&#xff0c;人工智能领域的发展迅猛&#xff0c;各大科技公司纷纷推出了自家的高级语言模型。在这场技术竞赛中&#xff0c;Anthropic的Claude系列模型凭借其强大的性能和创新的功能脱颖而出。最近&#xff0c;Anthropic发布了Claude 3.5 Sonnet模型&#xff0c;引起了广…

Vue73-命名路由

一、路由的name属性 二、小结

【机器学习】正则卷积群理论及Python代码实现

1. 引言 1.1.卷积神经网络CNN 卷积神经网络&#xff08;CNN&#xff09;的数学模型是深度学习中用于处理图像和其他高维数据的关键组成部分。那么&#xff0c;CNN究竟是什么呢&#xff1f; 总结起来&#xff0c;CNN网络主要完成以下操作&#xff1a; 卷积操作&#xff08;Co…

使用Naive UI的级联选择器 Cascader进行省市区选择

序言&#xff1a; 在进行PC版的功能开发时&#xff0c;进行客户管理时老板要让客户便捷的选择自己的省市区等信息&#xff0c;而不是让他们一个个去填写&#xff0c;这时就需要使用级联选择器来进行省市区的选择。 注&#xff1a;element ui/plus的级联选择器也是可以的。 步骤…

Master PDF Editor v5 解锁版安装教程(小巧多功能PDF )

前言 Master PDF Editor&#xff0c;小巧的多功能PDF编辑器&#xff0c;轻松查看&#xff0c;创建&#xff0c;修改&#xff0c;批注&#xff0c;签名&#xff0c;扫描&#xff0c;OCR和打印PDF文档。高级注释工具&#xff0c;可以添加任意便笺指示对象突出显示&#xff0c;加…

Redis单例部署

目录 1. 概述2. 参考3. 环境4. 部署4.1 操作系统4.1.1 修改系统参数4.1.2 关闭透明大页内存4.1.3 修改系统限制 4.2 安装Redis4.2.1 下载Redis4.2.2 创建redis账号4.2.3 添加Redis环境变量4.2.4 创建Redis使用目录4.2.5 安装Redis4.2.6 手动修改配置文件&#xff08;**可跳过&a…

Linux连接工具MobaXterm详细使用教程

目录 一、MobaXterm的下载 1、访问官网 2、下载便携版 3、启动MobaXterm 二、MobaXterm基本使用设置 1、新建会话 2、使用ssh连接第一个会话 3、设置主密码 4、主界面 5、sftp文件上传下载 6、文件拖拽的上传下载 7.右键粘贴 8、查看服务器监测信息​编辑 9、个…

在Linux下使用CMake加载自定义路径第三方库的指南

CMake是一个强大的跨平台构建系统&#xff0c;广泛应用于C项目中。它不仅能够处理标准的构建过程&#xff0c;还可以灵活地集成各种第三方库&#xff0c;包括自定义路径的库、已编译的共享库&#xff08;.so 文件&#xff09;&#xff0c;以及仅包含头文件的库&#xff08;如Ei…

qt 简单实验创建一个可以拖拽和缩放的矩形

1.概要 2.代码 2.1 resizablewidget.h #ifndef RESIZABLEWIDGET_H #define RESIZABLEWIDGET_H#include <QWidget> #include <QMouseEvent>class ResizableWidget: public QWidget {Q_OBJECT public:ResizableWidget(QWidget *parent nullptr); protected:void m…

IPD笔记

IPD笔记 先弄一个一图流&#xff0c;改天再过来继续补充 IPD&#xff08;Integrated Product Development&#xff09;即集成产品开发&#xff0c;是一套产品开发的模式、理念与方法。华为的IPD的核心思想是基于市场需求&#xff0c;将产品开发作为一项投资来管理&#xff0c;以…

【复旦邱锡鹏教授《神经网络与深度学习公开课》笔记】卷积

卷积经常用在信号处理中&#xff0c;用于计算信号的延迟累积。假设一个信号发射器每个时刻 t t t产生一个信号 x t x_t xt​&#xff0c;其信息的衰减率为 w k w_k wk​&#xff0c;即在 k − 1 k-1 k−1个时间步长后&#xff0c;信息为原来的 w k w_k wk​倍&#xff0c;时刻 …

vivado、vitis2022安装及其注意事项(省时、省空间)

1、下载 AMD官网-资源与支持-vivado ML开发者工具&#xff0c;或者vitis平台&#xff0c; 下载的时候有个官网推荐web安装&#xff0c;亲测这个耗时非常久&#xff0c;不建议使用&#xff0c;还是直接下载89G的安装包快。 注意&#xff1a;安装vitis平台会默认安装vivado&…