包装类与String

包装类

包装类基本类说明
Bytebyte
Shortshort
Integerintint i = 125; Integer intObj = Integer.valueOf(i); int i2 = intObj.intValue();
Longlong
Floatfloat
Doubledouble
Booleanboolean
Characterchar

包装类都实现了Serializable和Comparable接口;前六种数值型包装类还继承了Number类。

public abstract class Number implements java.io.Serializable
public final class Integer extends Number implements Comparable<Integer>
public final class Character implements java.io.Serializable, Comparable<Character>
public final class Boolean implements java.io.Serializable, Comparable<Boolean>

以Integer为例剖析包装类

  • 不可变性
    实例对象一旦创建即不可修改,通过以下方式实现:
    a. 包装类定义为final的,不能被继承;
    b. 包装类中包含一个与之匹配的基本类型,但是该类型是私有的且声明为final;
    c. 没有声明setter方法;
private final int value;

不可变使得程序更为简单,避免了线程安全问题。

  • 常量、缓存池、valueOf方法
    包装类中定义了一些比较特别的静态变量,如数值型包装类Integer定义了最值;
    @Native public static final int   MIN_VALUE = 0x80000000;
    @Native public static final int   MAX_VALUE = 0x7fffffff;

除Float和Double外,其他包装类缓存包装类对象,如Integer中有静态内部类IntegerCache,默认缓存-128~127范围内的Integer实例。

    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() {}
    }

再来看valueOf方法,当基础类数值位于缓存区时,返回的是缓存的包装类对象,否则new一个新的包装类对象缓存。因此,尽量使用valueOf方法而非new包装类实例,来获取包装类对象。
这种共享缓存对象的方式,可以节省内存空间,由于Integer具有不可变性,所以可以安全的共享。包装类这种共享对象的设计思路即为享元模式

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
  • 继承Number
    在这里插入图片描述
    六种数值型包装类继承了Number抽象类,其定义了以上6种获取值的方法,可以将任意类型的基础类型转为目标基础类型。具体包装类重写这些方法时,就是将其基础类数值做了强制类型转换,如:
   public short shortValue() {
        return (short)value;
    }
  • 重写Object的方法
    每种包装类都重写了根父类Object的equals、hashCode、toString方法,以Integer为例:
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
    public int hashCode() {
        return Integer.hashCode(value);
    }
    public static int hashCode(int value) {
        return value;
    }

Float和Double将其value值分别转换为int和long进行比较,以及计算hashCode。

  • 包装类和String
    除了重写Object的toString()方法,除Character外的其他包装类都有
    静态的valueOf(String)方法,根据字符串返回包装类实例;
    静态的parseInteger(String)方法,根据字符串返回基础类型值;
    静态的toString(int)方法,根据基础类型值返回字符串;
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
  • 实现Comparable
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

Integer中的二进制运算

位翻转

有两个位运算方法:按byte反转reverseBytes和按bit反转reverse;

    public static int reverseBytes(int i) {
        return ((i >>> 24)           ) |
               ((i >>   8) &   0xFF00) |
               ((i <<   8) & 0xFF0000) |
               ((i << 24));
    }

以i=0x12345678为例,按byte反转结果为0x78563412;
① i >>> 24结果为0x00000012;
② i >> 8结果为0x00123456,再和0xFF00按位与,结果为0x00003400;
③ i << 8结果为0x34567800,在和0xFF0000按位与,结果为0x00560000;
④ i << 24结果为0x78000000;
⑤ 以上结果按位或运算,即为结果0x78563412.

    public static int reverse(int i) {
        // HD, Figure 7-1
        i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
        i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
        i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
        i = (i << 24) | ((i & 0xff00) << 8) |
            ((i >>> 8) & 0xff00) | (i >>> 24);
        return i;
    }

实现思路:32bit的Integer实例——先交换相邻位;再交换相邻两位;再交换相邻四位;再交换相邻8位;再交换相邻16位
也适用于十进制,如8个十进制数12345678进行按位反转,只需要反转到4位相邻互换;那么包装类Long的按位反转,需要执行到交换相邻32位。
① 相邻位交换:21436587;
② 相邻两位交换:43218765;
③ 相邻四位交换:87654321;
代码剖析
① 0x55555555 = 0101 0101 0101 0101 0101 0101 0101 0101; 跟i按位与之后相当于提取到奇数位bit值;然后左移一位; i右移一位后与0x55555555按位与,相当于提取到偶数位bit值;再按位或即实现了相邻位交换;
② 0x33333333= 0011 0011 0011 0011 0011 0011 0011 0011; 思路与步骤①类似;
③ 0x0f0f0f0f= 0000 1111 0000 1111 0000 1111 0000 1111;同上;
④ 8bit交换时,变成了按字节反转。

CPU可以高效的实现位运算,要提升代码性能,利用该特性设计算法是必要的。

循环移位

循环左移和循环右移如下,需要注意真实的移位数等于(移位数 & (数值bit位数 - 1)),Integer.rotateLeft代码中"-distance"按位与 “0001 1111”,假如distance等于8,即1111 1111 1111 1111 1111 1111 1111 1000 & 0001 1111 = 11000 ,即十进制24;

    public static int rotateLeft(int i, int distance) {
        return (i << distance) | (i >>> -distance);
    }
    public static int rotateRight(int i, int distance) {
        return (i >>> distance) | (i << -distance);
    }

String

与包装类很类似,都具有不可变性、常量字符串、重写hashCode等。

不可变性

以一个final的内部数组表示字符串,一旦被赋值则不可修改。
String中的大部分方法是在操作这个字符数组,而且基本是通过新new String对象的方式实现的,如concat最后返回了一个新String对象。
对于频繁修改字符串的场景,每次修改都需要新建字符串,性能太低,要考虑使用StringBuilder和StringBuffer(线程安全)。

//String:
    private final char value[];
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length); // 返回一个新的数组
    }
    //字符串拼接
    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); //获取一个字符数组buf,长度为len+otherLen,但是前len个字符与value相同
        str.getChars(buf, len); //从字符的len下标开始,将str的chars数组复制到buf中
        return new String(buf, true);//此处share置为true,将buf直接赋值给value,不再new新的字符数组。
    }
    String(char[] value, boolean share) {
        // assert share : "unshared not supported";
        this.value = value;
    }
//Arrays:
    public static char[] copyOf(char[] original, int newLength) {
        char[] copy = new char[newLength];
        //将original内的元素从下标0开始,复制进copy数组从0开始的位置,复制的长度位指定的长度
        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

字符串常量池

字符串常量在Java中相当于String实例,可以直接赋值给String变量,还可以直接调用String的各种方法。
在内存中,它们被放在一个共享的常量池内。当通过常量的形式使用字符串时,会使用该常量池内对应的String实例。
String内的intern()方法可以确保优先使用字符串常量池内的实例,如果没有则先将该实例添加到常量池。

    public static void main(String[] args) {
        String a = "lala";
        String b = "lala";
        String c = new String("lala");//不建议如此赋值
        String d = new String("lala").intern();//多此一举
        System.out.println(a == b);
        System.out.println(c == b);
        System.out.println(d == b);
    }
true
false
true

hashCode

由于String具有不可变性,其hash值也就不可变。因此String中缓存了hash值。
Java中普遍使用类似的方式计算hash值。这样可以使hash与字符串中每个字符相关,并与字符所在的位置相关。

    private int hash; // Default to 0
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

StringBuilder和StringBuffer

二者的结构和实现基本一致,只是StringBuffer中的append、insert、delete等方法采用synchronized进行了修饰,实现了线程安全。
在这里插入图片描述在这里插入图片描述
来看AbstractStringBuilder类:

    char[] value;//以一个非final的字符数组存储数据
    int count; //记录字符数组value中真实存储的数据容量
    AbstractStringBuilder() {}
    // 指定value初始长度
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }

    public int length() {return count;}

    public int capacity() {return value.length;}

    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len); //确认并保障value的空间足以添加str
        str.getChars(0, len, value, count); //将str的[0,len)下标的字符复制到value以count下标开始的位置
        count += len;//value中有效字符数变更为count+len
        return this;
    }
    
    public AbstractStringBuilder insert(int offset, String str) {
        if ((offset < 0) || (offset > length()))
            throw new StringIndexOutOfBoundsException(offset);
        if (str == null)
            str = "null";
        int len = str.length();
        ensureCapacityInternal(count + len);//确保数组容量足够
        //将value从offset下标开始的(count-offset,即offset后所有)字符,复制到value的offset+len下标开始的位置,即向后移位了len个字符
        System.arraycopy(value, offset, value, offset + len, count - offset);
        //将str的字符复制到value的offset下标开始的位置,共len个字符
        str.getChars(value, offset);
        count += len;
        return this;
    }

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {//如果value空间不够大,则将value复制到新长度的数组,并赋值给value
            value = Arrays.copyOf(value, newCapacity(minimumCapacity)); 
        }
    }
    private int newCapacity(int minCapacity) {//扩容长度的确定,入参为 原数组长度+要添加的字符串长度
        // overflow-conscious code
        int newCapacity = (value.length << 1) + 2;//当前字符数组长度*2+2;加2是为了长度为0时仍可用
        if (newCapacity - minCapacity < 0) {
            newCapacity = minCapacity; //取入参和成倍增长长度较大者,
        }
        return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0) 
            ? hugeCapacity(minCapacity) : newCapacity; //基本返回newCapacity
    }
    private int hugeCapacity(int minCapacity) {
        if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
            throw new OutOfMemoryError();
        }
        return (minCapacity > MAX_ARRAY_SIZE)
            ? minCapacity : MAX_ARRAY_SIZE;
    }

上述分析的数组容量扩展方式是指数扩展方式,应用于不知道最终需要多长的情况下,应用比较广泛。折中了两个方面的考虑:① 避免浪费空间;② 减少扩容频率,即内存分配次数。

StringBuilder类默认生成16个字符的字符数组。

    public StringBuilder() {
        super(16);
    }

关于String的+和+=运算

Java编译器会转换为new StringBuilder,并在此基础上进行append操作。
但是当+或+=位于循环体内,每一次循环都会new一个新的StringBuilder对象,对造成对象过多,增加内存消耗。
因此需要在循环体外声明StringBuilder,并在循环内进行append操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值