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类型的数据还需要在堆内存里面开辟框架。这块内存里的内容就是字符数组的内容。随后还是返回堆内存里面的地址0x0022
给str1
执行第三行代码String str2 = new String(chs);
一切照旧。虽然str2
和str1
创建的内容是一样的,但是在堆内存里面并没有引用而是创建了两个"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
}
}
先从代码表面看,str1
和str1
的值都是一样的abc
,因此是返回true
,str1
和str3
的值不一样,返回false
。单纯这样理解只能半对。上面代码中创建字符串对象用的是直接复制方式,创建的字符串都放在串池里面。如果创建相同的字符串,是复用,那么肯定相同。
public class StringDemo03 {
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); // false
}
}
看上去str1
和str1
的值都是一样的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,否则返回false | boolean |
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 = aaabbbccc
、aaabbbccc + s4 = aaabbbcccddd
、aaabbbcccddd + s5 = aaabbbcccdddeee
,aaabbbcccdddeee
赋值给s6。
然而产生的aaabbb
,aaabbbccc
,aaabbbcccddd
,aaabbbcccdddeee
这些都会单独开辟空间进行存放。其实这些都没用了,最后我们就关心s6
的结果。
而StringBuilder就不一样,这是个容器。相当于箱子。完成字符串拼接时会直接吧这堆字符串(s1
,s2
,s3
,s4
,s5
)放进箱子里,顺其自然也就有了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);
}
}
看一下这段代码的内存图
虽然这里的字符串a
,b
,c
,不是直接赋值的方式创建的,但是字符还是放在了串池。如果细看底层源码,肯定离不开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=10
又10>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。growth
是newLength - 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”。
总结
字符串操作在编程中很常用的。