String知识点-这一篇全部了解

字符串是常量,在定义之后不能被改变

String源码(java7)

定义

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

属性

private final char value[];

final类型字符数组实现存储字符串内容

private int hash;

缓存字符串的hash Code,默认值为 0

private static final long serialVersionUID = -6849794470754667710L;

private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0];

String实现了Serializable接口,所以支持序列化和反序列化支持。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)。

构造方法

1.使用字符数组、字符串构造一个String

使用字符数组创建String的时候,会用到Arrays.copyOf方法和Arrays.copyOfRange方法。这两个方法是将原有的字符数组中的内容逐一的复制到String中的字符数组中。也可以用一个String类型的对象来初始化一个String,直接将源String中的valuehash两个属性直接赋值给目String。因为String一旦定义之后是不可以改变的,所以不用担心改变源String的值会影响到目标String的值。

使用字符数组来创建一个新的String对象时,只要多传入两个参数int offsetint count就可以实现使用字符数组的一部分。

2.使用字节数组构造一个String

在Java中,char[]字符数组是以unicode码来存储的,String 和 char 为内存形式,byte是网络传输或存储的序列化形式。所以在很多传输和存储的过程中需要将byte[]数组和String进行相互转化。所以,String提供了一系列重载的构造方法来将一个字符数组转化成String,因此要关注编码问题。String(byte[] bytes, Charset charset)是指通过charset来解码指定的byte数组,将其解码成unicodechar[]数组,构造成新的String

这里的bytes字节流是使用charset进行编码的,想要将他转换成unicodechar[]数组,而又保证不出现乱码,那就要指定其解码方式

同样使用字节数组来构造String也有很多种形式,按照是否指定解码方式分的话可以分为两种:

String(byte bytes[]) String(byte bytes[], int offset, int length)

String(byte bytes[], Charset charset)

String(byte bytes[], String charsetName)

String(byte bytes[], int offset, int length, Charset charset)

String(byte bytes[], int offset, int length, String charsetName)

如果我们在使用byte[]构造String的时候,使用的是下面这四种构造方法(带有charsetName或者charset参数)的一种的话,那么就会使用StringCoding.decode方法进行解码,使用的解码的字符集就是我们指定的charsetName或者charset。如果没有指明解码使用的字符集,那么StringCodingdecode方法首先调用系统的默认编码格式(ISO-8859-1)进行编码操作。主要体现代码如下:
 

static char[] decode(byte[] ba, int off, int len) {

        String csn = Charset.defaultCharset().name();

        try {

            // use charset name decode() variant which provides caching.

            return decode(csn, ba, off, len);

        } catch (UnsupportedEncodingException x) {

            warnUnsupportedCharset(csn);

        }

        try {

            return decode("ISO-8859-1", ba, off, len);

        } catch (UnsupportedEncodingException x) {

            // If this code is hit during VM initialization, MessageUtils is

            // the only way we will be able to get any kind of error message.

            MessageUtils.err("ISO-8859-1 charset not available: "

                             + x.toString());

            // If we can not find ISO-8859-1 (a required encoding) then things

            // are seriously wrong with the installation.

            System.exit(1);

            return null;

        }

    }

3.使用StringBufferStringBuider构造一个String

   

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

    }

这两个构造方法是很少用到,StringBuffer或者StringBuilfer对象之后可以直接使用他们的toString方法来得到String。关于效率问题,Java的官方文档有提到说使用StringBuilder的toString方法会更快一些,原因是StringBuffer的toString方法是synchronized的,在牺牲了效率的情况下保证了线程安全。

 public String toString() {

    // Create a copy, don't share the array

    return new String(value, 0, count);

 }


this.value = Arrays.copyOfRange(value, offset, offset+count);

4.一个特殊的保护类型的构造方法

String提供了一个保护类型的构造方法(Java 7)

String(char[] value, boolean share) {

    // assert share : "unshared not supported";

    this.value = value;

}

String(char[] value)有两点区别,第一,多了一个参数: boolean share,其实这个参数在方法体中根本没被使用,也给了注释,目前不支持使用false,只使用true。那么可以断定,加入这个share的只是为了区分于String(char[] value)方法,不加这个参数就没办法定义这个函数,只有参数不能才能进行重载。第二,区别是具体的方法实现不同。String(char[] value)方法在创建String的时候会用到 会用到ArrayscopyOf方法将value中的内容逐一复制到String当中,而这个String(char[] value, boolean share)方法则是直接将value的引用赋值给String的value。那么也就是说,这个方法构造出来的String和参数传过来的char[] value共享同一个数组。 那么,为什么Java会提供这样一个方法呢?

首先,性能好,这个很简单,一个是直接给数组赋值(相当于直接将String的value的指针指向char[]数组),一个是逐一拷贝。当然是直接赋值快了。

其次,共享内部数组节约内存

但是,该方法之所以设置为protected,是因为一旦该方法设置为公有,在外面可以访问的话,就破坏了字符串的不可变性。例如如下情形:
 

char[] arr = new char[] {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};

String s = new String(0, arr.length, arr); // "hello world"

arr[0] = 'a'; // replace the first character with 'a'

System.out.println(s); // aello world

如果构造方法没有对arr进行拷贝,那么其他人就可以在字符串外部修改该数组,由于它们引用的是同一个数组,因此对arr的修改就相当于修改了字符串。

所以,从安全性角度考虑,他也是安全的。对于调用他的方法来说,由于无论是原字符串还是新字符串,其value数组本身都是String对象的私有属性,从外部是无法访问的,因此对两个字符串来说都很安全。

在Java 7 之前有很多String里面的方法都使用这种“性能好的、节约内存的、安全”的构造函数。比如:substringreplaceconcatvalueOf等方法(实际上他们使用的是public String(char[], int, int)方法,原理和本方法相同,已经被本方法取代)。

但是在Java 7中,substring已经不再使用这种优秀的方法了,为什么呢? 虽然这种方法有很多优点,但是他有一个致命的缺点,可能造成内存泄露。 看一个例子,假设一个方法从某个地方(文件、数据库或网络)取得了一个很长的字符串,然后对其进行解析并提取其中的一小段内容,这种情况经常发生在网页抓取或进行日志分析的时候。下面是示例代码。

String aLongString = "...a very long string...";

String aPart = data.substring(20, 40);

return aPart;

在这里aLongString只是临时的,真正有用的是aPart,其长度只有20个字符,但是它的内部数组却是从aLongString那里共享的,因此虽然aLongString本身可以被回收,但它的内部数组却不能(如下图)。这就导致了内存泄漏。

新的实现虽然损失了性能,而且浪费了一些存储空间,但却保证了字符串的内部数组可以和字符串对象一起被回收,从而防止发生内存泄漏。

share数组的方法还是有一些其他方法在使用的,这是为什么呢?首先呢,这种方式构造对应有很多好处,其次呢,其他的方法不会将数组长度变短,也就不会有前面说的那种内存泄露的情况(内存泄露是指不用的内存没有办法被释放,比如说concat方法和replace方法)。

其他方法

length() 返回字符串长度

isEmpty() 返回字符串是否为空

charAt(int index) 返回字符串中第(index+1)个字符

char[] toCharArray() 转化成字符数组

trim() 去掉两端空格

toUpperCase() 转化为大写

toLowerCase() 转化为小写

String concat(String str) //拼接字符串

String replace(char oldChar, char newChar) //将字符串中的oldChar字符换成newChar字符

//以上两个方法都使用了String(char[] value, boolean share)

boolean matches(String regex) //判断字符串是否匹配给定的regex正则表达式

boolean contains(CharSequence s) //判断字符串是否包含字符序列s

String[] split(String regex, int limit) 按照字符regex将字符串分成limit份。

String[] split(String regex)

String string = "h,o,l,l,i,s,c,h,u,a,n,g";

String[] splitAll = string.split(",");

String[] splitFive = string.split(",",5);

splitAll =  [h, o, l, l, i, s, c, h, u, a, n, g] 

splitFive =  [h, o, l, l, i,s,c,h,u,a,n,g]

getBytes

可以使用byte[]数组创建String,将一个字节数组转换成字符串,同样,我们可以将一个字符串转换成字节数组, String提供了很多重载的getBytes方法。但是,值得注意的是,在使用这些方法的时候一定要注意编码问题。比如:

String s = "你好,世界!";

byte[] bytes = s.getBytes();

这段代码在不同的平台上运行得到结果是不一样的。由于我们没有指定编码方式,所以在该方法对字符串进行编码的时候就会使用系统的默认编码方式,比如在中文操作系统中可能会使用GBK或者GB2312进行编码,在英文操作系统中有可能使用iso-8859-1进行编码。这样写出来的代码就和机器环境有很强的关联性了,所以,为了避免不必要的麻烦,要指定编码方式

String s = "你好,世界!";

byte[] bytes = s.getBytes("utf-8");

比较方法

boolean equals(Object anObject);

boolean contentEquals(StringBuffer sb);

boolean contentEquals(CharSequence cs);

boolean equalsIgnoreCase(String anotherString);

int compareTo(String anotherString);

int compareToIgnoreCase(String str);

boolean regionMatches(int toffset, String other, int ooffset,int len)  //局部匹配

boolean regionMatches(boolean ignoreCase, int toffset,String other, int ooffset, int len)   //局部匹配

字符串有一系列方法用于比较两个字符串的关系。 前四个返回boolean的方法很容易理解,前三个比较就是比较String和要比较的目标对象的字符数组的内容,一样就返回true,不一样就返回false,核心代码如下:

 int n = value.length;

 while (n-- != 0) {

     if (v1[i] != v2[i])

         return false;

     i++;

 }

v1 v2分别代表String的字符数组和目标对象的字符数组。 第四个和前三个唯一的区别就是他会将两个字符数组的内容都使用toUpperCase方法转换成大写再进行比较,以此来忽略大小写进行比较。相同则返回true,不想同则返回false

在这里,看到这几个比较的方法代码,有很多编程的技巧我们应该学习。我们看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;

    }

该方法首先判断this == anObject ,也就是说判断要比较的对象和当前对象是不是同一个对象,如果是直接返回true,如不是再继续比较,然后在判断anObject是不是String类型的,如果不是,直接返回false,如果是再继续比较,到了能终于比较字符数组的时候,他还是先比较了两个数组的长度,不一样直接返回false,一样再逐一比较值。很大程度上提高比较的效率。

contentEquals有两个重载,StringBuffer需要考虑线程安全问题,再加锁之后调用contentEquals((CharSequence) sb)方法。contentEquals((CharSequence) sb)则分两种情况,一种是cs instanceof AbstractStringBuilder,另外一种是参数是String类型。具体比较方式几乎和equals方法类似,先做宏观比较,在做微观比较。

下面这个是equalsIgnoreCase代码的实现:   

public boolean equalsIgnoreCase(String anotherString) {

        return (this == anotherString) ? true

                : (anotherString != null)

                && (anotherString.value.length == value.length)

                && regionMatches(true, 0, anotherString, 0, value.length);

    }

看到这段代码,眼前为之一亮。使用一个三目运算符和&&操作代替了多个if语句

hashCode

hashCode的实现其实就是使用数学公式:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

s[i]是string的第i个字符,n是String的长度。那为什么这里用31,而不是其它数呢? 计算机的乘法涉及到移位计算。当一个数乘以2时,就直接拿该数左移一位即可!选择31原因是因为31是一个素数!

所谓素数:质数又称素数。指在一个大于1的自然数中,除了1和此整数自身外,没法被其他自然数整除的数。

在存储数据计算hash地址的时候,我们希望尽量减少有同样的hash地址,所谓“冲突”。如果使用相同hash地址的数据过多,那么这些数据所组成的hash链就更长,从而降低了查询效率!所以在选择系数的时候要选择尽量长的系数并且让乘法尽量不要溢出的系数,因为如果计算出来的hash地址越大,所谓的“冲突”就越少,查找起来效率也会提高。

31可以 由i*31== (i<<5)-1来表示,现在很多虚拟机里面都有做相关优化,使用31的原因可能是为了更好的分配hash地址,并且31只占用5bits!

在java乘法中如果数字相乘过大会导致溢出的问题,从而导致数据的丢失.

而31则是素数(质数)而且不是很长的数字,最终它被选择为相乘的系数的原因不过与此!

在Java中,整型数是32位的,也就是说最多有2^32= 4294967296个整数,将任意一个字符串,经过hashCode计算之后,得到的整数应该在这4294967296数之中。那么,最多有 4294967297个不同的字符串作hashCode之后,肯定有两个结果是一样的, hashCode可以保证相同的字符串的hash值肯定相同,但是,hash值相同并不一定是value值就相同。

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

}

前面我们介绍过,java 7 中的substring方法使用String(value, beginIndex, subLen)方法创建一个新的String并返回,这个方法会将原来的char[]中的值逐一复制到新的String中,两个数组并不是共享的,虽然这样做损失一些性能,但是有效地避免了内存泄露。

replaceFirstreplaceAllreplace区别

String replaceFirst(String regex, String replacement)

String replaceAll(String regex, String replacement)

String replace(CharSequence target, CharSequence replacement)

1)replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换

2)replaceAll和replaceFirst的参数是regex,即基于规则表达式的替换,比如,可以通过replaceAll(“\d”, “*”)把一个字符串所有的数字字符都换成星号; 相同点是都是全部替换,即把源字符串中的某一字符或字符串全部换成指定的字符或字符串, 如果只想替换第一次出现的,可以使用 replaceFirst(),这个方法也是基于规则表达式的替换,但与replaceAll()不同的是,只替换第一次出现的字符串; 另外,如果replaceAll()和replaceFirst()所用的参数据不是基于规则表达式的,则与replace()替换字符串的效果是一样的,即这两者也支持字符串的操作;

copyValueOf valueOf

String的底层是由char[]实现的:通过一个char[]类型的value属性!早期的String构造器的实现呢,不会拷贝数组的,直接将参数的char[]数组作为String的value属性。然后test[0] = 'A';将导致字符串的变化。为了避免这个问题,提供了copyValueOf方法,每次都拷贝成新的字符数组来构造新的String对象。但是现在的String对象,在构造器中就通过拷贝新数组实现了,所以这两个方面在本质上已经没区别了。

valueOf()有很多种形式的重载,包括: 

 public static String valueOf(boolean b) {

      return b ? "true" : "false";

  }


  public static String valueOf(char c) {

       char data[] = {c};

       return new String(data, true);

  }

  public static String valueOf(int i) {

      return Integer.toString(i);

  }


  public static String valueOf(long l) {

     return Long.toString(l);

  }


 public static String valueOf(float f) {

     return Float.toString(f);

 }


 public static String valueOf(double d) {

    return Double.toString(d);

}

可以看到这些方法可以将六种基本数据类型的变量转换成String类型。

intern()方法

public native String intern();

该方法返回一个字符串对象的内部化引用。 众所周知:String类维护一个初始为空的字符串的对象池,String 的 intern 方法首先将尝试在常量池中查找该对象,如果找到则直接返回该对象在常量池中的地址;找不到则将该对象放入常量池后再返回其地址.当一个String实例调用intern()方法时,Java会去查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则把String实例放进常量池,并返回引用。

String str = new String("王强");
String intern = str.intern();
System.out.println(intern == str);//false

String str1 = new String("小") + new String("王强");
String str2 = "小王强";
String s1 = str1.intern();
System.out.println(">>>str1:"+str1.hashCode()+">>str2:"+str2.hashCode()+">>>s1:"+s1.hashCode());
//>>>str1:23589214>>str2:23589214>>>s1:23589214
System.out.println(str1 ==s1);//false
System.out.println(str2 ==s1);//true
System.out.println(str1 ==str2);//false

String str3 = new String("熊小") + new String("王强");
String s2 = str3.intern();
String str4 = "熊小王强";
System.out.println(">>>str3:"+str3.hashCode()+">>str4:"+str4.hashCode()+">>>s2:"+s2.hashCode());
//>>>str3:889494420>>str4:889494420>>>s2:889494420
System.out.println(str3 ==s2);//true
System.out.println(str4 ==s2);//true
System.out.println(str3 ==str4);//true

 通过Javap -v查看常量池:

String“+”的重载

Java是不支持重载运算符,String的“+”是java中唯一的一个重载运算符

public static void main(String[] args) {

    String string="hollis";

    String string2 = string + "chuang";

}

然后我们将这段代码反编译

public static void main(String args[]){

   String string = "hollis";

   String string2 = (new StringBuilder(String.valueOf(string))).append("chuang").toString();

}

看了反编译之后的代码我们发现,其实String对“+”的支持其实就是使用了StringBuilder以及他的append、toString两个方法。

String.valueOfInteger.toString的区别

接下来我们看以下这段代码,我们有三种方式将一个int类型的变量变成呢过String类型,那么他们有什么区别?

1.int i = 5;

2.String i1 = "" + i;

3.String i2 = String.valueOf(i);

4.String i3 = Integer.toString(i);

1、第三行和第四行没有任何区别,因为String.valueOf(i)也是调用Integer.toString(i)来实现的。 2、第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。

String的不变性

String s = "abcd";

s中保存了string对象的引用。下面的箭头可以理解为“存储他的引用”。

String s2 = s;

s2保存了相同的引用值,因为他们代表同一个对象。

s = s.concat("ef");

s中保存的是一个重新创建出来的string对象的引用。

总结:

一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。

如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。

为什么设计成不变的

字符串池是方法区中的一部分特殊存储。当一个字符串被被创建的时候,首先会去这个字符串池中查找,如果找到,直接返回对该字符串的引用。

下面的代码只会在堆中创建一个字符串

String string1 = "abcd";

String string2 = "abcd";

如果字符串可变的话,当两个引用指向指向同一个字符串时,对其中一个做修改就会影响另外一个。

Java中经常会用到字符串的哈希码(hashcode),String对象被创建的时候会保存一个hash值。例如,在HashMap中,字符串的不可变能保证其hashcode永远保持一致,这样就可以避免一些不必要的麻烦。这也就意味着每次在使用一个字符串的hashcode的时候不用重新计算一次直接返回即可,这样更加高效。

同事String的不可变性还为Set等其他类的特性提供了很大方便。

因为不可变对象不能被改变,所以可以自由地在多个线程之间共享。不需要任何同步处理。

总之,String被设计成不可变的主要目的是为了安全和高效。

” “VS构造函数创建字符串对象

实例一

String a = "abcd";

String b = "abcd";

System.out.println("a == b : "+(a == b)); // true

System.out.println("a.equals(b) : "+(a.equals(b))); // true

a== b等于true 是因为x和y指向方法区中同一个字符串常量,他们的引用是相同的(==比较的是引用)。

当相同的字符串常量被多次创建时,只会保存字符串常量的一份副本,这称为“字符串驻留”。在Java中,所有编译时字符串常量都是驻留的。

实例二

String c = new String("abcd");

String d = new String("abcd");

System.out.println("c == d : "+(c == d)); // false

System.out.println("c.equals(d) : "+(c.equals(d))); // true

c== d等于false 是因为c和d指向堆中不同的对象。不同的对象拥有不同的内存引用。

下面图论证了以上的结论。

运行时字符串驻留

运行时也会发生字符串驻留,即使两个字符串是由构造函数方法创建的。

String c = new String("abcd").intern();

String d = new String("abcd").intern();

System.out.println("c == d : "+(c == d)); // true

System.out.println("c.equals(d) : "+(c.equals(d))); // true    (JDK1.7)

因为字面值“abcd”已经是字符串类型,那么使用构造函数方式只会创建一个额外没有用处的对象。

因此,如果你只需要创建一个字符串,你可以使用双引号的方式,如果你需要在堆中创建一个新的对象,你可以选择构造函数的方式。

相关扩展

equals()和hashcode()之间关系

所有Java类的父类——java.lang.Object中定义了两个重要的方法:

public boolean equals(Object obj)

public int hashCode()

import java.util.HashMap;


public class Apple {

    private String color;


    public Apple(String color) {

        this.color = color;

    }


    public boolean equals(Object obj) {

        if(obj==null) return false;

        if (!(obj instanceof Apple))

            return false;  

        if (obj == this)

            return true;

        return this.color.equals(((Apple) obj).color);

    }


    public static void main(String[] args) {

        Apple a1 = new Apple("green");

        Apple a2 = new Apple("red");


        //hashMap stores apple type and its quantity

        HashMap<Apple, Integer> m = new HashMap<Apple, Integer>();

        m.put(a1, 10);

        m.put(a2, 20);

        System.out.println(m.get(new Apple("green")));

    }

}

上面的代码执行过程中,先是创建个两个Apple,一个green apple和一个red apple,然后将这来两个apple存储在map中,存储之后再试图通过map的get方法获取到其中green apple的实例。读者可以试着执行以上代码,数据结果为null。也就是说刚刚通过put方法放到map中的green apple并没有通过get方法获取到。你可能怀疑是不是green apple并没有被成功的保存到map中,但是,通过debug工具可以看到,它已经被保存成功了。

造成以上问题的原因其实比较简单,是因为代码中并没有重写hashcode方法。hashcodeequals的约定关系如下:

1、如果两个对象相等,那么他们一定有相同的哈希值(hash code)。

2、如果两个对象的哈希值相等,那么这两个对象有可能相等也有可能不相等。(需要再通过equals来判断)

Map的工作原理,是通过把key值进行hash来定位对象的,这样可以提供比线性存储更好的性能。Map的底层数据结构就是一个数组的数组(准确的说其实是一个链表+数组)。第一个数组的索引值是key的哈希码。通过这个索引可以定位到第二个数组,第二个数组通过使用equals方法进行线性搜索的方式来查找对象。(HashMap完全解读)

 

其实,一个哈希码可以映射到一个桶(bucket)中,hashcode的作用就是先确定对象是属于哪个桶的。如果多个对象有相同的哈希值,那么他们可以放在同一个桶中。如果有不同的哈希值,则需要放在不同的桶中。至于同一个桶中的各个对象之前如何区分就需要使用equals方法了。

hashcode方法的默认实现会为每个对象返回一个不同的int类型的值。所以,上面的代码中,第二个apple被创建出来时他将具有不同的哈希值。可以通过重写hashCode方法来解决。

public int hashCode(){

    return this.color.hashCode();  

}

总结

在判断两个对象是否相等时,不要只使用equals方法判断。还要考虑其哈希码是否相等。尤其是和hashMap等与hash相关的数据结构一起使用时。

length和length()

String的内部机制是char数+组实现的,所以String的长度用length()方法,数组是length属性。

Java中的Switch

目前为止switch支持这样几种数据类型:byteshort int char String ,枚举。

能够支持String,其实是用的String的HashCode,hashCode 是int类型,枚举类型实际用的是enum 的ordinal(),也是int类型;所以本质上还是int类型。

String长度限制

编译期定义String时候会将字符串存储于常量池一份,常量池中有自己的数据类型(CONSTANT_Utf8);通过CONSTANT_Utf8_info表述;

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 byte[length];
}

u2是16位整数,理论上最大长度2^16=65536,而null值使用两个字节表示,所以剩下65536-2=65534个字节。也就是说,在Java中所有需要保存常量池中的数据,长度最大不能超过65535.

运行期,根据String源码可以看到是Integer.MAX_VALUE值约等于4G的限制。

参考:

《成神之路-基础篇》Java基础知识——String相关-HollisChuang's Blog

猜你喜欢

转载自blog.csdn.net/w372426096/article/details/81510654