一、String类型 引用博文连接: https://blog.csdn.net/ylyg050518/article/details/52352993
成员变量
1 //用于存储字符串 2 private final char value[]; 3 4 //缓存String的hash值 5 private int hash; // Default to 0 6 7 /** use serialVersionUID from JDK 1.0.2 for interoperability */ 8 private static final long serialVersionUID = -6849794470754667710L;
构造方法
//不含参数的构造函数,一般没什么用,因为value是不可变量 public String() { this.value = new char[0]; } //参数为String类型 public String(String original) { this.value = original.value; this.hash = original.hash; } //参数为char数组,使用java.utils包中的Arrays类复制 public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } //从bytes数组中的offset位置开始,将长度为length的字节,以charsetName格式编码,拷贝到value public String(byte bytes[], int offset, int length, String charsetName) throws UnsupportedEncodingException { if (charsetName == null) throw new NullPointerException("charsetName"); checkBounds(bytes, offset, length); this.value = StringCoding.decode(charsetName, bytes, offset, length); } //调用public String(byte bytes[], int offset, int length, String charsetName)构造函数 public String(byte bytes[], String charsetName) throws UnsupportedEncodingException { this(bytes, 0, bytes.length, charsetName); }
常用方法
1, int hashCode()
1 public int hashCode() { 2 int h = hash; 3 //如果hash没有被计算过,并且字符串不为空,则进行hashCode计算 4 if (h == 0 && value.length > 0) { 5 char val[] = value; 6 7 //计算过程 8 //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 9 for (int i = 0; i < value.length; i++) { 10 h = 31 * h + val[i]; 11 } 12 //hash赋值 13 hash = h; 14 } 15 return h; 16 }
String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。
Object的hashCode方法,
1 public native int hashCode();
源码翻译:
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 hashCode 的常规协定是: 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
实际上,由 Object 类定义的 hashCode 方法确实会针对不同的对象返回不同的整数。(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是 JavaTM 编程语言不需要这种实现技巧。)
2, boolean equals(Object anObject)
1 public boolean equals(Object anObject) { 2 //如果引用的是同一个对象,返回真 3 if (this == anObject) { 4 return true; 5 } 6 //如果不是String类型的数据,返回假 7 if (anObject instanceof String) { 8 String anotherString = (String) anObject; 9 int n = value.length; 10 //如果char数组长度不相等,返回假 11 if (n == anotherString.value.length) { 12 char v1[] = value; 13 char v2[] = anotherString.value; 14 int i = 0; 15 //从后往前单个字符判断,如果有不相等,返回假 16 while (n-- != 0) { 17 if (v1[i] != v2[i]) 18 return false; 19 i++; 20 } 21 //每个字符都相等,返回真 22 return true; 23 } 24 } 25 return false; 26 }
1. 内存地址相同,则为真。 2. 如果对象类型不是String类型,则为假。否则继续判断。 3. 如果对象长度不相等,则为假。否则继续判断。 4. 从后往前,判断String类中char数组value的单个字符是否相等,有不相等则为假。如果一直相等直到第一个数,则返回真。
由此可以看出,如果对两个超长的字符串进行比较还是非常费时间的。
3, int compareTo(String anotherString)
public int compareTo(String anotherString) { //自身对象字符串长度len1 int len1 = value.length; //被比较对象字符串长度len2 int len2 = anotherString.value.length; //取两个字符串长度的最小值lim int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; //从value的第一个字符开始到最小长度lim处为止,如果字符不相等,返回自身(对象不相等处字符-被比较对象不相等字符) while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } //如果前面都相等,则返回(自身长度-被比较对象长度) return len1 - len2; }
equals方法是重写了Object的equals方法,而compareTo是因为String implements Comparable<String>,重写了Comparable的compareTo方法。
boolean startsWith(String prefix,int toffset)
1 public boolean startsWith(String prefix, int toffset) { 2 char ta[] = value; 3 int to = toffset; 4 char pa[] = prefix.value; 5 int po = 0; 6 int pc = prefix.value.length; 7 // Note: toffset might be near -1>>>1. 8 //如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假 9 if ((toffset < 0) || (toffset > value.length - pc)) { 10 return false; 11 } 12 //从所比较对象的末尾开始比较 13 while (--pc >= 0) { 14 if (ta[to++] != pa[po++]) { 15 return false; 16 } 17 } 18 return true; 19 } 20 21 public boolean startsWith(String prefix) { 22 return startsWith(prefix, 0); 23 } 24 25 public boolean endsWith(String suffix) { 26 return startsWith(suffix, value.length - suffix.value.length); 27 }
4,String concat(String str)
1 public String concat(String str) { 2 int otherLen = str.length(); 3 //如果被添加的字符串为空,返回对象本身 4 if (otherLen == 0) { 5 return this; 6 } 7 int len = value.length; 8 //复制指定的数组,截取或用 null 字符填充(如有必要),以使副本具有指定的长度。 10 char buf[] = Arrays.copyOf(value, len + otherLen); 11 str.getChars(buf, len); 12 return new String(buf, true); 13 }
5,String trim()
1 public String trim() { 2 int len = value.length; 3 int st = 0; 4 char[] val = value; /* avoid getfield opcode */ 5 6 //找到字符串前段没有空格的位置 7 while ((st < len) && (val[st] <= ' ')) { 8 st++; 9 } 10 //找到字符串末尾没有空格的位置 11 while ((st < len) && (val[len - 1] <= ' ')) { 12 len--; 13 } 14 //如果前后都没有出现空格,返回字符串本身 15 return ((st > 0) || (len < value.length)) ? substring(st, len) : this; 16 }
其中的val[st] <= ' ',比较的是字符在字典位置,String trim()只能去掉字符串的前后空格,中间空格去不掉!
可以通过 char[] toCharArray() 得到字符串的字符数组
1 public static void main(String[] args) { 2 String str = " klp m "; 3 //得到字符串字符数组 4 char[] val = str.toCharArray(); 5 6 System.out.println("字符是"+val[4]+"!"); 7 System.out.println("字符字典位置"+Integer.valueOf(val[4])+"!"); 8 System.out.println("字符是"+val[0]+"!"); 9 System.out.println("字符字典位置"+Integer.valueOf(val[1])+"!"); 10 }
结果:
1 字符是p! 2 字符字典位置112! 3 字符是 ! 4 字符字典位置32!
6,String intern()
1 public native String intern();
源码解释:
返回字符串对象的规范化表示形式。 一个初始为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。
否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。 所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。字符串字面值在 Java Language Specification 的 §3.10.5 定义。 返回: 一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。
详细使用方法及解释参考链接: https://blog.csdn.net/wjzhang5514/article/details/70209403
1 public static void main(String[] args) { 2 String s1 = "HelloWorld"; 3 String s2 = new String("HelloWorld"); 4 String s3 = "Hello"; 5 String s4 = "World"; 6 String s5 = "Hello" + "World"; 7 String s6 = s3 + s4; 8 9 System.out.println(s1 == s2); 10 System.out.println(s1 == s5); 11 System.out.println(s1 == s6); 12 System.out.println(s1 == s6.intern()); 13 System.out.println(s2 == s2.intern()); 14 }
结果:
1 false 2 true 3 false 4 true 5 false
解释:
s1 创建的 HelloWorld 存在于方法区中的常量池其中的字符串池,而 s2 创建的 HelloWorld 存在于堆中,故第一条 false 。 s5 的创建是先进行右边表达式的字符串连接,然后才对 s5 进行赋值。赋值的时候会搜索池中是否有 HelloWorld 字符串,
若有则把 s5 引用指向该字符串,若没有则在池中新增该字符串。显然 s5 的创建是用了 s1 已经创建好的字面量,故 true 。 第三个比较容易弄错,s6 = s3 + s4; 其实相当于 s6 = new String(s3 + s4); s6 是存放于堆中的,不是字面量。所以 s1 不等于 s6 。 第四个 s6.intern() 首先获取了 s6 的 HelloWorld 字符串,然后在字符串池中查找该字符串,找到了 s1 的 HelloWorld 并返回。
这里如果事前字符串池中没有 HelloWorld 字符串,那么还是会在字符串池中创建一个 HelloWorld 字符串再返回。总之返回的不是堆中的 s6 那个字符串。 第四条也能解释为什么第五条是 false 。s2是堆中的 HelloWorld,s2.intern() 是字符串池中的 HelloWorld 。String s6 = (s3 + s4).intern();
String s7 = (s3 + s4).intern(); 则s7 存储的 HelloWorld 是存放字符串池中!!!
关于String类是不可变类
所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。
但是需要注意的是:
String s="abc";
s="def";
s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,
String类被设计成不可变的原因
1,字符串常量池的需要
字符串常量池(String pool, String intern pool, String保留池) 是Java方法区中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,
则不会创建一个新的对象,而是引用已经存在的对象。 如下面的代码所示,将会在字符串常量池中只创建一个实际String对象.
代码如下:
1 String s1 = "abcd"; 2 String s2 = "abcd";
假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.
1 String s1= "ab" + "cd"; 2 String s2= "abc" + "d";
也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件.
2. 允许String对象缓存HashCode
Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。
字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.
3. 安全性
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。
假如有如下的代码:
1 boolean connect(string s){ 2 if (!isSecure(s)) { 3 throw new SecurityException(); 4 } 5 // 如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误 6 causeProblem(s); 7 }
4. 线程安全
因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
总体来说, String不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面.
----------------------------------------------------若有不正之处,欢迎大家指正,不胜感激!!!!!