字符串类-String

1、初识String类

package aString类的初识;

/*
 * 关于java JDK中内置的一个类:java.lang.String
 * 			1、String表示的是字符串类型,是引用数据类型,不是基本数据类型
 * 			2、java中用""括起来的都是String对象。如:"abc","def","hellow world"这三个都是String对象
 * 			3、java中规定,双引号括起来的字符串是不可改变的,如"abc","def"这些都是直接存到方法区的字符串常量池
 *			中的,就是说当你定义一个String类型的变量并赋值为"abc"时,另外想定义一个String类型变量赋值为"abc"+"xv"
 *			时,在字符串常量池中的字符串"abc"是不会改变的,但是可以被拿去与新创建的String对象"xv"拼接,然后底
 *			层创建一个新的String对象,即字符串"abcxv"。
 *
 *		SUN公司将字符串放进字符串常量池中原因是:字符串在实际开发中使用太频繁,不能每一次使用都在底层创建String对象
 *		放在方法区的字符串常量池中即可随调随用,执行显效率高。
 *
 */

public class StringTest1 {
	
	public static void main(String[] args) {
		//以下两行代码在底层是创建了三个String对象的,都在字符串常量池中
		String s1 = "abc";
		String s2 = "abc"+"zrj";//并不是"abc"变成"abczrj","abc"只是被拿过来拼接而已
		
		//分析:用new方式创建的字符串对象,以下代码中的"xy"是哪来的
		//首先一定要记得凡是双引号括起来的字符串都在字符串常量池中有一份
		//然后是"new出来的"对象一定会在堆内存中开辟内存空间
		//所以在字符串常量池中有字符串"xy",堆中new出一个String对象,该对象保存了"xy"的内存地址,栈
		//中的String类型变量s3存储堆中String对象的内存地址
		String s3 = new String("xy");//
		
	}

}

2、String字符串存储原理

package bString字符串存储原理;


public class StringTest {
	
	public static void main(String[] args) {
		
		//垃圾回收器是不会释放字符串常量池中的对象的,GC只是对堆中的对象进行释放
		String s1 = "hello";
		String s2 = "hello";//"hello"是存储在字符串常量池中,这个"hello"对象不会新建
		System.out.println(s1 == s1);//true
		
		//思考?由于字符串对象保存在字符串常量池中,同一个字符串唯一,所以内存地址唯一,是不是比较两个字符串就不需要比较其中的内容了呢
		String x = new String("xy");
		String y = new String("xy");
		System.out.println(x == y);//false-->比较的是x与y保存的String对象的引用地址
		//通过以上输出字符串比较用==不保险,要用String中重写的equals方法
		System.out.println(x.equals(y));//true-->比较的是这两个String对象中保存的指向字符串对象"xy"的内存地址
		
		String k = new String("test");
		//字符串后面加 . 是因为"test"是一个字符串对象,只要是对象都能调用方法,所以可以加.
		//如:String s = "kkk";--->我们可以发现可以s与"kkk"是等价的,s可以s.  那么"kkk"也可以
		System.out.println("test".equals(k));//建议使用这种方式
		System.out.println(k.equals("test"));//不建议使用这种方式,k可能为null,报异常
		
		
	}

}

3、String中的构造方法

package cString类中的构造方法;

public class StringTest {
	
	public static void main(String[] args) {
		
		//创建字符串对象最常用的一种方式
		String s1 = "hello world";
		//s1这个变量中保存的是一个内存地址
		//以下代码应该输出一个内存地址
		System.out.println(s1);//hello world  -->却输出了字符串,说明String类中重写了toString方法
		
		/*
		 * 常用构造方法(1)
		 */
		byte[] bs = {97,98,99};//97为a,98为b,99为c
		String s2 = new String(bs);//abc
		//输出某个类型的引用时,会自动调用该类中的toString方法,该方法未重写时与Object中的一样,即输出内存地址
		//其输出结果是字符串,如下,说明String已经重写了toString方法
		//输出字符串对象的话输出的是字符串本身,而不是地址
		System.out.println(s2);	
		
		/*
		 * 常用的构造方法(2)
		 * String(字节数组,要转换成字符串的数组元素的起始下标,要转换的长度)
		 * 将byte数组的一部分转换成字符串
		 */
		String s3 = new String(bs, 1, 2);
		System.out.println(s3);//bc
		
		//将char数组全部转换成字符串
		char[] chars = {'我','是','中','国','人'};
		String s4 = new String(chars);
		System.out.println(s4);
		//将char数组部分转换成字符串
		System.out.println(new String(chars, 2, 3));
		
		/*
		 * 常用构造方法(3)
		 * 
		 */
		
	}

}

4、String类中的常用方法

package dString类中的常用方法;

public class StringTest {
	
	public static void main(String[] args) {
		
		//String类中的常用方法
		/*
		 * (1)掌握 char charAt(int index)
		 * 根据输入的索引(下标)取出字符串中的某个字符
		 */
		char c = "中国人".charAt(1);
		String s = String.valueOf(c);
		System.out.println(c+s);//国
		
		/*
		 * (2)了解  int compareTo(String anotherString)
		 *    根据字典的排列顺序比较字符串中的每个字符,即比较每个字符的ASCLL码的值
		 * 	  顺序后面的相当于数值大的,而且是相同索引的进行比较,只要一个被比出来后面就不比了
		 *    字符串之间比较大小
		 */        
		int result = "abc".compareTo("abc");
		System.out.println(result);//0(等于0)		数值相等--10-10=0
		
		int result1 = "abce".compareTo("abcd");
		System.out.println(result1);//-1(小于0)		数值不等-前小后大--9-10=-1<0

		int result2 = "abcd".compareTo("abce");
		System.out.println(result2);//1(大于0)		数值不等-前大后小--10-9=1>0

		
		System.out.println( "abcd".compareTo("acbd"));//-1
		
		/*
		 * (3)掌握  boolean contains(CharSequence str)
		 * 判断前面的字符串是否包含后面的字符串
		 */
		System.out.println("Hello world".contains("world"));//true
		System.out.println("http://www.baidu.com".contains("world"));//false
		
		/*
		 * (4)掌握  boolean endsWith(String suffix)
		 * 判断当前字符串是否以某个字符串结尾
		 */
		System.out.println("test.txt".endsWith(".txt"));//true
		System.out.println("test.txt".endsWith(".java"));//false
		
		/*
		 * (5)掌握 boolean equals(Object object)
		 *比较两个字符串用equals方法
		 *equals有没有调compareTo方法呢?
		 *重写的equals方法是将输入的字符串拆分成字符然后转换成char类型或者byte类型的数组,
		 * 然后遍历数组,通过比较每个相同索引的字符的ASCLL码比较的,和compareTo的原理一样
		 */
		System.out.println("abc".equals("abc"));
		
		/*
		 * (6) 掌握  boolean equalsIgnoreCase(String anotherString)
		 * 		比较两个字符串是否相等,同时忽略大小写
		 */
		System.out.println("ABC".equalsIgnoreCase("abc"));//true
		
		/*
		 * (7)掌握  byte[] getBytes()
		 * 将字符串对象转换成byte型数组,就是这些字符的ASCLL码的值
		 * 如果把这个byte数组的值放到char类型数组中数组,就可以把
		 * 这些ASCLL码的值对应的字符一个一个的输出出来了
		 */
		byte[] bytes = "abcdefghijkmlnopqrst".getBytes();
		for (int i = 0; i < bytes.length; i++) {
			System.out.print(bytes[i]+" ");//97 98 99 100 101 102 103 104 105 106 107 109 108 110 111 112 113 114 115 116 
		}
		System.out.println();
		
		/*
		 * (8)掌握  int indexOf(String str)
		 * 判断某个子字符串在当前字符串中第一次出现处的索引
		 */
		System.out.println("oraclejava.netc++com.phppythonjavago".indexOf("java"));//6
		
		/*
		 * (9)掌握   boolean isEmpty()
		 * 判断一个字符串是否为空
		 */
		System.out.println("".isEmpty());//true
		System.out.println("a".isEmpty());//false
		
		/*
		 * (10)掌握  int length()
		 * 返回字符串的长度
		 * 判断数组的长度的length是属性,没有括号,字符串的是方法
		 */
		System.out.println("sdasdsa".length());//7
		System.out.println("".length());//0
		
		/*
		 * (11)掌握  int lastIndexOf(String str)
		 * 判断某个子字符串在当前字符串中最后一次出现在的索引(下标)
		 */
		System.out.println("oraclejava.netc++com.phppythonjavago".lastIndexOf("java"));//30
		
		/*
		 * (12)掌握   String replace(CharSequence target, CharSequence replacement) 
		 * 将所有目标字符串target替换成所需的字符串replacement
		 * String的分类接口是CharSequence
		 */
		String newString = "http://www.baidu.com".replace("http://", "https://");
		System.out.println(newString);//https://www.baidu.com
		//将下列的:替换成=
		System.out.println("name:zhongrongjie&password:123&age:20".replace(":", "="));//name=zhongrongjie&password=123&age=20
		
		/*
		 * (13)掌握   String[] split(String regex) 
		 * 根据给定正则表达式的匹配拆分此字符串。
		 */
		String[] ymd = "1997-02-15".split("-");//"1997-02-15"以"-"为分隔符进行拆分,放入一个String类型的数组中
		for (int i = 0; i < ymd.length; i++) {
			System.out.print(ymd[i]+" ");
		}
		System.out.println();
		
		/*
		 * (14)掌握  boolean startsWith(String prefix)
		 * 判断当前字符串是否以某个子字符串开始
		 */
		System.out.println("http://www.baidu.com".startsWith("http://"));//true
		System.out.println("http://www.baidu.com".startsWith("https://"));//false
		
		/*
		 * (15)掌握  String substring(int beginIndex) 
		 * 返回一个新的字符串,它是此字符串的一个子字符串,从输入的索引开始往后截取当前字符串
		 */
		System.out.println("http://www.baidu.com".substring(7));//www.baidu.com
		// String substring(int beginIndex, int endIndex) -->指定截取字符串的开始索引与接收索引
		//该子字符串从指定的 beginIndex 处开始,直到索引 endIndex - 1 处的字符
		System.out.println("http://www.baidu.com".substring(7, 10));//www -->索引10不截
		
		/*
		 * (16)掌握   char[] toCharArray() 
		 * 将字符串转换成char数组
		 */
		char[] chars = "我是中国人".toCharArray();
		for (int i = 0; i < chars.length; i++) {
			System.out.print(chars[i]+" ");
		}
		System.out.println();
		
		/*
		 * (18)掌握   String toLowerCase()  &   String toUpperCase()  
		 * 将字符串转换成小写(左边)或大写(右边)
		 */
		System.out.println("ABCSDJDIjdsid".toLowerCase());//abcsdjdijdsid
		System.out.println("ABCSDJDIjdsid".toUpperCase());//ABCSDJDIJDSID
		

		/*
		 * (19)掌握   String trim() 
		 *  返回字符串的副本,忽略前导空白和尾部空白。去除字符串前后的空白,只是前后
		 */
		System.out.println("    my neme is lihuauhau        ".trim());//my neme is lihuauhau

		/*
		 * (17)掌握  static String valueOf() 
		 * String中只有该方法是静态的,不需要创建对象
		 * 作用是将非字符串(如boolean、int、char等类型)转换成字符串
		 * 该方法在String类中实现了方法重载(不是重写),
		 * 根据输入的参数类型自动调用针对该类型的valueOf方法
		 */
		System.out.println(String.valueOf(true));//true
		System.out.println(String.valueOf(100));//100,这个底层调用Integer的toString方法
		System.out.println(String.valueOf(3.14));//3.14,这个底层调用Double的toString方法
		//如果传入的是一个对象,底层就会调用该对象类型的重写的toString方法,可以看看源码
		System.out.println(String.valueOf(new Object()));//java.lang.Object@cac736f
		System.out.println(String.valueOf(new StringTest()));//重写toString方法
		//能打印到控制台上的都是字符串,println底层会调用String.valueOf()
		//所以为什么输出一个会调用toString,因为System.out.println()的println方法底层会调用String.valueOf(),而该方法又调toString
		//如下println()中输入的是对象的话调用的println方法源码
		/*
			 public void println(Object x) {
			    String s = String.valueOf(x);
			    synchronized (this) {
			        print(s);
			        newLine();//换行
			    }
			}
		 */



	}
	//重写该类的toString
	@Override
	public String toString() {
		// TODO 自动生成的方法存根
		//return super.toString();
		return "重写toString方法";
	}

}

5、StringBuffer和StringBuilder

package eStringBuffer与StringBuilder区别;

/*
 * 		StringBuffer与StringBuilder的区别:
 * 			StringBuffer中的方法前面都有synchronized关键字修饰,表示其在多线程环境下运行是安全的
 * 			StringBuilder中的方法前面没有synchronized,表示其在多线程环境下运行是不安全的
 * 		总之:
 * 				StringBuffer线程安全
 * 				StringBuilder非线程安全
 */	

public class StringBuilderTest {

}

package eStringBuffer进行字符串拼接;

/*
 * 		在使用+进行字符串拼接时,会创建一些多余的字符串对象,造成方法区的字符串常量池的内存浪费
 * 		如果需要进行大量的字符串拼接时,建议使用JDK中自带的:
 * 					java.lang.StringBuffer 或
 * 					java.lang.StringBuilder
 * 
 * 		优化StringBuffer的性能:在创建StringBuffer的时候给定一个初始化容量
 * 							   以减少底层的扩容次数,自己预估给一个大一点的初始化容量
 */

public class StringBufferTest {
	
	public static void main(String[] args) {
		
		//final修饰的引用变量一旦被赋值不可再指向其他,数组是引用类型
		
		//创建一个StringBuffer对象,而底层中该类的构造方法调用super(16)的是其父类AbstractStringBuilder的构造方法
		//其父类的构造方法是创建一个容量capacity=16的char(或者是byte)数组,创建什么类型看JDK版本而定
		//就是说下面创建对象就是创建一个容量默认为16的非final修饰的char[]-->value
		StringBuffer stringBuffer = new StringBuffer();//默认16
		
		//拼接字符串统一使用append()方法,append是追加的意思
		//就是说,char数组容量是16,要拼接字符串容量不够用,就需要用append()方法,该方法底层调用父类的append方法
		//父类的append有确保容量方法,该方法就调用数组拷贝方法Arrays.copyOf(),所以可以自动扩容,拷贝完数组后之前的数组就被垃圾回收器回收了
		stringBuffer.append('a');
		stringBuffer.append(true);
		stringBuffer.append(3.14);
		stringBuffer.append(100);
		stringBuffer.append("柏德汉");
		System.out.println(stringBuffer);//atrue3.14100柏德汉
		//因为追加的字符串被拆分为字符放进的是char数组中,不占用字符串常量池,而且该数组相比于比String中用final修饰的数组存字符对象
		//赋值后依然可以改变(可以加也可以减),所以用其进行字符串的拼接,final修饰的引用一旦被赋值不可再指向其他,数组是引用类型
		
		//指定初始化容量的StringBuffer对象(字符串缓冲区对象)
		StringBuffer stringBuffer2 = new StringBuffer(100);
		
	}

}

6、String和StringBuffer、StringBuilder的区别

6.1、String和其他两个类的区别是
  • String是final修饰的类型,每次声明的都是不可变的对象。而且String内部用来存储字符的char或byte数组也是final修饰的,所以每次操作都会产生新的String对象,然后将指针指向新的String对象
6.2、StringBuffer和StringBuilder都是在原有的对象上进行操作
  • 两者相比,前者是线程安全的,后者是线程不安全的

    一个对象线程安全,表示的是在多线程环境下,各个线程对这个对象的访问不需要添加任何同步机制,如添加synchronized关键字的人为操作,都能确保操作的数据是正确的。
    这里的StringBuffer就是这样的一个类,原因是该类中的方法都有synchronized修饰,不需要我们添加
    
  • 一般来说,线程不安全的效率更高,因为可以多线程并发:StringBuilder>StringBuffer>String。当我们在线程安全的情况下可以使用StringBuilder,线程不安全的情况下使用StringBuffer,不需要大量字符拼接时使用String。一般开发者优先使用StringBuilder

6.3、开发中为什么可以优先使用StringBuilder?案例
1、我们在service类中有一个方法,其中有字符串拼接内容如下:
    StringBuilder sb = new StringBuilder();
    sb.append("...");
    ...
当前端有多个请求过来时,一起调用这个方法,这时这个StringBuilder对象是每个线程独有的,没有线程安全问题。
因为此时是一个局部变量,线程不共享。
ArrayList、HashMap、HashSet等集合都不是线程安全的,我们一般都是将其使用在局部变量中,所以很少出现线程安全问题