本文主要是介绍Java中的String能存储多少字符?不可变吗?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
能存储多少字符,通过以下步骤来看
- 首先String的length方法返回是int。所以理论上长度一定不会超过int的最大值。
- 编译器对字符串字面量长度的限制源自Java编译器(如
javac
)在处理常量池时的实现。编译器源码如下,限制了字符串长度大于等于65535就会编译不通过:
// src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Pool.java
public class Pool {// .../*** Add a new Utf8 string to the constant pool, checking for duplicates* and sharing the entry if one already exists.*/public int putUtf8(String x) {Assert.checkNonNull(x);byte[] bytes;try {ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream();DataOutputStream dataoutputstream = new DataOutputStream(bytearrayoutputstream);dataoutputstream.writeUTF(x);dataoutputstream.close();bytes = bytearrayoutputstream.toByteArray();} catch (IOException e) {throw new AssertionError(e);}if (bytes.length > 65535)throw new UTFDataFormatException("encoded string too long: " + bytes.length + " bytes");return put(new Pool.Utf8Entry(bytes));}// ...
}
Java中的字符常量都是使用UTF 8编码的,UTF 8编码使用1~4个字节来表示具体的Unicode字符。所以有的字符占用一个字节,而平时所用的大部分中文都需要3个字节来存储。
//65534个字母,编译通过
String s1 = "dd..d";//21845个中文”自“,编译通过
String s2 = "自自...自";//一个英文字母d加上21845个中文”自“,编译失败
String s3 = "d自自...自";
-
对于s1,一个字母d的UTF8编码占用一个字节,65534个字母占用65534个字节,长度是65534,长度和存储都没超过限制,所以可以编译通过。
-
对于s2,一个中文占用3个字节,21845个正好占用65535个字节,而且字符串长度是21845,长度和存储也都没超过限制,所以可以编译通过。
-
对于s3,一个英文字母d加上21845个中文”自“占用65536个字节,超过了存储最大限制,编译失败。
当然,这个限制是特定于编译器的实现,而不是Java语言本身的限制。
- JVM规范对常量池有所限制。
量池中的每一种数据项都有自己的类型。Java中的UTF-8编码的Unicode字符串在常量池中以CONSTANTUtf8
类型表示。CONSTANTUtf8
的数据结构如下:
CONSTANT_Utf8_info {u1 tag;u2 length;u1 bytes[length];
}
重点关注长度为 length 的那个bytes数组,这个数组就是真正存储常量数据的地方,而 length 就是数组可以存储的最大字节数,而不是字符数。length 的类型是u2,u2是无符号的16位整数,因此理论上允许的的最大长度是2^16-1=65535
。所以上面byte数组的最大长度可以是65535。
当然,考虑到UTF-8是一种变长编码,一个字符可能需要1到4个字节来表示(取决于字符的具体值)。因此,如果你的字符串包含大量使用多个字节编码的字符,那么它能包含的实际字符数将会少于65535。
- 运行时限制
String 运行时的限制主要体现在 String 的构造函数上。下面是 String 的一个构造函数:
public String(char value[], int offset, int count) {...
}
上面的count值就是字符串的最大长度。在Java中,int的最大长度是2^31-1。所以在运行时,String 的最大长度是2^31-1。
但是这个也是理论上的长度,实际的长度还要看JVM的内存。来看下,最大的字符串会占用多大的内存。
highlighter-
(2^31-1)*16/8/1024/1024/1024 = 2GB
所以在最坏的情况下,一个最大的字符串要占用4GB的内存。如果JVM不能分配这么多内存的话,会直接报错的。
总结
因此,主要的还是看编译器对常量池的限制,使得byte数组的最大长度不能超过65535;以及JVM的内存限制
补充:JDK9以后对String的存储进行了优化。底层不再使用char数组存储字符串,而是使用byte数组。对于LATIN1字符的字符串可以节省一倍的内存空间。
Java中的String是不可变对象
在面向对象及函数编程语言中,不可变对象(英语:Immutable object)是一种对象,在被创造之后,它的状态就不可以被改变。至于状态可以被改变的对象,则被称为可变对象(英语:mutable object)。-- 来自百度百科
Java8 String源码
public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];//Java 9已经优化为byte数组了/** Cache the hash code for the string */private int hash; // Default to 0...
}
显然String字符串内部是使用char[]数组来存储。
而这个char[]数组是用 private final来修饰的,private就体现着面向对象的封装特性,并且String没有提供供外部访问的方法,这就意味着这个属性无法被外部访问;final则意味着这个属性无法修改,无法重新指向其他对象。且String 类没有提供/暴露修改这个字符串的方法。
因此,String是不可变对象
不可变的优点
- 线程安全。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
- 支持hash映射。因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性也就使得hash值不会变,不需要重新计算。
- 字符串常量池优化。String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。
一定不可变吗
事实上,可以通过反射来改变String中的值
String str = "abcdef";
System.out.println("修改前的地址值:" + str + ",hash值"+ str.hashCode());
Class<? extends String> aClass = str.getClass();
Field value = aClass.getDeclaredField("value");
value.setAccessible(true);
value.set(str,"seven".getBytes());
System.out.println("修改后的地址值:" + str + ",hash值"+ str.hashCode());
显然修改后的还是同一个地址和hash值
修改前的地址值:abcdef,hash值-1424385949 修改后的地址值:seven,hash值-1424385949
查看源码可以看到,计算hashcode后hash值是由一个常量缓存下来的,所以通过反射修改后hashCode并不会变,除非进行重新计算。
注意:用反射修改String的值破坏了String的immutable特征,可能会带来各种问题,以上只是提供一个思路,不建议这么做。
不可变类都建议参考String类一样,写个变量缓存hashcode,从而防止高并发下的计算
public int hashCode() {int h = hash;if (h == 0 && !hashIsZero) {h = isLatin1() ? StringLatin1.hashCode(value): StringUTF16.hashCode(value);if (h == 0) {hashIsZero = true;} else {hash = h;}}return h;
}
这篇关于Java中的String能存储多少字符?不可变吗?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!