String的总结

String类分析

一:简述:

参考官方API来进行学习String类,String中文意思是字符串。

在java中,String是一个类,而并非是基本类型的数据类型

String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc",“helloworld” )都作为此类的实例,可以理解成是对象。

二:字符和字符串

众所周知,计算机的起源是在美国的。美国的所有的单个字符加起来还不到128个。可以在ASCII码表中查询到,但是世界上每个国家的文字都并非是一样的,所有有了编码格式来对每个国家的文字来进行存储。根据不同的编码类型,可以存储每个国家的文字。

字符串就是将字符串起来的一种表现形式,因为在现实生活中,单个字符不足以用来描述所有的信息,但是字符串是可以的。

官方API中是这样子来进行解释的:

String string = "abc";
等价于
char[] chars = {'a','b','c'};
String s = new String(chars);

三:字符串对象

首先写几个例子

String s1 = "";  // 没有空格
String s2 = " "; // 有空格
String s3 = "X"; 

在上面的几个案例中,

""," ","X",这几个都是String类的实例。

四:字符串的操作分类

1、转换方法

toLowerCase():将字符串转换为小写形式这里的大小写指的是英文大小写,而不是中文大小写
toUpperCase():将字符串转换为大写形式
toCharArray():将字符串转换为字符数组(需要一个新的char[]数组来保存)
valueOf(xxx):将xxx类型的数据转换为String类型(几乎所有的类型都可以转)

演示:

    public static void main(String[] args) {
        // 定义用户名字
        String username = "LiGuang";

        // 将username转换成大写
        String s = username.toLowerCase();
        System.out.println(s);
        // 将username转换成小写
        String s1 = username.toUpperCase();
        System.out.println(s1);

        // 将字符数组转换成字符串
        char[] chars = username.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            System.out.println(chars[i]);
        }

        // 将其他类型的数据转换成String类型。valueOf是一个静态方法
        int num = 97;
        String s2 = String.valueOf(num);
        if (s2 instanceof String){
            System.out.println(s2);
        }
    }

注意

valueOf是一个静态方法,直接用String.valueOf(引用类型的变量)来使用即可;

2、处理方法

trim():去除前字符头部空白和尾部空白,但是无法去除掉字符串中间空白的
split():对字符串进行分割
concat():与其他字符串进行连接(一般不常用)
replace():替换字符串

3、测试方法

startsWith():测试是否以指定的字符串开头
endsWith():测试是否以指定的字符串结尾

4、取值方法

length():返回字符串的长度(字符个数)
subString():返回字符串的子字符串(可指定)
charAt():返回字符串中指定索引处的字符
indexOf():返回指定字符在字符串中第一次出现处的索引值
lastIndexOf():返回指定字符在字符串中最后一次出现处的索引值
getBytes():返回字符串的编码序列(需要一个新的byte[]数组来保存)

5、比较方法

compareTo():以字典顺序比较字符串是否相同,区分大小写
compareToIgnoreCase():以字典顺序比较字符串是否相同,忽略大小写
equals():与指定的对象比较,若相同则返回true,区分大小写
equalsIgnoreCase():与另一个字符串比较,若相同则返回true,忽略大小写(注意不是与对象比较)

五:源码分析

3.1、String类的数据结构

String类的数据结构,最为重要的一点:

private final char value[];
// c++的写法,改变成java写法
private final char[] value;

private:表示只能在本类中进行访问这个数组,在外部类中是无法来进行操作的;

final修饰的的数组(本质上是一个地址,也是一个对象),地址值一旦赋值了,就无法再进行改变了。所以每个字符串一旦赋值之后,那么char就指向了一块内存空间,那么就再也无法再指向其他的内存空间了。

char[]:说明了java中的字符串对象是用字符数组来进行存储的。

既然是数组,那么说明了一点。方法肯定有大量的对数组的操作方法

3.2、hash属性

既然存在了属性,那么必然存在着对hash属性进行操作的方法。

private int hash; // Default to 0

读到了这里,说明字符串对象要重写Object类中的hashCode方法了,要有这个敏锐的意识。这里直接去看String类中的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];
        }
        // 将最后的h值赋值给hash值
        hash = h;
    }
    return h;
}

这里说明了什么?重写了hashCode方法,是根据每个字符来进行计算最终的hash值的。

那么这种情况,可能存在着不同的字符串的hash值是一样的。

String a="Aa";
String b="BB";
int aa=a.hashCode();
System.out.println("aa的hash值"+aa);
int bb=b.hashCode();
System.out.println("bb的hash值"+bb);
System.out.println(aa==bb);
System.out.println(a.hashCode()==b.hashCode());
System.out.println(a.hashCode()==b.hashCode());

控制台结果:

aa的hash值2112
bb的hash值2112
true
true

说明了什么问题???根据字符的值来进行计算的hashCode值是一样的。但是因为这里的hashCode值是相同的,但是这里的hashCode算法是重写过的,所以说并不能作为是同一个对象的条件。

在java中,认为Object类中的hashCode相同的是同一个对象。那么String类重写了这个方法,会认为hash值是一样的是同一个对象吗?接着往下看。

看下String类型的equals方法

    public boolean equals(Object anObject) {
        // 对于引用类型来说,比较的是在内存中的地址值是否是一样的
        if (this == anObject) {
            return true;
        }
        // 判断anOjbect是否是String的,如果是的,判断语句中
        if (anObject instanceof String) {
            // 强制类型转换
            String anotherString = (String)anObject;
            // 判断length
            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;
    }

总结:

两个字符串要是相等,底层采用的是在内存中的地址相等或者是每个字符都是一样的;

四、构造函数

第一种:

public String() {
    this.value = "".value;
}

如果直接使用:

String string = new String();

那么new出来的是一个空字符串,指向的也是内存中的一个空字符串。

小结:

final修饰的数组指向的地址是无法改变的,也就表示了对象是无法改变的了
但是对于对象的引用来说,它不是固定的。它是可以进行改变的。

第二种:

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

看下源码:

public static char[] copyOf(char[] original, int newLength) {
    // new了一个新的数组出来
    char[] copy = new char[newLength];
    // 将原来数组中的元素赋值一份到新建的数组中去
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

所以说,这个字符串底层的数组和原来的数组不是同一个。这也是必须的,不然外界改了数组中的值,那么字符串中的内容也就改了。hash是使用数组中字符依次计算得到的值。

第二个:拿到一部分字符,应该不常用

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

4.1、面试题几道题型

Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的

例子一:

String s1 = "helloworld";
String s2 = "helloworld";

例子二:

String s1 = "hello";
String s2 = "world";
String s3 =  s1+s2;
System.out.print("helloworld"==s1+s2)  // false

相当于:

s1+s2========String s3 =new String("helloworld")
System.out.print(s3=="helloworld");    

将堆内存中的地址和字符串常量池的内存进行相比较

例子三:

String s1 = "hello";
final String s2 = "world";
String s3 =  s1+s2;
System.out.print(3=="helloworld");  

相当于:

s1+s2========String s3 =new String("helloworld")
System.out.print(s3=="helloworld");    

将堆内存中的地址和字符串常量池的内存进行相比较

例子四:

final String s1 = "hello";
final String s2 = "world";
System.out.print("helloworld"==s1+s2); //true

对于两个字符串字符串常量来说,JVM对其进行了优化。

例子五:

final String s1 = null
final String s2 = "world";
sout("helloworld"==s1+s2);  // true

例子六:

String s1 = null;
String s2 = "hello";
System.out.println(s1+s2);

总结:

1、final修饰的字符串变量相加会优化
2、两个字符串对象相加会优化
3、两个非final修饰的相加,在堆内存中产生了新的对象
4、直接写字符串对象,字符串对象存在于常量池;new的存在于堆内存中。

五、常用方法

一、获取得到字符串长度

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

观察发现,底层调用的是length的值。需要注意的是这个数组,length就是数组中元素的个数,这是静态数组和动态数组的区别。

二、判断字符串是否是空的

public boolean isEmpty() {
    return value.length == 0;
}

三、根据下标来取值

public char charAt(int index) {
    if ((index < 0) || (index >= value.length)) {
        throw new StringIndexOutOfBoundsException(index);
    }
    return value[index];
}

得到指定下标的元素值

四、将字符串底层数组中的字符转换成字节数组

public byte[] getBytes() {
    return StringCoding.encode(value, 0, value.length);
}

第五个:两个字符串比较。必须都是String类型的字符串

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof 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;
}

五、对hash值进行赋值

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计算出来的hash值,是根据字符数组中的字符来进行计算的。

六、转换成字符串对象

public String toString() {
    return this;
}

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

获取得到值的时候

public static Integer valueOf(int i) {    if (i >= IntegerCache.low && i <= IntegerCache.high)        return IntegerCache.cache[i + (-IntegerCache.low)];    return new Integer(i);}

优先会从这里来进行查询;

如果没有找到,才会去内存中创建一个新的地址来存储整数。

posted @ 2021-10-08 23:24  写的代码很烂  阅读(712)  评论(0编辑  收藏  举报