String源码

来源:互联网 发布:制作报价单软件 编辑:程序博客网 时间:2024/05/01 01:24

public final class String implements java.io.Serializable,Comparable<String> CharSequence{

private final char value[];//维护了一个字符数组,s1.contcat()方法底层原理就是数组复制

private final int offset;//记录开始

private final int count;//字符的个数

private int hash;

}

concat

  public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
   return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
    }



compareTo

public int compareTo(String paramString)

{

int i = this.value.length;

int j =paramString.length;

int k =Math.min(i,j);

char[] arrayOfChar1 = this.value;

char[] arrayOfChar2 = paramString.value;

for(int m = 0;m<k;m++){

int n =arrayOfChar1.length;

int i1 = arrayOdChar2 ,length;

if(n !=i1)

return n-i1;

}

return i-j

}

equals

public boolean equals(Object paramObject)

{

if(this == paramObject){

return true;

}

if(paramObject instanceof String)

{

String str =(String) paramObject;

int i = this.value.length;

if( i == str.value.length){

char[] arrayOfChar1 = this.value;

char[] arrayOfChar2 = str.value;

for(int j = 0;i-- !=0;j++)

           if(arrayOfChar1[j] !=arrayOfChar2[j])

           return false;

  return true;

}

}

return false;

}

indexOf

getBytes

split*以及自己如何实现一个split方法,利用反射

substring


String s="123abc";
String ss=s+"";//变量运算在运行期执行,产生新对象
System.out.println(s==ss);//flase
System.out.println(s.equals(ss));//true

String s1="123abc";
String s2="123"+"abc";//字面量常量运算在javac时候优化
System.out.println(s1==s2);//true
System.out.println(s1.equals(s2));//true

String s3="1"+"23"+"abc";//123abc
String s4='1'+'2'+'3'+"abc";//150abc
System.out.println(s3);
System.out.println(s4);
System.out.println(s3==s4);//flase
System.out.println(s3.equals(s4));//false

String s5=1+"23abc";//123abc
String s6=1+2+"3abc";//33abc
System.out.println(s5);
System.out.println(s6);
System.out.println(s5==s6);//false
System.out.println(s5.equals(s6));//false

String s7=new String("123abc");
String s8=new String("123"+"abc");//new String("123abc");
System.out.println(s7);
System.out.println(s8);
System.out.println(s7==s8);//false
System.out.println(s7.equals(s8));//true

字符串常量:static final修饰的变量

1. 声明String对象

[java] view plain copy
  1. String s = "abcd";  
图1
2. 将一个字符串变量赋值给另一个String变量
[java] view plain copy
  1. String s2 = s;  
图2
3. 字符串连接
[java] view plain copy
  1. s = s.concat("ef");  
  2. // s = s + "ef"; // 等价  
图3
总结:
一个String对象在 堆内存 中创建以后, 就不能被改变了. 请注意String对象的所有方法都不会改变其本身,而是会返回一个新的String对象.
如果我们需要可改变的字符串,则需要使用 StringBuffer 或者 StringBuilder. 否则每次创建一个新String对象的话,就会造成大量的内存浪费,需要耗时来执行垃圾回收。

为什么设计为不可变?

什么是不可变对象?

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

区分对象和对象的引用

对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. String s = "ABCabc";  
  2. System.out.println("s = " + s);  
  3.   
  4. s = "123456";  
  5. System.out.println("s = " + s);  

打印结果为:
s = ABCabc
s = 123456

首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。
也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:



Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。

为什么String对象是不可变的?

要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String的成员变量有以下几个:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class String  
  2.     implements java.io.Serializable, Comparable<String>, CharSequence  
  3. {  
  4.     /** The value is used for character storage. */  
  5.     private final char value[];  
  6.   
  7.     /** The offset is the first index of the storage that is used. */  
  8.     private final int offset;  
  9.   
  10.     /** The count is the number of characters in the String. */  
  11.     private final int count;  
  12.   
  13.     /** Cache the hash code for the string */  
  14.     private int hash; // Default to 0  

在JDK1.7中,String类做了一些改动,主要是改变了substring方法执行时的行为,这和本文的主题不相关。JDK1.7中String类的主要成员变量就剩下了两个:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public final class String  
  2.     implements java.io.Serializable, Comparable<String>, CharSequence {  
  3.     /** The value is used for character storage. */  
  4.     private final char value[];  
  5.   
  6.     /** Cache the hash code for the string */  
  7.     private int hash; // Default to 0  


由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章java中数组的特性)。 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

value,offset和count这三个变量都是private的,并且没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。

那么在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代码:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. String a = "ABCabc";  
  2. System.out.println("a = " + a);  
  3. a = a.replace('A''a');  
  4. System.out.println("a = " + a);  

打印结果为:
a = ABCabc
a = aBCabc

那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题:

读者可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. String ss = "123456";  
  2.   
  3. System.out.println("ss = " + ss);  
  4.   
  5. ss.replace('1''0');  
  6.   
  7. System.out.println("ss = " + ss);  

打印结果:
ss = 123456
ss = 123456


String对象真的不可变吗?

从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public static void testReflection() throws Exception {  
  2.       
  3.     //创建字符串"Hello World", 并赋给引用s  
  4.     String s = "Hello World";   
  5.       
  6.     System.out.println("s = " + s); //Hello World  
  7.       
  8.     //获取String类中的value字段  
  9.     Field valueFieldOfString = String.class.getDeclaredField("value");  
  10.       
  11.     //改变value属性的访问权限  
  12.     valueFieldOfString.setAccessible(true);  
  13.       
  14.     //获取s对象上的value属性的值  
  15.     char[] value = (char[]) valueFieldOfString.get(s);  
  16.       
  17.     //改变value所引用的数组中的第5个字符  
  18.     value[5] = '_';  
  19.       
  20.     System.out.println("s = " + s);  //Hello_World  
  21. }  

打印结果为:
s = Hello World
s = Hello_World

在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。


需要综合内存,同步,数据结构以及安全等方面的考虑:

1. 字符串常量池的需要

字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

如下面的代码所示,将会在堆内存中只创建一个实际String对象.

[java] view plain copy
  1. String s1 = "abcd";  
  2. String s2 = "abcd";  
示意图如下所示:

图1

假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.

思考: 假若代码如下所示,s1和s2还会指向同一个实际的String对象吗?

[javascript] view plain copy
  1. String s1= "ab" + "cd";  
  2. String s2= "abc" + "d";  
也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件.

2. 允许String对象缓存HashCode
Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。

字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. 在String类的定义中有如下代码:

[javascript] view plain copy
  1. private int hash;//用来缓存HashCode  
3. 安全性
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

假如有如下的代码:

[javascript] view plain copy
  1. boolean connect(string s){  
  2.     if (!isSecure(s)) {   
  3. throw new SecurityException();   
  4. }  
  5.     // 如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误   
  6.     causeProblem(s);  
  7. }  


4.因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

5.类加载器用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。



StringBuffer  和  StringBulider 共同父类AbstractStringBulider      StringBuffer是线程安全的里面的方法是synchronized

append    insert

0 0
原创粉丝点击