String
String表示字符串,所有的字符串都是String类的实例,字符串是不可变的,一旦创建后就无法修改。下面来说说他的方法和特点。
String类中常用方法
char charAt(int index) 获取指定索引位置的字符
char[] toCharArray() 把字符串中的所有字符以字符数组形式返回
boolean equals(String str) 判断字符串内容是否一样(分大小写)
boolean equalsIgnoreCase(String str) 判断字符串内容是否一样(不分大小写)
boolean contains(String str) 判断某字符串是否存在此字符串中(子串判断)
int indexOf(String str) 从前往后查找字符串在此字符串中的位置(不存在返回 -1)
int indexOf(String str , int from) 同上 从from位置开始
int lastIndexOf(String str) 同上上, 反向查找
int lastIndexOf(String str , int from) 同上 , 从from开始
boolean startsWith(String str) 判断此字符串是否以某字符串开头
boolean startsWith(String str ,int to) 同上 从 to 位置开始
boolean endsWith(String str ) 判断此字符串是否以某字符串结尾
String replaceAll(String regex , String rep) 用指定字符串替换指定字符串
String replaceFirst(String regex , String rep) 用指定字符串替换指定字符串(只替换第一个)
String[] split(String regex) 指定字符串作为分割符,进行分割
String[] split(String regex , int limmit) 同上 (只分割成指定个数)
String subString(int begin) 从指定位置截取字符串至结尾
String subString(int begin , int end) 从指定位置截取字符串至指定位置
boolean isEmpty() 判断字符串是否为空
int length() 获取字符串长度
String trim() 去除左右空格
String toUpperCase() 字符串中的小写父母全都转换成大写字母
String toLowerCase() 字符串中的大写父母全都转换成小写字母
代码演示几个常用方法:
// 直接赋值
String str1="Android_小黑";
// 通过构造方法实例化
String str2=new String("Android_小黑");
//false
System.out.println(str1 == str2);
//true
System.out.println("equals方式:" + str1.equals(str2));
//演示几个常用的方法
System.out.println(str1.substring(5));//结果:id_小黑
System.out.println(str1.length());//结果: 10
System.out.println(str1.isEmpty());//结果: false
System.out.println(str1.toLowerCase());//结果:android_小黑
System.out.println(str1.toUpperCase());//结果:ANDROID_小黑
String[] strings = str1.split("_");
System.out.println(strings[0]+" "+strings[1]);//结果:Android 小黑
String str3=" 123 ";
System.out.println(str3.trim());//结果:123
接下来说说他的特点
1、不可变类,线程安全。
不可变类在java语言中使用final关键字实现,final有三个作用,简单的说修饰的类不可继承,方法不可重写,变量不可修改。而String类和存储字符数据的Char数组就是用final修饰的(简单来说我们说的字符串就是一串字符,内部是由char数组实现的),因此string类不可继承,内部数据(char数组)不能修改,对String进行替换,裁剪,连接都是新生成一个String对象,因此String是不可变类,不可变类都是线程安全的,最典型的就是JAVA中的包装类Integer,Long等。为什么不可变类是线程安全的呢?因为String对象是无状态对象,无状态对象可以理解为状态不能改变的对象,这里的状态也可以理解为对象里的数据。
2、可共享
jdk7运行时常量池存储在方法区,也就是永久代(hotspot),jdk8永久代被移除,运行时常量池存储在本地内存的元空间中,运行时常量池存储了字符串常量的引用,字符串常量存储在堆中。
new创建与直接赋值:(jdk1.7)针对创建的String类的对象,通过构造方法创建的String对象,存放在java虚拟机的堆内存,堆内存里存放的是字符串常量的地址,字符串常量存放在方法区的常量池中;通过直接赋值所创建的对象直接是方法区中的常量池中的字符串常量。而语句(s1==s2)是比较s1和s2中存放的地址,显然不相同(s1是通过new对象的方式),但是s2和s3的地址相同(创建字符串的时候看看方法区里面有没有,如果有就不再开辟空间,直接公用一块内存地址),则会得到true结果。其示意图如下图所示:
3.简单高效,但是耗费内存(不推荐)
程序中最常见的就是字符串拼接操作,java中可以直接使用+运算符来表示字符串拼接,我们看以下代码。
public static void main(String[] args) {
String text1 = "我喜欢";
String text2 = "学习";
String text3 = "Java";
text1 = text1+text2+text3;
System.out.println(text1);
}
内存分析:+意味着要返回结果,此时在栈中有三个引用指向常量池组(因为不是new的所以没有堆的事情),当执行text1+text2时候,生成了一个新的引用指向常量池,然后执行ext1+text2+text3,这时栈中又出现一个新的引用指向常量池,此时text1+text2(被当作垃圾,但因为String是永久带的不可被回收),然后赋值给text1,此时之前的text1成了垃圾,但无法被回收,极大的消耗了内存,这也是为什么不推荐使用String拼接的原因。我们更希望使用Stringnuilder和Stringbuffer来进行拼接操作。
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("1");
stringBuilder.append("2");
stringBuilder.append("3");
String text = stringBuilder.toString();
System.out.println(text);
StringBuilder类基本属性描述:
String类与StringBuilder,Stringbuffer类的区别:String类的内容是固定的,StringBuilder,Stringbuffer类的内容是可变的。
构造方法:
StringBuilder() : 构造一个不带任何字符的字符串生成器,其初始容量为 16 个字符。
StringBuilder(String str) : 构造一个字符串生成器,并初始化为指定的字符串内容,这里如果入参为“helloworld”,则其容量变为16+10 = 26。
append方法
可以append 任何东西进去,当容量存满了就自动扩容(自动扩容算法),字符串不缓存,每次产生垃圾都会及时回收。
(1)虚拟机角度描述:首先,我们实例化一个String类的字符串对象s,让其存储字符串“hello”,这时s存储的是“hello”的地址(假设在常量池中的地址为001),这时我们运行代码s += “world”,此时“world”会存储在常量池中的地址为002,而字符串“helloworld”则会存储在常量池中的地址003中,并且此时对象s存储的地址为003,即“helloworld”的地址,而此时001和002地址中的常量就浪费了,使用StringBuilder类可以避免拼接字符串时造成的资源浪费这个问题。
相比于String,他创建了更少的对象,制造的垃圾还可以被回收。
StringBuffer
可变的对象
线程安全的,因为StringBuffer的所有公开方法都是synchronized修饰的
StringBuilder
可变的对象
线程不安全的,StringBuilder是没有对方法加锁同步的,所以StringBuilder的性能要远大于StringBuffer
速度更快
package pra_07;
import java.util.Scanner;
public class J_13 {
public static void main(String[] args) {
StringBuffer sbs=new StringBuffer();
System.out.println(sbs.length());
System.out.println(sbs.capacity()); //16
StringBuffer sbs2=new StringBuffer(10);
System.out.println(sbs2.length());
System.out.println(sbs2.capacity()); //10
StringBuffer sbs3=new StringBuffer("abcdefgh");
System.out.println(sbs3.length()); //8
System.out.println(sbs3.capacity()); //16+8 初始容量+字符串length
//添加功能
StringBuffer sbs4=new StringBuffer();
sbs4.append("ijklmnopq");
System.out.println(sbs4.toString()); //ijklmnopq,不用再new对象,重写了toString方法,显示了对象属性值
sbs4.insert(2,"rstuvw"); //如果没有指定位置的索引就会报索引越界异常
System.out.println(sbs4.toString()); //ijrstuvwklmnopq
//删除功能
StringBuffer sbs5=new StringBuffer();
sbs5.append("abcdefg");
sbs5.deleteCharAt(5);
System.out.println(sbs5.toString()); //abcdeg
sbs5.delete(0, 2);
System.out.println(sbs5.toString()); //cdeg,删除时,包含头不包含尾
sbs5.delete(0,sbs5.length()); //清空缓存区
//替换和反转功能
StringBuffer sbs6=new StringBuffer();
sbs6.append("qwerty");
sbs6.replace(0, 5, "aaaaaaaa");
System.out.println(sbs6.toString()); //aaaaaaaay
sbs6.reverse();
System.out.println(sbs6.toString()); //yaaaaaaaa
//截取功能
StringBuffer sbs7=new StringBuffer();
sbs7.append("icanfly");
String str=sbs7.substring(4);
System.out.println(str); //fly,包含头,sbs7不变
String str2=sbs7.substring(0, 4);
System.out.println(str2); //ican,包含头不包含尾
//StringBuffer和String的相互转换
//String——StringBuffer
StringBuffer sbs8=new StringBuffer("qwer"); //通过构造方法将字符串转换成StringBuffer
System.out.println(sbs8);
sbs8.append("asdf"); //通过append方法将字符串转换成StringBuffer
//StringBuffer——String
StringBuffer sbs9=new StringBuffer("mnbvc");
String s9=new String(sbs9);
String s10=sbs9.toString();
String s11=sbs9.substring(0,sbs9.length());
System.out.println(s9);
System.out.println(s10);
System.out.println(s11);
//把数组转换成字符串
int[] arr={1,2,3,4};
StringBuffer sbs10=new StringBuffer();
sbs10.append("[");
for (int i = 0; i < arr.length; i++) {
sbs10.append(arr[i]+",");
}
sbs10.append("]");
System.out.println(sbs10.toString());
//字符串反转
Scanner sc=new Scanner(System.in);
String str12=sc.nextLine();
StringBuffer sbs12=new StringBuffer(str12);
sbs12.reverse();
System.out.println(sbs12);
}
}
StringBuilder StringBuffer 方法一样
使用场景
在字符串不经常发生的变化的业务场景优先使用String
在单线程下,比如有大量的字符创操作的情况下,应该使用StringBuilder
在多线程下,有大量的字符串大的操作的情况下,应该使用StringBuffer