String 详解以及内存分析

字符串(java.lang.String 类)的使用

Java 字符串就是 Unicode 字符序列,例如串 “Java” 就是 4 个 Unicode 字符 J,a,v,a 组成的。Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义的类String,每个用双引号括起来的字符串都是 String 类的一个实例。String e = “” ; // an empty stringString greeting = “Hello World”;Java 允许使用符号 "+" 把两个字符串连接起来String s1 = “Hello”;String s2 = “World!”;符号 “+” 把两个字符串按给定的顺序连接在一起,并且是完全按照给定的形式。当 “+” 运算符两侧的操作数中只要有一个是字符串(String)类型,系统会自动将另一个操作数转换为字符串然后再进行连接int age = 18;String s = "age is" + age; //s赋值为age is 18这种特性通常被用在输出语句中:字符串类常用的方法char charAt(int index)返回字符串中第 index 个字符。boolean equals(String other)如果字符串与 other 相等,返回 true。因为 String 类重写了 equals 方法。boolean equalsIgnoreCase(String other)如果字符串与 other 相等(忽略大小写),则返回 trueint indexOf(String str) lastIndexOf()返回与 str 匹配的第一个字符串的开始位置,该位置从 0 开始计算,如果原始串中不存在 str,返回 -1。int indexOf(String str,int fromIndex)返回与 str 匹配的第一个字符串的开始位置,该位置从 fromIndex 开始计算,如果原始串中不存在 str,返回 -1。int length()返回字符串的长度。String replace(char oldChar,char newChar)返回一个新串,它是通过用 newChar 替换此字符串中出现的所有oldChar而生成的boolean startsWith(String prefix)如果字符串以prefix开始,则返回 trueboolean endsWith(String prefix)如果字符串以 prefix 结尾,则返回 trueString substring(int beginIndex)返回一个新字符串,该串包含从原始字符串 beginIndex 到串尾的所有字符String substring(int beginIndex,int endIndex)返回一个新字符串,该串包含从原始字符串 beginIndex 到串尾或 endIndex-1 的所有字符String toLowerCase()返回一个新字符串,该串将原始字符串中的所有大写字母改成小写字母String toUpperCase()返回一个新字符串,该串将原始字符串中的所有小写字母改成大写字母String trim()返回一个新字符串,该串删除了原始字符串头部和尾部的空格

注:在 Java 中某个索引区间进行一些操作的方法,索引取值范围一般都是包头不包尾,就拿上面的String substring(int beginIndex,int endIndex) 来说,它是截取子串 从beginIndex 开始 到endIndex - 1 结束。字符串相等的判断equals 方法用来检测两个字符串内容是否相等。如果字符串 s 和 t 内容相等,则s.equals(t) 返回 true,否则返回 false.s 和 t 既可以是字符串变量,也可以是字符串常量,例如: “Hello”.equals(t);要测试两个字符串除了大小写区别外是否是相等的,需要使用 equalsIgnoreCase 方法,例如:“Hello”.equalsIgnoreCase(“hellO”); //true判断字符串是否相等不要使用 "==","=="比较是引用是否相等(是否为同一个对象)String 内存分析

创建了 4 个对象分析:先去 "字符串池" 中找 "a", 没有找到,在 "字符串池" 中创建 "a" 这个String 对象,先去先去 "字符串池" 中找 "a",找到 "a", new String("a") 在堆中创建一个 String 对象(因为 new 关键字一出现,肯定会创建一个对象)。循环i = 0; gh = "a0"; 先去 "字符串池" 中找 "a0", 没有找到,在 "字符串池" 中创建 "a0" 这个String 对象i = 1; gh = "a01"; 先去 "字符串池" 中找 "a01", 没有找到,在 "字符串池" 中创建 "a01" 这个String 对象思考String 不是不可变对象吗?那为什么可以字符串拼接啊?你是在逗我吗?String JDK 源码

final 修饰了 String 类,使得 String 类不可被继承。final 修饰了 char value[],使得字符串的值不可以改变。但是 final 并没有修饰 String 的引用,即 final String str;这样的话,字符串的引用可以改变指向,比如上面的字符串拼接,gh 刚开始指向 "a",随着拼接又指向了 "a0"、"a01"。实际上这个过程中只是 gh 这个引用的指向在改变,"a"、"a0"、"a01" 并没有被改变。注:这是我画的几个草图为了帮助大家理解,实际上字符串的内存分析要比这个复杂点。

明显可以看出来(在字符串拼接的过程中,创建出来的这些中间 String 对象并不会被回收),"+" 字符串拼接对于内存的浪费比较大,如果是服务器端编程,多线程将会很浪费空间。

字符串拼接存在的问题

要得到上面的 s4,就会 s1 和 s2 拼接生成临时一个 String 对象 t1,内容为 "hello word",然后有 t1 和 s3 拼接生成最终我们需要的 s4 对象,这其中,产生了一个中间的 t1,而且 t1 创建之后,没有主动回收,势必会占一定的空间。如果是一个很多(尤其是一个部署到服务器上的项目,每个用户开一个线程,那用户越来越多的时候这个性能的损耗就很明显了)字符串的拼接,那么代价就更大了,性能一下会降低很多。一个 Java 程序如果想运行起来,需要经过两个时期,编译时和运行时。在编译时,Java 编译器(Compiler)将 java 文件转换成字节码。在运行时,Java 虚拟机(JVM)运行编译时生成的字节码。通过这样两个时期,Java 做到了所谓的一处编译,处处运行。早期的版本中,字符串拼接是会在常量池创建对象的,所以不少编程规范都会说不要直接用加号去拼接字符串,因为老是去常量池创建对象的话,开销也不小。在 jdk5.0 之后 java 对字符串拼接做了编译器的优化处理。当 Java 编译器遇到字符串拼接的时候,会创建一个 StringBuilder 对象,后面的拼接,实际上是调用 StringBuilder 对象的 append 方法。这样就不会有我们上面担心的问题了。

分析上边这个代码,看起来没毛病。但是这里面有一个很重要的就是 StringBuilder 对象创建发生在循环之间,也就是意味着有多少次循环会创建多少个 StringBuilder 对象,这样明显不好。稍微优化一下。

StringBuilder 对象的创建在循坏外面,这样就只创建了一个对象,比较好。

总结

我们在循环体中需要尽量避免隐式或者显式创建 StringBuilder。 也就是说平时简单的拼接,直接 "+" 号拼接就行;如果要用循环,就在外面 new 个StringBuilder 对象,然后循环里 append 拼接就行了好。

其实对于循坏来说,尽量避免在循坏里创建对象,可以将创建对象这个操作放在循坏外面,这样我们就让这个对象达到复用了。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180720G1Z6ZM00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券