`

理解String 及 String.intern() 在实际中的应用

    博客分类:
  • J2EE
阅读更多
1. 首先String不属于8种基本数据类型,String是一个对象。 

  因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。 

  2. new String()和new String(“”)都是申明一个新的空字符串,是空串不是null; 

  3. String str=”kvill”; 
String str=new String (“kvill”);的区别: 

  在这里,我们不谈堆,也不谈栈,只先简单引入常量池这个简单的概念。 

  常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。 

  看例1: 

String s0=”kvill”; 
String s1=”kvill”; 
String s2=”kv” + “ill”; 
System.out.println( s0==s1 ); 
System.out.println( s0==s2 );  

  结果为: 

true 
true  

  首先,我们要知道Java会确保一个字符串常量只有一个拷贝。 

  因为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中”kvill”的一个引用。 

  所以我们得出s0==s1==s2; 

  用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。 

  看例2: 

String s0=”kvill”; 
String s1=new String(”kvill”); 
String s2=”kv” + new String(“ill”); 
System.out.println( s0==s1 ); 
System.out.println( s0==s2 ); 
System.out.println( s1==s2 );  

  结果为: 

false 
false 
false  

  例2中s0还是常量池中”kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分new String(“ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。 

  4. String.intern(): 

  再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了 

  例3: 

String s0= “kvill”; 
String s1=new String(”kvill”); 
String s2=new String(“kvill”); 
System.out.println( s0==s1 ); 
System.out.println( “**********” ); 
s1.intern(); 
s2=s2.intern(); //把常量池中“kvill”的引用赋给s2 
System.out.println( s0==s1); 
System.out.println( s0==s1.intern() ); 
System.out.println( s0==s2 );  

  结果为: 

false 
********** 
false //虽然执行了s1.intern(),但它的返回值没有赋给s1 
true //说明s1.intern()返回的是常量池中”kvill”的引用 
true  

  最后我再破除一个错误的理解: 

  有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的: 

  看例4: 

String s1=new String("kvill"); 
String s2=s1.intern(); 
System.out.println( s1==s1.intern() ); 
System.out.println( s1+" "+s2 ); 
System.out.println( s2==s1.intern() );  

  结果: 

false 
kvill kvill 
true  

  在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。 

  s1==s1.intern()为false说明原来的“kvill”仍然存在; 

  s2现在为常量池中“kvill”的地址,所以有s2==s1.intern()为true。 

  5. 关于equals()和==: 

  这个对于String简单来说就是比较两字符串的Unicode序列是否相当,如果相等返回true;而==是比较两字符串的地址是否相同,也就是是否是同一个字符串的引用。 

  6. 关于String是不可变的

  这一说又要说很多,大家只要知道String的实例一旦生成就不会再改变了,比如说:String str=”kv”+”ill”+” “+”ans”; 
就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” “ 生成 ”kvill “存在内存中,最后又和生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的“不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的

 出处:http://www.iteye.com/topic/122206

 

By the way,关于 String.intern() 在实际中的应用,我在tomcat的源码中找到了一个地方用到了,如下:

 

/*
 * Copyright 1999,2004-2005 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.jasper.xmlparser;

/**
 * This class is a symbol table implementation that guarantees that
 * strings used as identifiers are unique references. Multiple calls
 * to <code>addSymbol</code> will always return the same string
 * reference.
 * <p>
 * The symbol table performs the same task as <code>String.intern()</code>
 * with the following differences:
 * <ul>
 *  <li>
 *   A new string object does not need to be created in order to
 *   retrieve a unique reference. Symbols can be added by using
 *   a series of characters in a character array.
 *  </li>
 *  <li>
 *   Users of the symbol table can provide their own symbol hashing
 *   implementation. For example, a simple string hashing algorithm
 *   may fail to produce a balanced set of hashcodes for symbols
 *   that are <em>mostly</em> unique. Strings with similar leading
 *   characters are especially prone to this poor hashing behavior.
 *  </li>
 * </ul>
 *
 * @author Andy Clark
 * @version $Id: SymbolTable.java 306179 2005-07-27 15:12:04Z yoavs $
 */
public class SymbolTable {

    //
    // Constants
    //

    /** Default table size. */
    protected static final int TABLE_SIZE = 101;

    //
    // Data
    //

    /** Buckets. */
    protected Entry[] fBuckets = null;

    // actual table size
    protected int fTableSize;

    //
    // Constructors
    //

    /** Constructs a symbol table with a default number of buckets. */
    public SymbolTable() {
        this(TABLE_SIZE);
    }

    /** Constructs a symbol table with a specified number of buckets. */
    public SymbolTable(int tableSize) {
        fTableSize = tableSize;
        fBuckets = new Entry[fTableSize];
    }

    //
    // Public methods
    //

    /**
     * Adds the specified symbol to the symbol table and returns a
     * reference to the unique symbol. If the symbol already exists,
     * the previous symbol reference is returned instead, in order
     * guarantee that symbol references remain unique.
     *
     * @param symbol The new symbol.
     */
    public String addSymbol(String symbol) {

        // search for identical symbol
        int bucket = hash(symbol) % fTableSize;
        int length = symbol.length();
        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
            if (length == entry.characters.length) {
                for (int i = 0; i < length; i++) {
                    if (symbol.charAt(i) != entry.characters[i]) {
                        continue OUTER;
                    }
                }
                return entry.symbol;
            }
        }

        // create new entry
        Entry entry = new Entry(symbol, fBuckets[bucket]);
        fBuckets[bucket] = entry;
        return entry.symbol;

    } // addSymbol(String):String

    /**
     * Adds the specified symbol to the symbol table and returns a
     * reference to the unique symbol. If the symbol already exists,
     * the previous symbol reference is returned instead, in order
     * guarantee that symbol references remain unique.
     *
     * @param buffer The buffer containing the new symbol.
     * @param offset The offset into the buffer of the new symbol.
     * @param length The length of the new symbol in the buffer.
     */
    public String addSymbol(char[] buffer, int offset, int length) {

        // search for identical symbol
        int bucket = hash(buffer, offset, length) % fTableSize;
        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
            if (length == entry.characters.length) {
                for (int i = 0; i < length; i++) {
                    if (buffer[offset + i] != entry.characters[i]) {
                        continue OUTER;
                    }
                }
                return entry.symbol;
            }
        }

        // add new entry
        Entry entry = new Entry(buffer, offset, length, fBuckets[bucket]);
        fBuckets[bucket] = entry;
        return entry.symbol;

    } // addSymbol(char[],int,int):String

    /**
     * Returns a hashcode value for the specified symbol. The value
     * returned by this method must be identical to the value returned
     * by the <code>hash(char[],int,int)</code> method when called
     * with the character array that comprises the symbol string.
     *
     * @param symbol The symbol to hash.
     */
    public int hash(String symbol) {

        int code = 0;
        int length = symbol.length();
        for (int i = 0; i < length; i++) {
            code = code * 37 + symbol.charAt(i);
        }
        return code & 0x7FFFFFF;

    } // hash(String):int

    /**
     * Returns a hashcode value for the specified symbol information.
     * The value returned by this method must be identical to the value
     * returned by the <code>hash(String)</code> method when called
     * with the string object created from the symbol information.
     *
     * @param buffer The character buffer containing the symbol.
     * @param offset The offset into the character buffer of the start
     *               of the symbol.
     * @param length The length of the symbol.
     */
    public int hash(char[] buffer, int offset, int length) {

        int code = 0;
        for (int i = 0; i < length; i++) {
            code = code * 37 + buffer[offset + i];
        }
        return code & 0x7FFFFFF;

    } // hash(char[],int,int):int

    /**
     * Returns true if the symbol table already contains the specified
     * symbol.
     *
     * @param symbol The symbol to look for.
     */
    public boolean containsSymbol(String symbol) {

        // search for identical symbol
        int bucket = hash(symbol) % fTableSize;
        int length = symbol.length();
        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
            if (length == entry.characters.length) {
                for (int i = 0; i < length; i++) {
                    if (symbol.charAt(i) != entry.characters[i]) {
                        continue OUTER;
                    }
                }
                return true;
            }
        }

        return false;

    } // containsSymbol(String):boolean

    /**
     * Returns true if the symbol table already contains the specified
     * symbol.
     *
     * @param buffer The buffer containing the symbol to look for.
     * @param offset The offset into the buffer.
     * @param length The length of the symbol in the buffer.
     */
    public boolean containsSymbol(char[] buffer, int offset, int length) {

        // search for identical symbol
        int bucket = hash(buffer, offset, length) % fTableSize;
        OUTER: for (Entry entry = fBuckets[bucket]; entry != null; entry = entry.next) {
            if (length == entry.characters.length) {
                for (int i = 0; i < length; i++) {
                    if (buffer[offset + i] != entry.characters[i]) {
                        continue OUTER;
                    }
                }
                return true;
            }
        }

        return false;

    } // containsSymbol(char[],int,int):boolean

    //
    // Classes
    //

    /**
     * This class is a symbol table entry. Each entry acts as a node
     * in a linked list.
     */
    protected static final class Entry {

        //
        // Data
        //

        /** Symbol. */
        public String symbol;

        /**
         * Symbol characters. This information is duplicated here for
         * comparison performance.
         */
        public char[] characters;

        /** The next entry. */
        public Entry next;

        //
        // Constructors
        //

        /**
         * Constructs a new entry from the specified symbol and next entry
         * reference.
         */
        public Entry(String symbol, Entry next) {
            this.symbol = symbol.intern();
            characters = new char[symbol.length()];
            symbol.getChars(0, characters.length, characters, 0);
            this.next = next;
        }

        /**
         * Constructs a new entry from the specified symbol information and
         * next entry reference.
         */
        public Entry(char[] ch, int offset, int length, Entry next) {
            characters = new char[length];
            System.arraycopy(ch, offset, characters, 0, length);
            symbol = new String(characters).intern();
            this.next = next;
        }

    } // class Entry

} // class SymbolTable

 

分享到:
评论
6 楼 lankk 2017-03-12  
lankk 写道
事实上,在运行String s1=new String("kvill");这句代码时,JVM就已经为"kvill"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"kvill"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址。
根据1楼的结果
String s1=new String("kv") + "ill";  
String s2=s1.intern();  
System.out.println( s1==s1.intern() );   //true
System.out.println( s1+" "+s2 );   // kvill kvill
System.out.println( s2==s1.intern() ); // false
可以知道,常量池的kvill对象就是s1自己,所以
“如果在表中没有相同值的字符串,则将自己的地址注册到表中”这句话没毛病


局部变量s实际上存储 这里s应为s1
5 楼 lankk 2017-03-12  
事实上,在运行String s1=new String("kvill");这句代码时,JVM就已经为"kvill"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"kvill"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后用这个拘留字符串的值来初始化堆中用new指令创建出来的新的String对象,局部变量s实际上存储的是new出来的堆对象地址。
根据1楼的结果
String s1=new String("kv") + "ill";  
String s2=s1.intern();  
System.out.println( s1==s1.intern() );   //true
System.out.println( s1+" "+s2 );   // kvill kvill
System.out.println( s2==s1.intern() ); // false
可以知道,常量池的kvill对象就是s1自己,所以
“如果在表中没有相同值的字符串,则将自己的地址注册到表中”这句话没毛病
4 楼 lankk 2017-03-12  
同意1楼的说法
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#intern%28%29
3 楼 hongdanning 2016-01-20  
理解了。之前困惑的一些明白了。谢谢分享。
2 楼 ffshi 2015-06-12  
 有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:  


如果在表中没有相同值的字符串,则将自己的地址注册到表中,应该是这么举例子吧:
这样才是true

    String ss = new String("hello").intern();
    String s1 = "hello";
   
    System.out.println(ss == s1);
1 楼 vk14311 2014-03-06  
String s1=new String("kvill");  
String s2=s1.intern();  
System.out.println( s1==s1.intern() );  
System.out.println( s1+" "+s2 );  
System.out.println( s2==s1.intern() );

这个例子应该改为:

String s1=new String("kv") + "ill";  
String s2=s1.intern();  
System.out.println( s1==s1.intern() );  
System.out.println( s1+" "+s2 );  
System.out.println( s2==s1.intern() );

因为在String s1=new String("kvill"); 这句的时候已经存在"kvill"常量了。

相关推荐

    C#中字符串优化String.Intern、IsInterned详解

    在实际应用中,字符串优化可以带来巨大的性能提升。例如,在大型项目中,字符串的数量可能是巨大的。如果不进行优化,可能会出现几百、几千、甚至几万的重复字符串存在,这将占用巨额的存储空间。通过使用String....

    jdk1.8之后的String.intern()方法内存分析

    关于String.intern()方法,这个问题都被问烂了,有的文章在分析的时候还在用jdk1.7,jdk1.8之后内存模型发生了变化,内存的变化也会影响intern方法的执行,这里有必要写文章分析一下,请大家务必从头开始看,这样...

    JVM系列之String.intern的性能解析

    String对象有个特殊的StringTable字符串常量池,为了减少Heap中生成的字符串的数量,推荐尽量直接使用String Table中的字符串常量池中的元素。 那么String.intern的性能怎么样呢?我们一起来看一下。 String.intern...

    全面理解java中的String.doc

    在Java编程语言中,String是一个非常重要的类,它代表不可变的字符序列。这篇文档主要讲解了String对象的几个核心...在实际编程中,根据需求合理选择创建和操作字符串的方法,可以避免不必要的内存开销,提升程序性能。

    深入理解JavaString#intern()内存模型Ja

    在Java编程语言中,`String`类是极其重要的,它提供了许多用于操作字符串的方法,其中之一便是`intern()`。深入理解`String#intern()`...在实际开发中,合理利用`intern()`,可以有效地提升程序性能并优化内存使用。

    String类的intern、split方法

    该方法返回一个字符串对象的内部化引用,由 String 类维护一个初始为空的字符串的对象池,当 intern 方法被调用时,如果对象池中已经包含这一个相等的字符串对象则返回对象池中的实例,否则添加字符串到对象池并返回...

    深入理解 Java String#intern() 内存模型.docx

    在实际开发中,String#intern() 方法可以用于减少字符串对象的创建,提高程序的性能。但是,需要注意的是,在 Java6 中,字符串常量池的大小是有限的,需要合理地使用该方法,以避免 Perm 空间溢出。 Java String#...

    关于java String中intern的深入讲解

    Java String 中的 intern 方法是一个非常重要的概念,它可以将字符串对象存储在字符串常量池中,以便重复使用相同的字符串对象,减少内存的占用。下面我们将深入讲解 Java String 中 intern 的相关知识点。 什么是 ...

    解析Java中的String对象的数据类型 字符串

    最后,需要破除一个错误的理解,即使用String.intern()方法不能将一个String对象保存到一个全局String表中。如果具有相同值的Unicode字符串已经在这个表中,那么String.intern()方法将返回该表中的字符串常量的引用...

    String和StringBuffer深入.doc

    这是因为`String`对象在Java中存储在常量池中,每次对`String`对象进行修改时,实际上都会创建一个新的`String`对象,而不是改变原来的对象。这种特性使得`String`对象适合用作不可变数据,比如作为方法参数或返回值...

    通过String.intern()方法浅谈堆中常量池

    总结起来,String.intern()方法是Java中处理字符串对象优化的一种手段,它帮助减少内存中重复字符串对象的数量,特别是在大量字符串操作的应用中,可以显著提高程序性能。从JDK 7开始,常量池的迁移带来了新的内存...

    10个Java经典的String面试题!.zip

    理解这些知识点对于Java开发者来说至关重要,它们不仅有助于在面试中脱颖而出,也能在实际编程中提升代码质量和效率。熟练掌握String类的特性和用法,能够使你在处理字符串问题时更加得心应手。

    java中Object对象String对象的解析.pdf

    使用字面值赋值方式创建的 String 对象会被存储在 String 池中, String 池是一个特殊的存储区域,用于存储字符串常量。使用 new 关键字创建的 String 对象则会被存储在堆中。 在比较 String 对象时,需要使用 ...

    java,string类练习题.docx

    在Java编程语言中,String类是一个非常核心且重要的部分,它代表不可变的字符序列。...在实际编程中,理解并熟练运用这些知识是非常关键的,因为它涉及到字符串的操作、内存管理以及字符串常量池的概念。

    String字符串比较1

    Java中的字符串操作是一个重要的知识点,尤其在内存管理和字符串比较方面。在Java中,字符串是不可变的,这意味着一旦创建,就不能更改。字符串的比较通常涉及到`==`和`.equals()`方法,以及`intern()`方法。 首先...

    提高 Java 代码性能的各种技巧.docx

    在 Java 6 中,String.intern() 方法将所有共享的 String 对象存储在 PermGen 中 —— 堆中固定大小的部分主要用于存储加载的类对象和字符串池。 PermGen 字符串池的最大问题是它的位置 —— PermGen。PermGen 的...

    StringTable.pdf

    理解JVM中的StringTable对于优化Java应用程序性能至关重要。开发者应关注字符串的创建、使用和垃圾回收,合理利用常量池,避免不必要的内存开销,同时关注JVM参数调整,以提升程序的运行效率。通过深入理解这一机制...

Global site tag (gtag.js) - Google Analytics