【学习分享】
*学习了解String类特性,掌握字符串相关的概念
*拓展学习StringBuffer和StringBuilder类,结合String类,归纳总结
*结合实际场景,应对常见的面试题型
1.什么是String类?
java.lang.String 类代表字符串。在面向对象Java中所有的字符串文字都是可以看做是实现此类的实例。如常见的的字符串是常量。它们的值在创建之后不能被更改,即字符串对象不可变,一旦修改则就会产生新的对象。字符串对象支持“+”拼接,字符串与任意类型“+”,结果都是字符串;String类型是final修饰的,不能被继承,类似的,System、Math等。
1.1.字符串常量对象为什么不可变?
在JDK的API 1.6说明文档中,我们看到官方对String类的解释说明:
-
public final class String extends Objectimplements Serializable, Comparable, CharSequence`
String类代表字符串。Java 程序中的所有字符串字面值都作为此类的实例实现字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。
1.2.字符串常量对象存在哪?
一般在字符串常量池。
1.3.字符串常量池在哪?
我们第一反应,字符串常量池是在Jvm的方法区,其实这样说是没有错,但是不同版本的JDK是有差距的,如果严谨的回答,我们应用注意到以下几点,如果你有更多的时间,也可以研究一下源码,这样也能更好的了解其原理。
(1)在包括JDK1.6版本之前:是在方法区;
(2)在JDK1.7版本中,是在堆中;
(3)在JDK1.8版本中,是在元空间。
可以看出,不能版本的软件开发工具包(JDK),字符常量池开辟存放的位置都不一样,这一点我们需要注意。
1.4.在底层,字符串是如何存储数据内容?
在这里,我就做一下简要的说明。在包括JDK1.8版本之前,使用的是char[]存储,在后面我们讲到的StringBuffer、StringBuilder类都是一样的;在JDK1.9版本中,使用的是是byte[]存储。在这里一般我们都认为是char[]存储,因为在项目组中还是应用JDK1.8版本居多,相对稳定安全。
1.5.为什么能用byte[]数组存储?
因为在ASCII码表范围内的字符,用单个字节就足以,这样也能更好的节省空间,所以可以看出JDK版本的更迭,优化的东西还是不少。
2.String、StringBuffer及其StingBuilder间的区别
String是不可变类,如果在大量的修改字符串操作或拼接字符串操作时,修改其实也就一个不断创建对象的过程,就会显得效率不高;因此,JDK又在java.lang包提供了可变字符序列StringBuilder和StringBuffer类型;
StringBuffer:是可变的字符序列,线程安全的,内容和长度是可变的;
StringBuilder:也是一个可变的字符序列,此类提供一个与StringBuffer兼容的API,但是不保证同步,即线程不安全,如果使用单线程,建议使用StringBuilder,因为效率会更高。
/*
* String、StringBuffer及其StingBuilder,在测试循环10k次时可以看出,其性能是不一样的:
* String 拼接用时368毫秒 占用53063104字节
* StringBuffer 拼接用时4毫秒 占用1951416字节
* StringBuilder 拼接用时4毫秒 占用1951416字节
* 以上数据可以看出:String拼接效率最差,已经多出可变字符序列的百倍,想想,现在我们还是单线程的场景,
* 优先选择可变字符序列无可非议,而StringBuffer与StringBuilder耗时和占用空间一致,其实还是有差距的,只是单线程差距很小,具体场景具体而定。效率上选择StringBuilder,线程安全上选择StringBuffer!
*/
public class TestTime {
public static void main(String[] args) {
testStringBuilder();//测试1
//测试2 testStringBuffer();
//测试3 testString();
}
public static void testString(){
long start = System.currentTimeMillis();
String s = new String("0");
for(int i=1;i<=10000;i++){
s += i;
}
long end = System.currentTimeMillis();
System.out.println("String拼接+用时:"+(end-start));//368
//Runtime.getRuntime().totalMemory():总内存
//Runtime.getRuntime().freeMemory():空闲内存
//总内存-空闲内存=使用内存
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("String拼接+memory占用内存: " + memory);//53063104字节
}
public static void testStringBuilder(){
long start = System.currentTimeMillis();
StringBuilder s = new StringBuilder("0");
for(int i=1;i<=10000;i++){
s.append(i);
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder拼接+用时:"+(end-start));//4
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("StringBuilder拼接+memory占用内存: " + memory);//1951416
}
public static void testStringBuffer(){
long start = System.currentTimeMillis();
StringBuffer s = new StringBuffer("0");
for(int i=1;i<=10000;i++){
s.append(i);
}
long end = System.currentTimeMillis();
System.out.println("StringBuffer拼接+用时:"+(end-start));//4
long memory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println("StringBuffer拼接+memory占用内存: " + memory);//1951416
}
}
3.经典应用场景–随机生成验证码
(1)验证码由0-9,A-Z,a-z的字符组成的一共6位字符组成的验证码
(2)让用户输入验证码,必须输入6位验证码,不能不输入,或者纯空白格
(3)对用户输入的验证码进行校验,不区分大小写,如果正确打印正确,否则重新输入
3.1.实现方式一
package com.daxia.case1;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
/**
* @Description---随机生成验证码1
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月17日
*/
public class VerificationCode01 {
public static void main(String[] args) {
// (1)验证码由0-9,A-Z,a-z的字符组成,长度即是62
char[] arr = new char[10 + 26 + 26];
// 为arr[0]-arr[9]赋值字符[0,9],循环10次
for (int i = 0; i < 10; i++) {
arr[i] = (char) ('0' + i);
}
// 为arr[10]-arr[25]赋值字符[a,z],循环26次
for (int i = 10; i < 10 + 26; i++) {
arr[i] = (char) ('a' + (i - 10));
}
// 为arr[26]-arr[61]赋值字符[A,Z],循环26次
for (int i = 36; i < arr.length; i++) {
arr[i] = (char) ('A' + (i - 36));
}
System.out.println(Arrays.toString(arr));
/*
* 此时打印: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f, g, h, i, j, k,
* l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D, E, F, G, H,
* I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
*/
Scanner input = new Scanner(System.in);
while (true) {
char[] code = new char[6];
Random rand = new Random();
// (2)输入6位验证码
for (int i = 0; i < code.length; i++) {
/*
* 说明: public int nextInt(int n) 参数:n - 要返回的随机数的范围。必须为正数。
* 返回:下一个伪随机数,在此随机数生成器序列中 0(包括)和 n(不包括)之间均匀分布的 int 值。
*/
int index = rand.nextInt(arr.length); // 随机生成arr数组[0,62)的下标
code[i] = arr[index];
}
System.out.println(Arrays.toString(code));
// 去除字符串格式输出,创建对象strCode接收
String strCode = new String(code);
System.out.println(strCode);
System.out.println("随机生成的验证码:" + strCode);
String str;
while (true) {
System.out.print("请输入验证码:");
// public String nextLine()此扫描器执行当前行,并返回跳过的输入信息。
str = input.nextLine();
if ("".equals(str.trim())) {
System.out.print("必须输入验证码:");
} else {
break;
}
}
if (str.equals(strCode)) {
System.out.println("您输入的验证码匹配正确!");
break;
} else {
System.out.println("您输入的验证码有误,请重新验证!");
}
}
}
}
3.2.实现方式二
针对随机生成验证码实现方式一进行了改进,这种方式也不错。
package com.daxia.case1;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
/**
* @Description---随机生成验证码2
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月17日
*/
public class VerificationCode02 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
Random rand = new Random();
while (true) {
char[] code = new char[6];
for (int i = 0; i < code.length; i++) {
int type = rand.nextInt(3);// 0、1、2
switch (type) {
case 0:
// (rand.nextInt(10)表示范围[0,10) 0默认SCAII值是48
code[i] = (char) (rand.nextInt(10) + 48);
break;
case 1:
// (rand.nextInt(10)表示范围[0,26) A默认SCAII值是65
code[i] = (char) (rand.nextInt(26) + 65);
break;
case 2:
// (rand.nextInt(10)表示范围[0,26) a默认SCAII值是97
code[i] = (char) (rand.nextInt(26) + 97);
break;
}
}
System.out.println(Arrays.toString(code));
String strCode = new String(code);
System.out.println(strCode);
System.out.println("随机生成的验证码:" + strCode);
String str;
while (true) {
System.out.print("请输入验证码:");
str = input.nextLine();
if ("".equals(str.trim())) {
System.out.print("必须输入验证码:");
} else {
break;
}
}
if (str.equals(strCode)) {
System.out.println("您输入的验证码匹配正确!");
} else {
System.out.println("您输入的验证码有误,请重新验证!");
}
}
}
}
4.必试必问的字符串题型
4.1.字符串的length与数组的length有什么区别?
这是细节题,字符串中的length()是方法,而数组中的length是属性。
4.2.看程序写结果(方法参数传递机制)-不可变对象
我们知道数据类型主要分为两个类型:
(1)基本数据类型:实参给形参传递数据值,形参的修改和实参是无关的,即不能印象实参的值;
(2)引用数据类型:实参给形参传递数据值,同形参去修改实参的属性或者元素,实际上这是一个指向关系,操作的是同一个对象,即会影响实参。目前我们知道,包装类和String类的对象是不可变的对象,一旦进行修改的操作就会产生新的对象,即与实参没有关系,实例代码如下:
package com.daxia.case1;
/**
* @Description---方法参数传递机制
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月17日
*/
public class ParamTransmit {
public static void param(TestInfo tIn, int intIn, Integer integerIn, String strIn) {
tIn.num = 200; // 引用数据类型
tIn.str = "bcd";// 引用数据类型
intIn = 200;// 基本数据类型
integerIn = 200;// 包装类型
strIn = "bcd";// String类型
}
public static void main(String[] args) {
TestInfo tIn = new TestInfo(100, "abc"); // 实参传递无作用
int intIn = 100;// 实参传递有作用,修改操作有效
Integer integerIn = 100;// 实参传递无作用
String strIn = "abc";// 实参传递无作用
// 调用静态方法param()
param(tIn, intIn, integerIn, strIn);
System.out.println(tIn.num + "\t" + tIn.str + "\t" + intIn + "\t" + integerIn + "\t" + strIn);
//参数传递前结果:200 bcd 200 100 abc
//参数传递后结果:200 bcd 100 100 abc
//原基本数据类型 参数intIn值200,参数传递后,变为值100,与上面的结论一致!
}
}
class TestInfo {
public int num;
public String str;
public TestInfo() {
super();
}
public TestInfo(int num, String str) {
this.num = num;
this.str = str;
}
}
4.3.经典题型–字符串方法应用程序实现
4.3.1.应用Trim方法,去除字符串两端的空格。
package com.daxia.case1;
import org.junit.Test;
/**
* @Description---把str字符串的前后空格去掉,返回结果字符串
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月
*/
public class TrimCodeTest {
@Test
public void test01() {
String str = myTrim1(" hello world ");
System.out.println("str = [" + str + "]");
}
public String myTrim1(String str) {
// 思路一:
// (1)判断str是否是以空格开头,如果是,把开头的空格截掉,重复多次,开头的空格就都去掉了
while (str.startsWith(" ")) {
str = str.substring(1);// [0]被截掉了,[1]被截掉...
}
// (2)判断str是否以空格结尾,如果是,把最后的空格截掉,重复多次,最后的空格就都去掉了
while (str.endsWith(" ")) {
str = str.substring(0, str.length() - 1);
}
return str;
}
@Test
public void test02() {
String str = myTrim2(" hello world ");
System.out.println("str = [" + str + "]");
}
public String myTrim2(String str) {
// 思路二:
char[] charArray = str.toCharArray();
// (1)找出开头第一个不是空格的下标
int start = 0;
for (int i = 0; i < charArray.length; i++) {
if (charArray[i] != ' ') {
start = i;
break;
}
}
// (2)找出最后一个不是空格的下标
int end = str.length() - 1;
for (int i = charArray.length - 1; i >= 0; i--) {
if (charArray[i] != ' ') {
end = i;
break;
}
}
// (3)截取[start,end]
return str.substring(start, end + 1);
}
@Test
public void test03() {
String str = myTrim3(" hello world ");
System.out.println("str = [" + str + "]");
}
public String myTrim3(String str) {
return str.replaceAll("^\\s*|\\s*$", "");
}
}
4.3.2.反转一个字符串,要求对字符串指定部分反转。如:“sdfabcdefrt”可以反转为“sdfadcbsfrt”。
package com.daxia.case1;
import org.junit.Test;
/**
* @Description---反转一个字符串,要求对字符串指定部分反转
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月
*/
public class ReverseCodeTest {
@Test
public void test(){
String str = "sdfabcdefrt";
str = reverse(str, 2, 6);
System.out.println(str);
}
//将字符串中指定部分进行反转。
//将str的[start,end)部分进行反转
public String reverse(String str,int start,int end){
char[] charArray = str.toCharArray();
/*
* charArray[start]--charArray[end]
* charArray[start+1]--charArray[end-1]
* charArray[start+2]--charArray[end-2]
* ...
* 总次数 = (end-start)/2
*/
for (int i = 0; i < (end-start)/2; i++) {//循环次数是交换次数
char temp = charArray[start+i];
charArray[start+i] = charArray[end-1-i];
charArray[end-1-i] = temp;
}
//重新构建字符串
return new String(charArray);
}
}
4.3.3.统计一个字符串在另一个字符串中出现的次数。如:"ab"在"adsfdsffjjkababae"出现的次数。
package com.daxia.case1;
/**
* @Description---统计一个字符串在另一个字符串中出现的次数
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月
*/
public class CountStrTest {
public static void main(String[] args) {
String str = "ab";
String string = "adsfdsffjjkababae";
int count = 0;// 统计次数默认为0
while (string.contains(str)) { // string对象包含于str对象
/*
* public int indexOf(String
* str)如果字符串参数作为一个子字符串在此对象中出现,则返回第一个这种子字符串的第一个字符的
* 索引;如果它不作为一个子字符串出现,则返回 -1。
*/
int index = string.indexOf(str);
/*
* public String substring(int beginIndex)返回一个新的字符串,它是此字符串的一个子字
* 符串。该子字符串从指定索引处的字符开始,直到此字符串末尾。
*/
string = string.substring(index + str.length()); // 主次截取字符串,返回新的字符串
count++;
}
System.out.println("count=" + count);
}
}
4.3.4.比较出两个字符串中的最大相同子串。如 str1=’“adsfdsffjjkababae” str2=“asddsffjjkedd”。
package com.daxia.case1;
/**
* @Description---比较出两个字符串中的最大相同子串
* @author Email:1603234088@qq.com
* @version jdk1.8-eclispe
* @date 2019年7月
*/
import org.junit.Test;
public class CompareStrTest {
@Test
public void test(){
String str1 = "adsfdsffjjkababae";
String str2 = "asddsffjjkedd";
String maxSameStr = maxSameStr(str1, str2);
System.out.println(maxSameStr);
}
/*
* str1 = "abcwerthelloyuiodef"
* str2 = "cvhellobnm"
*
* 第1次:str1中包含str2,那么最大就是str2
* 第2轮--第n次:str2左边不动,右边依次减n个字符,看是否包含截掉的部分
* 第3轮--第n次:str2左边去掉1个,右边依次减n个字符,看是否包含截掉的部分
* 。。。。
*/
public String maxSameStr(String s1, String s2){
//找出短的和长的
String max = s1.length()>s2.length()?s1:s2;
String min = s1.length()<s2.length()?s1:s2;
//用短的去与长的进行比较,依次将短的截掉n个字符
String result = "";
for (int i = 0; i < min.length(); i++) {//i是左边的要去掉的字符的个数
for (int j = 0; j < min.length()-i; j++) {//j是右边要去掉的字符的个数
/*
* 第一轮:i=0
* 第1次:temp = min.substring(0, min.length());
* 第2次:temp = min.substring(0, min.length()-1);
* 第3次:temp = min.substring(0, min.length()-2);
* ...
*第二轮:i=1
* 第1次:temp = min.substring(1, min.length());
* 第2次:temp = min.substring(1, min.length()-1);
* 第3次:temp = min.substring(1, min.length()-2);
* ...
*/
String temp = min.substring(i, min.length()-j);
if(max.contains(temp)){
if(temp.length() > result.length()){
result = temp;
}
}
}
}
return result;
}
}
推荐阅读往期博文:
#轻松一刻
☝上述分享来源个人总结,如果分享对您有帮忙,希望您积极转载;如果您有不同的见解,希望您积极留言,让我们一起探讨,您的鼓励将是我前进道路上一份助力,非常感谢!我会不定时更新相关技术动态,同时我也会不断完善自己,提升技术,希望与君同成长同进步!
☞本人博客:https://coding0110lin.blog.csdn.net/ 欢迎转载,一起技术交流吧!