String类–深入理解

String类–深入理解

String类的特点:

1    不可改变。String对象是不可改变的。

    public String toUpperCase() {

        return toUpperCase(Locale.getDefault());
    }

    public String toUpperCase(Locale locale) {

        if (locale == null) {throw new NullPointerException();}

        //省略
        return new String(0, count+resultOffset, result);
    }

2    重载过的运算符: + +=

String s1 = “ac”;

String s = “b” + s1 + “ef” + 3;

用javap查看编译器做的事: StringBuilder对象. 调用append()来生成最后的结果. compiler做了优化. 最后StringBuilder.toString();

直接用+=来做,由编译器来优化, 还是自己直接用StringBuilder来做?

结论: 写toString()方法时,如果操作简单,由用+=来做,相信编译器的优化.

        如果有循环,则还是自己StringBuilder一下吧.

无意识的递归: toString()中, 若有”abc” + this + “..”;

分词: StringTokenizer, 过时了,

现在用Pattern.compile()正则表达式和Scanner类.

Pattern p = Pattern.compile("a*b");
 Matcher m = p.matcher("aaaaab");
 boolean b = m.matches();

String,

StringBuffer(线程安全,效率低), 

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

    public synchronized int length() {

        return count;
    }

StringBuilder(线程不安全,效率高).

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

Java中,String对象可以说是对char[] 字符数组的进一步封装.

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{

    /** The value is used for character storage. */
    private final char value[];  它是String对象所表示的字符串的超集.

    /** The offset is the first index of the storage that is used. */
    private final int offset;  String对象的真实内容是value[]的子集. 由offset定位. 由count定长.

    /** The count is the number of characters in the String. */
    private final int count;

String对象的三大特性:

不变性,  适用于多线程中.

针对常量池的优化, ****

final类. 没有子类, 对系统安全性的保护. jdk 1.5之前,可以帮助jvm内联final方法.提高效率, 之后不太明显.

String中的subString方法之内存泄露:

1    public String substring(int beginIndex) {

        return substring(beginIndex, count);
        }

2    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);
    }

——-

// Package private constructor which shares value array for speed.
    String(int offset, int count, char value[]) {

    this.value = value;
    this.offset = offset;
    this.count = count;
    }

value[]没有改变,仅仅改变了offset, count的值.

其目地是为了快速地共享String内的char数组对象.

原生内容被复制到一个新的字符串中.

生成字符串的速度很快,可是浪费了大量的空间.如: 截取的字符串长度很短, 而原来的字符串很大,这样一个新的字符串明明很短,却要整个copy过来.

以空间换时间的String构造函数.

String s = new String(new char[100000]);

s.subString(0,2); //浪费空间. 内部调用了一个包级别的构造函数. 应用程序无法使用它.

new String(s.subString(0,2));//这就没有浪费空间了. 调用了下面这个public构造函数. 它是没有以空间换时间的.

—理由—-

public String(String original) {

    int size = original.count;
    char[] originalValue = original.value;
    char[] v;
      if (originalValue.length > size) {

         // The array representing the String is bigger than the new
         // String itself.  Perhaps this constructor is being called
         // in order to trim the baggage, so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
     } else {

         // The array representing the String is the same
         // size as the String, so no point in making a copy.
        v = originalValue;
     }
    this.offset = 0;
    this.count = size;
    this.value = v;
    }

除了String中的subString方法会使用包级别的String构造方法之外,还有一些其他的类的方法也使用了,所以它们也可以导致内存泄露.

字符串分割–性能比较:

1    String.split()功能强大,效率极差.

2    StringTokenizer性能比split好.

3    用String.indexOf, String.subString()它们来实现自己的分割算法,性能最好.

        只要处理好subString内存的问题. 但是代码的可读性和维护性差.

———-

String.charAt()多个组合用 效率 高于 String.startWith()/String.endsWith()

修改字符串的工具类: StringBuilder/StringBuffer

String变量的累加:

String s1=”a”;

String s2=”c”;

String result = s1+s2;

编译器用StringBuilder来处理,  提高性能.

String常量的累加:

String result  = “a”+”c”+”bc”;

这个在编译时就被编译器合成了一个长和字符串. 提高了性能.

String类的concat方法效率 高于 +/+= , concat效率 低于 StringBuilder.

StringBuffer/ StringBuilder:

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

 public final class StringBuffer
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{

———-容量=======

    public StringBuffer() {

        super(16);
    }

    public StringBuffer(int capacity) {

        super(capacity);
    }

    public StringBuilder() {

        super(16);
    }
    public StringBuilder(int capacity) {

        super(capacity);
    }

若超出16时,则调用 父类 AbstractStringBuilder的方法

void expandCapacity(int minimumCapacity) {

    int newCapacity = (value.length + 1) * 2; 翻倍容量
        if (newCapacity < 0) {

            newCapacity = Integer.MAX_VALUE;
        } else if (minimumCapacity > newCapacity) {

        newCapacity = minimumCapacity;
    }
        value = Arrays.copyOf(value, newCapacity);复制数据
    }

所以对于大对象的扩容会做很多的复制.

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

    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是可改变的

JVM对于字符串常量的”+”号连接,在程序编译期,JVM就将常量字符串的”+”连接优化为连接后的值,拿”a” + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来

JVM对于字符串引用,由于在字符串的”+”连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即”a” + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b

       String a = “ab”;
  final String bb = “b”;
  String b = “a” + bb;
  System.out.println((a == b)); //result = true
  分析:和[3]中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的”a” + bb和”a” + “b”效果是一样的

下面是常见的例子:

package jdk.lang;
public class StringTest {

    public static void main(String[] args) {

        String s1 = “abc”;
        String s2 = “abc”;
                System.out.println(s1 == s2);
        }
}

输出 true。也就是只生成了一个对象。

package jdk.lang;
public class StringTest {

    public static void main(String[] args) {

        String s1 = “abc”;
        String s2 = “abc”;
        String s3 = s1 + s2;
        String s4 = “abc” + “abc”;
        String s5 = “ab” + “cabc”;
        System.out.println(s3 == s4);
        System.out.println(s4 == s5);
        }
}

输出:false true。上面的程序,”abc” + “abc”是常量运算表达式constant expression。编译器就可以计算出值了,这就是编译时计算。同时这个表达式会被看做是string literal。但是:String s3 = s1+s2;那么s3的值就只能在运行的时候才能确定,这就是运行时计算。可见运行时会创建一个新的String,并且不会被当成string literal。
package jdk.lang;
public class StringTest {

    public static void main(String[] args) {

        String s1 = “abc”;
        String s2 = “abc”;
        String s3 = s1 + s2;
        String s4 = s3.intern();
        String s5 = “ab” + “cabc”;
        System.out.println(s4 == s5);
        System.out.println(s3 == s5);
                System.out.println(s3.intern() == s3);
        System.out.println(s3.intern() == s4);
        System.out.println(s5.intern() == s5);
        }
}

String提供了方法Intern让我们把显示的调用来把String对象放入到literal pool里面并返回这个新的引用。

输出: true false false true true。s4是一个新的引用,这个引用和s5一样,但是和s3不同。也就是说,intern的时候创建了一个新的对象。但是不是每次都新建一个,只要有了,就会返回存在的。最后两个结果就说明了这点。

4. 垃圾回收
GC是不会收集Literal Pool的。但是会收集没有intern的String 变量对象。对上面的例子,s3会被回收,s4就不会。

常量表达式,比如 “ab” + “cabc”会被当成literal,也就等同”abcabc”。

private static void IntegerLiteral() {

        Integer i1 = 6;
        Integer i2 = 6;
        Integer i3 = 3 + 3;
        Integer i4 = 3 * 2;
        System.out.println(i1 == i2);//true
        System.out.println(i3 == i2);//true
        System.out.println(i4 == i2);//true

        Integer i5 = 128;
        Integer i6 = 128;
        System.out.println(i5 == i6);//false
    }
private static void BoolearnLiteral() {

        Boolean b1 = true;
        Boolean b2 = true;
        System.out.println(b1 == b2);//true
    }

看看Boolean类的源码:当Boolean b1 = true;这句话会被编译器改成:Boolean b1 = Boolean.valueOf(true);

看看之前的关于Integer的那篇文章,类比出来的。

public static Boolean valueOf(boolean b) {

        return (b ? TRUE : FALSE);
}

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{

    public static final Boolean TRUE = new Boolean(true);
    public static final Boolean FALSE = new Boolean(false);

。。。}

————————————–

下面这几句,楼主说说结果是什么呢?

下面这段代码在我的机器上,jdk1.6返回false, 而jdk1.7返回true.

Java代码 复制代码 收藏代码09222430_uN3F.gif

  1. String s1 = “abc”;     

  2.         String s2 = “abc”;     

  3.         String s3 = s1 + s2;     

  4.         System.out.println(s3.intern() == s3);  

String s1 = "abc";           String s2 = "abc";           String s3 = s1 + s2;           System.out.println(s3.intern() == s3);

这个的结果是false,s3.intern()在常量池 s3在堆

一篇有营养的帖子  http://rednaxelafx.iteye.com/blog/774673

非也,jdk1.7  JRockit 上华丽丽的是true

详见 http://www.iteye.com/topic/1112592

其实咱们都理解的不完全

javadoc(sun的jdk 1.6和1.7的)描述:

引用

It follows that for any two strings  s and t, s.intern() == t.intern() is true  if and only if s.equals(t) is true

也就是说
s.equals(t) 则 s.intern() == t.intern()
s==t,则 s.intern() == t.intern()

s3.intern() == s3 这个没有定义结果是什么,要看虚拟机(如我以sun hotspot 1.6来说的结果是false,而你是以(sun hotspot 1.7 和 jrockit来说的 是true)

此处引用RednaxelaFX的原话 原文http://www.iteye.com/topic/1112592?page=3#2216483

引用

为什么JRockit与新的JDK7里的HotSpot会返回true其实很简单:它们的string pool并不拷贝输进来intern()的java.lang.String实例,只是在池里记录了每组内容相同的String实例首个被intern的那个实例的引用

老的HotSpot之所以在前面的一些实验里会返回false是因为它的string pool实现是要求要将被string pool引用的String实例放在PermGen里的,而随便造个普通的String实例不在PermGen里,所以这里会在PermGen内创建一个原String的拷贝 。这样的话,每组内容相同的String实例首个被intern的那个实例不一定是最终放到string pool里的那个实例,自然就使得前面实验的str.intern() != str了。

使用“Java(TM) SE Runtime Environment (build 1.7.0-b147)”测试如下代码的

Java代码 复制代码 收藏代码09222430_uN3F.gif

  1. String s1 = “abc”;     

  2. s2 = “abc”;     

  3. String s3 = s1 + s2;     

  4. String s4 = s1 + s2;     

  5. System.out.println(s3.intern() == s3);//true    

  6. System.out.println(s3.intern() == s4);//false   

        String s1 = "abc";   String s2 = "abc";           String s3 = s1 + s2;           String s4 = s1 + s2;           System.out.println(s3.intern() == s3);//true          System.out.println(s3.intern() == s4);//false

“只是在池里记录了每组内容相同的String实例首个被intern的那个实例的引用” 所以s3.intern()==s4 false

意义是不大,只是看到LZ上有这么段测试代码,就拿出来说说

不同的虚拟机实现不一样,但是所有虚拟机应该保证 s.equals(t) 怎s.intern()==t.intern()即可。这是大前提,其他的都是不确定的

请别再拿“String s = new String(“xyz”);创建了多少个String实例”来面试了吧

http://rednaxelafx.iteye.com/blog/774673

String到底创建多少个对象及相不相等等问题的汇总

http://www.iteye.com/topic/1122533

java中的对象池技术,是为了方便快捷地创建某些对象而出现 的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间。对象池其实也就是一个内存空 间,不同于使用new关键字创建的对象所在的堆空间。本文只从java使用者的角度来探讨java对象池技术,并不涉及对象池的原理及实现方法。个人认 为,如果是真的专注java,就必须对这些细节方面有一定的了解。但知道它的原理和具体的实现方法则不是必须的。

 

1,对象池中对象和堆中的对象

public class Test{

 Integer i1=new Integer(1);
  Integer i2=new Integer(1);
 //i1,i2分别位于堆中不同的内存空间

  System.out.println(i1==i2);//输出false


  Integer i3=1;
  Integer i4=1;
 //i3,i4指向对象池中同一个内存空间

  System.out.println(i3==i4);//输出true

 

 //很显然,i1,i3位于不同的内存空间

 System.out.println(i1==i3);//输出false

}

 

2,8种基本类型的包装类和对象池

java中基本类型的包装类的大部分都实现了对象池技术,这些类 是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外 Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责 创建和管理大于127的这些类的对象。以下是一些对应的测试代码:

public class Test{

public static void main(String[] args){

  //5种整形的包装类Byte,Short,Integer,Long,Character的对象,

  //在值小于127时可以使用对象池

  Integer i1=127;

  Integer i2=127;

  System.out.println(i1==i2)//输出true

  //值大于127时,不会从对象池中取对象

  Integer i3=128;

  Integer i4=128;

  System.out.println(i3==i4)//输出false

  //Boolean类也实现了对象池技术

  Boolean bool1=true;

  Boolean bool2=true;

  System.out.println(bool1==bool2);//输出true

  //浮点类型的包装类没有实现对象池技术

  Double d1=1.0;

  Double d2=1.0;

  System.out.println(d1==d2)//输出false

  

}

}

 

3,String也实现了对象池技术

String类也是java中用得多的类,同样为了创建String对象的方便,也实现了对象池的技术,测试代码如下:

public class Test{

public static void main(String[] args){

 //s1,s2分别位于堆中不同空间

 String s1=new String(“hello”);

 String s2=new String(“hello”);

 System.out.println(s1==s2)//输出false

 //s3,s4位于池中同一空间

 String s3=”hello”;

 String s4=”hello”;

 System.out.println(s3==s4);//输出true

}

}

最后:

细节决定成败,写代码更是如此。

转载于:https://my.oschina.net/kepler/blog/206582

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/109894.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号