关于String类的一些浅谈

关于String类的一些浅谈

作为频繁使用的String类,一个合格的程序员需要对其本来的面貌有所了解。

大家都知道基本类型中并不包括String类,java中的对象大都是存放在堆中的,但是String对象是一个特例,它被存放在常量池中;
当创建了String类的对象时,首先会在常量池中其查看这个类是否存在,如果存在则不会创建,如果不存在才创建,这一部分程序员是看不见。

String类:

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

点开String会发现String是被final修饰的,也就是说,String不能被继承,同时它实现了Serializable接口可以序列化和反序列化,实现了Comparable支持字符串的比较,实现了CharSequence接口说明它是一个字符序列。

成员变量:

private final char value[];  
private int hash; //字符串的hashcode,默认是0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;

value[]是用来存储字符串的,String字符串实际上就是字符数组组成的,而且这个数组也是被final修饰的,不然会造成数据的不安全。

有参构造:

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

该构造函数经常被用来做面试题,问new String(“abc”);共创建了几个对象?
答案是两个,字符串"abc"创建一个对象放在常量池中,new String()又创建一个对象放在堆中。

public String(char value[]) {
    
    
    this.value = Arrays.copyOf(value, value.length);
}

通过整个char数组参数来构造String对象,实际将参数char数组值复制给String对象的char数组。

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

截取入参数组的一部分来构造String对象,具体哪一部分由offset和count决定,其中做了些参数检查,传入非法参数会报数组越界异常StringIndexOutOfBoundsException

//…

常用方法:
Equals():(面试有可能问到:Equals如何实现?)

public boolean equals(Object anObject) {
    
      // Object是所有类直接或间接的父类
    if (this == anObject) {
    
       //首先是跟自己比,看是否是相同的对象,如果是直接返回true,且这里比的是内存地址
        return true;
    }
    if (anObject instanceof String) {
    
          //判断被比较的对象是不是String类的,只有是同类才有比下去的必要
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
    
      //比较长度是不是一样
            char v1[] = value;         //将比较的字符串转成字符数组
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
    
         //循环遍历两个字符数组中对应的字符是否相等
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

length():

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

获取字符串的长度,实际上就是返回内部数组的长度。因为char数组被final修饰是不可变的,只要构造完成char数组中的内容长度都不会改变,所以这里可以直接返回数组的长度。

substring(int beginIndex):

public String substring(int beginIndex) {
    
    
    if (beginIndex < 0) {
    
    
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;  //截取的位置不能比字符串长
    if (subLen < 0) {
    
    
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);   //如果beginIndex=0则返回原串,否则创建一个新的字符串返回。
}

截取字符串的子串。截取从指定位置beginIndex开始(包含这个位置),到字符串结束之间的字符串内容。

substring(int beginIndex, int endIndex):

public String substring(int beginIndex, int endIndex) {
    
    
    if (beginIndex < 0) {
    
                     //对输入的参数进行必要的效验
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > value.length) {
    
    
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    int subLen = endIndex - beginIndex;
    if (subLen < 0) {
    
    
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return ((beginIndex == 0) && (endIndex == value.length)) ? this
            : new String(value, beginIndex, subLen);//如果beginIndex,endIndex是从0到字符串的长度,就返回本身,否则新建字符串返回
}

截取原字符串的子串,同上个方法类似,上一个方法的结束位置是原串的结尾,这个方法是指定结束位置endIndex(不包含这个位置)。需要注意的是这个方法是包含头不包含尾。

hashCode():

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

这里的hashCode重写了Object的hashCode方法。

java中的hashCode有两个作用。
一:Object的hashCode返回对象的内存地址。
二:对象重写的hashCode配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable等。对于大量的元素比较时直接比较equals效率低下,可先判断hashCode再判断equals,因为不同的对象可能返回相同的 hashCode(如"Aa"和"BB"的hashCode就一样),所以比较时有时需要再比较equals。hashCode只是起辅助作用。

为了使字符串计算出来的hashCode尽可能的少重复,即降低哈希算法的冲突率,设计者选择了31这个乘数。选31有两个好处。1:31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一,其它的像37、41、43也是不错的乘数选择。2:31可以被 JVM 优化,31 * i = (i << 5) - i。计算hashCode的原理也很简单,即用原hashCode乘以31再加上char数组的每位值。

replace(char oldChar, char newChar):
将字符串中所有的旧字符oldChar,替换为新的字符newChar

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) {
    
          //先找到字符串中第一次出现oldChar字符的位置i
            if (val[i] == oldChar) {
    
    
                break;
            }
        }
        if (i < len) {
    
    
            char buf[] = new char[len];
            for (int j = 0; j < i; j++) {
    
       //将之前的字符数组复制给新数组buf
                buf[j] = val[j];
            } 

/**然后从i后将字符数组中的内容复制给buf,如果字符为oldCha则替换为newChar.然后再通过buf创建新的字符串返回**/
            while (i < len) {
    
         
                char c = val[i];
                buf[i] = (c == oldChar) ? newChar : c;
                i++;
            }
            return new String(buf, true);
        }
    }
    return this;
}

Intern():

public native String intern();

这个方法是本地方法,无方法体。
使用String的intern方法可以节省内存。在某些情况下可以使用 intern() 方法,它能够使内存中的不同字符串都只有一个实例对象。
原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池。

看下面的例子:

public static void main(String[] args) {
    
    
    String str2 = new String("hello") + new String("world");
    str2.intern();         // 使用intern方法后str2与str1指向同一个对象,否则它们指向两个不同的对象。这样就能达到节省内存的效果。
    String str1 = "helloworld";
    System.out.println("比较结果:");
    System.out.println(str2 == str1);
}

运行结果:
在这里插入图片描述
剩余方法参看原类。

欢迎留言讨论,感谢你的关注!

猜你喜欢

转载自blog.csdn.net/weixin_42409759/article/details/107136339