为什么字符串如此重要
写了多年java的开发应该对String不陌生,只是越来越觉得陌生。 每次学习一门编程语言,都会和关键字串打交道很多。 看来这真的很重要。
字符串是一系列字符串。 如果你写过C/C++,你应该知道有很多用于字符串操作的函数和类来简化代码开发。 一方面是因为代码中经常使用字符串,另一方面是因为字符串的操作非常麻烦。
起初我知道在delphi中对String的特殊处理,因为String在delphi中是关键字,与其他基本类型不同。 那时候,我学到了很多相关的知识。 在java/.net中,字符串也是经过特殊处理的,可见其重要性。
只是因为程序中字符串的使用比较多,操作比较多,这样会带来内存占用和性能问题。 需要特别小心。 想象一下日志系统一天使用多少字符串变量。
了解 Java 中的字符串
Java提供了String类的功能来支持字符串。 毕竟字符串本质上就是字符的组合,那么我们就来看看它的特点吧。
String还是将字符串存储在一个char数组中java如何判断字符串是否为纯数字,数据操作都是围绕着它进行的,但是有一些特别的地方,代码如下
private final char value[];
可以发现这个char value[]是finalized的,也就是说这个值一旦创建就不能再改变了。 这将导致每次创建 String 时只有一个值,然后对其进行字符串操作也必须生成新值。 Java 使用字符串常量池的概念来进行此处理。 就是把字符串丢到一个池子里,如果相同就用同一个。当然这也有一个前提,就是要用下面的方法
String s = "abc";
这样做时,jvm 会在编译时确定它。 在运行时,它会先检查常量池中是否有“abc”。 这样做的好处是不需要为同一个字符串创建重复项。但是如果使用下面的代码
String s1 = new String("abc");
这时,情况发生了变化。 这里jvm会在栈中创建一个对象s1,但是s1中的值也指向“abc”。 稍后在查看字符串比较时,您会发现不同之处。
String s = "abc";
String s1 = "abc";
if (s == s1) {
System.out.println("s == s1");
}
问题:此时是s==s1吗?
答案是一样的,为什么呢? 其实jvm在创建s1的时候会去常量区找是否有相同值的字符串,如果有就返回给s1,这样s1和s就指向同一个字符串,所以是平等的。
但是还有一种情况不一样,
String s = "abc";
String s3 = new String("abc");
if (s == s3) {
System.out.println("s == s3");
}
else {
System.out.println("s != s3");
}
这时候应该会打印出s != s3,因为new一个String对象之后确实会创建一个新的变量。 所以如果用==来比较,自然会返回false。
用equals做比较怎么样?
String s = "abc";
String s2 = new String("abc");
if (s.equals(s2)) {
System.out.println("s = s2");
}
else {
System.out.println("s != s2");
}
打印出来的是s = s2,因为==是用来比较两个地址的java如何判断字符串是否为纯数字,equals是用来比较两个变量的值的。可以看看equals的代码
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
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;
}
}
return false;
}
equals中先比较地址是否相同,如果不相同再比较值,因为值都是“abc”,自然会返回true。
String中有一个intern方法,我们可以先试试下面的代码。
String s = "abc";
String s3 = new String("abc");
if (s.intern() == s3.intern()) {
System.out.println("s.intern = s3.intern");
}
else {
System.out.println("s.intern != s3.intern");
}
还是在s和s3上面,如果比较它们各自的intern方法返回的值,就会输出s.intern = s3.intern。 查找资料结合笔记得知,这个intern方法其实是从字符串常量池中返回当前字符串。 如果当前字符串已经存在,则返回当前字符串。 如果当前字符串不存在,则返回当前字符串。 放入常量池并返回。
这么一解释就明白了,s和s3都是intern返回的,所以在常量池中都是“abc”,所以比较起来intern是相等的。
认识 StringBuffer 和 StringBuilder
面试的时候遇到这个问题,突然有点懵。 没怎么关注这两个类,记得java里面只有一个StringBuffer? 回头看代码,原来StringBuffer是线程安全的,即字符串操作的所有方法都是同步的。
于是打开代码注释,发现Jdk1.5开始有了StringBuilder,并且在后面的版本中增加了一个unlocked类。 好像解决了非并发场景下的效率问题。 解锁对于操作大字符串还是很有用的。 性能增强。 好奇的看了下这两个类的代码,和String有些类似,只是此时chat[]不是final,避免调用时生成一堆string对象操作字符串类。 问题。
char[] value;
既然我们已经有了String,那这两个家伙有什么用呢? 其实问题还是跟String的原理有关。 因为String是通过常量池来管理的,这样就解决了重复创建同一个字符串的问题,但是大部分的字符串都是不一样的,尤其是在做字符串拼接操作的时候,如果使用String的+来拼接大量的字符串常量会生成,这会消耗大量的性能和空间。
为了解决这个问题,使用了StringBuffer。 本质上,通过一个可变的字符序列,在字符串操作时不需要产生新的对象,从而提高了内存的使用率。
让我们看看 StringBuffer 是如何提高拼接性能的。 查看StringBuffer/StringBuilder(JDK1.5+)的代码,发现都是继承自AbstractStringBuilder。 很多代码其实都是在AbstractStringBuilder中完成的。 因为这个问题是拼接引起的,所以这里重点说一下append方法。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);//确定容量
str.getChars(0, len, value, count);//取出str的字符放入到value数组中
count += len;//count累加
return this;
}
代码比较清晰。 整个过程中最重要的是使用String的getChars方法将str的值写入到当前对象的值中。 而String的getChars方法如下:
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
可以看出最后复制的是一个数组,因为AbstractStringBuilder中的value是一个可变的char数组,所以只需要对char数组进行字符串操作即可。 它不会像String那样产生新的对象,自然就变得更高效了。