std:std string substr有什么缺点

既然R大 &a data-hash=&a06cfb38e37dacdf4d7f032& href=&///people/a06cfb38e37dacdf4d7f032& class=&member_mention& data-editable=&true& data-title=&@RednaxelaFX& data-hovercard=&p$b$a06cfb38e37dacdf4d7f032&&@RednaxelaFX&/a& 没时间写,我就代为整理一下。想深入了解的可以直接戳下面三篇文献,本人已经试吃,均可放心食用。这里我只是简单地做个总结。&br&&a href=&///?target=http%3A///blog/774673& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&请别再拿“String s = new String(&xyz&);创建了多少个String实例”来面试了吧&i class=&icon-external&&&/i&&/a& - By R神-@RednaxelaFX&br&&a href=&///?target=http%3A///blog/1847971& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&借HSDB来探索HotSpot VM的运行时数据&i class=&icon-external&&&/i&&/a& - By R神&br&&a href=&///?target=http%3A///JVMInternals.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JVM Internals&i class=&icon-external&&&/i&&/a& - By James D Bloom&br&&a href=&///?target=http%3A///journal/200409/ScjpTipLine-StringsLiterally.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&The SCJP Tip Line&i class=&icon-external&&&/i&&/a& - By Corey McGlone&br&&br&String有两种赋值方式,&b&第一种是通过“字面量”赋值&/b&。比如下面这行,&br&&div class=&highlight&&&pre&&code class=&language-text&&String str = &Hello&;
&/code&&/pre&&/div&&br&&b&第二种是通过new关键字创建新对象&/b&。比如下面这样,&br&&div class=&highlight&&&pre&&code class=&language-text&&String str = new String(&Hello&);
&/code&&/pre&&/div&&br&这两种方式到底有什么不同。程序执行的时候,内存里到底有几个实例?&b&“实例”&/b&存在了内存的哪里?&b&”字面量“&/b&又存在了哪里?&b&”变量“&/b&又存在了哪里?概念很容易搞混。下面我们一个一个来讲。讲之前,先回顾一下内存。&br&&br&&img src=&/a5fb2f8f898adaeb7038c3_b.jpg& data-rawwidth=&610& data-rawheight=&400& class=&origin_image zh-lightbox-thumb& width=&610& data-original=&/a5fb2f8f898adaeb7038c3_r.jpg&&&br&&br&上面这张是虚拟机的结构图,其他先不管,我们主要看中间五彩这一条叫 “&b&运行时数据区(Run-time Data Areas)”&/b&。就是虚拟机管理的内存。就是大白话的“内存”。其中后面两个,一个程序计数器(PC Registers),一个本地方法栈(Native Method Stack)和今天讲的没关系,先忽略。一般讲起来虚拟机内存最主要的就是三块:&br&&ol&&li&&b&堆(Heap)&/b&:最大一块空间。存放对象实例和数组。全局共享。&/li&&li&&b&栈(Stack)&/b&:全称 “虚拟机栈(JVM Stacks)”。存放基本型,以及对象引用。线程私有。&/li&&li&&b&方法区(Method Area)&/b&:“类”被加载后的信息,常量,静态变量存放在这儿。全局共享。在HotSpot里也叫“永生代”。但两者不能等同。&/li&&/ol&&br&下面把这三块放大看,用显微镜照照,&br&&img src=&/51f4a70e8f951c8c3d9c0eee21e2157c_b.png& data-rawwidth=&2856& data-rawheight=&1662& class=&origin_image zh-lightbox-thumb& width=&2856& data-original=&/51f4a70e8f951c8c3d9c0eee21e2157c_r.png&&上图中,首先Heap堆分成“新生代”,“老年代”,先不用管它,这是GC垃圾回收时候的事。重要的是Stack栈区里的&b&“局部变量表(Local Variables)”&/b&和&b&“操作数栈(Operand Stack)”&/b&。因为栈是线程私有的,每个方法被执行的时候都会创建一个&b&“栈帧(Stack Frame)”&/b&。而每个栈帧里对应的都维护着一个局部变量表和操作数栈。我们老说基本型和对象引用存在栈里,其实就是存在局部变量表里。而操作数栈是线程实际的操作台。看下面这张图,做个加法100+98,局部变量表就是存数据的地方,一直不变,到加法做完再把和加进去。操作数栈就很忙了,先把两个数字压进去,再求和,算出来以后再弹出去。&br&&img src=&/48fde3be580dac7c9ff9fc89a93c35b0_b.png& data-rawwidth=&1068& data-rawheight=&454& class=&origin_image zh-lightbox-thumb& width=&1068& data-original=&/48fde3be580dac7c9ff9fc89a93c35b0_r.png&&&br&中间这个非堆(Non-Heap)可以粗略地理解为非堆里包含了永生代,而永生代里又包括了方法区。上面说了,每个类加载完之后,类的信息都存在方法区里。和String最相关的是里面的&b&“运行时常量池(Run-time Constant Pool)”&/b&。它是每个类私有的。后面会说到,每个class文件里的“常量池”在类被加载器加载之后,就映射存放在这个地方。另外一个是&b&“字符串常量池(String Pool)”&/b&。和运行时常量池不是一个概念。字符串常量池是全局共享的。位置就在第二张图里Interned String的位置,可以理解为在永生代里,方法区外面。后面会讲到,String.intern()方法,字符串驻留之后,引用就放在这个String Pool。&br&&br&心里有个内存的印象之后就可以开始说String了。&br&&br&比如下面这个&b&Test.java&/b&文件。在主线程方法main里声明了一个字面量是&Hello&的字符串str。&br&&div class=&highlight&&&pre&&code class=&language-text&&package com.ciao.shen.java.
class Test{
public void f(String s){...};
public static void main(String[] args){
String str = &Hello&;
&/code&&/pre&&/div&&br&编译成&b&Test.class&/b&文件之后,如下图,除了版本、字段、方法、接口等描述信息外,还有一个也叫&b&“常量池(Constant Pool Table)”&/b&的东西(淡绿色区块)。但这个常量池和内存里的常量池不是一个东西。class文件里的常量池主要存两个东西:&b&“字面量(&/b&&b&Literal)”&/b&和&b&“符号引用量(Symbolic References)”&/b&。其中字面量就包括类中定义的一些常量,因为String是不可变的,由final关键字修饰过了,所以代码里的“Hello”字符串,就是作为字面量(常量)写在class的常量池里。&br&&img src=&/efe43f0a055aa7f3fb05811_b.png& data-rawwidth=&1552& data-rawheight=&1206& class=&origin_image zh-lightbox-thumb& width=&1552& data-original=&/efe43f0a055aa7f3fb05811_r.png&&&br&&br&运行程序用到Test类的时候,Test.class文件的信息就会被解析到内存的方法区里。class文件里常量池里大部分数据会被加载到“运行时常量池”。但String不是。例子中的&Hello&的一个引用会被存到同样在Non Heap区的字符串常量池(String Pool)里。而“Hello”本体还是和所有对象一样,创建在Heap堆区。R大的文章里,测试的结果是在新生代的Eden区。但因为一直有一个引用驻留在字符串常量池,所以不会被GC清理掉。这个Hello对象会生存到整个线程结束。如下图所示,字符串常量池的具体位置是在过去说的永生代里,方法区的外面。&br&&img src=&/e368cf97fe6c9e73fa02a_b.png& data-rawwidth=&2784& data-rawheight=&1584& class=&origin_image zh-lightbox-thumb& width=&2784& data-original=&/e368cf97fe6c9e73fa02a_r.png&&&br&&blockquote&&b&!注意:&/b&这只是在Test类被类加载器加载时候的情形。主线程中的str变量这时候都还没有被创建,但Hello的实例已经在Heap里了,对它的引用也已经在字符串常量池里了。&/blockquote&&br&等主线程开始创建str变量的时候,虚拟机就会到字符串常量池里找,看有没有能equals(&Hello&)的String。如果找到了,就在栈区当前栈帧的局部变量表里创建str变量,然后把字符串常量池里对Hello对象的引用复制给str变量。找不到的话,才会在heap堆重新创建一个对象,然后把引用驻留到字符串常量区。然后再把引用复制栈帧的局部变量表。&br&&img src=&/ef5e400716_b.png& data-rawwidth=&2752& data-rawheight=&1502& class=&origin_image zh-lightbox-thumb& width=&2752& data-original=&/ef5e400716_r.png&&&br&如果我们当时定义了很多个值为&Hello&的String,比如像下面代码,有三个变量str1,str2,str3,也不会在堆上增加String实例。局部变量表里三个变量统一指向同一个堆内存地址。&br&&div class=&highlight&&&pre&&code class=&language-text&&package com.ciao.shen.java.
class Test{
public void f(String s){...};
public static void main(String[] args){
String str1 = &Hello&;
String str2 = &Hello&;
String str3 = &Hello&;
&/code&&/pre&&/div&&img src=&/8ebde8bf35f332_b.png& data-rawwidth=&2776& data-rawheight=&1572& class=&origin_image zh-lightbox-thumb& width=&2776& data-original=&/8ebde8bf35f332_r.png&&&br&上图中str1,str2,str3之间可以用==来连接。&br&&br&但如果是用new关键字来创建字符串,情况就不一样了,&br&&div class=&highlight&&&pre&&code class=&language-text&&package com.ciao.shen.java.
class Test{
public void f(String s){...};
public static void main(String[] args){
String str1 = &Hello&;
String str2 = &Hello&;
String str3 = new String(&Hello&);
&/code&&/pre&&/div&这时候,str1和str2还是和之前一样。但str3因为new关键字会在Heap堆申请一块全新的内存,来创建新对象。虽然字面还是&Hello&,但是完全不同的对象,有不同的内存地址。&br&&img src=&/fe6b27f35beda573c238_b.png& data-rawwidth=&2796& data-rawheight=&1552& class=&origin_image zh-lightbox-thumb& width=&2796& data-original=&/fe6b27f35beda573c238_r.png&&当然String#intern()方法让我们能手动检查字符串常量池,把有新字面值的字符串地址驻留到常量池里。&br&&br&最后补充一下,JDK 7开始Hotspot把Interned String从PermGen挪到Heap堆,JDK 8又彻底取消了PermGen。但不管怎样,基本原理还是不变的。&br&&br&点赞的话,别忘了也赞一下R大的回答。&br&------------------------------------------------------&br&我的笔记栈 &a href=&///?target=http%3A//& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a& (笔记向,非教程)
没时间写,我就代为整理一下。想深入了解的可以直接戳下面三篇文献,本人已经试吃,均可放心食用。这里我只是简单地做个总结。
- By R神-@RednaxelaFX
大白话解释就是:String很多实用的特性,比如说“不可变性”,是工程师精心设计的艺术品!艺术品易碎!用final就是拒绝继承,防止世界被熊孩子破坏,维护世界和平!&br&&br&&b&1. 什么是不可变?&/b&&br&String不可变很简单,如下图,给一个已有字符串&abcd&第二次赋值成&abcedl&,不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。&br&&img src=&/46c03ae5abffcc_b.png& data-rawwidth=&2614& data-rawheight=&846& class=&origin_image zh-lightbox-thumb& width=&2614& data-original=&/46c03ae5abffcc_r.png&&&br&&br&&b&2. String为什么不可变?&/b&&br&翻开JDK源码,&b&java.lang.String&/b&类起手前三行,是这样写的:&br&&div class=&highlight&&&pre&&code class=&language-text&&public final class String implements java.io.Serializable, Comparable&String&, CharSequence {
/** String本质是个char数组. 而且用final关键字修饰.*/
private final char value[];
&/code&&/pre&&/div&首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[ ]数组,而且是用&b&final&/b&修饰的。final修饰的字段创建以后就不可改变。&br&&br&有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住&b&Array数组是可变的&/b&事实。Array的数据结构看下图,&br&&img src=&/356d116d3fd43b622fc1_b.jpg& data-rawwidth=&487& data-rawheight=&185& class=&origin_image zh-lightbox-thumb& width=&487& data-original=&/356d116d3fd43b622fc1_r.jpg&&&br&也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&final&/span& &span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&value&/span&&span class=&o&&={&/span&&span class=&mi&&1&/span&&span class=&o&&,&/span&&span class=&mi&&2&/span&&span class=&o&&,&/span&&span class=&mi&&3&/span&&span class=&o&&}&/span&
&span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&another&/span&&span class=&o&&={&/span&&span class=&mi&&4&/span&&span class=&o&&,&/span&&span class=&mi&&5&/span&&span class=&o&&,&/span&&span class=&mi&&6&/span&&span class=&o&&};&/span&
&span class=&n&&value&/span&&span class=&o&&=&/span&&span class=&n&&another&/span&&span class=&o&&;&/span&
&span class=&c1&&//编译器报错,final不可变&/span&
&/code&&/pre&&/div&value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&final&/span& &span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&value&/span&&span class=&o&&={&/span&&span class=&mi&&1&/span&&span class=&o&&,&/span&&span class=&mi&&2&/span&&span class=&o&&,&/span&&span class=&mi&&3&/span&&span class=&o&&};&/span&
&span class=&n&&value&/span&&span class=&o&&[&/span&&span class=&mi&&2&/span&&span class=&o&&]=&/span&&span class=&mi&&100&/span&&span class=&o&&;&/span&
&span class=&c1&&//这时候数组里已经是{1,2,100}&/span&
&/code&&/pre&&/div&或者 @&a href=&/people/liu-shuo-84-23& class=&internal&&刘硕 &/a&评论说喜欢更粗暴的反射直接改,也是可以的。&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&final&/span& &span class=&kt&&int&/span&&span class=&o&&[]&/span& &span class=&n&&array&/span&&span class=&o&&={&/span&&span class=&mi&&1&/span&&span class=&o&&,&/span&&span class=&mi&&2&/span&&span class=&o&&,&/span&&span class=&mi&&3&/span&&span class=&o&&};&/span&
&span class=&n&&Array&/span&&span class=&o&&.&/span&&span class=&na&&set&/span&&span class=&o&&(&/span&&span class=&n&&array&/span&&span class=&o&&,&/span&&span class=&mi&&2&/span&&span class=&o&&,&/span&&span class=&mi&&100&/span&&span class=&o&&);&/span& &span class=&c1&&//数组也被改成{1,2,100}&/span&
&/code&&/pre&&/div&&br&所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以&b&String是不可变的关键都在底层的实现,而不是一个final。&/b&考验的是工程师构造数据类型,封装数据的功力。&br&&br&&b&3. 不可变有什么好处?&/b&&br&这个最简单地原因,就是为了&b&安全&/b&。看下面这个场景(有评论反应例子不够清楚,现在完整地写出来),一个函数appendStr( )在不可变的String参数后面加上一段“bbb”后返回。appendSb( )负责在可变的StringBuilder后面加“bbb”。&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&class&/span& &span class=&nc&&Test&/span&&span class=&o&&{&/span&
&span class=&c1&&//不可变的String&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&n&&String&/span& &span class=&nf&&appendStr&/span&&span class=&o&&(&/span&&span class=&n&&String&/span& &span class=&n&&s&/span&&span class=&o&&){&/span&
&span class=&n&&s&/span&&span class=&o&&+=&/span&&span class=&s&&&bbb&&/span&&span class=&o&&;&/span&
&span class=&k&&return&/span& &span class=&n&&s&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&c1&&//可变的StringBuilder&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&n&&StringBuilder&/span& &span class=&nf&&appendSb&/span&&span class=&o&&(&/span&&span class=&n&&StringBuilder&/span& &span class=&n&&sb&/span&&span class=&o&&){&/span&
&span class=&k&&return&/span& &span class=&n&&sb&/span&&span class=&o&&.&/span&&span class=&na&&append&/span&&span class=&o&&(&/span&&span class=&s&&&bbb&&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&main&/span&&span class=&o&&(&/span&&span class=&n&&String&/span&&span class=&o&&[]&/span& &span class=&n&&args&/span&&span class=&o&&){&/span&
&span class=&c1&&//String做参数&/span&
&span class=&n&&String&/span& &span class=&n&&s&/span&&span class=&o&&=&/span&&span class=&k&&new&/span& &span class=&n&&String&/span&&span class=&o&&(&/span&&span class=&s&&&aaa&&/span&&span class=&o&&);&/span&
&span class=&n&&String&/span& &span class=&n&&ns&/span&&span class=&o&&=&/span&&span class=&n&&Test&/span&&span class=&o&&.&/span&&span class=&na&&appendStr&/span&&span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&);&/span&
&span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&String aaa &&& &&/span&&span class=&o&&+&/span&&span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&na&&toString&/span&&span class=&o&&());&/span&
&span class=&c1&&//StringBuilder做参数&/span&
&span class=&n&&StringBuilder&/span& &span class=&n&&sb&/span&&span class=&o&&=&/span&&span class=&k&&new&/span& &span class=&n&&StringBuilder&/span&&span class=&o&&(&/span&&span class=&s&&&aaa&&/span&&span class=&o&&);&/span&
&span class=&n&&StringBuilder&/span& &span class=&n&&nsb&/span&&span class=&o&&=&/span&&span class=&n&&Test&/span&&span class=&o&&.&/span&&span class=&na&&appendSb&/span&&span class=&o&&(&/span&&span class=&n&&sb&/span&&span class=&o&&);&/span&
&span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&StringBuilder aaa &&& &&/span&&span class=&o&&+&/span&&span class=&n&&sb&/span&&span class=&o&&.&/span&&span class=&na&&toString&/span&&span class=&o&&());&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&c1&&//Output: &/span&
&span class=&c1&&//String aaa &&& aaa&/span&
&span class=&c1&&//StringBuilder aaa &&& aaabbb &/span&
&/code&&/pre&&/div&如果程序员不小心像上面例子里,直接在传进来的参数上加&bbb&,因为Java对象参数传的是引用,所以可变的的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了&aaabbb&。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。&br&&br&再看下面这个&b&HashSet&/b&用StringBuilder做元素的场景,问题就更严重了,而且更隐蔽。&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&class&/span& &span class=&nc&&Test&/span&&span class=&o&&{&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&void&/span& &span class=&nf&&main&/span&&span class=&o&&(&/span&&span class=&n&&String&/span&&span class=&o&&[]&/span& &span class=&n&&args&/span&&span class=&o&&){&/span&
&span class=&n&&HashSet&/span&&span class=&o&&&&/span&&span class=&n&&StringBuilder&/span&&span class=&o&&&&/span& &span class=&n&&hs&/span&&span class=&o&&=&/span&&span class=&k&&new&/span& &span class=&n&&HashSet&/span&&span class=&o&&&&/span&&span class=&n&&StringBuilder&/span&&span class=&o&&&();&/span&
&span class=&n&&StringBuilder&/span& &span class=&n&&sb1&/span&&span class=&o&&=&/span&&span class=&k&&new&/span& &span class=&n&&StringBuilder&/span&&span class=&o&&(&/span&&span class=&s&&&aaa&&/span&&span class=&o&&);&/span&
&span class=&n&&StringBuilder&/span& &span class=&n&&sb2&/span&&span class=&o&&=&/span&&span class=&k&&new&/span& &span class=&n&&StringBuilder&/span&&span class=&o&&(&/span&&span class=&s&&&aaabbb&&/span&&span class=&o&&);&/span&
&span class=&n&&hs&/span&&span class=&o&&.&/span&&span class=&na&&add&/span&&span class=&o&&(&/span&&span class=&n&&sb1&/span&&span class=&o&&);&/span&
&span class=&n&&hs&/span&&span class=&o&&.&/span&&span class=&na&&add&/span&&span class=&o&&(&/span&&span class=&n&&sb2&/span&&span class=&o&&);&/span&
&span class=&c1&&//这时候HashSet里是{&aaa&,&aaabbb&}&/span&
&span class=&n&&StringBuilder&/span& &span class=&n&&sb3&/span&&span class=&o&&=&/span&&span class=&n&&sb1&/span&&span class=&o&&;&/span&
&span class=&n&&sb3&/span&&span class=&o&&.&/span&&span class=&na&&append&/span&&span class=&o&&(&/span&&span class=&s&&&bbb&&/span&&span class=&o&&);&/span&
&span class=&c1&&//这时候HashSet里是{&aaabbb&,&aaabbb&}&/span&
&span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&n&&hs&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&c1&&//Output:&/span&
&span class=&c1&&//[aaabbb, aaabbb]&/span&
&/code&&/pre&&/div&StringBuilder型变量sb1和sb2分别指向了堆内的字面量&aaa&和&aaabbb&。把他们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先&aaa&的地址上改。导致sb1的值也变了。这时候,HashSet上就出现了两个相等的键值&aaabbb&。&b&破坏了HashSet键值的唯一性&/b&。所以&b&千万不要用可变类型做HashMap和HashSet键值。&/b&&br&&br&还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竟态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以&b&线程安全&/b&。&br&&br&最后别忘了String另外一个&b&字符串常量池&/b&的属性。像下面这样字符串one和two都用字面量&something&赋值。它们其实都指向同一个内存地址。&br&&div class=&highlight&&&pre&&code class=&language-text&&String one = &someString&;
String two = &someString&;
&/code&&/pre&&/div&&img src=&/71db82c93d06f8a9c0b247_b.png& data-rawwidth=&1000& data-rawheight=&602& class=&origin_image zh-lightbox-thumb& width=&1000& data-original=&/71db82c93d06f8a9c0b247_r.png&&&br&这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。&br&&br&------------------------------------------------------&br&我的笔记栈 &a href=&///?target=http%3A//& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a& (笔记向,非教程)
大白话解释就是:String很多实用的特性,比如说“不可变性”,是工程师精心设计的艺术品!艺术品易碎!用final就是拒绝继承,防止世界被熊孩子破坏,维护世界和平! 1. 什么是不可变? String不可变很简单,如下图,给一个已有字符串"abcd"第二次赋值成"abc…
&ul&&li&首先,在编译期间有种东西叫做常量折叠&br&&/li&&/ul&比如&br&&div class=&highlight&&&pre&&code class=&language-text&&String s1 = &a& + &b&;
int a = 1 + 3;
&/code&&/pre&&/div&前端编译器(简单理解就是javac)会给你把值算出来,也就是说变成字节码后,会变成下面这样&br&&div class=&highlight&&&pre&&code class=&language-text&&String s1 = &ab&;
int a = 4;
&/code&&/pre&&/div&于是你的代码相当于&br&&div class=&highlight&&&pre&&code class=&language-text&&String s1 = &ab&;
String s2 = &a&;
String s3 = s2 + &b&;
System.out.println(s1 == &ab&);
System.out.println(s3 == &ab&);
&/code&&/pre&&/div&&ul&&li&然后,题主或许知道有种东西叫常量池&/li&&/ul&比如&br&&div class=&highlight&&&pre&&code class=&language-text&&String str1 = &ab&;
String str2 = &ab&;
&/code&&/pre&&/div&str1和str2是一模一样的对吧,然后String又是不可变的,这就没有必要弄出两个&ab&对象了,在内存中(常量池中)只有一个&ab&,str1和str2都指向它,所以这里str1=str2应该不难理解。&br&于是System.out.println(s1 == &ab&);结果是true&br&&ul&&li&最后,对字符串进行+操作的内部实现&/li&&/ul&也就是String s3 = s2 + &b&;内部是怎么回事,其实就是创建了一个StringBuilder对象,然后一直append。换句话说String s3 = s2 + &b&;就是String s3 = new StringBuilder().append(s2).append(&b&).toString()。唉,直接看StringBuilder的toString方法吧:&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&nd&&@Override&/span&
&span class=&kd&&public&/span& &span class=&n&&String&/span& &span class=&nf&&toString&/span&&span class=&o&&()&/span& &span class=&o&&{&/span&
&span class=&c1&&// Create a copy, don't share the array&/span&
&span class=&k&&return&/span& &span class=&k&&new&/span& &span class=&n&&String&/span&&span class=&o&&(&/span&&span class=&n&&value&/span&&span class=&o&&,&/span& &span class=&mi&&0&/span&&span class=&o&&,&/span& &span class=&n&&count&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&它new了一个对象,new出来的东西位于堆上,也就是说s3指向了堆上的一块内存,而s1指向的东东位于常量池,所以使用==会返回false,这就是你第二次打印是false的原因&br&&ul&&li&扩展,字符串内部化技术&/li&&/ul&String类提供了intern()方法来返回与当前字符串内容相同但已经被包含在常量池中的对象引用&br&&div class=&highlight&&&pre&&code class=&language-text&&System.out.println(new String(&Hello&) == &Hello&);
System.out.println(new String(&He&).intern() == &He&); // true
&/code&&/pre&&/div&
首先,在编译期间有种东西叫做常量折叠 比如 String s1 = "a" + "b";
int a = 1 + 3;
前端编译器(简单理解就是javac)会给你把值算出来,也就是说变成字节码后,会变成下面这样 String s1 = "ab";
int a = 4;
于是你的代码相当于 String s1 = "ab";
Java很无辜:“你只是让我截取个字符串,没让我输出啊?”&br&System.out.println(...)才能输出。
Java很无辜:“你只是让我截取个字符串,没让我输出啊?” System.out.println(...)才能输出。
&p&String a = &a&的时候:&/p&&p&会申请一块内存(一号),用来存放&a&作为一个字符串常量&/p&&p&然后再申请一块内存(二号),用来存放变量a的内容:之前创建的&a&的内存地址&/p&&p&String b = &a&的时候:&/p&&p&会申请一块内存(三号),用来存放变量b的内容:之前创建的“a”的内存地址&/p&&p&&br&&/p&&p&a==b,比较的是二号内存和三号内存的值。&/p&&p&它们的值都是一号内存的地址。&/p&&p&所以是true&/p&&p&&br&&/p&&p&题主的问题就是为什么我总是不建议别人先学java的原因……&/p&&p&很烦的,还有string object,stringbuffer,stringbuilder。&/p&&p&我这种没有在java上花多少时间的老咸鱼(熟练度--------),&/p&&p&不仔细想想看看doc都会踩坑,&/p&&p&没有关于内存管理的基础知识,很容易就知其然不知其所以然了……&/p&
String a = "a"的时候:会申请一块内存(一号),用来存放"a"作为一个字符串常量然后再申请一块内存(二号),用来存放变量a的内容:之前创建的"a"的内存地址String b = "a"的时候:会申请一块内存(三号),用来存放变量b的内容:之前创建的“a”的内存地…
&Hello&字面量对应的java.lang.String对象会存在于Java heap里。它在一次运行中只会有一份,不会被重复创建。&br&&br&传送门:&a href=&///?target=http%3A///blog/774673& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&请别再拿“String s = new String(&xyz&);创建了多少个String实例”来面试了吧&i class=&icon-external&&&/i&&/a&&br&有耐心读完的话,真相在里面。不想再多写了。
"Hello"字面量对应的java.lang.String对象会存在于Java heap里。它在一次运行中只会有一份,不会被重复创建。 传送门: 有耐心读完的话,真相在里面。不想再多写了。
&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&n&&string&/span& &span class=&p&&(&/span&&span class=&o&&&&/span&&span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&))[&/span&&span class=&mi&&10&/span&&span class=&p&&]&/span&
&span class=&p&&{&/span&
&span class=&k&&static&/span& &span class=&n&&string&/span& &span class=&n&&fuck&/span&&span class=&p&&[&/span&&span class=&mi&&10&/span&&span class=&p&&];&/span&
&span class=&k&&return&/span& &span class=&n&&fuck&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&
string (&f(void))[10]
static string fuck[10];
很多搞C++的人有一个缺点,就是看不到需求的变化,总觉得在目前的条件下能hack进去完事就行了。然而std::string并不能hack。&br&&br&譬如说,我知道这个object的ref count在这里只有1(也不写个assert,就是猜的),于是我可以乱改他,虽然本来object设计成immutable的每次都要创建新的也不要紧。于是就给这个object加了一个friend class——于是后面需求变更了,就创造了一个烂屁股。&br&&br&我觉得天朝的IT公司(特别是那些不好好做code review的)应该成立一个制度,如果一个人的代码的屁股太臭还被擦干净了,本人应该直播用砂纸擦屁股一次。该协议不包含在劳动合同里面,终身有效。
很多搞C++的人有一个缺点,就是看不到需求的变化,总觉得在目前的条件下能hack进去完事就行了。然而std::string并不能hack。 譬如说,我知道这个object的ref count在这里只有1(也不写个assert,就是猜的),于是我可以乱改他,虽然本来object设计成immutab…
&p&受R大委托更新回答。&/p&&br&&h2&都有哪些常量池?&/h2&&p&1.Class文件中的常量池&/p&&p&这里面主要存放两大类常量:&/p&&p&
字面量(Literal):文本字符串等&/p&&p& 符号引用(Symbolic References):属于编译原理方面的概念,包含三类常量:&/p&&p&
类和接口的全限定名(Full Qualified Name)&/p&&p&
字段的名称和描述符(Descriptor)&/p&&p&
方法的名称和描述符&/p&&p&这个用javap看一下就能明白,这里只涉及字符串就不谈其他的了。简单地说,用双引号引起来的字符串字面量都会进这里面。&/p&&p&2.运行时常量池&/p&&p&方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池(Constant Pool Table),存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池。&/p&&p&3.全局字符串常量池&/p&&p&HotSpot VM里,记录interned string的一个全局表叫做StringTable,它本质上就是个HashSet&String&。这是个纯运行时的结构,而且是惰性(lazy)维护的。注意它只存储对java.lang.String实例的引用,而不存储String对象的内容。 注意,它只存了引用,根据这个引用可以得到具体的String对象。&/p&&p&一般我们说一个字符串进入了全局的字符串常量池其实是说在这个StringTable中保存了对它的引用,反之,如果说没有在其中就是说StringTable中没有对它的引用。&/p&&br&&h2&String的 intern 方法干了什么?&/h2&&p&题中没有涉及JDK6就不谈了。JDK7中,如果常量池中已经有了这个字符串,那么直接返回常量池中它的引用,如果没有,那就将它的引用保存一份到字符串常量池,然后直接返回这个引用。敲黑板,这个方法是有返回值的,是返回引用。&/p&&br&&h2&s1.intern(); 和 s1 = s1.intern();一样吗?&/h2&&p&差远了。然而题主给的那篇东东里面,毛看一眼似乎这俩在混用,但愿是我看花眼了。。。一个引用a,指向了一个具体的对象,然后调用了一个方法func,请问这个方法会对a本身产生什么影响吗?没有吧,换句话说,a.func(..)执行完之后,a原来指向谁还是指向谁吧,对不对?所以s1.intern();对s1有什么影响吗?一点影响都没有,原来指向哪现在还指向哪。s1 = s1.intern();就不一样了,你把intern方法的返回值给了s1,s1是可以重新指向的对吧。&/p&&p&&img src=&&a href=&///?target=https%3A///v2-9ded5d5c2dd61f0b73227e7_b.jpg& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/v2-9ded5&/span&&span class=&invisible&&d5c2dd61f0b73227e7_b.jpg&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&& data-rawwidth=&681& data-rawheight=&430& class=&content_image& width=&681& data-original=&&a href=&///?target=https%3A///v2-9ded5d5c2dd61f0b73227e7_r.jpg& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/v2-9ded5&/span&&span class=&invisible&&d5c2dd61f0b73227e7_r.jpg&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&&&/p&&h2&字面量进入字符串常量池的时机 &/h2&&p&题主根据R大文章一开始便得出了两个结论,其中第二个是:&/p&&blockquote&在类加载阶段, JVM会在堆中创建 对应这些 class文件常量池中的 字符串对象实例
并在字符串常量池中驻留其引用。具体在resolve阶段执行。这些常量全局共享。 &/blockquote&&p&这里说的比较笼统,没错,是resolve阶段,但是并不是大家想的那样,立即就创建对象并且在字符串常量池中驻留了引用。 &b&JVM规范里明确指定resolve阶段可以是lazy的。&/b&&/p&&p&JVM规范里Class文件的常量池项的类型,有两种东西:
CONSTANT_Utf8
CONSTANT_String
后者是String常量的类型,但它并不直接持有String常量的内容,而是只持有一个index,这个index所指定的另一个常量池项必须是一个CONSTANT_Utf8类型的常量,这里才真正持有字符串的内容。
&/p&&p&在HotSpot VM中,运行时常量池里,
CONSTANT_Utf8 -& Symbol*(一个指针,指向一个Symbol类型的C++对象,内容是跟Class文件同样格式的UTF-8编码的字符串)
CONSTANT_String -& java.lang.String(一个实际的Java对象的引用,C++类型是oop) &/p&&p&CONSTANT_Utf8会在类加载的过程中就全部创建出来,而&b&CONSTANT_String则是lazy resolve的,例如说在第一次引用该项的ldc指令被第一次执行到的时候才会resolve&/b&。那么在尚未resolve的时候,HotSpot VM把它的类型叫做JVM_CONSTANT_UnresolvedString,内容跟Class文件里一样只是一个index;等到resolve过后这个项的常量类型就会变成最终的JVM_CONSTANT_String,而内容则变成实际的那个oop。 &/p&&p&看到这里想必也就明白了, 就HotSpot VM的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在StringTable中并没有相应的引用,在堆中也没有对应的对象产生)&/p&&br&&h2&&b&ldc指令是什么东西?&/b&&/h2&&p&简单地说,它用于将int、float或String型常量值从常量池中推送至栈顶&/p&&p&以下面代码为例&/p&&p&public class Abc { public static void main(String[] args) { String a = &AA&; } } &/p&&p&查看其编译后的Class文件如下&/p&&p&&img src=&&a href=&///?target=https%3A///v2-4d64e037afb334c2d6e6350c15bbc4d8_b.jpg& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/v2-4d64e&/span&&span class=&invisible&&037afb334c2d6e6350c15bbc4d8_b.jpg&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&& data-rawwidth=&531& data-rawheight=&329& class=&content_image& width=&531& data-original=&&a href=&///?target=https%3A///v2-4d64e037afb334c2d6e6350c15bbc4d8_r.jpg& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/v2-4d64e&/span&&span class=&invisible&&037afb334c2d6e6350c15bbc4d8_r.jpg&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&&&/p&&p&使用ldc将&AA&送到栈顶,然后用astore_1把它赋值给我们定义的局部变量a,然后就没什么事了return了。&/p&&p&根据上面说的,在类加载阶段,这个 resolve 阶段( constant pool resolution )是lazy的。换句话说并没有真正的对象,字符串常量池里自然也没有,那么ldc指令还怎么把人推送至栈顶?或者换一个角度想,既然resolve 阶段是lazy的,那总有一个时候它要真正的执行吧,是什么时候?&/p&&p&&b&执行ldc指令就是触发这个lazy resolution动作的条件 &/b&&/p&&p&ldc字节码在这里的执行语义是:到当前类的运行时常量池(runtime constant pool,HotSpot VM里是ConstantPool + ConstantPoolCache)去查找该index对应的项,如果该项尚未resolve则resolve之,并返回resolve后的内容。
在遇到String类型常量时,resolve的过程如果发现StringTable已经有了内容匹配的java.lang.String的引用,则直接返回这个引用,反之,如果StringTable里尚未有内容匹配的String实例的引用,则会在Java堆里创建一个对应内容的String对象,然后在StringTable记录下这个引用,并返回这个引用出去。&/p&&p&可见,ldc指令是否需要创建新的String实例,全看在第一次执行这一条ldc指令时,StringTable是否已经记录了一个对应内容的String的引用。 &/p&&br&&h2&这叫个什么标题好?&/h2&&p&对于HotSpot VM的实现,考虑题主给出的第一段代码: &/p&&p&class NewTest1{
public static String s1=&static&;
public static void main(String[] args) {
String s1=new String(&he&)+new String(&llo&); //第二句
s1.intern();
String s2=&hello&;
System.out.println(s1==s2);//第五句,输出是true。
&/p&&p&&static& &he& &llo& &hello&都会进入Class的常量池, 按照上面说的,类加载阶段由于resolve 阶段是lazy的,所以是不会创建实例,更不会驻留字符串常量池了。但是要注意这个“static”和其他三个不一样,它是静态的,在类加载阶段中的初始化阶段,会为静态变量指定初始值,也就是要把“static”赋值给s1(main方法里面怎么还有个s1,这里说的是外面那个静态的),这个赋值操作要怎么搞啊,先ldc指令把它放到栈顶,然后用putstatic指令完成赋值。注意,ldc指令,根据上面说的,会创建&static&字符串对象,并且会保存一个指向它的引用到字符串常量池。&/p&&p&额,我好像把第一句已经说了。&/p&&p&运行main方法后,首先是第二句,一样的,要先用ldc把&he&和&llo&送到栈顶,换句话说,会创建他俩的对象,并且会保存引用到字符串常量池中;然后有个+号对吧,内部是创建了一个StringBuilder对象,一路append,最后调用StringBuilder对象的toString方法得到一个String对象(内容是hello,注意这个toString方法会new一个String对象),并把它赋值给s1。注意啊,没有把hello的引用放入字符串常量池。&/p&&p&然后是第三句,intern方法一看,字符串常量池里面没有,它会把上面的这个hello对象的引用保存到字符串常量池,然后返回这个引用,但是这个返回值我们并没有使用变量去接收,所以没用。&/p&&p&第四句,字符串常量池里面已经有了,直接用嘛&/p&&p&第五句,已经很明显了。&/p&&p&再看另外一段代码:&/p&&p&class NewTest2{ public static void main(String[] args) { String s1=new String(&he&)+new String(&llo&); // ① String s2=new String(&h&)+new String(&ello&); // ② String s3=s1.intern(); // ③ String s4=s2.intern(); // ④ System.out.println(s1==s3); System.out.println(s1==s4); } } &/p&&p&类加载阶段,什么都没干。&/p&&p&然后运行main方法,先看第一句,会创建&he&和&llo&对象,并放入字符串常量池,然后会创建一个&hello&对象,没有放入字符串常量池,s1指向这个&hello&对象。&/p&&p&第二句,创建&h&和&ello&对象,并放入字符串常量池,然后会创建一个&hello&对象,没有放入字符串常量池,s2指向这个&hello&对象。&/p&&p&第三句,字符串常量池里面还没有,于是会把s1指向的String对象的引用放入字符串常量池(换句话说,放入池中的引用和s1指向了同一个对象),然后会把这个引用返回给了s3,所以s3==s1是true。&/p&&p&第四句,字符串常量池里面已经有了,直接将它返回给了s4,所以s4==s1是true。&/p&&br&&p&写的我腰酸背痛的... (逃 &/p&
受R大委托更新回答。 都有哪些常量池?1.Class文件中的常量池这里面主要存放两大类常量: 字面量(Literal):文本字符串等 符号引用(Symbolic References):属于编译原理方面的概念,包含三类常量:
类和接口的全限定名(Full Qualified Name)
字段的名称和…
无规定,但是我认为内部没有理由不 以零结尾或不预留结尾零的位置&br&&br&原因在于&b&c_str()&/b&这个函数的调用&br&&br&&b&这个函数会返回c风格的字符串,是以零结尾的。如果内部不以零结尾或不预留结尾零的位置,那么这个函数的实现会比较低效率,因为意味着要重新分配更大的缓冲区来盛放数据。&/b&&br&&br&因此(或还有其他原因),主流实现都会以零结尾或预留结尾零的位置。
无规定,但是我认为内部没有理由不 以零结尾或不预留结尾零的位置 原因在于c_str()这个函数的调用 这个函数会返回c风格的字符串,是以零结尾的。如果内部不以零结尾或不预留结尾零的位置,那么这个函数的实现会比较低效率,因为意味着要重新分配更大的缓冲…
对我来说,std::string几乎无缺点,完全符合我的胃口:最简化模块设计;&br&我先来说下我对std::string的理解:以字符作为元素的vector特化版本;在std::string中,没0字符结尾这个概念,也能装入\0这种数据;一定要牢记,这是个容器,拥有容器的一切特征。&br&现在开始根据大家的槽点,一一解决:&br&1,不支持split,find & substr即可解决,何况还有几个加强版的find, 虽然在C++11中,你可以使用更通用的闭包版本来解决,但他的确提供了几个方便又好理解的加强版:find_first_of, find_last_of, find_first_not_of, find_last_not_of;&br&2,不支持正则表达式,麻烦你告诉我那个C++的字符串类本身就支持正则表达式,我观摩下;如果你需要这个功能,C++11你可以使用std::regex, 不支持的你可以找个第三库,比如说Boost!&br&3,不支持文本处理,这是容器,他支持迭代器呀,比如说你想把所有字母变成小写,Just:transform(s.begin(), s.end(), s.begin(), tolower); C++ 11中,配合lamada表达式简直好用极了,如果还不过瘾,你还可以构建一个stringstream,是不是爽到爆;&br&4,效率问题,是的,C++ Standard只是定义了string的接口,具体的实现并没要求,所以一些string实现并没采用reference counting的方式,但这又有什么关系,引用计数的实现只是加速了复制和拷贝,并且这还是有限制的,必须是const reference,如果的确是const,那一个const shared_ptr&string&是不是就解决了这个问题(题外话:如果你还有一点点C++程序员的节操,还在乎性能,请尽量使用const);此外如果你真的真的真的那么在乎效率,这是个容器呀,他支持allocator,你可以实现一个自己的内存模型,想怎么高效就怎么高效;&br&&br&最后:如果我说我近几年一直把string当做数据缓冲来用,会不会有人吐槽我;好吧,这真的是最好的数据缓冲了,特别是对于字符型数据,一经拥有,别无所求!
对我来说,std::string几乎无缺点,完全符合我的胃口:最简化模块设计; 我先来说下我对std::string的理解:以字符作为元素的vector特化版本;在std::string中,没0字符结尾这个概念,也能装入\0这种数据;一定要牢记,这是个容器,拥有容器的一切特征。 现…
因为&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&n&&str2&/span&&span class=&o&&=&/span& &span class=&n&&str1&/span& &span class=&o&&+&/span& &span class=&p&&(&/span&&span class=&n&&str1&/span&&span class=&p&&.&/span&&span class=&n&&size&/span&&span class=&p&&()&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span&&span class=&sc&&' '&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&br&的意思就是(不是很精确但是你意会就好)&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&n&&str1&/span&&span class=&p&&.&/span&&span class=&n&&size&/span&&span class=&p&&()&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&n&&str2&/span& &span class=&o&&=&/span& &span class=&n&&str1&/span& &span class=&o&&+&/span& &span class=&sc&&' '&/span&&span class=&p&&;&/span&
&/code&&/pre&&/div&&br&你要写成&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&n&&str2&/span&&span class=&o&&=&/span& &span class=&n&&str1&/span& &span class=&o&&+&/span& &span class=&n&&string&/span&&span class=&p&&(&/span&&span class=&n&&str1&/span&&span class=&p&&.&/span&&span class=&n&&size&/span&&span class=&p&&()&/span& &span class=&o&&-&/span& &span class=&mi&&1&/span&&span class=&p&&,&/span&&span class=&sc&&' '&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&br&就可以达到你的目的
因为 str2= str1 + (str1.size() - 1,' '); 的意思就是(不是很精确但是你意会就好) str1.size() - 1;
str2 = str1 + ' '; 你要写成 str2= str1 + string(str1.size() - 1,' '); 就可以达到你的目的
送上当时添加这个功能的介绍文:&a href=&///?target=https%3A///darcy/entry/project_coin_string_switch_anatomy& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Project Coin: Anatomy of adding strings in switch to javac (Joseph D. Darcy's Oracle Weblog)&i class=&icon-external&&&/i&&/a&&br&&br&Java 7的switch里的string case支持通过两层switch实现,&br&第一层先通过hashCode缩小要比较的字符串的范围,然后里面逐个equals判断到底是哪个字符串来解决hash冲突,并相应设置到底是哪个case。其结构只跟case里出现了哪些字符串常量相关,跟原本代码里的switch的结构无关(是否fallthrough之类);&br&第二层switch根据第一层switch选出的case映射到用户指定的动作,其结构跟原本代码里的switch的结构一致:&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&k&&switch&/span& &span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&case&/span& &span class=&s&&&alpha&&/span&&span class=&o&&:&/span& &span class=&c1&&// fall through&/span&
&span class=&k&&case&/span& &span class=&s&&&beta&&/span&&span class=&o&&:&/span&
&span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&ab&&/span&&span class=&o&&);&/span& &span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&k&&case&/span& &span class=&s&&&Ea&&/span&&span class=&o&&:&/span&
&span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&e&&/span&&span class=&o&&);&/span& &span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&k&&case&/span& &span class=&s&&&FB&&/span&&span class=&o&&:&/span&
&span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&f&&/span&&span class=&o&&);&/span& &span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&会变成:&br&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kt&&int&/span& &span class=&n&&_switchSelection_&/span& &span class=&o&&=&/span& &span class=&o&&-&/span&&span class=&mi&&1&/span&&span class=&o&&;&/span&
&span class=&k&&switch&/span& &span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&na&&hashCode&/span&&span class=&o&&())&/span& &span class=&o&&{&/span&
&span class=&k&&case&/span& &span class=&mi&&&/span&&span class=&o&&:&/span& &span class=&c1&&// &alpha&.hashCode()&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&na&&equals&/span&&span class=&o&&(&/span&&span class=&s&&&alpha&&/span&&span class=&o&&))&/span&
&span class=&n&&_switchSelection_&/span& &span class=&o&&=&/span& &span class=&mi&&0&/span&&span class=&o&&;&/span&
&span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&k&&case&/span& &span class=&mi&&3020272&/span&&span class=&o&&:&/span&
&span class=&c1&&// &beta&.hashCode()&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&na&&equals&/span&&span class=&o&&(&/span&&span class=&s&&&beta&&/span&&span class=&o&&))&/span&
&span class=&n&&_switchSelection_&/span& &span class=&o&&=&/span& &span class=&mi&&1&/span&&span class=&o&&;&/span&
&span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&k&&case&/span& &span class=&mi&&2236&/span&&span class=&o&&:&/span&
&span class=&c1&&// &Ea&.hashCode() and &FB&.hashCode()&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&na&&equals&/span&&span class=&o&&(&/span&&span class=&s&&&FB&&/span&&span class=&o&&))&/span&
&span class=&n&&_switchSelection_&/span& &span class=&o&&=&/span& &span class=&mi&&3&/span&&span class=&o&&;&/span&
&span class=&k&&else&/span& &span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&na&&equals&/span&&span class=&o&&(&/span&&span class=&s&&&Ea&&/span&&span class=&o&&))&/span& &span class=&n&&_switchSelection_&/span& &span class=&o&&=&/span& &span class=&mi&&2&/span&&span class=&o&&;&/span&
&span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&span class=&k&&switch&/span& &span class=&o&&(&/span&&span class=&n&&_switchSelection_&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&case&/span& &span class=&mi&&0&/span&&span class=&o&&:&/span& &span class=&c1&&// fall through&/span&
&span class=&k&&case&/span& &span class=&mi&&1&/span&&span class=&o&&:&/span& &span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&ab&&/span&&span class=&o&&);&/span& &span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&k&&case&/span& &span class=&mi&&2&/span&&span class=&o&&:&/span& &span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&e&&/span&&span class=&o&&);&/span& &span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&k&&case&/span& &span class=&mi&&3&/span&&span class=&o&&:&/span& &span class=&n&&System&/span&&span class=&o&&.&/span&&span class=&na&&out&/span&&span class=&o&&.&/span&&span class=&na&&println&/span&&span class=&o&&(&/span&&span class=&s&&&f&&/span&&span class=&o&&);&/span& &span class=&k&&break&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&java.lang.String的hashCode()算法被规范规定死了,如下:&br&&blockquote&The hash code for a String object is computed as&br&&div class=&highlight&&&pre&&code class=&language-text&&s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
&/code&&/pre&&/div&&/blockquote&&a href=&///?target=http%3A///javase/7/docs/api/java/lang/String.html%23hashCode%28%29& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/javase/&/span&&span class=&invisible&&7/docs/api/java/lang/String.html#hashCode()&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&
送上当时添加这个功能的介绍文: Java 7的switch里的string case支持通过两层switch实现, 第一层先通过hashCode缩小要比较的字符串的范围,然后里面逐个equ…
感觉像是在设计Java语言层面的annotation功能时,受JVM当时直接支持的常量类型的限制而做的一个取舍。&br&&br&我们可以看看JVM规范(Java SE 8版)的&a href=&///?target=https%3A///javase/specs/jvms/se8/html/jvms-4.html%23jvms-4.7.16.1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&4.7.16.1. The element_value structure&i class=&icon-external&&&/i&&/a&对annotation的element可以取的值在Class文件中的记录的规定。&br&&br&首先,在Class文件里,所有原始类型、String、Class常量都是有特殊支持的,而对一般Object的“常量”则是不支持的,对enum常量也是不支持的。&br&Annotation的参数(element)也是一种编译时常量,必须能够被Class文件所支持的常量项所表述;所以它可以支持原始类型、String、Class常量是很直观的事,不需要对Class文件以及JVM动多少刀子。&br&&br&但光支持这些类型对Java语言层面的新功能来说太憋屈了。好歹给支持个enum对不对?参数里还想放别的annotation值对不对?另外如果需要放一串同类型的值的话,还得支持数组对不对?&br&所以上面链接里提到的Class文件里的element_value结构就对这几种特殊的扩展情况做了特定的支持。仅此而已。这特定的支持的机制是无法泛化到支持一般的Object“常量”的。&br&&br&===================================&br&&br&今年刚开过的的JVM Language Summit 2016上,John Rose的VM Futures演讲还提到了希望未来能在Class文件里支持复杂对象类型的常量。等那个功能真的提上日程之后,题主这个疑问或许就能得到新的答案了——任意能合理的看作常量的对象类型都应该能作为annotation的参数值了。&br&演讲的录像:&a href=&///?target=https%3A///watch%3Fv%3Dgii6ySfsVfs& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/watch?&/span&&span class=&invisible&&v=gii6ySfsVfs&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&(跟新常量类型相关的部分在31:30左右开始)
感觉像是在设计Java语言层面的annotation功能时,受JVM当时直接支持的常量类型的限制而做的一个取舍。 我们可以看看JVM规范(Java SE 8版)的对annotation的element可以取的值在Class文件中的记录的规定。 首先,在Cl…
C++11开始要求必须以\0结尾,具体的:&br&operator[]的参数可以是size(),此时返回\0,但是你不能修改这个返回的char&&br&data()和c_str()都返回\0结尾的字符串
C++11开始要求必须以\0结尾,具体的: operator[]的参数可以是size(),此时返回\0,但是你不能修改这个返回的char& data()和c_str()都返回\0结尾的字符串
&p&这要从一个基本概念谈起 - &b&Code Point &/b&- 码点:它表示一个完整的Unicode字符。Unicode 码点范围:&b&U+0000&/b&到&b&U+10FFFF&/b&,一共1114112个码位。其中U+0000至U+FFFF被称为 &i&Basic Multilingual Plane (&b&BMP&/b&) - &/i&&b&基本多语言面&/b&;U+10000 to U+10FFFF被称为&b&&i&Supplementary Characters&/i&&/b& - 增补字符。&/p&&p&在JDK1.5之前,Java语言对Unicode的支持是有限的,仅支持BMP范围的码点;JDK1.5之后,实现了&a href=&///?target=https%3A//www.jcp.org/en/jsr/detail%3Fid%3D204& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&JSR 204: Unicode Supplementary Character Support&i class=&icon-external&&&/i&&/a&,才对增补字符有了正式的支持。上面答主都说了Java内部使用UTF16的字符编码方式, 即使用1个或2个char来表示一个Unicode字符。我们看到BMP的范围是U+0000至U+FFFF,刚好在两个byte之内,所以使用一个char就能表示一个码点;对于增补字符呢,它对应的码点的值域范围一个char已经不够了,于是就扩充到了使用一个int即2个char来表示——这就是所谓的&b&&i&Unicode surrogate pair(USP)&/i&&/b&。USP分成两部分,每个部分都用一个char来表示,高位char称之为&i&&b&High Surrogate(HS)&/b&&/i&,低位char称之为&i&&b&Low Surrogate(LS)&/b&&/i&,于是&b&USP = HS | LS&/b&。码点转UTF16计算USP的方法可以参见:&a href=&///?target=http%3A//h2appy./639& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GONE WITH THE WIND&i class=&icon-external&&&/i&&/a&。在JDK中的实现可以参见:&i&&b&java.lang.Character#highSurrogate&/b&&/i&和&i&&b&java.lang.Character#lowSurrogate&/b&&/i&。&/p&&p&HS的值域范围是'\uD800' - '\uDBFF',LS的值域范围是'\uDC00' - '\uDFFF', &b&HS和LS所占的2048个码位(从0xD800到0xDFFF)被保留不能分配给其他字符使用&/b&。事实上,JDK的String类库中就是根据这个值域范围来判定一个char是否属于HS还是LS的:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&boolean&/span& &span class=&nf&&isHighSurrogate&/span&&span class=&o&&(&/span&&span class=&kt&&char&/span& &span class=&n&&ch&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&c1&&// Help VM constant- MAX_HIGH_SURROGATE + 1 == MIN_LOW_SURROGATE&/span&
&span class=&k&&return&/span& &span class=&n&&ch&/span& &span class=&o&&&=&/span& &span class=&n&&MIN_HIGH_SURROGATE&/span& &span class=&o&&&&&/span& &span class=&n&&ch&/span& &span class=&o&&&&/span& &span class=&o&&(&/span&&span class=&n&&MAX_HIGH_SURROGATE&/span& &span class=&o&&+&/span& &span class=&mi&&1&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&span class=&kd&&public&/span& &span class=&kd&&static&/span& &span class=&kt&&boolean&/span& &span class=&nf&&isLowSurrogate&/span&&span class=&o&&(&/span&&span class=&kt&&char&/span& &span class=&n&&ch&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&n&&ch&/span& &span class=&o&&&=&/span& &span class=&n&&MIN_LOW_SURROGATE&/span& &span class=&o&&&&&/span& &span class=&n&&ch&/span& &span class=&o&&&&/span& &span class=&o&&(&/span&&span class=&n&&MAX_LOW_SURROGATE&/span& &span class=&o&&+&/span& &span class=&mi&&1&/span&&span class=&o&&);&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&回到题主的问题,如何“精准”计算一个unicode字符串的字数,就可以转化成计算有效的码点的个数了。算法无非就是从头到尾迭代每个字符,如果该字符在BMP的值域范围且不属于&b&0xD800到0xDFFF,&/b&就可认为这是一个基本语言字符;如果该字符在HS的值域范围且相邻的下一个字符在LS的值域范围之内那么我们认为这两个char共同构成一个SP,即这两个char表示一个“字”。JDK中的实现比较简约,可以参见&b&&i&java.lang.Character#codePointCountImpl&/i&&/b&:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&kd&&static&/span& &span class=&kt&&int&/span& &span class=&nf&&codePointCountImpl&/span&&span class=&o&&(&/span&&span class=&kt&&char&/span&&span class=&o&&[]&/span& &span class=&n&&a&/span&&span class=&o&&,&/span& &span class=&kt&&int&/span& &span class=&n&&offset&/span&&span class=&o&&,&/span& &span class=&kt&&int&/span& &span class=&n&&count&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&endIndex&/span& &span class=&o&&=&/span& &span class=&n&&offset&/span& &span class=&o&&+&/span& &span class=&n&&count&/span&&span class=&o&&;&/span&
&span class=&kt&&int&/span& &span class=&n&&n&/span& &span class=&o&&=&/span& &span class=&n&&count&/span&&span class=&o&&;&/span&
&span class=&k&&for&/span& &span class=&o&&(&/span&&span class=&kt&&int&/span& &span class=&n&&i&/span& &span class=&o&&=&/span& &span class=&n&&offset&/span&&span class=&o&&;&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&n&&endIndex&/span&&span class=&o&&;&/span& &span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&isHighSurrogate&/span&&span class=&o&&(&/span&&span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&++])&/span& &span class=&o&&&&&/span& &span class=&n&&i&/span& &span class=&o&&&&/span& &span class=&n&&endIndex&/span& &span class=&o&&&&&/span&
&span class=&n&&isLowSurrogate&/span&&span class=&o&&(&/span&&span class=&n&&a&/span&&span class=&o&&[&/span&&span class=&n&&i&/span&&span class=&o&&]))&/span& &span class=&o&&{&/span&
&span class=&n&&n&/span&&span class=&o&&--;&/span&
&span class=&n&&i&/span&&span class=&o&&++;&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&span class=&k&&return&/span& &span class=&n&&n&/span&&span class=&o&&;&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&更多内容,请参考:&/p&&p&&a href=&///?target=http%3A///blog/512083& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Java中的字符集编码入门(六)Java中的增补字符 - 就只会点Java - ITeye博客&i class=&icon-external&&&/i&&/a&&/p&&p&&a href=&///?target=https%3A///developerworks/library/j-unicode/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unicode surrogate programming with the Java language&i class=&icon-external&&&/i&&/a&&/p&&p&&a href=&///?target=http%3A///javase/tutorial/i18n/text/terminology.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Terminology (The Java(TM) Tutorials & Internationalization & Working with Text)&i class=&icon-external&&&/i&&/a&&/p&
这要从一个基本概念谈起 - Code Point - 码点:它表示一个完整的Unicode字符。Unicode 码点范围:U+0000到U+10FFFF,一共1114112个码位。其中U+0000至U+FFFF被称为 Basic Multilingual Plane (BMP) - 基本多语言面;U+10000 to U+10FFFF被称为Supplementary…
① 使用字符串直接量时会在常量池创建对象,当然必须是常量折叠之后的。&br&② 使用new String()时,new产生的字符串对象是位于堆中,而不是常量池中。&br&③ JDK7之后intern()发生过变化,现在如果常量池中不存在这个对像,不会复制到常量池中,而是简单的使用堆中已有字符串对象。&br&④ JDK7以前的intern()不是这样子的,以前会在常量池中创建一个新的对象,你可以将你的代码,在JDK6中测试一下,结果应该会不同。&br&&br&所以,你的问题不在new String()上,而是在intern()上,前者与常量池从来就没有关系。
① 使用字符串直接量时会在常量池创建对象,当然必须是常量折叠之后的。 ② 使用new String()时,new产生的字符串对象是位于堆中,而不是常量池中。 ③ JDK7之后intern()发生过变化,现在如果常量池中不存在这个对像,不会复制到常量池中,而是简单的使用堆…
&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span& &span class=&p&&(&/span&&span class=&o&&&&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&kt&&void&/span&&span class=&p&&))[&/span&&span class=&mi&&10&/span&&span class=&p&&]&/span&
&span class=&p&&{&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span&&span class=&o&&*&/span& &span class=&n&&str&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span&&span class=&p&&[&/span&&span class=&mi&&10&/span&&span class=&p&&];&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span&&span class=&o&&&&/span& &span class=&n&&strRef&/span& &span class=&o&&=&/span& &span class=&o&&*&/span&&span class=&n&&str&/span&&span class=&p&&;&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span& &span class=&p&&(&/span&&span class=&o&&&&/span&&span class=&n&&strARef&/span&&span class=&p&&)[&/span&&span class=&mi&&10&/span&&span class=&p&&]&/span& &span class=&o&&=&/span& &span class=&k&&reinterpret_cast&/span&&span class=&o&&&&/span&&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span& &span class=&p&&(&/span&&span class=&o&&&&/span&&span class=&p&&)[&/span&&span class=&mi&&10&/span&&span class=&p&&]&/span&&span class=&o&&&&/span&&span class=&p&&(&/span&&span class=&n&&strRef&/span&&span class=&p&&);&/span&
&span class=&k&&return&/span& &span class=&n&&strARef&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&(直接用 deref 就行,編譯器見到 lvalue 會把 deref 取消直接傳地址。) 發現要實現原來的語意還是得用上 reinterpret_cast.
std::string (& f(void))[10]
std::string* str = new std::string[10];
std::string& strRef = *
std::string (&strARef)[10] = reinterpret_cast&std::string (&)[10]&(strRef);
return strAR
}(直接用 deref 就行,編譯器見到 lvalue 會把 …
已有帐号?
无法登录?
社交帐号登录}

我要回帖

更多关于 std string replace 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信