面向对象基础#高频的、必会的-String常用类

【学习分享】

*学习了解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/  欢迎转载,一起技术交流吧!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值