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);  // abcdchs[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);  // trueSystem.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);  // falseSystem.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();  // helloString 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);  // 后裔你玩什么啊,TMDSystem.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,ZZString[] 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~14String 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);  // abcSystem.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);  // aaabbbcccdddSystem.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);  // 2System.out.println(sb);  // aaabbbcccdddSystem.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());  // 16System.out.println(sb1.length());  // 0// 存放内容不超过16sb2.append("abc");System.out.println(sb2.capacity());  // 16System.out.println(sb2.length());  // 3// 存放内容超过16sb3.append("abcdefghijklmnopqrstuvwxzy");System.out.println(sb3.capacity());  // 34System.out.println(sb3.length());  // 26// 扩容之后还不够sb4.append("abcdefghijklmnopqrstuvwxzy0123456789");System.out.println(sb4.capacity());  // 36System.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.*/@IntrinsicCandidatepublic 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@IntrinsicCandidatepublic 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*/@Overridepublic 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 codeint 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 > 0int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflowif (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {return prefLength;} else {// put code cold in a separate methodreturn 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}.*/@IntrinsicCandidatepublic 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 codeint 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+26char[] 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.mzph.cn/pingmian/32080.shtml

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

相关文章

【办公技巧】如何编辑带有限制编辑密码的PDF文件?

PDF文件打开之后发现设置了限制编辑&#xff0c;功能栏中的编辑按钮都是灰色的&#xff0c;导致PDF文件里的内容无法编辑。那么带有限制编辑的PDF文件&#xff0c;如何编辑&#xff1f;今天分享两个方法。 方法一&#xff1a; 我们可以将PDF文件转换成其他格式&#xff0c;有…

简单理解爬虫的概念

简单来说&#xff1a; 爬虫&#xff0c;即网络蜘蛛&#xff0c;是伪装成客户端与服务器进行数据交互的程序。 代码 代码教程分享&#xff08;无偿&#xff09;&#xff1a; 思路 1.获取网页的源码 pythondef askURL(url):head{"User-Agent":"Mozilla/5.0 (L…

SQL综合查询-学校教务管理系统数据库

一、一个完整的数据查询语句的格式 SELECT 【ALL|DISTINCT】<目标列表达式>【&#xff0c;<目标列表达式2>&#xff0c;...】 FROM <表名或视图名1>【&#xff0c;<表名或视图名2>&#xff0c;...】 【WHERE <元组选择条件表达式>】 【GROUP…

利用streamlit开发大模型API调用对话网页应用

利用streamlit开发大模型API调用对话网页应用 介绍 Streamlit是一个用于构建数据应用的开源框架&#xff0c;其简单易用的界面使得数据科学家和开发人员能够快速创建交互式应用。而OpenAI API则提供了强大的语言模型&#xff0c;可以生成自然语言响应。将这两者结合起来&…

redis-基础篇(2)

黑马redis-基础篇笔记 3. redis的java客户端-Jedis 在Redis官网中提供了各种语言的客户端&#xff0c;地址&#xff1a;https://redis.io/docs/clients/ 标记为❤的就是推荐使用的java客户端&#xff0c;包括&#xff1a; Jedis和Lettuce&#xff1a;这两个主要是提供了Redi…

# 消息中间件 RocketMQ 高级功能和源码分析(十)

消息中间件 RocketMQ 高级功能和源码分析&#xff08;十&#xff09; 一、消息中间件 RocketMQ 源码分析&#xff1a; 消息消费概述 1、集群模式和广播模式 消息消费以组的模式开展&#xff0c;一个消费组内可以包含多个消费者&#xff0c;每一个消费者组可订阅多个主题&…

PointCloudLib 点云边缘点提取 C++版本

0.实现效果 1.算法原理 PCL(Point Cloud Library)中获取点云边界的算法主要基于点云数据的几何特征和法向量信息。以下是对该算法的详细解释,按照清晰的格式进行归纳: 算法概述 PCL中的点云边界提取算法主要用于从3D点云数据中识别并提取出位于物体边界上的点。这些边界…

邀请函 | 人大金仓邀您相聚第十三届中国国际国防电子展览会

盛夏六月 备受瞩目的 第十三届中国国际国防电子展览会 将于6月26日至28日 在北京国家会议中心盛大举办 作为数据库领域国家队 人大金仓 将携系列行业解决方案 和创新实践成果亮相 期待您莅临指导 ↓↓↓↓↓↓ CIDEX 2024 中国国际国防电子展览会&#xff08;简称CIDEX&#xf…

前端核心框架Vue指令详解

目录 ▐ 关于Vue指令的介绍 ▐ v-text与v-html ▐ v-on ▐ v-model ▐ v-show与v-if ▐ v-bind ▐ v-for ▐ 前言&#xff1a;在学习Vue框架过程中&#xff0c;大家一定要多参考官方API &#xff01; Vue2官方网址https://v2.cn.vuejs.org/v2/guide/ ▐ 关于Vue指令的…

multiprocessing多进程计算及与rabbitmq消息通讯实践

1. 需求与设计 我所设计的计算服务旨在满足多个客户对复杂计算任务的需求。由于这些计算任务通常耗时较长且资源消耗较大&#xff0c;为了优化客户体验并减少等待时间&#xff0c;我采取了并行计算的策略来显著提升计算效率。 为实现这一目标&#xff0c;我计划利用Python的m…

基于Java实训中心管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

碳化硅陶瓷膜的生产工艺和应用

一、生产工艺 碳化硅陶瓷膜的生产工艺多样&#xff0c;其中浸渍提拉法和喷涂法为两大主流技术。 浸渍提拉法 浸渍提拉法是一种广泛应用的制备方法。其过程主要包括&#xff1a;先将陶瓷颗粒或者聚合物前体分散在水或有机溶剂中&#xff0c;形成均质稳定的制膜液。随后&#xff…

Jenkins macos 下 failed to create dmg 操作不被允许hdiutil: create failed - 操作不被允许?

解决方案&#xff1a; 打开设置&#xff0c;选择“隐私与安全”&#xff0c;选择“完全磁盘访问权限”&#xff0c;点击“”&#xff0c;选择jenkins的路径并添加。 同理&#xff0c;添加java的访问权限。

Python14 面向对象编程

1.什么是面向对象编程OOP Python的面向对象编程&#xff08;Object-Oriented Programming&#xff0c;简称OOP&#xff09;是一种编程范式&#xff0c;它使用“对象”来设计应用程序和计算机程序。这些对象由数据和能够操作这些数据的方法组成。面向对象编程的主要目标是提高软…

Webpack4从入门到精通以及和webpack5对比_webpack现在用的是哪个版本

3.1 打包样式资源css-loader、style-loader… {// 匹配哪些文件test: /\.less$/,// 使用哪些loader进行处理use: [// use数组中loader执行顺序&#xff1a;从右到左&#xff0c;从下到上&#xff0c;依次执行(先执行css-loader)// style-loader&#xff1a;创建style标签&#…

【C++】一个极简但完整的C++程序

一、一个极简但完整的C程序 我们编写程序是为了解决问题和任务的。 1、任务&#xff1a; 某个书店将每本售出的图书的书名和出版社&#xff0c;输入到一个文件中&#xff0c;这些信息以书售出的时间顺序输入&#xff0c;每两周店主会手工计算每本书的销售量、以及每个出版社的…

Vue74-路由传参2

一、$route中的params参数 二、在配置路由的index.js文件中&#xff0c;声明传参 占位符用的什么名字&#xff0c;params里面的key就是什么。 三、<router-link>标签中传参 3-1、to字符串写法 3-2、to的对象写法 注意&#xff1a;若是用params携带参数&#xff0c;不…

mysql的安装以及分享navicat for MySQL

前言 根据网上分享的安装方法以及自己遇到的问题解决方法 一、mysql是什么&#xff1f; mysql 是一个开放源码的小型关联式数据库管理系统 二、安装过程 1.下载安装包 下载地址&#xff1a;MySQL :: Download MySQL Community Server 跳过直接下载&#xff0c;解压即可 …

DPDK的Cache预取和Cache一致性

1.什么是Cache预取 众所周知&#xff0c;CPU访问Cache中的数据是比访问内存中的数据是要快的&#xff0c;而因为程序都有时间局部性和空间局部性&#xff0c;时间局部性简单来说就是某一条或几条指令在一段时间内会被CPU多次执行&#xff1b;空间局部性简单来说就是某一段数据块…

五十五、openlayers官网示例Loading Spinner解析——给地图添加loading效果,瓦片图层加载时等待效果

官网demo地址&#xff1a; Loading Spinner 这篇介绍了一个非常简单的loading效果 利用地图的loadstart和loadend事件&#xff0c;动态的添加和删除class名。 map.on("loadstart", function () {map.getTargetElement().classList.add("spinner");});map…