jdk源码解读-String/Integer

Jdk源码解读-String/Integer

String

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

从上面的String定义可以看出,String类由final修饰,无法被继承,并且实现Serializable接口,可以序列化和反序列化。它有两个私有变量:

 /** The value is used for character storage. */
    private final char value[];
    /** Cache the hash code for the string */
    private int hash; // Default to 0

从注释就可以看出他们分别是用于存储字符串的char型数组和缓存字符串的hashcod值。从构造方法里也能看出,是由这两个属性决定了String的值,value数组由final修饰,也就使String有了不可变的特性。

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

我们日常使用的‘+’用于连接字符串其实是为java 提供的一种特殊的连接操作符,并且内部是由StringBuilder或StringBuffer的append实现的

/*The Java language provides special support for the string
 * concatenation operator (&nbsp;+&nbsp;), and for conversion of
 * other objects to strings. String concatenation is implemented
 * through the {@code StringBuilder}(or {@code StringBuffer})
 * class and its {@code append} method.
*/

常用方法:

//去除首尾的一个或n个空格
public String trim() {
        int len = value.length;
        int st = 0;
        char[] val = value;    /* avoid getfield opcode */

        while ((st < len) && (val[st] <= ' ')) {//计算前面的空格数量
            st++;
        }
        while ((st < len) && (val[len - 1] <= ' ')) {//长度减去尾部空格的数量
            len--;
        }
        return ((st > 0) || (len < value.length)) ? substring(st, len) : this;//通过截取的方式获取新的
    }
//根据字符串的每个字符的unicode码按照从左到右的顺序比较,如果大于就返回正数,如果小于就返回负数
public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;
            }
            k++;
        }
        return len1 - len2;
    }

jdk1.6以前String里面的substring截取字符串出现过内存泄漏的问题:

private static char[] value;
private static int offset;
private static int count;
public String(int offset, int count, char value[]) {
	this.value = value;
	this.offset = offset;
	this.count = count;
 }
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)//value是String的静态全局变量
}
public static void main(String[] args){
	String str = "abcdefghijklmnopqrst";//当str为1G的字符串时
	String sub = str.substring(1, 3);
	str = null;
}

执行main方法,当str够大或执行substring次数过多时,就会发生out of memory 异常,原因就是当substring时调用的new String()构造方法里面的参数value用的是String的静态全局变量,str和sub共同指向"abcdefghijklmnopqrst"所在的常量池空间,当str置空时,并不会回收这部分的内存,因为sub仍然还指向那段空间,这部分内存会在jvm运行周期内一直存在,当str够大时或substring执行次数过多时,则会导致内存溢出。jdk1.7之后sun就修改了substring的实现方式,即:

public String(char value[], int offset, int count) {
        if (offset < 0) {
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
            if (count < 0) {
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }
//Arrays.copyOfRange
public static char[] copyOfRange(char[] original, int from, int to) {
        int newLength = to - from;
        if (newLength < 0)
            throw new IllegalArgumentException(from + " > " + to);
        char[] copy = new char[newLength];//新建了一个字符数组用于保存薪的字符串
        System.arraycopy(original, from, copy, 0,
                         Math.min(original.length - from, newLength));
        return copy;
    }

replace方法替换字符,通过while循环找到第一个需要替换的字符的下标i,然后复制从0到i的字符数组a,再循环i和i之后的字符数组,将oldchar替换为newchar,并将值都插入到a中。

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

String针对常量池的优化;
  针对常量池的优化指:当2个String对象拥有相同的值时,他们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。

  String str1="abc";//jvm开辟了2个空间(1个栈空间和1个常量池)
  String str2="abc";//jvm开辟了1个空间(1个栈空间),共用了str1的abc常量池
  String str3=new String("abc");//jvm开辟了2个空间(1个栈空间和一个堆空间),共用了str1的abc常量池
  System.out.println(str1==str3.intern()); //返回true,intern方法返回string在常量池中的引用

总结:
1.不可变性:String内部通过final、private修饰的字符数组实现,且String是一个用final修饰的类,不能被继承,所以使它在赋值之后不能改变它的值。(重新赋值之后只是改变了它指向的新的内存空间)
2.对常量池的特殊优化:当重复使用同一个字符串时,String会返回常量池中同一个字符串的引用,大幅节省内存消耗,通过intern()方法可以查看指向的常量池地址。
3.jdk1.6及之前版本存在内存泄漏问题,原因是subString()实现的方法中调用的构造方法new String(offset, count, value)中的参数value为String中的全局变量,多次调用将不会被gc回收,最后出现内存泄漏的情况。

Integer

以下将部分代码段加注释便于理解

public final class Integer extends Number implements Comparable<Integer> {
@Native public static final int   MIN_VALUE = 0x80000000;//Integer存储最小值-2^31
@Native public static final int   MAX_VALUE = 0x7fffffff;//Integer存储最大值2^31-1
public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");//表示是int的包装类型
private final int value;//表示初始化之后不可修改,即常量

//Integer对象创建的时候会初始化-128到127区间内的整数到内存缓存起来
private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];//存储的数组

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
}

如上代码,其中IntegerCache是Integer定义的一个静态内部类,当Integer被加载的时候将会初始化一段缓存用于存储-128-127,总共256个数字。这也是出现下面这种情况的原因

		Integer integer1 = 127;
        Integer integer2 = 127;
        Integer integer3 = 128;
        Integer integer4 = 128;
        System.out.println(integer1 == integer2);//true
        System.out.println(integer3 == integer4);//false
        //当直接值在-128-127区间内时,则会直接返回常量池中该值引用,当不在区间时,则会重新new一个Integer存放该值,也就出现了128 != 128的情况

十进制转换为2~32进制:

public static String toString(int i, int radix) {
...
while (i <= -radix) {
            buf[charPos--] = digits[-(i % radix)];//和进制取余,放入数组尾部
            i = i / radix;//得到除数继续下次循环
        }
...
}

再来看看toString(int i )

public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);//得到i的位数,当为负数时+1位
        char[] buf = new char[size];//创建存储字符的空间
        getChars(i, size, buf);//存入字符数组
        return new String(buf, true);
    }
    static int stringSize(int x) {
        for (int i=0; ; i++)
            if (x <= sizeTable[i])
                return i+1;
    }
    final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                      99999999, 999999999, Integer.MAX_VALUE };
  static void getChars(int i, int index, char[] buf) {
        int q, r;
        int charPos = index;
        char sign = 0;

        if (i < 0) {
            sign = '-';
            i = -i;
        }

        // Generate two digits per iteration
        while (i >= 65536) {
            q = i / 100;
        // really: r = i - (q * 100);
            r = i - ((q << 6) + (q << 5) + (q << 2));//得到大于等于65536这部分的数据的字符,应该是两位数
            i = q;//继续往前取剩下的数字
            buf [--charPos] = DigitOnes[r];//得到的两位数r%10得到个位数
            buf [--charPos] = DigitTens[r];//得到的两位数r/10得到十位数
        }

        // Fall thru to fast mode for smaller numbers
        // assert(i <= 65536, i);
        for (;;) {
            q = (i * 52429) >>> (16+3);//得到小于65536这部分的个位数
            // r = i-(q*10) ...
            r = i - ((q << 3) + (q << 1));  //得到除了q之外的其他数字
            buf [--charPos] = digits [r];
            i = q;
            if (i == 0) break;
        }
        if (sign != 0) {
            buf [--charPos] = sign;//符号位
        }
    }

之前我没看懂上面的代码,左移右移什么鬼(不就是int转string至于这么麻烦么,直接1111+""不就行了么…),然后看了一大神的博文,顿时豁然开朗,原来代码还能这么写:

r = i - ((q << 6) + (q << 5) + (q << 2))
可以转换为
r = i - (q * 2^6 + q * 2^5+q * 2^2) =>r = i - (q * 100)

q = (i * 52429) >>> (16+3)
可以转换为
r = i * 52429/2^19 =>r = i - (q * 100) =>q = (i * 52429) / 524288 => q= i * 0.1 = > q=i/10
用左右移的意义是可以更快的得到想得到的位数
即:

移位的效率比直接乘除的效率要高
乘法的效率比除法的效率要高

由此可见sun的工程师是真的厉害,致敬一波

总结:

  1. Integer也是常量,无法改变(重新赋值之后只是改变了它指向的新的内存空间)
  2. 有缓冲池,缓存-128~127之间的值,当取的数不在此区间时,相同的值指向的地址会不同
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值