作者:NiceCui

  • 本文谢绝转载,如需转载需征得作者本人同意,谢谢。
  • 本文链接:http://www.cnblogs.com/NiceCui/p/8046564.html
  • 邮箱:moyi@moyibolg.com
  • 日期:2017-12-15

1. String 介绍,常用方法源码分析

2. String 常量池分析

  • 常用方法

  1. equals

  2. trim

  3. replace

  4. concat

  5. split

  6. startsWith 和 endsWith

  7. substring

  8. toUpperCase() 和 toLowerCase()

  9. compareTo

  • String 介绍

String类被final所修饰,也就是说String对象是不可变量,并发程序最喜欢不可变量了。String类实现了Serializable, Comparable, CharSequence接口。

从一段代码说起:

public void stringTest(){String a = "a"+"b"+1;String b = "ab1";System.out.println(a == b);
}

大家猜一猜结果如何?如果你的结论是true。好吧,再来一段代码:

public void stringTest(){String a = new String("ab1");String b = "ab1";System.out.println(a == b);
}

结果如何呢?正确答案是false。

让我们看看经过编译器编译后的代码如何

//第一段代码
public void stringTest() {String a = "ab1";String b = "ab1";System.out.println(a == b);
}
//第二段代码
public void stringTest() {String a1 = new String("ab1");String b = "ab1";System.out.println(a1 == b);
}

也就是说第一段代码经过了编译期优化,原因是编译器发现"a"+"b"+1和"ab1"的效果是一样的,都是不可变量组成。但是为什么他们的内存地址会相同呢?如果你对此还有兴趣,那就一起看看String类的一些重要源码吧。

  • 源码

一、 String属性

String类中包含一个不可变的char数组用来存放字符串,一个int型的变量hash用来存放计算后的哈希值。

/** The value is used for character storage. */
private final char value[];/** Cache the hash code for the string */
private int hash; // Default to 0/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;

二、 String构造函数

//不含参数的构造函数,一般没什么用,因为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);
}

三、 String常用方法

1. equals

boolean equals(Object anObject)public boolean equals(Object anObject) {//如果引用的是同一个对象,返回真if (this == anObject) {return true;}//如果不是String类型的数据,返回假if (anObject instanceof String) {String anotherString = (String) anObject;int n = value.length;//如果char数组长度不相等,返回假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;
}
String e1 = "good";
String e2 = "good everyDay";
e1.equals(e2); // 返回 false
  • 1 首先判断是否 引用同一个对象 == 也就是判断 这两个引用的 内存地址是否相同,如果相同 直接返回 true
  • 2 会判断是否类型 相同,是否是同一种数据类型
  • 3 类型 相同 就会比较 转换成的 字符 数组的长度 是否相同
  • 4 从后往前 比较 每一个字符 是否 相同

  • 判断顺序 =》 1.内存地址 2.数据类型 3.字符数组长度 4.单个字符比较

2. compareTo

int compareTo(String anotherString)public int compareTo(String anotherString) {//自身对象字符串长度len1int len1 = value.length;//被比较对象字符串长度len2int len2 = anotherString.value.length;//取两个字符串长度的最小值limint 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;
}
String co1 = "hello" ;
String co2 = "hello";
String co3 = "hello you";System.out.println(co1.compareTo(co2)); // 0
System.out.println(co1.compareTo(co3)); // -4
  • 这个方法写的很巧妙,先从0开始判断字符大小。
  • 如果两个对象能比较字符的地方比较完了还相等,就直接返回自身长度减被比较对象长度,如果两个字符串长度相等,则返回的是0,巧妙地判断了三种情况。

3.hashCode

int hashCode()public int hashCode() {int h = hash;//如果hash没有被计算过,并且字符串不为空,则进行hashCode计算if (h == 0 && value.length > 0) {char val[] = value;//计算过程//s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}//hash赋值hash = h;}return h;
}
String a = "toyou";
char val[] = a.toCharArray();
char c1 = 't';
char c2 = 'a';
int f = c1;
int e = c2;System.out.println(e);           // 97   a
System.out.println(f);           // 116  t
System.out.println(31*val[0]);   // 3596
System.out.println(31*c1);       // 3596// hashCode 计算中 因为char 字符可以自动转换成对应的 int 整形
  • String类重写了hashCode方法,Object中的hashCode方法是一个Native调用。
  • String类的hash采用多项式计算得来,我们完全可以通过不相同的字符串得出同样的hash,所以两个String对象的hashCode相同,并不代表两个String是一样的。
  • 同一个String 对象 hashCode 一定相同, 但是 hashCode相同 ,不一定是同一个对象

4.startsWith

boolean startsWith(String prefix,int toffset)public boolean startsWith(String prefix, int toffset) {char ta[] = value;int to = toffset;char pa[] = prefix.value;int po = 0;int pc = prefix.value.length;// Note: toffset might be near -1>>>1.//如果起始地址小于0或者(起始地址+所比较对象长度)大于自身对象长度,返回假if ((toffset < 0) || (toffset > value.length - pc)) {return false;}//从所比较对象的末尾开始比较while (--pc >= 0) {if (ta[to++] != pa[po++]) {return false;}}return true;
}public boolean startsWith(String prefix) {return startsWith(prefix, 0);
}public boolean endsWith(String suffix) {return startsWith(suffix, value.length - suffix.value.length);
}
 String d = "www.58fxp.com";System.out.println(d.startsWith("www")); // trueSystem.out.println(d.endsWith("com"));   // true
  • 起始比较和末尾比较都是比较经常用得到的方法,例如在判断一个字符串是不是http协议的,或者初步判断一个文件是不是mp3文件,都可以采用这个方法进行比较。

5.concat

String concat(String str)public String concat(String str) {int otherLen = str.length();//如果被添加的字符串为空,返回对象本身if (otherLen == 0) {return this;}int len = value.length;char buf[] = Arrays.copyOf(value, len + otherLen);str.getChars(buf, len);return new String(buf, true);
}
String cat = "much";String newcat = cat.concat(" yes"); // much yes
  • concat方法也是经常用的方法之一,它先判断被添加字符串是否为空来决定要不要创建新的对象。
  • 1 如果 拼接的字符 长度为0 直接返回 原字符对象
  • 2 拼接的字符 不为空 返回 新的 字符对象
  • 判断字符长度 生成新对象

6.replace

String replace(char oldChar,char newChar)public String replace(char oldChar, char newChar) {//新旧值先对比if (oldChar != newChar) {int len = value.length;int i = -1;char[] val = value; /* avoid getfield opcode *///找到旧值最开始出现的位置while (++i < len) {if (val[i] == oldChar) {break;}}//从那个位置开始,直到末尾,用新值代替出现的旧值if (i < len) {char buf[] = new char[len];for (int j = 0; j < i; j++) {buf[j] = val[j];}while (i < len) {char c = val[i];buf[i] = (c == oldChar) ? newChar : c;i++;}return new String(buf, true);}}return this;
}
String r1 = "how do you do";String r2 = r1.replace("do","is");
System.out.println(r2); // how is you is
  • 这个方法也有讨巧的地方,例如最开始先找出旧值出现的位置,这样节省了一部分对比的时间。
  • replace(String oldStr,String newStr)方法通过正则表达式来判断。

7.trim

String trim()public String trim() {int len = value.length;int st = 0;char[] val = value;    /* avoid getfield opcode *///找到字符串前段没有空格的位置while ((st < len) && (val[st] <= ' ')) {st++;}//找到字符串末尾没有空格的位置while ((st < len) && (val[len - 1] <= ' ')) {len--;}//如果前后都没有出现空格,返回字符串本身return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
String t1 = " public void "; // 前后各一个空格System.out.println("t1:"+t1.length()); // 13 带空格长度String t2 = t1.trim();System.out.println("t2:"+t2.length()); // 11 去掉空格System.out.println(t2);

8.intern

String intern()public native String intern();
String dd = new String("bb").intern();
  • intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,
  • 如果没有找到则在常量池中开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用。
  • 可以为new方法创建的 字符对象 也去强制查看常量池 是否已存在

将引言中第二段代码

//String a = new String("ab1");
//改为
String a = new String("ab1").intern();
  • 则结果为为真,原因在于a所指向的地址来自于常量池,而b所指向的字符串常量默认会调用这个方法,所以a和b都指向了同一个地址空间。
int hash32()private transient int hash32 = 0;
int hash32() {int h = hash32;if (0 == h) {// harmless data race on hash32 here.h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);// ensure result is not zero to avoid recalcingh = (0 != h) ? h : 1;hash32 = h;}return h;
}
  • 在JDK1.7中,Hash相关集合类在String类作key的情况下,不再使用hashCode方式离散数据,而是采用hash32方法。
  • 这个方法默认使用系统当前时间,String类地址,System类地址等作为因子计算得到hash种子,通过hash种子在经过hash得到32位的int型数值。
public int length() {return value.length;
}
public String toString() {return this;
}
public boolean isEmpty() {return value.length == 0;
}
public char charAt(int index) {if ((index < 0) || (index >= value.length)) {throw new StringIndexOutOfBoundsException(index);}return value[index];
}

以上是一些简单的常用方法。

总结

  • String对象是不可变类型,返回类型为String的String方法每次返回的都是新的String对象,除了某些方法的某些特定条件返回自身。

  • String对象的三种比较方式:

  • ==内存比较:直接对比两个引用所指向的内存值,精确简洁直接明了。

  • equals字符串值比较:比较两个引用所指对象字面值是否相等。

  • hashCode字符串数值化比较:将字符串数值化。两个引用的hashCode相同,不保证内存一定相同,不保证字面值一定相同。

字符串常量池的设计思想

一.字符串常量池设计初衷

每个字符串都是一个String对象,系统开发中将会频繁使用字符串,如果像其他对像那样创建销毁将极大影响程序的性能。

  • JVM为了提高性能和减少内存开销,在实例化字符串的时候进行了优化
  1. 为字符串开辟了一个字符串常量池,类似于缓存区
  2. 创建字符串常量时,首先判断字符串常量池是否存在该字符串
  3. 存在该字符串返回引用实例,不存在,实例化字符串,放入池中
  • 实现基础
  1. 实现该优化的基础是每个字符串常量都是final修饰的常量,不用担心常量池存在数据冲突
  2. 运行时实例创建的全局字符串常量池中有一个表,总是为池中每个唯一的字符串对象维护一个引用,这就意味着它们一直引用着字符串常量池中的对象,所以,在常量池中的这些字符串不会被垃圾收集器回收

堆、栈、方法区

了解字符串常量池,首先看一下 堆栈方法区

  1. 存储的是对象,每个对象都包含一个与之对应的class
  2. JVM只存在一个堆区,被所有线程共享,堆中不存在基本类型和对象引用,只存在对象本身
  3. 对象由垃圾回收器负责回收,因此大小和生命周期不需要确定
  1. 每个线程都包含一个栈区,栈区只存放基础数据类型对象和自定义对象引用
  2. 每个栈中的数据(原始类型和对象引用)都是私有的
  3. 栈分为三个部分,基本类型变量区、执行环境上下文、操作指令区(存放操作指令)
  4. 数据大小和生命周期是可以确定的,当没有引用指向这个数据时,这个数据就会消失
  • 方法区
  1. 静态区,跟堆一样,被所有的线程共享
  2. 方法区包含的都是在整个程序中永远唯一的元素,如class、static变量;

字符串常量池

字符串常量池存在于方法区

代码:堆栈方法区存储字符串

String str1 = “abc”;
String str2 = “abc”;
String str3 = “abc”;
String str4 = new String(“abc”);
String str5 = new String(“abc”);

面试题

  • String str4 = new String(“abc”) 创建多少个对象?
  1. 拆分: str4 = 、 new String()、"abc"
  2. 通过new 可以创建一个新的对象,new 方法创建实例化对象不会去常量池寻找是否已存在,只要new 都会实例化一个新的对象出来
  3. "abc"每个字符串 都是一个String 对象,如果常量池中没有则会创建一个新对象放入常量池,否则返回对象引用
  4. 将对象地址赋值给str4,创建一个引用
  5. 所以,常量池中没有“abc”字面量则创建两个对象,否则创建一个对象,以及创建一个引用
  • String str1 = new String("A"+"B") ; 会创建多少个对象? String str2 = new String("ABC") + "ABC" ; 会创建多少个对象?

转载于:https://www.cnblogs.com/NiceCui/p/8046564.html

java基础进阶一:String源码和String常量池相关推荐

  1. Java String源码解析

    String类概要 所有的字符串字面量都属于String类,String对象创建后不可改变,因此可以缓存共享,StringBuilder,StringBuffer是可变的实现 String类提供了操作 ...

  2. JAVA源码学习--String

    最近发四重新学习java的基础,从基本类型以及里面的各种方法开始看起,看的一本书<JAVA核心技术卷1,基础知识>,这是第十版,讲的JDK8的一些特性. 我在想我们创建对象的时候都是这样进 ...

  3. Java源码详解四:String源码分析--openjdk java 11源码

    文章目录 注释 类的继承 数据的存储 构造函数 charAt函数 equals函数 hashCode函数 indexOf函数 intern函数 本系列是Java详解,专栏地址:Java源码分析 Str ...

  4. 程序兵法:Java String 源码的排序算法(一)

    摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 <程序兵法:Java Str ...

  5. 【JAVA】String源码浅谈

    String源码浅谈 String这个类可以说是我们使用得最为频繁的类之一了,前几次去面试,都被问到String的底层源码,回答得都不是很好,今天就来谈谈一下String的源码. 一.String类 ...

  6. java连接mongodb_java连接mongodb源码解读

    用mongdb也大半年了,一直是业务上的逻辑实现了就ok.然而这样并不能进步--因此今天查了查java连接mongodb驱动的源码,搜到的各种信息整合一下,方便以后深入的使用. 先贴连接数据库代码Li ...

  7. java B2B2C Springboot电子商务平台源码-Feign 基本使用

    1. [microcloud-consumer-feign]为了可以使用到 feign 支持,需要修改 pom.xml 配置文件,引入相关依赖包:需要JAVA Spring Cloud大型企业分布式微 ...

  8. java.util.concurrent.locks.Condition 源码

    2019独角兽企业重金招聘Python工程师标准>>> 相关类图: 使用Condition和 object作为条件队列的区别: object: 只能绑定一个内部队列,使用notify ...

  9. 死磕 java同步系列之ReentrantReadWriteLock源码解析

    问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的 ...

最新文章

  1. Java学习-jsp内置对象Session
  2. 网页怎么在图片上添加文字_想给图片添加文字,留白,添加小印章?用手机三步搞定...
  3. Sql sever 分组排序
  4. idea 调试技巧1
  5. cookie中JSESSIONID的由来
  6. LeetCode 279. Perfect Squares
  7. colordialog通过哪属性取其颜色_IT兄弟连 HTML5教程 CSS3揭秘 CSS常见的样式属性和值1...
  8. 微信小程序 data中数据值的更改与储存
  9. 看后至少多活十年--只需十分钟
  10. bzoj 2190: [SDOI2008]仪仗队 -- 欧拉函数
  11. java泛型方法的使用
  12. 汇总|医学图像分析领域论文
  13. mysql两种事务管理器_MyBatis事务管理的两种方式
  14. 基础平面设计(文字排版篇)
  15. OpenInfra基金会成立多元化董事会领袖组,任命总干事助推OpenInfra下一个十年的发展...
  16. python小程序源码合集
  17. SeaWeedfs 分布式网络文件存储介绍
  18. 云轴科技 ZStack 与和信创天完成兼容性认证,打造稳定安全的桌面云!
  19. 罗技K375s重新配置和连接
  20. 7.2 MVC 实现登录验证

热门文章

  1. 机器学习知识体系(强烈推荐)
  2. Python 在子类中调用父类方法详解(单继承、多层继承、多重继承)
  3. Java虚拟机JVM学习05 类加载器的父委托机制
  4. SQL Server 2005 处理交叉表
  5. wingide的使用方法积累
  6. 使用cacti对mysql监控的图像解释_Linux下的监控软件cacti的安装与配置
  7. CloudStack相关技术-主存储和二级存储
  8. OVS ofproto(二十三)
  9. Python全栈 MySQL 数据库 (索引、数据导入、导出)
  10. 【全网最全的博客美化系列教程】06.推荐和反对炫酷样式的实现