宁波教育平台网站建设,韩国儿童才艺网站建设模板,网站建设大德通众包,南充公司网站建设相信String这个类是Java中使用得最频繁的类之一#xff0c;并且又是各大公司面试喜欢问到的地方#xff0c;今天就来和大家一起学习一下String、StringBuilder和StringBuffer这几个类#xff0c;分析它们的异同点以及了解各个类适用的场景。下面是本文的目录大纲#xff1a…相信String这个类是Java中使用得最频繁的类之一并且又是各大公司面试喜欢问到的地方今天就来和大家一起学习一下String、StringBuilder和StringBuffer这几个类分析它们的异同点以及了解各个类适用的场景。下面是本文的目录大纲 一.你了解String类吗 二.深入理解String、StringBuffer、StringBuilder 三.不同场景下三个类的性能测试 四.常见的关于String、StringBuffer的面试题辟谣网上流传的一些曲解String类的说法 若有不正之处请多多谅解和指正不胜感激。 请尊重作者劳动成果转载请标明转载地址 http://www.cnblogs.com/dolphin0520/p/3778589.html 一.你了解String类吗 想要了解一个类最好的办法就是看这个类的实现源代码String类的实现在 \jdk1.6.0_14\src\java\lang\String.java 文件中。 打开这个类文件就会发现String类是被final修饰的 public final class Stringimplements java.io.Serializable, ComparableString, CharSequence
{/** The value is used for character storage. */private final char value[];/** The offset is the first index of the storage that is used. */private final int offset;/** The count is the number of characters in the String. */private final int count;/** 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;......} 从上面可以看出几点 1String类是final类也即意味着String类不能被继承并且它的成员方法都默认为final方法。在Java中被final修饰的类是不允许被继承的并且该类中的成员方法都默认为final方法。在早期的JVM实现版本中被final修饰的方法会被转为内嵌调用以提升执行效率。而从Java SE5/6开始就渐渐摈弃这种方式了。因此在现在的Java SE版本中不需要考虑用final去提升方法调用效率。只有在确定不想让该方法被覆盖时才将方法设置为final。 2上面列举出了String类中所有的成员属性从上面可以看出String类其实是通过char数组来保存字符串的。 下面再继续看String类的一些方法实现 public String substring(int beginIndex, int endIndex) {if (beginIndex 0) {throw new StringIndexOutOfBoundsException(beginIndex);}if (endIndex count) {throw new StringIndexOutOfBoundsException(endIndex);}if (beginIndex endIndex) {throw new StringIndexOutOfBoundsException(endIndex - beginIndex);}return ((beginIndex 0) (endIndex count)) ? this :new String(offset beginIndex, endIndex - beginIndex, value);}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);}public String replace(char oldChar, char newChar) {if (oldChar ! newChar) {int len count;int i -1;char[] val value; /* avoid getfield opcode */int off offset; /* avoid getfield opcode */while (i len) {if (val[off i] oldChar) {break;}}if (i len) {char buf[] new char[len];for (int j 0 ; j i ; j) {buf[j] val[offj];}while (i len) {char c val[off i];buf[i] (c oldChar) ? newChar : c;i;}return new String(0, len, buf);}}return this; 从上面的三个方法可以看出无论是sub操、concat还是replace操作都不是在原有的字符串上进行的而是重新生成了一个新的字符串对象。也就是说进行这些操作后最原始的字符串并没有被改变。 在这里要永远记住一点 “对String对象的任何改变都不影响到原对象相关的任何change操作都会生成新的对象”。 在了解了于String类基础的知识后下面来看一些在平常使用中容易忽略和混淆的地方。 二.深入理解String、StringBuffer、StringBuilder 1.String strhello world和String strnew String(hello world)的区别 想必大家对上面2个语句都不陌生在平时写代码的过程中也经常遇到那么它们到底有什么区别和联系呢下面先看几个例子 public class Main {public static void main(String[] args) {String str1 hello world;String str2 new String(hello world);String str3 hello world;String str4 new String(hello world);System.out.println(str1str2);System.out.println(str1str3);System.out.println(str2str4);}
} 这段代码的输出结果为 为什么会出现这样的结果下面解释一下原因 在前面一篇讲解关于JVM内存机制的一篇博文中提到 在class文件中有一部分 来存储编译期间生成的 字面常量以及符号引用这部分叫做class文件常量池在运行期间对应着方法区的运行时常量池。 因此在上述代码中String str1 hello world;和String str3 hello world; 都在编译期间生成了 字面常量和符号引用运行期间字面常量hello world被存储在运行时常量池当然只保存了一份。通过这种方式来将String对象跟引用绑定的话JVM执行引擎会先在运行时常量池查找是否存在相同的字面常量如果存在则直接将引用指向已经存在的字面常量否则在运行时常量池开辟一个空间来存储该字面常量并将引用指向该字面常量。 总所周知通过new关键字来生成对象是在堆区进行的而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象创建出的一定是不同的对象即使字符串的内容是相同的。 2.String、StringBuffer以及StringBuilder的区别 既然在Java中已经存在了String类那为什么还需要StringBuilder和StringBuffer类呢 那么看下面这段代码 public class Main {public static void main(String[] args) {String string ;for(int i0;i10000;i){string hello;}}
} 这句 string hello;的过程相当于将原有的string变量指向的对象内容取出与hello作字符串相加操作再存进另一个新的String对象当中再让string变量指向新生成的对象。如果大家还有疑问可以反编译其字节码文件便清楚了 从这段反编译出的字节码文件可以很清楚地看出从第8行开始到第35行是整个循环的执行过程并且每次循环会new出一个StringBuilder对象然后进行append操作最后通过toString方法返回String对象。也就是说这个循环执行完毕new出了10000个对象试想一下如果这些对象没有被回收会造成多大的内存资源浪费。从上面还可以看出stringhello的操作事实上会自动被JVM优化成 StringBuilder str new StringBuilder(string); str.append(hello); str.toString(); 再看下面这段代码 public class Main {public static void main(String[] args) {StringBuilder stringBuilder new StringBuilder();for(int i0;i10000;i){stringBuilder.append(hello);}}
} 反编译字节码文件得到 从这里可以明显看出这段代码的for循环式从13行开始到27行结束并且new操作只进行了一次也就是说只生成了一个对象append操作是在原有对象的基础上进行的。因此在循环了10000次之后这段代码所占的资源要比上面小得多。 那么有人会问既然有了StringBuilder类为什么还需要StringBuffer类查看源代码便一目了然事实上StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同区别是StringBuffer类的成员方法前面多了一个关键字synchronized不用多说这个关键字是在多线程访问时起到安全保护作用的,也就是说StringBuffer是线程安全的。 下面摘了2段代码分别来自StringBuffer和StringBuilderinsert方法的具体实现 StringBuilder的insert方法 public StringBuilder insert(int index, char str[], int offset,int len){super.insert(index, str, offset, len);return this;} StringBuffer的insert方法 public synchronized StringBuffer insert(int index, char str[], int offset,int len){super.insert(index, str, offset, len);return this;} 三.不同场景下三个类的性能测试 从第二节我们已经看出了三个类的区别这一小节我们来做个小测试来测试一下三个类的性能区别 public class Main {private static int time 50000;public static void main(String[] args) {testString();testStringBuffer();testStringBuilder();test1String();test2String();}public static void testString () {String s;long begin System.currentTimeMillis();for(int i0; itime; i){s java;}long over System.currentTimeMillis();System.out.println(操作s.getClass().getName()类型使用的时间为(over-begin)毫秒);}public static void testStringBuffer () {StringBuffer sb new StringBuffer();long begin System.currentTimeMillis();for(int i0; itime; i){sb.append(java);}long over System.currentTimeMillis();System.out.println(操作sb.getClass().getName()类型使用的时间为(over-begin)毫秒);}public static void testStringBuilder () {StringBuilder sb new StringBuilder();long begin System.currentTimeMillis();for(int i0; itime; i){sb.append(java);}long over System.currentTimeMillis();System.out.println(操作sb.getClass().getName()类型使用的时间为(over-begin)毫秒);}public static void test1String () {long begin System.currentTimeMillis();for(int i0; itime; i){String s Ilovejava;}long over System.currentTimeMillis();System.out.println(字符串直接相加操作(over-begin)毫秒);}public static void test2String () {String s1 I;String s2 love;String s3 java;long begin System.currentTimeMillis();for(int i0; itime; i){String s s1s2s3;}long over System.currentTimeMillis();System.out.println(字符串间接相加操作(over-begin)毫秒);}} 测试结果win7EclipseJDK6) 上面提到stringhello的操作事实上会自动被JVM优化看下面这段代码 public class Main {private static int time 50000;public static void main(String[] args) {testString();testOptimalString();}public static void testString () {String s;long begin System.currentTimeMillis();for(int i0; itime; i){s java;}long over System.currentTimeMillis();System.out.println(操作s.getClass().getName()类型使用的时间为(over-begin)毫秒);}public static void testOptimalString () {String s;long begin System.currentTimeMillis();for(int i0; itime; i){StringBuilder sb new StringBuilder(s);sb.append(java);ssb.toString();}long over System.currentTimeMillis();System.out.println(模拟JVM优化操作的时间为(over-begin)毫秒);}} 执行结果 得到验证。 下面对上面的执行结果进行一般性的解释 1对于直接相加字符串效率很高因为在编译器便确定了它的值也就是说形如Ilovejava; 的字符串相加在编译期间便被优化成了Ilovejava。这个可以用javap -c命令反编译生成的class文件进行验证。 对于间接相加即包含字符串引用形如s1s2s3; 效率要比直接相加低因为在编译器不会对引用变量进行优化。 2String、StringBuilder、StringBuffer三者的执行效率 StringBuilder StringBuffer String 当然这个是相对的不一定在所有情况下都是这样。 比如String str hello world的效率就比 StringBuilder st new StringBuilder().append(hello).append(world)要高。 因此这三个类是各有利弊应当根据不同的情况来进行选择使用 当字符串相加操作或者改动较少的情况下建议使用 String strhello这种形式 当字符串相加操作较多的情况下建议使用StringBuilder如果采用了多线程则使用StringBuffer。 四.常见的关于String、StringBuffer的面试题 下面是一些常见的关于String、StringBuffer的一些面试笔试题若有不正之处请谅解和批评指正。 1. 下面这段代码的输出结果是什么 String a hello2; String b hello 2; System.out.println((a b)); 输出结果为true。原因很简单hello2在编译期间就已经被优化成hello2因此在运行期间变量a和变量b指向的是同一个对象。 2.下面这段代码的输出结果是什么 String a hello2; String b hello; String c b 2; System.out.println((a c)); 输出结果为:false。由于有符号引用的存在所以 String c b 2;不会在编译期间被优化不会把b2当做字面常量来处理的因此这种方式生成的对象事实上是保存在堆上的。因此a和c指向的并不是同一个对象。javap -c得到的内容 3.下面这段代码的输出结果是什么 String a hello2; final String b hello; String c b 2; System.out.println((a c)); 输出结果为true。对于被final修饰的变量会在class文件常量池中保存一个副本也就是说不会通过连接而进行访问对final变量的访问在编译期间都会直接被替代为真实的值。那么String c b 2;在编译期间就会被优化成String c hello 2; 下图是javap -c的内容 4.下面这段代码输出结果为 public class Main {public static void main(String[] args) {String a hello2;final String b getHello();String c b 2;System.out.println((a c));}public static String getHello() {return hello;}
} 输出结果为false。这里面虽然将b用final修饰了但是由于其赋值是通过方法调用返回的那么它的值只能在运行期间确定因此a和c指向的不是同一个对象。 5.下面这段代码的输出结果是什么 public class Main {public static void main(String[] args) {String a hello;String b new String(hello);String c new String(hello);String d b.intern();System.out.println(ab);System.out.println(bc);System.out.println(bd);System.out.println(ad);}
} 输出结果为JDK版本 JDK6) 这里面涉及到的是String.intern方法的使用。在String类中intern方法是一个本地方法在JAVA SE6之前intern方法会在运行时常量池中查找是否存在内容相同的字符串如果存在则返回指向该字符串的引用如果不存在则会将该字符串入池并返回一个指向该字符串的引用。因此a和d指向的是同一个对象。 6.String str new String(abc)创建了多少个对象 这个问题在很多书籍上都有说到比如《Java程序员面试宝典》包括很多国内大公司笔试面试题都会遇到大部分网上流传的以及一些面试书籍上都说是2个对象这种说法是片面的。 如果有不懂得地方可以参考这篇帖子 http://rednaxelafx.iteye.com/blog/774673/ 首先必须弄清楚创建对象的含义创建是什么时候创建的这段代码在运行期间会创建2个对象么毫无疑问不可能用javap -c反编译即可得到JVM执行的字节码内容 很显然new只调用了一次也就是说只创建了一个对象。 而这道题目让人混淆的地方就是这里这段代码在运行期间确实只创建了一个对象即在堆上创建了abc对象。而为什么大家都在说是2个对象呢这里面要澄清一个概念 该段代码执行过程和类的加载过程是有区别的。在类加载的过程中确实在运行时常量池中创建了一个abc对象而在代码执行过程中确实只创建了一个String对象。 因此这个问题如果换成 String str new String(abc)涉及到几个String对象合理的解释是2个。 个人觉得在面试的时候如果遇到这个问题可以向面试官询问清楚”是这段代码执行过程中创建了多少个对象还是涉及到多少个对象“再根据具体的来进行回答。 7.下面这段代码1和2的区别是什么 public class Main {public static void main(String[] args) {String str1 I;//str1 lovejava; 1)str1 str1lovejava; //2)}
} 1的效率比2的效率要高1中的lovejava在编译期间会被优化成lovejava而2中的不会被优化。下面是两种方式的字节码 1的字节码 2的字节码 可以看出在1中只进行了一次append操作而在2中进行了两次append操作。 参考文章http://rednaxelafx.iteye.com/blog/774673/ http://www.blogjava.net/Jack2007/archive/2008/06/17/208602.html http://www.jb51.net/article/36041.htm http://blog.csdn.net/yirentianran/article/details/2871417 http://www.jb51.net/article/33398.htm转载于:https://www.cnblogs.com/lijia0511/p/4988555.html