Java中的String与intern方法
来源:互联网 发布:java字符串转json数组 编辑:程序博客网 时间:2024/06/02 00:21
常量池
在理解Java中的String之前有一个必须要知道的概念-常量池
在java的class文件中,有一块常量集中存放的区域,这块地方被称为常量池。常量池中存储的常量通常包括关于类,方法,接口等中的常量,以及字符串常量,如String s = “java”这种申明方式;当然也可扩充,执行器产生的常量也会放入常量池。而且在JDK1.7对常量池所处的位置也做了变动。在1.7以前,常量池位于JVM运行时内存的方法区(永久代或叫PermGen)。而到了1.7,常量池逐渐迁移到了MetSpace(元空间)中,此空间不存在于JVM中,其使用本地内存。
常量池和堆中的字符串
存储在常量池中的字符串通常包括以下形式
1. String a=”a”这种字符串字面值类的声明,这种声明会先去常量池中寻找是否有字符串”a”(或指向字符串的引用),如果有,则直接返回此引用。如果木有,则将字符串放入常量池中,并返回其引用;
2. String ab=”a”+”b”这种字符串字面值类的拼接,编译完成后,会直接返回一个”ab”字符串,处理过程和a类似。
3. String ab=new String(“ab”)这种形式声明的字符串是直接在堆中生成一个字符串对象。
4. String ab=a+”b”这种形式的字符串拼接过程要分两种情况讨论。假如a变量是个普通变量,这种情况下,通过生成一个StringBuilder,并调用append方法。这种方式生成字符串和使用new方式生成的字符串并没有区别。都是直接在堆上生成字符串。而假如a是final类型的,在编译时,编译期会自动吧a变量替换成”a”字符串,从而该表达式类似于第二种情况,直接在常量池中生成字符串。
以一段代码结果来验证以上陈述
public class StringTest { public static void main(String[] args) { String b="b"; final String finalb="b"; String ab="ab"; String abNewString=new String("ab"); String abString="a"+"b"; String abVar="a"+b; String abFinalVar="a"+finalb; System.out.println(abNewString==ab); System.out.println(abString==ab); System.out.println(abVar==ab); System.out.println(abFinalVar==ab); }}
上面程序的执行结果如下:
falsetruefalsetrue
下面再来看下其反编译后的结果:
public class com.person.blogcases.StringTest { public com.person.blogcases.StringTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // String b 2: astore_1 3: ldc #2 // String b 5: astore_2 6: ldc #3 // String ab 8: astore_3 9: new #4 // class java/lang/String 12: dup 13: ldc #3 // String ab 15: invokespecial #5 // Method java/lang/String."<init>":(Ljava/lang/String;)V 18: astore 4 20: ldc #3 // String ab 22: astore 5 24: new #6 // class java/lang/StringBuilder 27: dup 28: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 31: ldc #8 // String a 33: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 36: aload_1 37: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 40: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 43: astore 6 45: ldc #3 // String ab 47: astore 7 49: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 52: aload 4 54: aload_3 55: if_acmpne 62 58: iconst_1 59: goto 63 62: iconst_0 63: invokevirtual #12 // Method java/io/PrintStream.println:(Z)V 66: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 69: aload 5 71: aload_3 72: if_acmpne 79 75: iconst_1 76: goto 80 79: iconst_0 80: invokevirtual #12 // Method java/io/PrintStream.println:(Z)V 83: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 86: aload 6 88: aload_3 89: if_acmpne 96 92: iconst_1 93: goto 97 96: iconst_0 97: invokevirtual #12 // Method java/io/PrintStream.println:(Z)V 100: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 103: aload 7 105: aload_3 106: if_acmpne 113 109: iconst_1 110: goto 114 113: iconst_0 114: invokevirtual #12 // Method java/io/PrintStream.println:(Z)V 117: return}
在0、3、6行分别从常量池中将字符串引用( “b”,”b”,”ab” )压入栈,而2、5、8行则分别将入栈的String引用赋于本地变量1、2、3(b,finalb,ab)中,从第9行到第18行是整个String abNewString=new String("ab");
的反编译结果。而20、22两行则是String abString="a"+"b";
反编译的结果,可以看到,它也是直接从常量池中获取字符串引用的。从24到43行是String abVar="a"+b;
反编译的结果,这是Java的一个语法糖,类似这种字符串变量相加的情况,Java会生成一个StringBuilder并调用其append进行拼接,最后调用toString来返回结果。而45、47两行则是String abFinalVar="a"+finalb;
反编译的结果,可以看到其也是从常量池中直接获取到的字符串常量。这样的话上面程序的执行结果就比较好理解了,因为ab、abfinalVar、abString三个变量都是从常量池中获取的字符串变量的引用,因此在使用==判断的时候会是true。而对于abNewString、abVar,它们都是在堆上创建出来的对象,因此引用是不会和ab相等的,并且abNewString和abVar也不会是同一个引用。
理解了这些,其实intern就很好理解了,对一个变量调用intern方法的过程就相当用字符串字面值声明一个字符串变量的过程。
例如
String a=从文件或者哪里读来的字符串;String b=a.intern()
a.intern方法会先拿a字符串去常量池中寻找,如果找到了和其相同的字符串,则直接返回其引用。如果木有找到则会将a字符串的话要分两种情况。在JDK1.7以前,JVM会复制一份a字符串到常量池中,然后返回常量池中字符串的引用。而1.7以后,JVM不会将字符串往常量池中复制一份了,而是直接在常量池中存储一个指向a变量的引用,然后返回常量池中的这个引用。由这个特点可以看出来,对于程序中有大量重复字符串的场景,使用intern方法可以一定程度上节省内存消耗。
使用场景
由于常量池所在方法区,因此一般容量相较于堆空间较小。其适用场景也就比较固定。一般适用于存储大量字符串的集合类。例如要存储一亿个字符串,其中不同的字符串就3类,这时使用intern方法就可以显著的减少对堆空间浪费,因为所有相同字符串的引用都指向同一个字符串对象。不适合大量非重复字符串的场景,例如这一亿个字符串各不相同。这时如果都使用intern,在JDK1.7以前会出问题,因为对于调用intern后,jvm会复制字符串至常量池,常量池大小不够的话,就OOM了。JDK1.7以后,不再复制字符串实例置常量池后,这种场景先的适用性有所缓和。
举个面试的时候经常会被问到的问题作为例子,有一个日志文件,里面记录了各个Ip的访问信息,找出前十个访问记录最多的Ip,假设我们要先把这些Ip访问信息读到内存里(当然肯定有更好的方法)。这个时候使用intern方法就很合适,因为来访问的Ip必定有大量重复的。
- Java中的String与intern方法
- java-String中的 intern方法
- java-String中的 intern方法
- java-String中的 intern方法
- java String中的intern方法
- java-String中的 intern方法
- String中的intern方法与常量池
- Java的String类中的intern()方法
- java String类中的intern方法
- String中的intern方法
- java-String中的 intern()
- java-String中的 intern()
- java-String中的 intern()
- java-String中的 intern()
- java-String中的 intern()
- java-String中的 intern()
- java-String中的 intern()
- java-String中的 intern()(
- 使用Electron-package打包exe
- Maximum Increase CodeForces
- Android .so文件
- redhat5挂载nas盘报错: reason given by server: Permission denied
- C#中事件处理的个人体会
- Java中的String与intern方法
- 文章标题
- Spring Boot中使用Swagger2构建强大的RESTful API文档
- postgresql无视链接删除数据库
- Unity Shader学习笔记:遮罩纹理
- windows程序设计入门"hello world"
- 输入输出外挂
- Oracle 函数中 游标报表或视图不存在
- 世上最全的Android开源项目