String类之详解(一)

String类之详解(一)

1.String类的概述

public final class String

String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。

字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。例如:

     String str = "abc";

等效于:

     char data[] = {'a', 'b', 'c'};
     String str = new String(data);
String str = "abc";			//"abc"可以看成一个字符串对象
str = "def";				//当把"def"赋值给str,原来的"abc"就变成了垃圾
System.out.println(str);	//String类重写了toString方法返回的是该对象本身

 String对象虽然无法改变,但是可以被重新赋值,则之前的数值就会变成了垃圾。

2.常见构造方法

String()
          初始化一个新创建的 String 对象,使其表示一个空字符序列。
String(byte[] bytes)
          通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String
String(byte[] bytes, int offset, int length)
          通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String
String(char[] value)
          分配一个新的 String,使其表示字符数组参数中当前包含的字符序列。
String(char[] value, int offset, int count)
          分配一个新的 String,它包含取自字符数组参数一个子数组的字符。
String(String original)
          初始化一个新创建的 String 对象,使其表示一个与参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的副本。

 3.String类的常见面试题

(1)

                String s1 = "abc";
		String s2 = "abc";
		System.out.println(s1 == s2); 			//true	
		System.out.println(s1.equals(s2)); 		//true

s1==s2我们知道比较的是两个对象的地址值,这里涉及到了JAVA的常量优化机制,在内存的方法区中有一块常量池,由于字符串均为常量,所以给String对象赋值时,首先在常量池中寻找是否已经存在该常量,如果存在就直接让对象指向该地址;如果不存在,则创建该常量,然后让字符串指向该地址。具体如图所示:

(2)

//下面这句话在内存中创建了几个对象?
String s1 = new String("abc");	

这句话创建了两个对象,其一“abc”在常量池中创建,其二new String()在堆中创建,然后将“abc”的值赋给栈中该对象的属性,最后将栈中对象的地址值返回给s1。具体如图所示:

(3)

                String s1 = new String("abc");			//记录的是堆内存对象的地址值		
		String s2 = "abc";				//记录的是常量池中的地址值
		System.out.println(s1 == s2); 			//false
		System.out.println(s1.equals(s2)); 		//true

s1指向的是堆内存中对象的地址值,s2指向的是常量池中的地址值。具体请参考上图所示。

(4) 

                String s1 = "a" + "b" + "c";
		String s2 = "abc";
		System.out.println(s1 == s2); 			//true,java中有常量优化机制	
		System.out.println(s1.equals(s2)); 		//true

这里由于JAVA具体常量优化机制,所以"a"+"b"+"c"和"abc"在常量池中相同。

这里需要注意的是“+”,在JAVA规范中是这样阐述的:Java 语言提供对字符串串联符号("+")以及将其他对象转换为字符串的特殊支持。字符串串联是通过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。

所以在使用"+"连接字符串时,底层会创建另外一个对象(之后讲述),在之后的讲述中,该处会涉及到效率的问题。

(5)

                String s1 = "ab";
		String s2 = "abc";
		String s3 = s1 + "c";
		System.out.println(s3 == s2);           //false
		System.out.println(s3.equals(s2)); 		//true

其中s3使用了“+”,在(4)中我们提到过,此时会生成一个StringBuilder(或 StringBuffer)对象,之后通过该对象的toString 方法返回一个字符串对象,这些对象都是在堆中产生,并没有进入常量池中。而s1,s2指向的是常量池中的地址,所以两者不相等。

4.String类中判断功能的函数

boolean equals(Object anObject)
          将此字符串与指定的对象比较。
 boolean equalsIgnoreCase(String anotherString)
          将此 String 与另一个 String 比较,不考虑大小写。
 boolean contains(CharSequence s)
          当且仅当此字符串包含指定的 char 值序列时,返回 true。
boolean startsWith(String prefix)
          测试此字符串是否以指定的前缀开始。
 boolean endsWith(String suffix)
          测试此字符串是否以指定的后缀结束。
boolean isEmpty()
          当且仅当 length() 为 0 时返回 true。

 其中equals()方法的源码我们之前已经解读过(https://blog.csdn.net/qq_40298054/article/details/83622837

(1)equalsIgnoreCase(String anotherString)

public boolean equalsIgnoreCase(String anotherString) {
        return (this == anotherString) ? true     //三目运算符,如果地址相同返回true
                : (anotherString != null)         //判断是否为空
                && (anotherString.value.length == value.length)    //判断长度是否相同
                && regionMatches(true, 0, anotherString, 0, value.length);
    }
public boolean regionMatches(boolean ignoreCase, int toffset,
            String other, int ooffset, int len) {
        char ta[] = value;                //该字符串的字符数组
        int to = toffset;                 //该字符串的起始偏移量
        char pa[] = other.value;          //参数字符串的字符数组
        int po = ooffset;                 //参数字符串的起始偏移量
        // Note: toffset, ooffset, or len might be near -1>>>1.(无符号右移,2^32-1)
        // len为需比较的字符串长度
        if ((ooffset < 0) || (toffset < 0)            //判断偏移量是否小于0
                || (toffset > (long)value.length - len)    //判断起始偏移量到该字符串末尾的长度是否足够len    
                || (ooffset > (long)other.value.length - len)) {        
            return false;
        }
        while (len-- > 0) {
            char c1 = ta[to++];
            char c2 = pa[po++];
            if (c1 == c2) {            //比较是否两字符是否相等
                continue;
            }
            if (ignoreCase) {          //判断是否忽略大小写
                char u1 = Character.toUpperCase(c1);
                char u2 = Character.toUpperCase(c2);
                if (u1 == u2) {        //均转换为大写判断是否相等
                    continue;
                }
                if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) {
                    continue;
                }                      //均转换为小写判断是否相等
            }
            return false;              //没有continue则返回false
        }
        return true;                   //没有返回false,则返回true
    }

按照上述方法的实现,我认为equals方法则可以重写为:

public boolean equals(Object anObject) {
        return (this==anObject) ? true
                : (anObject != null) 
                && (anObject instanceof String)
                && (anObject.value.length == value.length)
                && regionMatches(false, 0, anObject, 0, value.length);
    }

 (2)contains(CharSequence s)

接口 CharSequence

所有已知子接口:

Name

所有已知实现类:

CharBuffer, Segment, String, StringBuffer, StringBuilder

CharSequence 是 char 值的一个可读序列。此接口对许多不同种类的 char 序列提供统一的只读访问。

    public boolean contains(CharSequence s) {
        return indexOf(s.toString()) > -1;
    }
    public boolean contains(CharSequence s) {
        return indexOf(s.toString()) > -1;
    }
    public int indexOf(String str, int fromIndex) {
        return indexOf(value, 0, value.length,
                str.value, 0, str.value.length, fromIndex);
    }
/**
     * @param   source       源字符串
     * @param   sourceOffset 源字符串起始偏移量
     * @param   sourceCount  源字符串长度
     * @param   target       目标字符串
     * @param   targetOffset 目标字符串起始偏移量
     * @param   targetCount  目标字符串长度
     * @param   fromIndex    搜索的起始偏移量
     */
    static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
        if (fromIndex >= sourceCount) {        //如果搜索起始偏移量 > 源字符串长度
            return (targetCount == 0 ? sourceCount : -1);    
        }
        if (fromIndex < 0) {                   //如果搜索的起始偏移量小于0,则从0开始
            fromIndex = 0;
        }
        if (targetCount == 0) {                //如果目标字符串长度=0,则返回搜索的起始偏移量
            return fromIndex;
        }

        char first = target[targetOffset];     //获取目标字符串起始的第一个字符
        //使得该位置之后的字符串长度不小于目标字符串长度的最大索引
        int max = sourceOffset + (sourceCount - targetCount);

        //i  从  源字符串起始偏移 + 搜索的起始偏移  到  max
        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* 找到第一个与first相等的字符 */
            if (source[i] != first) {        
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                //比较剩下字符
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);
                //如果j==end,证明已找到
                if (j == end) {
                    /* 返回起始索引 */
                    return i - sourceOffset;
                }
            }
        }
        //未找到,返回-1
        return -1;
    }

(3)""和null的区别

""是字符串常量,同时也是一个String类的对象,既然是对象当然可以调用String类中的方法

null是空常量,不能调用任何的方法,否则会出现空指针异常,null常量可以给任意的引用数据类型赋值

其他源码都比较简单,读者可自行阅读。

猜你喜欢

转载自blog.csdn.net/qq_40298054/article/details/83658987