Java修炼之路——基础篇——String

String

1:字符串的不可变性
什么是不可变对象?不可变对象是指创建后无法变更的对象
String为什么是不可变的?String类为final,并且内部字符数组也为final。所以String对象是不可变对象。
String类为什么要设计为不可变?
主要出于对效率和安全的考量。
当你复制一个对象的时候,如果你知道它是不可变的,那么你只需要复制此对象的引用即可,一般引用会比对象小很多,所以能提高效率;String是不可变的,所以字符串常量池才可以存在,减少很多heap内存的占用;因为String的不可变性,所以在创建的时候hashcode就可以缓存,很适合作为map的key值;
安全方面:不可变对象是线程安全的。在多线程情况下,可变对象的内部状态可能会被其他线程改变,导致不可预期的结果。比如数据库连接,socket连接的IP PORT,类加载器等,都是通过String传参的,如果String是可变的,那会引起很大的安全问题。

2:JDK 6和JDK 7中substring的原理及区别
subString(int beginIndex, int endIndex)方法用来截取字符串

String x = "qwertt";

x = x.substring(1,2);

System.out.println(x);

结果输出:

w

JDK6中的subString
String类有三个属性:
char[] value:字符数组
int offset:起始位置
int count:字符串长度
对于subString方法,生成的String对象,value相同,只是改变了offset和count。这样会导致一个严重的问题:本来只需要很短的字符串,但是因为指向了一个很长的字符串,导致这个长字符串无法回收,存在内存泄漏的风险。
Java修炼之路——基础篇——String_第1张图片

//JDK 6
String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

public String substring(int beginIndex, int endIndex) {
    //check boundary
    return  new String(offset + beginIndex, endIndex - beginIndex, value);
}

jdk6中,为解决上述问题,一般生成一个新的字符串并引用它:

x = x.substring(x, y) + ""

JDK7中的subString
在jdk7中,对上述问题进行了优化。每次执行subString的时候,都会去生成一个新的char[] ,从而避免了上述问题
Java修炼之路——基础篇——String_第2张图片
jdk7源码如下:

//JDK 7
public String(char value[], int offset, int count) {
    //check boundary
    this.value = Arrays.copyOfRange(value, offset, offset + count);
}

public String substring(int beginIndex, int endIndex) {
    //check boundary
    int subLen = endIndex - beginIndex;
    return new String(value, beginIndex, subLen);
}

3:replaceFirst、replaceAll、replace区别

先看一个示例:
String s = "my.test.txt";
System.out.println(s.replace(".", "#"));
System.out.println(s.replaceAll(".", "#"));
System.out.println(s.replaceFirst(".", "#"));
System.out.println(s.replaceFirst("\\.", "#"));

执行结果:

my#test#txt
###########
#y.test.txt
my#test.txt

原因:
replace方法有两个实现,一个是传入字符,循环匹配;一个是传入字符串,使用Pattern的逐个按字符进行匹配;
replaceFirst和replaceAll是使用Pattern,进行正则表达式的匹配。因为“.”在正则表达式中,表示任一字符,所以出现了“###########”的结果。

附源代码实现(jdk1.8版)

replace():

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

replace():

public String replace(CharSequence target, CharSequence replacement) {
        return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
    }

replaceAll():

public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
    }

replaceFirst():

public String replaceFirst(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
    }

4:String对“+”的重载、字符串拼接的几种方式和区别
字符串的拼接方式:“+”、StringBuffer、new String().concat、StringBuilder
“+”:底层是使用StringBuilder实现。如:

String s1 = "11";
String s2 = "22";
String s = s1+s2;
System.out.println(s);

其实此段代码基本等价于:

String s1= "11";
String s2= "22";
StringBuilder sb = new StringBuilder();
sb.append(s1);
sb.append(s2);
String str = sb.toString();
System.out.println(str);

在大量使用“+”进行字符串拼接的时候,会产生大量的StringBuilder和String对象,会严重影响效率

concat:
concat其实是申请一个新的数组,进行数组的拷贝,然后用来创建新的String对象。底层是调用:System.arraycopy()

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }

StringBuffer & StringBuilder
两者调用的父类方法如下,区别在于StringBuffer 方法用了synchronized,是线程安全的
与concat的区别在于:
扩容逻辑不同,concat为需要多少扩多少,StringBuilder等是指数级扩容;
concat每次会生成新的String对象,而StringBuilder不会

public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }

5:String.valueOf和Integer.toString的区别

直接看源代码就好

public static String valueOf(int i) {
        return Integer.toString(i);
}

//对null进行了处理
public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    } 

public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }

6:switch对String的支持
Java JDK7中switch添加了对String的支持,之前仅支持(short、int、byte、char),并且底层最终都会转为int类型。那么对于String,是如何支持的呢?
请看以下代码:

public static void test(String status) {
		switch (status) {
		case "INIT":
			System.out.println("INIT");	
			break;
		case "PAY_ING":
			System.out.println("PAY_ING");	
			break;
		case "PAY_SUCCESS":
			System.out.println("PAY_SUCCESS");	
			break;
		case "PAY_FAIL":
			System.out.println("PAY_FAIL");	
			break;
		default:
			System.out.println("default");	
			break;
		}
	}

反编译class文件得到:

public void test(String status)
  {
    String str;
    switch ((str = status).hashCode())
    {
    case -2113017739:
      if (str.equals("PAY_FAIL")) break label129; break;
    case -68158581:
      if (str.equals("PAY_ING")) break label107; break;
    case 2252048:
      if (str.equals("INIT")) break label96; break;
    case 1643683628:
      if (!(str.equals("PAY_SUCCESS"))) { break label140:

        System.out.println("INIT");
        return;

        System.out.println("PAY_ING");
        return;
      }
      System.out.println("PAY_SUCCESS");
      return;

      label129: System.out.println("PAY_FAIL");
      label140: break;
    default:
      label96: label107: System.out.println("default");
    }
  }

虽然看不懂有些带标签的break语句(break label;)但是很明显可以看出来支持String的方式:
将String转为了int类型的hashCode,因为hashCode可能会冲突,又加入了equals判断。

7:字符串池、常量池(运行时常量池、Class常量池)、intern
偷个懒,先放个链接,后续再慢慢完善
https://www.cnblogs.com/tiancai/p/9321338.html

你可能感兴趣的:(学习笔记,Java,String,不可变)