javaSE字符串学习笔记

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”。

总结

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

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值