首页 > 其他分享 >为什么String是不可变的?

为什么String是不可变的?

时间:2023-02-19 13:44:06浏览次数:52  
标签:为什么 String int 不可 len 字符串 byte buf

我应了一声后走到电脑桌前坐下来,顺手打开 Intellij IDEA,并找到了 String 的源码。

public final class String
   implements java.io.Serializable, Comparable<String>, CharSequence {
   @Stable
   private final byte[] value;
   private final byte coder;
   private int hash;
}

“第一,String 类是 final 的,意味着它不能被子类继承。”

“第二,String 类实现了 Serializable 接口,意味着它可以序列化。”

“第三,String 类实现了 Comparable 接口,意味着最好不要用‘==’来比较两个字符串是否相等,而应该用 compareTo() 方法去比较。”

“第四,StringBuffer、StringBuilder 和 String 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候就可以考虑一下 String 的另外两个好兄弟,StringBuffer 和 StringBuilder,它俩是可变的。”

“第五,Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码open in new window。在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。”

“第六,每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合来作为 HashMap 的键值。”

 

“String 可能是 Java 中使用频率最高的引用类型了,因此 String 类的设计者可以说是用心良苦。”

比如说 String 的不可变性。

  • String 类被 final 关键字修饰,所以它不会有子类,这就意味着没有子类可以重写它的方法,改变它的行为。

  • String 类的数据存储在 byte[] 数组中,而这个数组也被 final 关键字修饰了,这就表示 String 对象是没法被修改的,只要初始化一次,值就确定了。

 

“哥,为什么要这样设计呢?”三妹有些不解。

“我先简单来说下,三妹,能懂最好,不能懂后面再细说。”

第一,可以保证 String 对象的安全性,避免被篡改,毕竟像密码这种隐私信息一般就是用字符串存储的。

第二,保证哈希值不会频繁变更。毕竟要经常作为哈希表的键值,经常变更的话,哈希表的性能就会很差劲。

第三,可以实现字符串常量池。

 

substring()方法

“由于字符串的不可变性,String 类的一些方法实现最终都返回了新的字符串对象。”等三妹稍微缓了一会后,我继续说到。

“就拿 substring() 方法来说。”

public String substring(int beginIndex) {
   if (beginIndex < 0) {
       throw new StringIndexOutOfBoundsException(beginIndex);
  }
   int subLen = length() - beginIndex;
   if (subLen < 0) {
       throw new StringIndexOutOfBoundsException(subLen);
  }
   if (beginIndex == 0) {
       return this;
  }
   return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
          : StringUTF16.newString(value, beginIndex, subLen);
}

// StringLatin1.newString
public static String newString(byte[] val, int index, int len) {
   return new String(Arrays.copyOfRange(val, index, index + len),
           LATIN1);
}

// UTF16.newString
public static String newString(byte[] val, int index, int len) {
   if (String.COMPACT_STRINGS) {
       byte[] buf = compress(val, index, len);
       if (buf != null) {
           return new String(buf, LATIN1);
      }
  }
   int last = index + len;
   return new String(Arrays.copyOfRange(val, index << 1, last << 1), UTF16);
}

substring() 方法用于截取字符串,不管是 Latin1 字符还是 UTF16 字符,最终返回的都是 new 出来的新字符串对象。

 

concat()方法

“还有 concat() 方法。”

public String concat(String str) {
   int olen = str.length();
   if (olen == 0) {
       return this;
  }
   if (coder() == str.coder()) {
       byte[] val = this.value;
       byte[] oval = str.value;
       int len = val.length + oval.length;
       byte[] buf = Arrays.copyOf(val, len);
       System.arraycopy(oval, 0, buf, val.length, oval.length);
       return new String(buf, coder);
  }
   int len = length();
   byte[] buf = StringUTF16.newBytesFor(len + olen);
   getBytes(buf, 0, UTF16);
   str.getBytes(buf, len, UTF16);
   return new String(buf, UTF16);
}

concat() 方法用于拼接字符串,不管编码是否一致,最终也返回的是新的字符串对象。

replace() 替换方法其实也一样,三妹,你可以自己一会看一下源码,也是返回新的字符串对象。”

 

总结

“这就意味着,不管是截取、拼接,还是替换,都不是在原有的字符串上进行的,而是重新生成了新的字符串对象。也就是说,这些操作执行过后,原来的字符串对象并没有发生改变。”

“三妹,你记住,String 对象一旦被创建后就固定不变了,对 String 对象的任何修改都不会影响到原来的字符串对象,都会生成新的字符串对象。”

标签:为什么,String,int,不可,len,字符串,byte,buf
From: https://www.cnblogs.com/chaosssock/p/17134630.html

相关文章

  • CF923D Picking Strings
    偏简单的Ad-hoc题,但质量很高。Description传送门SolutionObservation1:我们可以将B换成C,也可以将C换成B。Proof:容易发现B->AC->AAB->AAAC->C,且C->......
  • day08-String和ArrayList
    1,基本介绍介绍两个API:String类和ArrayList类API(全称ApplicationProgrammingInterface:应用程序编程接口)就是别人写好的一些程序,给程序员直接拿去调用即可解决问题的......
  • 多线程lock 为什么要lock,lock了什么?
    一.为什么要lock,lock了什么?当我们使用线程的时候,效率最高的方式当然是异步,即各个线程同时运行,其间不相互依赖和等待。但当不同的线程都需要访问某个资源的时候,就需要同步......
  • C#两个特殊的集合类StringCollection与StringDictionary
    1、前言    为啥要写StringCollection与StringDictionary这两个集合呢?这两个集合都可以存储字符串的数据结构,都是非泛型的可以存储任何类型的数据,都是使用数组存储元......
  • 为什么子进程要继承处理器亲缘性?
    请先考虑一个典型的程序为什么需要启动一个子进程。(当然资源管理器不算一个典型的程序)这是因为手头的任务被分解为子任务,无论出于何种原因,这些子任务都被放入子流程中。......
  • 调用自定义的SplitString函数对字符串进行分割
    voidSplitString(conststd::string&s,std::vector<std::string>&v,conststd::string&c){ std::string::size_typepos1,pos2; pos2=s.find(c); pos1=0; while......
  • CF1037G A Game on Strings Sol
    有趣题。首先"分成若干个互不相干的子串"是子游戏的定义,可以用SG函数处理。然而接下来试着打了半个多小时的表,没有找到任何规律。但是发现SG函数的状态转移是很简单......
  • 线程池的回调工作函数为什么必须设置成static,类内静态成员函数?
    线程池的回调工作函数为什么必须设置成static,类内静态成员函数?pthread_create(pthread_t*thread_tid,constpthread_attr_t*attr,void*(*start_routine)(void*),......
  • 为什么C++既有指针又有引用?
    C++从C继承了指针,所以我不能在不导致严重的兼容性问题的情况下删除它们。引用在很多方面都很有用,但我在C++中引入它们的直接原因是为了支持运算符重载。例如:void......
  • 为什么默认情况下析构函数不是虚拟的
    因为很多类并不是设计来作为基类使用的,虚函数仅在用作派生类对象接口的类中有意义(通常分配在堆上并通过指针或引用访问)。那么我什么时候应该声明一个析构函数为虚拟的呢?......