String类可以说是Java中最常使用的类了,但是String所包含的知识也是相当的多。
首先为什么String类一定要用equals()方法来比较字符串呢?还有new 出来的字符串和""双印号的字符串有什么区别呢?
看看下面这个例子
String s1 = new String("helloworld");
String s2 = new String("helloworld");
String s3 = "helloworld";
String s4 = "helloworld";
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s3 == s4);
System.out.println("");
System.out.println(s1.intern() == s3);
System.out.println(s1.intern() == s3.intern());
System.out.println("");
String s5 = new String("hello");
String s6 = "hello";
System.out.println(s5 == s6);
s5 = s5.intern();
System.out.println(s5 == s6);
首先s1.equals(s2)是肯定是对的,等会我们分析equals的源代码。这里就不写出来了
s1 == s2 肯定是false 因为==只是比较两个String的引用 因为new出来 都是在堆上分配空间,所以是false
s1 == s2 不对 是因为 s3是在运行时放进常量区 也就是方法区里面的常量池,一个是堆上 一个是在常量区 所以false
s3 == s4 是true 是因为 代码经过编译后 认为是同一个常量 所以指向的引用地址相同。
s1.intern() == s3 和 s1.intern() == s3.intern() 返回true 是因为intern()函数查询常量池,如果常量池不存在,则放入常量池,然后返回引用,若是已经存在该常量则返回存在引用,所以相等
第一个s5 == s6 因为s5指向堆而s6指向常量区所以地址不同 false
第二个s5 == s6 因为s5变为常量区的引用 所以true
说到这里我想应该知道new出来的和""创建的String类型有什么不同了吗?一个是在编译时候赋予引用值,一个是在运行时决定。那么String对象到底是可变的吗?
一般来说String类型是不可变的,大多数情况下,String的正常操作String的子串,拼接都是返回一个新的字串。但是可以通过反射改变其值
Field field = s1.getClass().getDeclaredField("value");
field.setAccessible(true);
char[] value =(char[]) field.get(s5);
value[1]='c';
System.out.println(s5);
System.out.println(s6);
通过反射成功的改变了s5和s6的值。他们都是公用同一个常量引用。
输出为
hcllo
hcllo
还有不要滥用String类的charAt方法
为什么呢?char类型采用unicode编码 即2个字节表示一个字符即 最多能表达 65536个字符 但是目前的字符远远超过这么多
对于特殊字符来说,好吧 这里我上传的代码段应该是非正常字符,所以被过滤导致后面的都被截断,只能重写,上图了
所以对于字符处理使用codePointAt()比较于安全
源码分析
public int codePointAt(int index) {
if ((index < 0) || (index >= value.length)) {
throw new StringIndexOutOfBoundsException(index);
}
return Character.codePointAtImpl(value, index, value.length);
}
这里调用了Character的codePointAtImpl()方法转换codePoint编码
static int codePointAtImpl(char[] a, int index, int limit) {
char c1 = a[index];
if (isHighSurrogate(c1) && ++index < limit) {
char c2 = a[index];
if (isLowSurrogate(c2)) {
return toCodePoint(c1, c2);
}
}
return c1;
}
public static int toCodePoint(char high, char low) {
// Optimized form of:
// return ((high - MIN_HIGH_SURROGATE) << 10)
// + (low - MIN_LOW_SURROGATE)
// + MIN_SUPPLEMENTARY_CODE_POINT;
return ((high << 10) + low) + (MIN_SUPPLEMENTARY_CODE_POINT
- (MIN_HIGH_SURROGATE << 10)
- MIN_LOW_SURROGATE);
}
通过比较适否是正常字符,是则直接返回,不是则将char[i]和char[i+1]合并成新的编码
然后还需要注意的则是一般字符串的编码,这种情况在网络流的情况下遇到比较多
String base = "why君";
byte[] utf8s = base.getBytes("utf-8");
for (int i = 0; i < utf8s.length; i++) {
System.out.printf("%02x ", utf8s[i]);
}
System.out.println();
byte[] gbks = base.getBytes("gbk");
for (int i = 0; i < gbks.length; i++) {
System.out.printf("%2x ", gbks[i]);
}
System.out.println();
byte[] utf16s = base.getBytes("utf-16");
for (int i = 0; i < utf16s.length; i++) {
System.out.printf("%02x ", utf16s[i]);
}
System.out.println();
对于 why君 不同的编码产生的效果也是不同的
w h y 君
utf-8 77 68 79 e5 90 9b
gbk 77 68 79 be fd
utf-16(fe ff) 00 77 00 68 00 79 54 1b
feff为utf头封装
最后为String的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;
}
是否指向同一个对象引用,是true
是否String 然后比较长度是否相等 相等 则继续逐字符比较