zoukankan      html  css  js  c++  java
  • java源码阅读String

    1类签名与注释

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

    String类被定义为final类型的,所以String对象一旦创建了,就是不可变的。

    String类实现了Serializable接口,表示可以序列化。

    String类实现了Comparable<String>接口,表示String类型可以相互比较。(通过compareTo方法)

    String类实现了CharSequence接口,CharSequence是char值的可读序列。

    String类代表字符串。 Java程序中的所有字符串文字(例如"abc" )都被实现为此类的实例。Strings是不可变的,它们的值在创建后不能被更改。字符串缓冲区支持可变字符串。

    Java语言为字符串连接运算符(+)提供特殊支持,并为其他对象转换为字符串。 字符串连接是通过StringBuilder (或StringBuffer )类及其append方法实现的。 字符串转换是通过toString方法来实现,该方法定义在Object类中。

    2基本属性

    private final char value[];
    
    private int hash; // Default to 0

    String用字符数组存储值,内部还保留了自己的hash值。为什么要将hash值作为成员属性呢?我们先看一下String的hashCode方法

     1 public int hashCode() {
     2         int h = hash;
     3         if (h == 0 && value.length > 0) {
     4             char val[] = value;
     5 
     6             for (int i = 0; i < value.length; i++) {
     7                 h = 31 * h + val[i];
     8             }
     9             hash = h;
    10         }
    11         return h;
    12     }

    当调用String的hashCode方法时,首先会从hash属性取值,当hash属性值为0时(第一次调用该方法),才会重新计算hash值。hash值得计算如line7所示。计算后会将值赋给hash属性。这里保证只在第一次调用hashCode方法的时候计算hash值,之后直接从hash属性取值。这种优化只有当String是final的时候才可以,若String可变的话,hash值也会变化。

    3构造方法

    String的构造方法有很多,下面是eclipse中outline的所有构造方法的截图。

    红色方框部分表示废弃方法(我的版本JDK1.8)。通过outline可以看出,String可以通过String、字符数组、int数组、字节数组、StringBuffer/StringBuilder构造。下面贴出几个常用构造方法的代码。

    //1构造空String
    public String() {
            this.value = "".value;
        }
    
    //2通过String构造
    public String(String original) {
            this.value = original.value;
            this.hash = original.hash;
        }
    
    //3通过char数组构造
    public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
    
    //4通过byte数组构造
     public String(byte bytes[]) {
            this(bytes, 0, bytes.length);
        }
    public String(byte bytes[], int offset, int length) {
            checkBounds(bytes, offset, length);
            this.value = StringCoding.decode(bytes, offset, length);
        }
    
    //5通过字符串缓冲区构造
    public String(StringBuffer buffer) {
            synchronized(buffer) {
                this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
            }
        }
     public String(StringBuilder builder) {
            this.value = Arrays.copyOf(builder.getValue(), builder.length());
        }

    这里有几点自己的理解:

    (1)在通过char数组构造的时候,是通过Arrays的copyOf复制了一个新的数组(开辟了新的数组存储空间),这里不是通过简单的引用传递。

    (2)byte数组不能直接变成char数组,需要通过StringCodingdecode方法。

    (3)注意通过StringBuffer构造的时候加了同步,因为StringBuffer是线程安全的,这里复制的时候加锁也要保证线程安全

    4常用方法

    (1)equals方法

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

    只有满足类型一样,长度相等,char数组所有的值都相等时equals方法才返回true。

    (2)compareTo方法

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

    挨个比较字符数组的值,当较短的串所有字符都和较长串的相等时返回串的长度差。当compareTo方法的返回值等于0时,表示被比较的字符串和当前字符串相等,返回值大于0时表示当前字符串较大,返回值小于0表示当前字符串较小。

    (3)startsWith和endsWith

    //startsWith
    public boolean startsWith(String prefix) {
            return startsWith(prefix, 0);
        }
    
    public boolean startsWith(String prefix, int toffset) {
            char ta[] = value;
            int to = toffset;
            char pa[] = prefix.value;
            int po = 0;
            int pc = prefix.value.length;
            // Note: toffset might be near -1>>>1.
            if ((toffset < 0) || (toffset > value.length - pc)) {
                return false;
            }
            while (--pc >= 0) {
                if (ta[to++] != pa[po++]) {
                    return false;
                }
            }
            return true;
        }
    
    //endsWith
    public boolean endsWith(String suffix) {
            return startsWith(suffix, value.length - suffix.value.length);
        }

    starsWhith方法是从偏移量开始,挨个比较自身字符数组的值和参数对应的字符数组的值。endsWith用长度差当作偏移量。

    (4)indexOf(String)方法

     1 public int indexOf(String str) {
     2         return indexOf(str, 0);
     3     }
     4 
     5 public int indexOf(String str, int fromIndex) {
     6         return indexOf(value, 0, value.length,
     7                 str.value, 0, str.value.length, fromIndex);
     8     }
     9 
    10 static int indexOf(char[] source, int sourceOffset, int sourceCount,
    11             String target, int fromIndex) {
    12         return indexOf(source, sourceOffset, sourceCount,
    13                        target.value, 0, target.value.length,
    14                        fromIndex);
    15     }
    16 
    17 static int indexOf(char[] source, int sourceOffset, int sourceCount,
    18             char[] target, int targetOffset, int targetCount,
    19             int fromIndex) {
    20         if (fromIndex >= sourceCount) {
    21             return (targetCount == 0 ? sourceCount : -1);
    22         }
    23         if (fromIndex < 0) {
    24             fromIndex = 0;
    25         }
    26         if (targetCount == 0) {
    27             return fromIndex;
    28         }
    29 
    30         char first = target[targetOffset];
    31         int max = sourceOffset + (sourceCount - targetCount);
    32 
    33         for (int i = sourceOffset + fromIndex; i <= max; i++) {
    34             /* Look for first character. */
    35             if (source[i] != first) {
    36                 while (++i <= max && source[i] != first);
    37             }
    38 
    39             /* Found first character, now look at the rest of v2 */
    40             if (i <= max) {
    41                 int j = i + 1;
    42                 int end = j + targetCount - 1;
    43                 for (int k = targetOffset + 1; j < end && source[j]
    44                         == target[k]; j++, k++);
    45 
    46                 if (j == end) {
    47                     /* Found whole string. */
    48                     return i - sourceOffset;
    49                 }
    50             }
    51         }
    52         return -1;
    53     }

    主要实现在line17-53,这里就是挨个对比匹配。通过indexOf(String str)调用line17的方法是没有什么问题,因为sourceOffset偏移量总是为0,但是看源码的时候总感觉max(line31)的逻辑会导致数组越界问题。举个例子如下:

    public class Test {
    
        public static void main(String[] args) {
            
            String a = "abc1234";
            char[] aa = {'a','b','c','1','2','3','4'};
            String b = "25";
            char[] bb = {'2','5'};
            System.out.println(indexOf(aa,2,aa.length,
                    bb,0,bb.length,0));
        
        }
        
        static int indexOf(char[] source, int sourceOffset, int sourceCount,
                char[] target, int targetOffset, int targetCount,
                int fromIndex) {
                  //省略...
            }
    
          
    }
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 7
        at com.ouym.test.Test.indexOf(Test.java:44)
        at com.ouym.test.Test.main(Test.java:19)

    (5)substring

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

    String的subString方法是通过构造方法String(value, beginIndex, subLen)来实现的,截取的字串包含beginIndex,不包含endIndex。举个例子

    String a = "0123456";
    System.out.println(a.substring(2,4));
    
    输出:23

    (6)concat

    public String concat(String str) {
            int otherLen = str.length();
            if (otherLen == 0) {
                return this;
            }
            int len = value.length;
            //新建字符数组buf,并将主调String对象的value数组复制到buf
            char buf[] = Arrays.copyOf(value, len + otherLen);
            //将参数str的字符数组填充到buf的剩余空间
            str.getChars(buf, len);
            return new String(buf, true);
        }

    (7)replace

     1 public String replace(char oldChar, char newChar) {
     2         if (oldChar != newChar) {
     3             int len = value.length;
     4             int i = -1;
     5             char[] val = value; /* avoid getfield opcode */
     6 
     7             while (++i < len) {
     8                 if (val[i] == oldChar) {
     9                     break;
    10                 }
    11             }
    12             if (i < len) {
    13                 char buf[] = new char[len];
    14                 for (int j = 0; j < i; j++) {
    15                     buf[j] = val[j];
    16                 }
    17                 while (i < len) {
    18                     char c = val[i];
    19                     buf[i] = (c == oldChar) ? newChar : c;
    20                     i++;
    21                 }
    22                 return new String(buf, true);
    23             }
    24         }
    25         return this;
    26     }

    replace方法主要分四步:首先,找到oldChar的下标index。然后将index之前的所有字符复制到新的char数组。接下来将oldchar的值变成newchar。最后将剩下的复制到char数组并返回构造的新String。

    注意:replace方法是通过数组复制并构造新的String实现的。因为String是final的,内部的char数组也是final的。

    5一些问题

    (1)codePoint(代码点)

    代码点是指一个编码表中的某个字符对应的代码值,也就是Unicode编码表中每个字符对应的数值。

    举个例子:

    "sfdg".codePointAt(1); 返回结果是102,就是ASCII码中的小写字母 f
  • 相关阅读:
    电子工程师对程序员的一番心里话(转载)
    一个程序员的一生(转载)
    程序人生中的十个感悟...
    谈计算机软件发展观念(转载)
    ASP.NET 2.0服务器控件开发精要(转载)
    一个老程序员的心里话(转载)
    hdu 1316 斐波那契数
    hdu 3117 斐波那契数列
    hdu 1239 素数水题
    hdu 2256 神奇的矩阵
  • 原文地址:https://www.cnblogs.com/ouym/p/8981074.html
Copyright © 2011-2022 走看看