由于String的不可更改特性,在我们想要改变字符串的时候,都是在new的对象上进行改变,并没有改变字符串本身,为了能在字符串本身上进行的修改,不用创建大量临时对象,Java中提供StringBuilder和StringBuffer类
先来看看Stringbuffer的源码实现,以及栈和堆的内存分配:
可以看到stringBuffer一直都在内部进行操作,而不是在常量池里面,所以只会返回内部的内容,新加入的内容都会进入内部和常量池中,从而不需要创建大量临时对象来接收新的内容
我们再看一看Stringbuilder的源码实现,它的栈和堆的内存分配和Stringbuffer是一样的
我们可以发现在StringBuffer的源码实现比StringBuilder实现里面多了synchronized,synchronized代表线程安全,StringBuffer是线程安全的,StringBuilder不是线程安全的
线程安全是啥呢,线程安全指的是内存的安全,在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因。
所以线程安全指的是,在堆内存中的数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险。即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程“破坏”。
在一段函数执行的过程中,具有线程安全的代码不会被外界所影响,相当于把这段函数锁在一个空间里,当函数执行完毕之后,锁就开了
例如人们上厕所,当第一个人进去上厕所之后,后面的人排队上厕所,当你不锁门的情况下,后面的人就可能会进来,干扰到你,但是当你反锁了门锁之后,后面的人就不能随意进出了,就对你不会有影响,所以在这里的门锁就和我们的线程安全类似,当你具备线程安全后就不用担心被别的线程所干扰了
但是我们这里就会产生一个疑问了,既然StringBuffer比StringBuilder安全,那为什么还需要StringBuilder呢?
线程安全会频繁的加锁和释放锁会消耗系统的资源,而在有些情况下,是不需要线程安全的,所以这时候就用StringBuilder更好,总之得看情况而定
举例我们字符串的拼接
String str = "abc";
str = str + "123";
System.out.println(str);
在字节码文件里我们可以看到new了一个StringBuilder对象,字符串的拼接也用append的方法来写的,并且调用优化了toString方法
相同情况下,多次拼接就会多次new StringBuilder对象,使得内部执行异常繁琐,多个对象是的程序运行不能最大化执行代码,我们就必须要使用StringBuilder来优化了
String str = "abc";
for (int i = 0; i < 10; i++) {
str = str +i;
}
System.out.println(str);
优化后的代码
String str = "abc";
StringBuilder sb = new StringBuilder(str);
for (int i = 0; i < 10; i++) {
sb.append(i);
}
System.out.println(sb.toString());
优化后的代码就只有一个StringBuilder对象,极大的提升了代码的性能,有人会说为什么不用Stringbuffer,在这里没有涉及到线程,用了会浪费资源,所以这两种方法各有优势,使用得看情况而论
String、StringBuffer、StringBuilder的区别
①String的内容不可修改,StringBuffer与StringBuilder的内容可以修改
②StringBuffer与StringBuilder大部分功能是相似的
③StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
以下总共创建了多少个String对象【前提不考虑常量池之前是否存在】
String str = new String("ab");
String str1 = new String("a") + new String("b");
第一个str创建了两个对象,第一个对象是字符串"ab",属于常量池里面的,第二个对象是new了一个对象,里面有value和hash
第二个str创建了六个对象,第一个对象是字符串"a",第二个对象是字符串"b",第三个对象是a new的一个对象,里面有value和hash,第四个对象是b new的一个对象,里面有value和hash,第五个对象是第三第四个对象合起来创建的一个Stringbuilder对象,第六个对象就是Stringbuilder的toString方法生成的一个String对象
JDK1.8中
只会开辟一块堆内存空间,保存在字符串常量池中,然后str共享常量池中的String对象
会开辟两块堆内存空间,字符串"hello"保存在字符串常量池中,然后用常量池中的String对象给新开辟的String对象赋值。
现在在堆上创建一个String对象,然后利用copyof将重新开辟数组空间,将参数字符串数组中内容拷贝到String对象中(常量池没有东西,只有双引号的内容才会在常量池里面)
给定一个字符串s ,找到它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回-1 。
思路:
public int firstUniqChar(String s) {
//对参数进行判断
if(s == null || s.length() == 0){
return -1;
}
int[] array = new int[26];//0
for(int i = 0;i < s.length();i++){//先遍历一次数组,把每个元素出现的次数都记录下来
char ch = s.charAt(i);
array[ch-'a']++;
}
for(int i = 0;i < s.length();i++){//再次遍历数组,从左往右,输出记录后第一个只出现了一次的字符
char ch = s.charAt(i);
if(array[ch-'a'] == 1){
return i;
}
}
return -1;
}
计算字符串最后一个单词的长度,单词以空格隔开,字符串长度小于5000。(注:字符串末尾不以空格为结尾)
思路:
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
while(scan.hasNextLine()){
String str = scan.nextLine();
int index = str.lastIndexOf(" ");
String ret = str.substring(index+1);
System.out.println(ret.length());
}
}
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 s,如果它是回文串 ,返回 true ;否则,返回 false 。
思路:
private boolean isCharacter(char ch){
if (ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9'){//判断字符合理性
return true;
}
return false;
}
public boolean isPalindrome(String s){
//1.把所有的字符变成小写的
s = s.toLowerCase();
int i = 0;
int j = s.length()-1;
while(i < j){//i和j的下标要合法
while(i < j && !isCharacter(s.charAt(i))){//此处不用if是因为在第一个字母或数字之前可能会有空格
i++;//i下标就是一个合法的数字字符或字母
}
while(i < j && !isCharacter(s.charAt(j))){//判断j是不是合法的数字字符或字母
j--;//j下标就是一个合法的数字字符或字母
}
if(s.charAt(i) != s.charAt(j)){//当两边字母或数字不一样的时候,返回false
return false;
}else {
i++;
j--;
}
}
return true;
}