JAVA中MyString类的测试_java.lang(String)

public final class String

implements java.io.Serializable, Comparable, CharSequence {

/** The value is used for character storage. */

private final char value[];

private int hash; // Default to 0

public String() { //无参构造器

this.value = new char[0];

}

public String(String original) {

this.value = original.value;

this.hash = original.hash;

}

/*****传入一个字符数组的构造函数,使用java.utils包中的Arrays类复制******/

public String(char value[]) {

this.value = Arrays.copyOf(value, value.length);

}

/*******传入一个字符串数字,和开始元素,元素个数的构造函数******/

public String(char value[], int offset, int count) {

if (offset < 0) {

throw new StringIndexOutOfBoundsException(offset);

}

if (count < 0) {

throw new StringIndexOutOfBoundsException(count);

}

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

}

/*********************类似方法不介绍了**************************/

public String(StringBuffer buffer) {

synchronized(buffer) {

this.value = Arrays.copyOf(buffer.getValue(), buffer.length());

}

}

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;

}

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

public int hashCode() {

int h = hash;

/***如果hash没有被计算过,并且字符串不为空,则进行hashCode计算*****/

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;

}

情景一:字符串池

* JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象;

* 并且可以被共享使用,因此它提高了效率。

* 由于String类是final的,它的值一经创建就不可改变。

* 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。

/***intern方法是Native调用,它的作用是在方法区中的常量池里通过equals方法寻找等值的对象,如果没有找到则在常量池中

开辟一片空间存放字符串并返回该对应String的引用,否则直接返回常量池中已存在String对象的引用。*****/

public native String intern();

举例:

String a = "abc";

String b = new String("ab1").intern();

if ( a == b ) {

System.out.println("a == b");

} else {

System.out.println("a不等于b");

}

打印出:a == b

//String的compareTo其实就是依次比较两个字符串ASC码。如果两个字符的ASC码相等则继续后续比较,否则直接返回两个ASC的差值。如果两个字符串完全一样,则返回0

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;

}

1. 什么是不可变类

所谓不可变类,就是创建该类的实例后,该实例的属性是不可改变的,Java提供的包装类和java.lang.String类都是不可变类。当创建它们的实例后,其实例的属性是不可改变的。

需要注意的是,对于如下代码

String s="abc";

s="def";

你可能会感到疑惑,不是说String是不可变类吗,这怎么可以改变呢,平常我也是这样用的啊。请注意,s是字符串对象的”abc”引用,即引用是可以变化的,跟对象实例的属性变化没有什么关系,这点请注意区分。

2.String类被设计成不可变的原因

字符串常量池的需要

允许String对象缓存HashCode

Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。

字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码.

安全性:String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。

线程安全:因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。

3. 如何实现一个不可变类

既然不可变类有这么多优势,那么我们借鉴String类的设计,自己实现一个不可变类。

不可变类的设计通常要遵循以下几个原则:

将类声明为final,所以它不能被继承。

将所有的成员声明为私有的,这样就不允许直接访问这些成员。

对变量不要提供setter方法。

将所有可变的成员声明为final,这样只能对它们赋值一次。

通过构造器初始化所有成员,进行深拷贝(deep copy)。

在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝。

4、关于String的其他知识点:String有多长

在思考String能有多长之前,我们先看下String定义的不同形式。

//第一种

String s = "aaaaaaaaaaaaa...";//第二种

byte[] a = readFromFile(new File("someLargeText.txt"));

String superLongString= new String(a);

那么既然思考String的长度,那就应该想想为什么会有长度的限制,难道我在编译器里定义一个String时,有多长不是随便我们自己输入吗?还有上面两种方式有什么区别呢?

4.1 字面量的形式

对于第一种是字面量,Java将其存在常量池中,在Java1.6的版本中是在栈的常量池中,在1.7、1.8版本中将其放到了堆的常量池中。那就是说第一种这种方式中是受到常量池大小的约束了,不错,是会受到常量池的约束,但是在运行在JVM之前,被编译成字节码时就已经有了限制。

cf08d5d4c1d322a44f74165a29af5578.png

如上图所示,编译后的length的类型为u2(无符号16位),也就是讲length的最大值为2^16-1 = 65535,那就是讲我们的上面的字符串s长度按MUTF-8(字节码中的编码)编码可以存储65535个字节。

到这里为止,如果你是中级工程师,知道这么多已经很不错了。

可是事实上呢,我们实验后发现只能存储65534个字节,这是为什么呢?网上有很多猜想,大部分不正确。我们扒一下Java编译器的源码,会发现:

f016af624a3253c415ea28e1c00c5353.png

这下大家明白了吧,Java编译器在检查字符串常量时,判断的是长度只有<65535才会正常,否则报错。看起来像是编译器的Bug。如果你会修改编译器源码,你将上面的判断条件改成<=65535,这样你存一个65535个字符"a"的字符串就不会编译出错了。

我们知道上面我们是用拉丁字符"a"来测试的,a使用UTF-8编码刚好是一个字节,所以可以存储65534个,那如果存汉字呢,比如我们经常看到的"烫",它使用TF-8编码后占用三个字节,那么也就是说我们可以这样定义:

//按照我们刚才的分析,应该可以存储65534/3个"烫"汉字

String s = "烫烫烫...烫烫";

那我们尝试存储65535/3个汉字"烫"试试呢?结果是可以的,并没有报错。诶?这是为什么呢?我们继续扒下编译器的源码看到:

4115ea6697817280f9890efe4c8a786e.png

编译处理汉字这种的呢,他判断的逻辑不一样。条件是>65535才会抛异常,也就是小于等于65535是正常的。很有意思,写Java编译器的人也很有意思哈。

4.1 new的形式

对于第二种形式的,很显然只有在运行时受限于Java虚拟机了。我们知道String最后保存在char数组中,Java虚拟机是如何做的呢?简单参考下源码:

3eef94335e8485f7df220ac352fdeca3.png

虚拟机指令newarray [int],size是以整形定义的,所以它的限制其实就是int的最大值,但是在有一些虚拟机上会保留一些头信息在数组中,所以就变成了Integer.MAX_VALUE - 8个char;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值