1.String 对象创建以及存储
先简单介绍下相关概念
栈:运行时存放基本数据类型的变量数据和对应引用
堆:存放所有new 对象
常量池:输入堆中分配出来的一块内存区域,存放String常量和基本数据类型,或一些static final 数据。常量池数据是可以共享的。
按图分析
new 过程分析
String ns4 = new String("早上好");
String s4 = "早上好";
//返回false 表示创建了两个对象,常量池中的和new 堆中的
System.out.println(s4 == ns4);
String s5 = "中午好";
String ns5 = new String("中午好");
//返回false 表示创建了两个对象,常量池中的和new 堆中的
System.out.println(s5 == ns5);
//那么问题是,new的过程到底是怎么样的呢?
try {
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] valueChar = (char[]) valueField.get(ns4);
valueChar[2]='?';
//输出 早上?
System.out.println(ns4);
//输出 早上?,s4 和ns4 相等,表示表示常量池和堆中的对象内部引用同一个char[]
//所以常量池的对象 肯定是在new 之后就产生了的,而不是到String s4 = "早上好" 定义时才创建对象
//否则引用的char[] 不能是同一个
System.out.println(s4);
char[] valueChar5 = (char[]) valueField.get(ns5);
valueChar5[2]='?';
//输出 中午?
System.out.println(ns5);
//输出 中午?,s5 和ns5 相等,表示表示常量池和堆中的对象内部引用同一个char[]
//所以在new Sting的时候,回去判断常量池是否存在,如果存在,怎使用已有的char[]创建。
System.out.println(s5);
//综上所述,new String()时,会先在常量池里判断是否有等值对象,如果有则使引用其底层char[]创建对象;若没有,则
//创建对象,并在常量池中也创建一个等值对象,引用同一个char[]存储
} catch (Exception e) {
e.printStackTrace();
}
两种初始化分析:
//创建1个对象,1个引用,在常量池中创建了一个"hello"对象,在栈中分配了一个引用s1指向常量池"hello"对象
String s1 = "hello";
//创建 0个对象,1个引用,因为常量池中已经存在"hello" 对象,在栈中分配了一个引用s2指向常量池"hello"对象
String s2 = "hello";
//创建了2个对象,1个引用,在常量池中创建了一个"hi"对象,在堆中使用new 创建了一个"hi"对象,在栈中分配了一个引用ns1指向堆中"hi"对象
String ns1 = new String("hi");
//创建了1个对象,1个引用,因为常量池中已经存在"hi" 对象,在堆中使用new 创建了一个"hi"对象,在栈中分配了一个引用ns2指向堆中"hi"对象
String ns2 = new String("hi");
//创建1个对象,1个引用,在常量池中创建了一个"你好"对象,在栈中分配了一个引用s3指向常量池"你好"对象
String s3 = "你好";
//创建了1个对象,1个引用,因为常量池中已经存在"你好" 对象,在堆中使用new 创建了一个"你好"对象,在栈中分配了一个引用ns3指向堆中"你好"对象
String ns3 = new String("你好");
2.String 类为什么是finnal
String源码注释上表述:Strings 是常量,一旦被创建则值不可改变,String 对象是不可变的共享对象,我想如果是共享对象,那么不使用线程安全策略,那么会有极大的编程风险,其次String对象不可变性指的是值的不可变,String对象的值是存储在常量池中的,也就是堆中,String对象是基于字符数组实现的,当我们改变String对象的值时,不是在原数组上操作,而是新建了一个数组存储新值,原数组并没有改变,这就是String 类的不变性。
3.String、Stringbuilder、Stringbuffer区别
源码如下阐述:java语言提供了对字符串连接符(“+”),其他对象转为字符串对象的特别支持。字符串的连接是通过 StringBuilder 或者 StringBuffer 这两个类及它们的append 方法实现的。StringBuffer 和StringBuilder 几乎拥有相同的对外方法,StringBuffer对几乎所有的方法做了synchronized 同步,考虑到线程安全,多线程环境则使用StringBuffer。
看下StringBuilder 的append方法是如何优化字符串连接的
StringBuilder
public StringBuilder append(String str) {
//调用了抽象类AbstractStringBuilder的append 方法
super.append(str);
return this;
}
AbstractStringBuilder
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
//调用String 中的getChars方法将str中的数组copy到value数组中
str.getChars(0, len, value, count);
count += len;
return this;
}
String
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
//调用数组copy
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
哦,已经明白了吧,StringBuilder通过底层数组copy减少了一次,中间字符串对像的分配操作。
4.String 常用方法
length:返回字符串长度
public int length() {
//返回内部数组的长度
return value.length;
}
empty:是否为空数组
public boolean isEmpty() {
//返回内部数组长度是否为0
return value.length == 0;
}
charAt:返回字符串中的第n个字符
public char charAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
//返回内部数据相应索引位置的字符
return value[index];
}
equals:比较值是否相等
public boolean equals(Object anObject) {
if (this == anObject) {
//引用相同,则为true
return true;
}
//String 实例比较
//length相等,char数组遍历,同一个位置的char 必须相等
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
//不是String 实例,则返回false
return false;
}
intern: 返回字符串常量池对象,在重复对象上使用该方法可以大量减少内存消耗
/* 本地方法;当调用此方法时,如果常量池已包含和当前字符相同的对象,则返回常量池字符串引用
* 否则,这个对象会被加入常量池,返回该常量池对象引用
* 可以提高程序效率或者减少内存占用的情况
*/
public native String intern();
trim: 去除两端特殊字符,32(" ") 以下
split: 按正则切分成字符串数组
replace: 字符替换
contains: 是否包含
substring: 截取子串,左毕右开
indexOf : 子串存在当前字符串的开始位置索引
startWith: 是否以匹配的字符串开始