string_stringbuffer - CSDN
string 订阅
string是C++、java、VB等编程语言中的字符串,字符串是一个特殊的对象,属于引用类型。 在java、C#中,String类对象创建后,字符串一旦初始化就不能更改,因为string类中所有字符串都是常量,数据是无法更改,由于string对象的不可变,所以可以共享。对String类的任何改变,都是返回一个新的String类对象。 C++标准库中string类以类型的形式对字符串进行封装,且包含了字符序列的处理操作。 [1-2] 展开全文
string是C++、java、VB等编程语言中的字符串,字符串是一个特殊的对象,属于引用类型。 在java、C#中,String类对象创建后,字符串一旦初始化就不能更改,因为string类中所有字符串都是常量,数据是无法更改,由于string对象的不可变,所以可以共享。对String类的任何改变,都是返回一个新的String类对象。 C++标准库中string类以类型的形式对字符串进行封装,且包含了字符序列的处理操作。 [1-2]
信息
属    性
编程语言
外文名
String
所属领域
计算机
中文名
字符串
string基本信息
String就是C++、java、VB等编程语言中的字符串,用双引号引起来的几个字符,如"Abc","一天".
收起全文
精华内容
参与话题
  • String类型

    千次阅读 2018-09-28 15:33:42
    String类型 一、相关概念 1.定义 String类型是字符串的对象包装类型 创建String对象 var stringObject=new String("hello world"); ...

    String类型

    一、相关概念

    1.定义

    1. String类型是字符串的对象包装类型
    2. 创建String对象
    var stringObject=new String("hello world");
    

    2.补充

    1. String对象大方法也可以在所有基本的字符串值中访问到
    2. 继承的valueOf()toLocaleString()toString()方法,都返回对象所表示的基本字符串值

    二、属性

    1.length

    1. String类型的每个实例都有一个length属性,表示字符串中包含多个字符
    2. 字符串中包含双字节字符(不是占一个字节的ASCII字符),每个字符仍然算一个字符
    var stringValue=new String("hello world");
    alert(stringValue.length);/"11"
    

    三、方法

    (一).字符方法

    1.charAt()
    1. 接收一个参数,基于0的字符位置
    2. 以单字符字符串的形式返回给定位置的哪个字符(ECMAScript中没有字符类型)
    var stringValue=new String("hello world");
    alert(stringValue.charAt(1));//"e"
    
    2.charCodeAt()
    1. 接收一个参数,基于0的字符位置
    2. 以单字符字符串的形式返回给定位置的哪个字符的字符编码
    var stringValue=new String("hello world");
    alert(stringValue.charCodeAt(1));//"101"
    
    3.方括号表示法
    1. 在支持的浏览器中,可以使用方括号加数字索引来访问字符串中的特定字符
    2. 使用方括号表示法访问个别字符的语法得到了IE8FirefoxSafariChromeOpera所有版本的支持
    3. 如果是在IE7及更早版本中使用这种语法,会返回undefined值(尽管根本不是特殊的undefined值)
    var stringValue=new String("hello world");
    alert(stringValue[1]);//"e"
    

    (二).字符串操作方法

    1.concat()
    1. concat()用于将一或多个字符串拼接起来,返回拼接得到的新字符串。不改变原字符串
    2. concat()方法可以接收任意多个参数,也就是说可以通过它拼接任意多个字符串
    3. 虽然concat()是专门用来拼接字符串的方法,但实践中使用更多的还是加号操作符(+)。而且,使用加号操作符在大多数情况下都比使用concat()方法要简便易行(特别是在拼接多个字符串的情况下)
    var stringValue=new String("hello ");
    var result=stringValue.concat("world");
    alert(result);//"hello world"
    alert(stringValue);//"hello "
    
    var stringValue=new String("hello ");
    var result=stringValue.concat("world","!");
    alert(result);//"hello world!"
    alert(stringValue);//"hello "
    
    2.slice()
    1. 返回被操作数的一个子字符串,而且也接收一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束
    2. 第二个参数指定的是子字符串最后一个字符后面的位置
    3. 如果没有给这些方法传递第二个参数,则将字符串的末尾作为结束吧位置
    4. 不会修改字符串本身的值——只是返回一个基本类型的字符串值,对原字符串没有任何影响
    5. 传入参数是负数时,slice()会将传入的负值与字符串的长度相加
    var stringValue=new String("hello world");
    alert(stringValue.slice(3));//"lo  world" 
    alert(stringValue.slice(3,7));//"lo w" 
    
    var stringValue=new String("hello world");
    alert(stringValue.slice(-3));//"rld"
    alert(stringValue.slice(3,-4));//"lo w"
    
    3.substr()
    1. 返回被操作数的一个子字符串,而且也接收一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束
    2. 第二个参数指定的是返回的字符个数
    3. 如果没有给这些方法传递第二个参数,则将字符串的末尾作为结束吧位置
    4. 不会修改字符串本身的值——只是返回一个基本类型的字符串值,对原字符串没有任何影响
    5. 传入参数是负数时,substr()方法会将负的第一个参数加上字符串的长度,而将将负的第二个参数转换为0
    var stringValue=new String("hello world");
    alert(stringValue.substr(3));//"lo  world"
    alert(stringValue.substr(3,7));//"lo worl"
    
    var stringValue=new String("hello world");
    alert(stringValue.substr(-3));//"rld"
    alert(stringValue.substr(3,-4));//""
    //substr()会将第二个参数转换为0,返回的字符串包含零个字符串,也就是一个空字符串
    
    4.substring()
    1. 返回被操作数的一个子字符串,而且也接收一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束
    2. 第二个参数指定的是子字符串最后一个字符后面的位置
    3. 如果没有给这些方法传递第二个参数,则将字符串的末尾作为结束吧位置
    4. 不会修改字符串本身的值——只是返回一个基本类型的字符串值,对原字符串没有任何影响
    5. 传入参数是负数时,substring()方法会将所有的负值参数都转换为0
    var stringValue=new String("hello world");
    alert(stringValue.substring(3));//"lo  world"
    alert(stringValue.substring(3,7));//"lo w"
    
    var stringValue=new String("hello world");
    alert(stringValue.substring(-3));//"hello  world"
    alert(stringValue.substring(3,-4));//"hel"
    //substring()方法会把第二个参数转换为0,也就是substring(3,0),而由于这个方法会将较小的数作为开始位置,将较大数作为结束位置,最终相当于调用了substring(0,3)
    

    (三).字符串位置方法

    1.indexOf()
    1. 从一个字符串中搜索给定的字符串,然后返回怒字符串的位置(如果没有找到该字符串,则返回-1)
    2. indexOf()方法从字符串的开头向后搜索字符串
    3. 如果给定字符串仅出现一次,那么indexOf()lastIndexOf()会返回相同的位置值
    4. 第二个参数(可选)表示从字符串中的哪个位置开始搜索
    5. indexOf()会从该参数指定的位置向后搜索,忽略该位置之前的所有字符
    var stringValue=new String("hello world");
    alert(stringValue.indexOf("o"));//4
    
    var stringValue=new String("hello world");
    alert(stringValue.indexOf("o",6));//7
    
    var strinngValue="Lorem ipsum dolor sit amet, consectetur adipisicing elit";
    var positions=new Array();
    var pos=stringValue.indexOf("e");
    while(pos>-1){
    	positions.push(pos);
    	pos=stringValue.indexOf("e",pos+1);
    }
    alert(positions);
    
    2.lastIndexOf()
    1. 从一个字符串中搜索给定的字符串,然后返回怒字符串的位置(如果没有找到该字符串,则返回-1)
    2. lastIndexOf()方法是从字符串的末尾向前搜索字符串
    3. 第二个参数(可选)表示从字符串中的哪个位置开始搜索
    4. lastIndexOf()会从该参数指定的位置向前搜索,忽略该位置之后的所有字符
    var stringValue=new String("hello world");
    alert(stringValue.lastIndexOf("o"));//7
    
    var stringValue=new String("hello world");
    alert(stringValue.lastIndexOf("o",6));//4
    

    (四).trim()方法

    1. trim()会创建一个字符串的副本,删除前置及后缀的所有空格,然后返回结果
    2. trim()返回的是字符串的副本,所以原始字符串中的前置及后缀空格会保持不变
    3. 支持这个方法的浏览器有IE 9+Firefox 3.5+Safari 5+Opera 10.5+Chrome
    4. 此外,Firefox 3.5+Safari 5+Chrome 8+还支持非标准的trimLeft()trimRight()方法,分别用于删除字符串开头和末尾的空格
    var stringValue=new String(" hello world ");
    var trimmedStringValue=stringValue.trim();
    alert(stringValue);" hello world "
    alert(trimmedStringValue);//"hello world"
    

    (五).字符串大小写转换方法

    1.toLowerCase()
    1. 借鉴自java.lang.String中的同名方法,将对象转换为小写
    var stringValue=new String("hello world");
    alert(stringValue.toLowerCase());//"hello world"
    
    2.toLocaleLowerCase()
    1. 针对特定时区,将对象转换为小写
    2. 一般来说,在不知道自己的代码将在哪种语言环境中运行的情况下,还是使用针对地区的方法更稳妥一些
    var stringValue=new String("hello world");
    alert(stringValue.toLocaleLowerCase());//"hello world"
    
    3.toUpperCase()
    1. 借鉴自java.lang.String中的同名方法,将对象转换为大写
    var stringValue=new String("hello world");
    alert(stringValue.toUpperCase());//"HELLO WORLD"
    
    4.toLocaleUpperCase()
    1. 针对特定时区,将对象转换为大写
    2. 一般来说,在不知道自己的代码将在哪种语言环境中运行的情况下,还是使用针对地区的方法更稳妥一些
    var stringValue=new String("hello world");
    alert(stringValue.toLocaleUpperCase());//"HELLO WORLD"
    

    (六).字符串的模式匹配方法

    1.match()
    1. match(),在字符串上调用这个方法,本质上与调用RegExp()exec()方法相同
    2. match()方法只接收一个参数,要么是一个正则表达式,要么是一个RegExp对象
    var text=new String("cat,bat,sat,fat");
    var pattern=/.at/;
    //与pattern.exec(text)相同
    var matches=text.match(pattern);
    alert(matches.index);//0
    alert(matches[0]);
    alert(pattern.lastIndex);//0
    //match()方法返回了一个数组
    //如果是调用RegExp对象的exec()方法并传递本例中的字符串作为参数,那么也会得到与此相同的数组:数组的第一项是与整个模式匹配的字符串,之后的每一项(如果有)保存着与正则表达式中的捕获组匹配的字符串
    
    2.search()
    1. search()方法的唯一参数与match()方法的参数相同:由字符串或RegExp对象指定的一个正则表达式
    2. search()方法返回字符串中第一个匹配项的索引;如果没有找到匹配项,则返回-1
    3. search()方法始终是从字符串开头向后查找模式
    var text=new String("cat,bat,sat,fat");
    var pos=text.search(/at/);
    alert(pos);//1
    //search()方法返回1,即"at"在字符串中第一次出现的位置
    
    3.replace()
    1. replace()用于替换字符串
    2. 这个方法接收两个参数:第一个参数可以是一个RegExp对象或者一个字符串(这个字符串不会被转换成正则表达式),第二个参数可以是一个字符串或者一个函数
    3. 如果第一个参数是字符串,那么只会替换第一个字符串。要想替换所有子字符串,唯一的办法就是提供一个正则表达式,而且要指定全局(g)标志
    4. 如果第二个参数是字符串,那么还可以使用一些特殊的字符序列,将正则表达式操作得到的值插入到结果字符串中
    5. replace()方法的第二个参数也可以是一个函数。在只有一个匹配项(即与模式匹配的字符串)的情况下,会向这个函数传递3个阐述:模式匹配、模式匹配项在字符串中的位置和原始字符串。在正则表达式中定义了多个捕获组的情况下,传递给函数的参数依次是模式的匹配项、第一个捕获组的匹配项、第二个捕获组的匹配项······,但最后两个参数仍然分别是模式的匹配项在字符串中的位置和原始字符串。这个函数应该返回一个字符串,表示应该被替换的匹配项。使用函数作为replace()方法的第二个参数可以实现更加精细的替换操作
    function htmlEscape(text){
    	return text.replace(/[<>"&]/g,function(match,pos,originalText){
    		switch(match){
    			case "<":return "&lt;";
    			case ">":return "&map;";
    			case "\"":return "&quot;";
    		}
    	};)
    }
    alert(htmlEscape("<p class=\"greeting\">Hello world!</p>"));
    //&lt;p class=&quot;greeting&quot;&gt;Hello world!&lt;/p&gt;
    
    字符序列 替换文本
    $$ $
    $& 匹配整个模式的子字符串。与RegExp.lastMatch的值相同
    $' 匹配的子字符串之前的字符串。与RegExp.leftContext的值相同
    $` 匹配的子字符串之后的子字符串。与RegExp.rightContext的值相同
    $n 匹配第n个捕获组的子字符串,其中n等于0~9.例如,$1是匹配第一个捕获组的子字符串,$2是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串
    $nn 匹配第nn个捕获组的子字符串,其中nn等于01~99。例如,$01是匹配第一个捕获组的子字符串,$02是匹配第二个捕获组的子字符串,以此类推。如果正则表达式中没有定义捕获组,则使用空字符串
    var text=new String("cat,bat,sat,fat");
    var result=text.replace("at","ond");
    alert(result);//"cond,bat,sat,fat"
    //传入replace()方法的是字符串"at"和替换用的字符串"ond"。替换的结果是把"cat"变成了"cond",但字符串中的其他字符并没有收到任何影响
    
    result=text.replace(/at/g,"ond");
    alert(result);//"cond,bond,sond,fond"
    //通过将第一个参数修改为带有全局标志的正则表达式,就将全部"at"都替换成了"ond"
    
    var text=new String("cat,bat,sat,fat");
    result=text.replace(/(.at)/g,"word($1)");
    alert(result);//word(cat),word(bat),word(sat),word(fat)
    //在此,每个以"at"结尾的单词都被替换了,替换结果是"word"后跟一对圆括号,而圆括号中是被字符序列$1所替换的单词
    
    4.split()
    1. split()方法可以基于指定的分隔符将一个字符串分隔成多个字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个RegExp对象(这个方法不会将字符串看成正则表达式)
    2. split()方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小
    3. split()正正则表达式的支持因浏览器而异。尽管对于简单的模式没什么差别,但对于未发现匹配项及带有捕获组的模式,匹配的行为大不相同
      • IE8及之前版本会忽略捕获组。ECMA-262规定应该把捕获组拼接到结果数组中。IE9能正确地在结果中包含捕获组
      • Firefox 3.6及之前版本在捕获组未找到匹配项时,会在结果数组中包含空字符串;ECMA-262规定没有匹配项的捕获组在结果数组中应该用undefined表示
    var colorText=new String("red,blue,green,yellow");
    var colors1=colorText.split(",");//["red","blue","green","yellow"]
    var colors1=colorText.split(",",2);//["red","blue"]
    var colors1=colorText.split(/[^\,]+/);//["",",",",",""]
    //通过正则表达式指定的分隔符出现在了字符串的的开头(即子字符串"red")和末尾(即子字符串"yellow")
    

    (七).localeCompare()方法

    1. localeCompare()方法比较两个字符串,并返回下列值中的第一个
      • 如果字符串在字母表中应该在字符串参数之前,则返回一个负数(大多数情况下是-1,具体的值要视实现而定)
      • 如果字符串等于字符串参数,则返回0
      • 如果字符串在字母表中应该排在字符串参数之后,则返回一个正数(大多数情况下是-1,具体的值同样要视实现而定)
    var stringValue=new String("yellow");
    alert(stringValue.localeCompare("brick"));//1
    alert(stringValue.localeCompare("yellow"));//0
    alert(stringValue.localeCompare("zoo"));//-1
    
    function determineOrder(value){
    	var result=stringValue.localeCompare(value);
    	if(result<0){
    		alert("The string 'yellow' comes before the string '"+value+"'.");
    	}else if(result>0){
    		alert("The string 'yellow' comes after the string '"+value+"'.");
    	}else{
    		alert("The string 'yellow' is equal to the string '"+value+"'.");
    	}
    }
    determineOrder("brick");
    determineOrder("yellow");
    determineOrder("zoo");
    

    (八).fromCharCode()方法

    1. String构造函数本身还有一个静态方法:fromCharCode()。这个方法的任务是接受一或多个字符编码,然后将它们转换成一个字符串
    2. 从本质上看,这个方法与实例方法charCodeAt()执行的是相反的操作
    alert(String.fromCharCode(104,101,,108,108,111));//"hello"
    //给fromCharCode()传递的是字符串"hello"中每个字母的字符编码
    

    (九).HTML方法

    1. 早期的Web浏览器提供商觉察到了使用JavaScript动态格式化HTML的需求。这些提供商就扩展了标准,实现了一些专门用于简化常见HTML格式化任务的方法
    2. 应该尽量不使用这些方法,因为它们创建的标记通常无法表达语义
    方法 输出结果
    anchor(name) <a name="name">string</a>
    big() <big>string</big>
    bold() <b>string</b>
    fixed() <tt>string</tt>
    fontcolor() <font color="color">string</font>
    fontsize() <font size="size">string</font>
    italics() <i>string</i>
    link(url) <a href="url">string</a>
    small() <small>string</small>
    strike() <strike>string</strike>
    sub() <sub>string</sub>
    sup() <sup>string</sup>

    源自
    源于整理《JavaScript高级程序设计》

    展开全文
  • string 在c++中的使用

    万次阅读 多人点赞 2018-08-11 20:44:31
    字符类型和字符数组( 字符值知识点)要提前了解一下,能更好的了解string   string类型支持长度可变的字符串 在头文件 #include&lt;string&gt;中   一:string对象的定义和初始化   string s1; ...

    字符类型和字符数组( 字符值知识点)要提前了解一下,能更好的了解string

     

    string类型支持长度可变的字符串  在头文件 #include<string>中

     

    :string对象的定义和初始化

        

    string s1;

    默认构造函数,s1位空串

    string s2(s1); 将s2初始化为s1的一个副本
    string s3("value"); 将s3初始化为一个字符串字面值副本
    string s4(n,'c') 将s4初始化为字符'c'的n个副本

    当没有明确指定对象初始华式时,系统使用默认构造函数

    #include<iostream>
    #include<string>
    using namespace std;
    int main()
    {
        string s1;
        s1="chenchen";
        cout<<s1<<endl;
    
        string s2(s1);
        cout<<s2<<endl;
    
        string s3("value");
        cout<<s3<<endl;
    
        string s4(10,'c');
        cout<<s4<<endl;
    
        return 0;
    }
    

     

    :string对象的读写

        对:cin>>s;//和scanf()类似

        从标准输入读取string,并将读入的串存储在s中。

     a.读取并忽略开头所有的空白字符(如空格,换行符,制表符,进纸符)

    b.读取字符直至再次遇到空白符,读取终止

    #include<iostream>
    #include<string>
    #include<cstdio>
    
    using namespace std;
    int main()
    {
        int T;
        cin>>T;
        //如果没有这个getchar()那么s首先会把我们按空格给s,这里我们要注意下
        //cin是不会这样的
        //同样的是gets()也会和getline()出现一样的情况
        /*
        比如输入:
        1 adfs
        输出:adfs有个空格
    
        输入
        1
        adfs
        输出:那么string参数被置为空string
        */
        getchar();
        while(T--)
        {
            string s;
            getline(cin,s);
            cout<<s<<endl;
        }
        return 0;
    }
    

     

    getline()了解一下,在字符串知识里有,上面有链接//换行符是丢弃的

     

     

     

    :string对象的操作

        

    s.empty() 如果s为空串,则返回true,否则返回false
    s.size() 返回s 中字符的字符个数
    s[n]

    返回s中位置为n的字符,位置从0开始计数

    s1+s2 把s1和s2链接成一个新的字符串,返回新生成的字符串
    s1=s2 把s1内容替换为s2的副本
    v1==v2 判断v1与v2的内容,相等则返回true,否则返回false
    !=, <, <=, >, >= 保持这些操作的惯有含义

    a. string::size_type 类型

     注意s.size()返回的类型是string::size_type()类型,而不是int形,string::size_type 的类型长度是int的俩倍所以尽量让s.size()的返 回值给string::size_type 类型

    因为string 的字符会很长,所以有了string::size_type 类型

    #include<iostream>
    #include<string>
    #include<cstdio>
    
    using namespace std;
    int main()
    {
        string s("value");
        string::size_type it;
        it=s.size();
        cout<<it<<endl;//5
        return 0;
    }
    

    b.和字符串字面值的连接问题

    #include<iostream>
    #include<string>
    #include<cstdio>
    
    using namespace std;
    int main()
    {
        string s1="hello";
        string s2=("world");
        string s3=s1+", ";//正确的
        cout<<s3<<endl;
    //    string s4="hello"+", ";//错误的
        string s5=s1+", "+", "+"world";//错误的
    //    string s6="hello"+", "+s2;//错误的
        cout<<s5<<endl;
        return 0;
    }
    

    s5的理解为:string t=s1+", "; s5=t+"world";

    注意俩个字符串字面值相加,是非法的

     

     

    c.下标操作可以作左值

    如:s[x]='*';

     

     

    :string对象中字符的处理

    这些函数都在cctype头文件中定义

     

    isalnum(c) 如果c是字母或数字,则为true
    isalpha(c) 如果c是字母,则为true
    isdigit(c) 如果c是数字,则为true
    islower(c) 如果c是小写字母,则为ture
    isupper(c) 如果c是大写字母,则为true
    isspace(c) 如果c是空白字符,则为true
    ispunct(c) 如果c是标点符号,则为true
    iscntrl(c) 如果c是控制字符,则为true
    isgraph(c) 如果c不是空格,但是可打印,则为true
    isprint(c) 如果c是可打印的字符,则为true
    isxdigit(c) 如果c是十六进制数,则为true
    tolower(c) 如果c是大写字母,则返回其小写字母形式,否则直接返回c
    toupper(c) 如果c是小写字母,则返回其大写字母形式,否则直接返回c

    表中大部分函数是测试一个给定的字符是否符合条件,并返回一个int值作为真值。如果失败,则函数返回0,否则返回一个(无意义的)非0值,表示被测字符符合条件

     

    注意:除了一些特殊操作,string类型提供与vector容器相同的操作。string类型和vector容器不同的是,它不支持以栈方式操纵容器:在string类型中不能使用front,back,pop_back操作

    string 是支持push_back()的,因为string也是顺序容器

     

     

    :string 的遍历

    #include<iostream>
    #include<string>
    #include<cstdio>
    
    using namespace std;
    int main()
    {
        string s("Hiya");
        string::iterator it;
        for(it=s.begin();it!=s.end();++it)
            cout<<*it<<endl;
        for(int i=0;s[i];i++)
            cout<<s[i]<<endl;
        cout<<s<<endl;
        return 0;
    }
    

    :构造string对象的其他方法

    写这个之前学个知识点:

    char s[]="abcd";  这个是字符串,以'\0'结束的,用strlen(s)是4,'\0'不计,但是它是有五个字符的
    char s1[]={'a','b','c','d'};  这个是字符数组,没有'\0',用strlen(s1)是不确定的,它有四个字符

     a.使用只有一个指针参数的构造函数,该指针指向空字符结束的字符数组中的第一个元素。//前面已经介绍了

    b.构造函数需要一个指向字符数组元素的指针和一个标记要复制多少字符的计数器做参数,由于该构造函数有一个计数器,因此  数组不必以空字符结束

     

     

    string s(cp,n) 创建一个string对象,它被初始化为cp所指向数组的前n个元素副本
    string s(s2,pos2)

    创建一个string对象,它被初始化为一个已存在的string对象s2中从下标pos2开始的字符的副本

    如果pos2>s.size(),则该操作未定义

    string s(s2,pos2,len2)

    创建一个string对象,它被初始化为s2中从下标pos2开始的len2个字符的副本

    如果pos2>s2.size(),则该操作未定义

    无论len2的值是多少,最多只能复制s2.size()-pos2个字符

    注意:n,len2和pos2都是unsinged

    #include<iostream>
    #include<string>
    #include<cstdio>
    
    using namespace std;
    int main()
    {
        char *cp="Hiya";
        char c_array[]="world!!!!";
        char no_null[] = {'H','i'};
        string s1(cp);
        cout<<"s1="<<s1<<endl;
        string s2(c_array,5);
        cout<<"s2="<<s2<<endl;
        string s3(c_array+5,4);
        cout<<"s3="<<s3<<endl;
        //string s4(no_null);//s4是错误的
        //cout<<s4<<endl;
        string s5(no_null,2);
        cout<<"s5="<<s5<<endl;
        string s6(s1,2);
        cout<<"s6="<<s6<<endl;
        string s7(s1,0,2);
        cout<<"s7="<<s7<<endl;
        string s8(s1,0,8);
        cout<<"s8="<<s8<<endl;
        return 0;
    }

     

    :修改string对象的其他方法

     

    与容器共有的string操作
    s.insert(p,t) 在迭代器p指向的元素之前插入一个值为t的新元素。返回指向新插入元素的迭代器
    s.insert(p,n,t) 在迭代器p指向的元素之前插入n个值为t的新元素。返回void
    s.insert(p,b,e) 在迭代器p指向的元素之前插入b和e标记范围内所有的元素,返回void
    s.assign(b,e) 用迭代器b和e标记范围内的元素替换s。对于string类型,该操作返回s,对于容器类型,则返回void
    s.assign(n,t) 用值为t的n个副本替换s。对于string类型,该操作返回s,对于容器类型,返回void
    s.erase(p) 删除迭代器p指向的元素,返回一个迭代器,指向被删除元素后面的元素
    s.erase(b,e) 删除迭代器b和e标记范围内所有的元素,返回一个迭代器,指向被删除元素段后面的第一个元素
    reverse(b,e) 把迭代器b和e标记范围内的所有元素反转

     

    #include<iostream>
    #include<string>
    #include<cstdio>
    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        string s="12345abcde";
        string::iterator it;
        it=s.begin();
        s.insert(it+2,'s');
        cout<<"s="<<s<<endl;
        it=s.begin();
        s.insert(it+2,2,'t');
        cout<<s<<endl;
        it= s.begin();
        s.assign(it,it+3);
        cout<<s<<endl;
        s.assign(10,'c');
        cout<<s<<endl;
        string s1="sfdfsaf";
        s1.erase(s1.begin());
        cout<<s1<<endl;
        s1.erase(s1.begin(),s1.begin()+3);
        cout<<s1<<endl;
        reverse(s1.begin(),s1.end());
        cout<<s1<<endl;
        return 0;
    }

     

     

     

    string 类型特有的版本
    s.insert(pos,n,c) 在下表pos的元素之前插入n个字符c
    s.insert(pos,s2)

    在下标为pos的元素之前插入string对象s2的副本

    s.insert(pos,s2,pos2,len)

    在下标为pos的元素之前插入s2中从下标pos2开始len个字符

    s.insert(pos,cp,len) 在下标为pos的元素之前插入s2中从下标pos2开始的len个字符
    s.insert(pos,cp) 在下标为pos的元素之前插入cp所指向的以空字符结束的字符串副本
    s.assign(s2) 用s2的副本替换s
    s.assign(s2,pos2,len) 用s2中从下标pos2开始的len个字符副本替换s
    s.assign(cp,len) 用cp所指向数组的前len个字符副本替换s
    s.assign(cp) 用cp所指向的以空字符结束的字符串副本替换s

    s.erase(pos,len)

    删除从下标pos开始的len个字符

     

    #include<iostream>
    #include<string>
    #include<cstdio>
    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        string s="12345abcde";
        s.insert(2,2,'s');
        cout<<s<<endl;
        string s2="zyz";
        s.insert(1,s2);
        cout<<s<<endl;
        s.insert(0,s2,0,2);
        cout<<s<<endl;
        char *cp="Statelu pou";
        s.insert(0,cp);
        cout<<s<<endl;
        s.insert(0,cp,2);
        cout<<s<<endl;
        string s3="123456abc";
        s.assign(s3);
        cout<<s<<endl;
        string s4="987";
        s.assign(s4,0,3);
        cout<<s<<endl;
        s.erase(1,6);
        cout<<s<<endl;
    
        return 0;
    }

    除非特殊声明,上述所有操作都返回s的引用

     

    其实string 的特有版本,就是多了pos下标的使用还有就是控制了一下,比较方便了

     

    :只适用于string类型的操作

    a.substr函数,返回当前string对象的子串

    b.append和replace函数,用于修改string对象

    c.一系列find函数,用于查找string对象

     

    子串操作
    s.substr(pos,n) 返回一个string类型的字符串,它包含s中从下标pos开始的n个字符
    s.substr(pos) 返回一个string类型的字符串,它包含从下标pos开始到s末为的所有字符
    s.substr() 返回s的副本

     

    #include<iostream>
    #include<string>
    #include<cstdio>
    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        string s("hello world");
        string s2=s.substr(6,5);
        cout<<s2<<endl;
        string s3=s.substr(6);
        cout<<s3<<endl;
    
        return 0;
    }

     

     

    修改string对象的操作
    s.append(args) 将args串接在s后面。返回s的引用
    s.replace(pos,len,args)

    删除s中从下标pos开始的len个字符,用args指定的字符替换之,返回s的引用

    这个版本中,args不能为b2,e2

    s.replace(b,e,args)

    删除迭代器b和e标记的范围内所有的字符,用args代替之。返回s的引用

    这个版本中,args不能为 s1,pos2,len2

     

     

    append和replace操作的参数
    s2 string类型的字符串s2
    s2,pos1,pos2 字符串s2中从下标pos2开始的len2个字符
    cp 指针cp指向的以空字符结束的数组
    cp,len2

    cp指向的以空字符结束的数组中前len2个字符

    n,c 字符c的n个副本
    b2,e2 迭代器b2和e2标记的范围内所有字符

     

     

    #include<iostream>
    #include<string>
    #include<cstdio>
    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        string s("C++ Prime");
        s.append(" 3rd Ed");
        cout<<s<<endl;
        s.insert(s.size(),"w");
        cout<<s<<endl;
        s.replace(10,3,"4th");
        cout<<s<<endl;
        s.erase(10,3);
        cout<<s<<endl;
        s.insert(10,"4th");
        cout<<s<<endl;
        return 0;
    }
    

     

     

     

    string类型的查找操作

    string类提供了6中查找函数,每种函数以不同形式的find命名。这些操作全都返回string::size_type类型的值,以下标形式标记查找所发生的位置;或者返回一个名为string::npos 的特殊值,说明没有找到匹配。string类将npos定义为保证大于任何有效下标的值

     

    string类型的查找操作
    s.find(args) 在s中查找args的第一次出现
    s.rfind(args) 在s中查找args的最后一次出现
    s.find_first_of(args) 在s中查args的任意字符的第一次出现
    s.find_last_of(args) 在s中查找args的任意字符的最后一次出现
    s.find_first_not_of(args) 在s中查找第一个不属于args的字符
    s.find_last_not_of(args) 在s中查找最后一个不属于args的字符
    string类型提供的find操作的参数
    c,pos 在s中,从下标pos标记的位置开始,查找字符c、pos的默认值是0
    s2,pos 在s中,从下标pos标记的位置开始,查找string对象s2,pos的默认值为0
    cp,pos 在s中从下标pos标记的位置开始,查找cp所指向的c风格的以空格结束的字符串。pos默认值为0
    cp,pos,n 在s中,从下标pos标记的位置开始,查找指针cp所指向数组的前n个字符,pos和n都没有默认值

     

    #include<iostream>
    #include<string>
    #include<cstdio>
    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        //查找单个单词
        string ss="abcdefg";
        string::size_type pos=ss.find('c',0);
        cout<<pos<<endl;
        cout<<ss[pos]<<endl;
    
    
    
    
        //如果找不到就是npos
        //精确匹配的查找
        string name("AnnaBelle");
        string::size_type pos1=name.find("Anna");
        cout<<pos1<<endl;//子串Anna在name中的下标是0
    
        string s("annaBelle");
        pos1=s.find("Anna");//pos1=npos
        //cout<<pos1<<endl;
    
    
    
    
        //查找任意字符
        string s2("0123456789");
        string s3("r2d2");
    //    string::size_type pos=s3.find_first_of(s2);
    //    cout<<s3[pos]<<endl;//2
    //    cout<<pos<<endl;//1
    
    
    
    
    
    
        //指定查找的起点
        string::size_type pos2=0;
        while((pos2=s3.find_first_of(s2,pos2))!=string::npos)
        {
            cout<<"pos2="<<pos2<<endl;
            cout<<s3[pos2]<<endl;
            pos2++;//pos2值必须加1,以确保下一次循环从刚找到的数字后面开始查找下一个数字
        }
    
    
        //寻找不匹配点
        string nums("0123456789");
        string depet("03714p3");
        string::size_type pos3=depet.find_first_not_of(nums);
        cout<<"pos3="<<pos3<<endl;
        cout<<depet[pos3]<<endl;
    
        //反向查找
        string river("Mississippi");
        string::size_type first=river.find("is");
        string::size_type last=river.rfind("is");
        cout<<first<<"    "<<river[first]<<endl;
        cout<<last<<"    "<<river[last]<<endl;
    
        return 0;
    }

     

    九:string对象的比较

     

    string类型的compare操作
    s.compare(s2) 比较s和s2
    s.compare(pos1,n1,s2)

    让s中从pos下标位置开始的n1个字符与s2做比较

    s.compare(pos1,n1,s2,pos2,n2) 让s中从pos1下标位置开始的n1个字符与s2中从pos2下标位置开始的n2个字符做比较
    s.compare(cp) 比较s和cp所指向的以空字符结束的字符串
    s.compare(pos1,n1,cp) 让s从pos1下标位置开始的n1个字符与cp所指向的字符串做比较
    s.compare(pos1,n1,cp,n2)

    让s中从pos1下标位置开始的n1个字符与cp所指向字符串的前n2个字符做比较

    比如:s1.compare(args);

    a.正数,此时s1大于args所代表的string对象

    b.负数,此时s1小于args所代表的string对象

    c.0,此时s1恰好等于args所代表的string对象

     

    这次举个例子以前做的题

    #include<bits/stdc++.h>
    using namespace std;
    int main()
    {
        int n,q;
        scanf ("%d%d", &n,&q);
        string s;
        cin>>s;
        for ( int i=0; i<q;i++ )
        {
            int a,b,c,d;
            scanf ( "%d%d%d%d" ,&a,&b ,&c , &d );
            if ( s.compare( a-1,b-a+1,s,c-1,d-c+1)==0 )
                printf ( "YES\n" );
            else
                printf ( "NO\n" );
        }
     
        return 0;
    }
    
    

     

    展开全文
  • 深入理解Java String

    万次阅读 多人点赞 2019-09-11 10:43:47
    在Java语言了中,所有类似“ABC”的字面值,都是String类的实例;String类位于java.lang包下,是Java语言的核心类,提供了字符串的比较、查找、截取、大小写转换等操作;Java语言为“+”连接符(字符串连接符)以及...

    在Java语言中,所有类似“ABC”的字面值,都是String类的实例;String类位于java.lang包下,是Java语言的核心类,提供了字符串的比较、查找、截取、大小写转换等操作;Java语言为“+”连接符(字符串连接符)以及对象转换为字符串提供了特殊的支持,字符串对象可以使用“+”连接其他对象。String类的部分源码如下

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
    
        /** Cache the hash code for the string */
        private int hash; // Default to 0
        ...
    }
    

    从上面可以看出
    1)String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;字符串一旦创建就不能再修改。
    2)String类实现了Serializable、CharSequence、 Comparable接口。
    3)String实例的值是通过字符数组实现字符串存储的。


    1. “+”连接符

    1.1 “+”连接符的实现原理

    Java语言为“+”连接符以及对象转换为字符串提供了特殊的支持,字符串对象可以使用“+”连接其他对象。其中字符串连接是通过 StringBuilder(或 StringBuffer)类及其append 方法实现的,对象转换为字符串是通过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的所有类继承。有关字符连接和转换的更多信息,可以参阅 Gosling、Joy 和 Steele 合著的 《The Java Language Specification》

    我们可以通过反编译验证一下

    /**
     * 测试代码
     */
    public class Test {
        public static void main(String[] args) {
            int i = 10;
            String s = "abc";
            System.out.println(s + i);
        }
    }
    
    /**
     * 反编译后
     */
    public class Test {
        public static void main(String args[]) {    //删除了默认构造函数和字节码
            byte byte0 = 10;      
            String s = "abc";      
            System.out.println((new StringBuilder()).append(s).append(byte0).toString());
        }
    }
    

    由上可以看出,Java中使用"+"连接字符串对象时,会创建一个StringBuilder()对象,并调用append()方法将数据拼接,最后调用toString()方法返回拼接好的字符串。由于append()方法的各种重载形式会调用String.valueOf方法,所以我们可以认为:

    //以下两者是等价的
    s = i + ""
    s = String.valueOf(i);
     
    //以下两者也是等价的
    s = "abc" + i;
    s = new StringBuilder("abc").append(i).toString();
    

    1.2 “+”连接符的效率

    使用“+”连接符时,JVM会隐式创建StringBuilder对象,这种方式在大部分情况下并不会造成效率的损失,不过在进行大量循环拼接字符串时则需要注意。

    String s = "abc";
    for (int i=0; i<10000; i++) {
        s += "abc";
    }
    
    /**
     * 反编译后
     */
    String s = "abc";
    for(int i = 0; i < 1000; i++) {
         s = (new StringBuilder()).append(s).append("abc").toString();    
    }
    

    这样由于大量StringBuilder创建在堆内存中,肯定会造成效率的损失,所以在这种情况下建议在循环体外创建一个StringBuilder对象调用append()方法手动拼接(如上面例子如果使用手动拼接运行时间将缩小到1/200左右)。

    /**
     * 循环中使用StringBuilder代替“+”连接符
     */
    StringBuilder sb = new StringBuilder("abc");
    for (int i = 0; i < 1000; i++) {
        sb.append("abc");
    }
    sb.toString();
    

    与此之外还有一种特殊情况,也就是当"+"两端均为编译期确定的字符串常量时,编译器会进行相应的优化,直接将两个字符串常量拼接好,例如:

    System.out.println("Hello" + "World");
    
    /**
     * 反编译后
     */
    System.out.println("HelloWorld");
    
    /**
     * 编译期确定
     * 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。
     * 所以此时的"a" + s1和"a" + "b"效果是一样的。故结果为true。
     */
    String s0 = "ab"; 
    final String s1 = "b"; 
    String s2 = "a" + s1;  
    System.out.println((s0 == s2)); //result = true
    
    /**
     * 编译期无法确定
     * 这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定
     * 因此s0和s2指向的不是同一个对象,故上面程序的结果为false。
     */
    String s0 = "ab"; 
    final String s1 = getS1(); 
    String s2 = "a" + s1; 
    System.out.println((s0 == s2)); //result = false 
     
    public String getS1() {  
        return "b";   
    }
    

    综上,“+”连接符对于直接相加的字符串常量效率很高,因为在编译期间便确定了它的值,也就是说形如"I"+“love”+“java”; 的字符串相加,在编译期间便被优化成了"Ilovejava"。对于间接相加(即包含字符串引用,且编译期无法确定值的),形如s1+s2+s3; 效率要比直接相加低,因为在编译器不会对引用变量进行优化。

    2. 字符串常量池

    在Java的内存分配中,总共3种常量池,分别是Class常量池运行时常量池字符串常量池

    字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性,常量池中一定不存在两个相同的字符串

    /**
     * 字符串常量池中的字符串只存在一份!
     * 运行结果为true
     */
    String s1 = "hello world!";
    String s2 = "hello world!";
    System.out.println(s1 == s2);
    

    2.1 内存区域

    在HotSpot VM中字符串常量池是通过一个StringTable类实现的,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例中只有一份,被所有的类共享;字符串常量由一个一个字符组成,放在了StringTable上。要注意的是,如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用String.intern时性能会大幅下降(因为要一个一个找)。

    在JDK6及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中的,StringTable的长度是固定的1009;在JDK7版本中,字符串常量池被移到了堆中,StringTable的长度可以通过**-XX:StringTableSize=66666**参数指定。至于JDK7为什么把常量池移动到堆上实现,原因可能是由于方法区的内存空间太小且不方便扩展,而堆的内存空间比较大且扩展方便。

    2.2 存放的内容

    在JDK6及之前版本中,String Pool里放的都是字符串常量;在JDK7.0中,由于String.intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。

    /**
     * 运行结果为true false
     */
    String s1 = "AB";
    String s2 = "AB";
    String s3 = new String("AB");
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    

    由于常量池中不存在两个相同的对象,所以s1和s2都是指向JVM字符串常量池中的"AB"对象。new关键字一定会产生一个对象,并且这个对象存储在堆中。所以String s3 = new String(“AB”);产生了两个对象:保存在栈中的s3和保存堆中的String对象。
    这里写图片描述

    当执行String s1 = "AB"时,JVM首先会去字符串常量池中检查是否存在"AB"对象,如果不存在,则在字符串常量池中创建"AB"对象,并将"AB"对象的地址返回给s1;如果存在,则不创建任何对象,直接将字符串常量池中"AB"对象的地址返回给s1。

    3. intern方法

    直接使用双引号声明出来的String对象会直接存储在字符串常量池中,如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法是一个native方法,intern方法会从字符串常量池中查询当前字符串是否存在,如果存在,就直接返回当前字符串;如果不存在就会将当前字符串放入常量池中,之后再返回。

    JDK1.7的改动:

    1. 将String常量池 从 Perm 区移动到了 Java Heap区
    2. String.intern() 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
    /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();
    

    3.1 intern的用法

    static final int MAX = 1000 * 10000;
    static final String[] arr = new String[MAX];
    
    public static void main(String[] args) throws Exception {
        Integer[] DB_DATA = new Integer[10];
        Random random = new Random(10 * 10000);
        for (int i = 0; i < DB_DATA.length; i++) {
            DB_DATA[i] = random.nextInt();
        }
        long t = System.currentTimeMillis();
        for (int i = 0; i < MAX; i++) {
            //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
             arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
        }
    
        System.out.println((System.currentTimeMillis() - t) + "ms");
        System.gc();
    }
    

    运行的参数是:-Xmx2g -Xms2g -Xmn1500M 上述代码是一个演示代码,其中有两条语句不一样,一条是未使用 intern,一条是使用 intern。结果如下图

    未使用intern,耗时826ms:
    这里写图片描述
    使用intern,耗时2160ms:
    这里写图片描述
    通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。

    细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后,然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern 占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。

    4. String、StringBuilder和StringBuffer

    4.1 继承结构

    这里写图片描述

    4.2 主要区别

    1)String是不可变字符序列,StringBuilder和StringBuffer是可变字符序列。
    2)执行速度StringBuilder > StringBuffer > String。
    3)StringBuilder是非线程安全的,StringBuffer是线程安全的。

    5. 总结

    String类是我们使用频率最高的类之一,也是面试官经常考察的题目,下面是一个小测验。

    public static void main(String[] args) {
        String s1 = "AB";
        String s2 = new String("AB");
        String s3 = "A";
        String s4 = "B";
        String s5 = "A" + "B";
        String s6 = s3 + s4;
        System.out.println(s1 == s2);
        System.out.println(s1 == s5);
        System.out.println(s1 == s6);
        System.out.println(s1 == s6.intern());
        System.out.println(s2 == s2.intern());
    }
    

    运行结果:
    这里写图片描述

    解析:真正理解此题目需要清楚以下三点
    1)直接使用双引号声明出来的String对象会直接存储在常量池中;
    2)String对象的intern方法会得到字符串对象在常量池中对应的引用,如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用;
    3) 字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,这一点可以用javap -c命令获得class文件对应的JVM字节码指令就可以看出来。
    这里写图片描述

    参考文献
    https://docs.oracle.com/javase/8/docs/api/
    https://blog.csdn.net/sinat_19425927/article/details/38663461
    https://www.cnblogs.com/xiaoxi/p/6036701.html
    https://tech.meituan.com/in_depth_understanding_string_intern.html

    展开全文
  • 深入理解Java中的String(大坑)

    万次阅读 多人点赞 2019-03-07 11:49:07
    String源码分析 Srtring在JVM层解析 String典型案例 String被设计成不可变和不能被继承的原因 JVM相关知识 下面这张图是JVM的体系结构图: 下面我们了解下Java栈、Java堆、方法区和常量池: Java栈...

    目录

            JVM相关知识

            String源码分析

            Srtring在JVM层解析

            String典型案例

            String被设计成不可变和不能被继承的原因

     

    JVM相关知识

    下面这张图是JVM的体系结构图:

    下面我们了解下Java栈、Java堆、方法区和常量池:

     

    Java栈(线程私有数据区):

            每个Java虚拟机线程都有自己的Java虚拟机栈,Java虚拟机栈用来存放栈帧,每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

     

    Java堆(线程共享数据区):

           在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配。

     

    方法区(线程共享数据区):

           方法区在虚拟机启动的时候被创建,它存储了每一个类的结构信息,例如运行时常量池、字段和方法数据、构造函数和普通方法的字节码内容、还包括在类、实例、接口初始化时用到的特殊方法。在JDK8之前永久代是方法区的一种实现,而JDK8元空间替代了永久代,永久代被移除,也可以理解为元空间是方法区的一种实现。

     

    常量池(线程共享数据区):

            常量池常被分为两大类:静态常量池和运行时常量池。

            静态常量池也就是Class文件中的常量池,存在于Class文件中。

            运行时常量池(Runtime Constant Pool)是方法区的一部分,存放一些运行时常量数据。

    下面重点了解的是字符串常量池:

            字符串常量池存在运行时常量池之中(在JDK7之前存在运行时常量池之中,在JDK7已经将其转移到堆中)。

            字符串常量池的存在使JVM提高了性能和减少了内存开销。

            使用字符串常量池,每当我们使用字面量(String s=1;)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用s(引用s在Java栈中)。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)

     

            使用字符串常量池,每当我们使用关键字new(String s=new String(1);)创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s

    下图是API说明:

            翻译为:初始化一个新创建的字符串对象,以便它表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。除非需要显式的原始副本,否则使用此构造函数是不必要的,因为字符串是不可变的。

            由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串

            鉴于String.intern()在API上的说明和new String(a)创建字符串(创建了两个对象,如果字符串常量池存在则是一个对象)在官方API上的说明,我个人认为字符串常量池存的是字符串对象,当然在JKD7之后,常量池中存储的可能是堆对象的引用,后面会讲到。(可用javap -c反编译即可得到JVM执行的字节码内容,javap -verbose 反编译查看常量池内容)

            关于常量池,我会在后面的一篇相关文章中进行解析。。。。

     

    String源码分析

    下面是String类的部分源码:

    public final class String
    
        implements java.io.Serializable, Comparable<String>, CharSequence
    
    {
    
        /** The value is used for character storage. */
    
        private final char value[];
    
    
    
        /** The offset is the first index of the storage that is used. */
    
        private final int offset;
    
    
    
        /** The count is the number of characters in the String. */
    
        private final int count;
    
    
    
        /** Cache the hash code for the string */
    
        private int hash; // Default to 0
    
    
    
        /** use serialVersionUID from JDK 1.0.2 for interoperability */
    
        private static final long serialVersionUID = -6849794470754667710L;
    
    
    
        ........
    
    }

    首先我们来看看String类,String类是用final修饰的,这意味着String不能被继承,而且所有的成员方法都默认为final方法。

     

    接下来看看String类实现的接口:

             java.io.Serializable:这个序列化接口仅用于标识序列化的语意。

            Comparable<String>这个compareTo(T 0)接口用于对两个实例化对象比较大小。

           CharSequence这个接口是一个只读的字符序列。包括length(), charAt(int index), subSequence(int start, int end)这几个API接口,值得一提的是,StringBuffer和StringBuild也是实现了改接口。

     

    最后看看String的成员属性:

            value[] :char数组用于储存String的内容。

            offset :存储的第一个索引。

            count :字符串中的字符数。

            hash :String实例化的hashcode的一个缓存,String的哈希码被频繁使用,将其缓存起来,每次使用就没必要再次去计算,这也是一种性能优化的手段。这也是String被设计为不可变的原因之一,后面会讲到。

     

    下面是一个String类的一个方法实现:

    public String substring(int beginIndex, int endIndex) {
    
        if (beginIndex < 0) {
    
            throw new StringIndexOutOfBoundsException(beginIndex);
    
        }
    
        if (endIndex > count) {
    
            throw new StringIndexOutOfBoundsException(endIndex);
    
        }
    
        if (beginIndex > endIndex) {
    
            throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    
        }
    
        return ((beginIndex == 0) && (endIndex == count)) ? this :
    
            new String(offset + beginIndex, endIndex - beginIndex, value);
    
    }
    
    

            可以发现,最初传入的String并没有改变,其返回的是一个new String(),即新创建的String对象。其实String类的其他方法也是如此,并不会改变原字符串。这也是String的不可变性,后面会讲到。

     

    Srtring在JVM层解析

    创建字符串形式

            首先形如声明为S ss是一个类S的引用变量ss(我们常常称之为句柄,后面JVM相关内容会讲到),而对象一般通过new创建。所以这里的ss仅仅是引用变量,并不是对象。

            创建字符串的两种基本形式:

    1. String s1=”1”;
    2. String s2=new String(“1”);

     

     

            从图中可以看出,s1使用””引号(也是平时所说的字面量)创建字符串,在编译期的时候就对常量池进行判断是否存在该字符串,如果存在则不创建直接返回对象的引用;如果不存在,则先在常量池中创建该字符串实例再返回实例的引用给s1。注意:编译期的常量池是静态常量池,以后和会讲。。。。

            再来看看s2,s2使用关键词new创建字符串,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s2,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s2。注意:此时是运行期,那么字符串常量池是在运行时常量池中的。。。。

     

    “+”连接形式创建字符串(更多可以查看API):

            (1)String s1=”1”+”2”+”3”;

     

        使用包含常量的字符串连接创建是也是常量,编译期就能确定了,直接入字符串常量池,当然同样需要判断是否已经存在该字符串。

     

            (2)String s2=”1”+”3”+new String(“1”)+”4”;

     

            当使用“+”连接字符串中含有变量时,也是在运行期才能确定的。首先连接操作最开始时如果都是字符串常量,编译后将尽可能多的字符串常量连接在一起,形成新的字符串常量参与后续的连接(可通过反编译工具jd-gui进行查看)。

            接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建StringBuilder对象(可变字符串对象),然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。

            实际上的实现过程为:String s2=new StringBuilder(“13”).append(new String(“1”)).append(“4”).toString();

            当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。

     

            (3)String s3=new String(“1”)+new String(“1”);

     

     

            这个过程跟(2)类似。。。。。。

     

    String.intern()解析

            String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,源码注释:当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。

    下面我们来看个案例:

    public class StringTest {
    
    
    
    public static void main(String[] args) {
    
    // TODO 自动生成的方法存根
    
           String s3 = new String("1") + new String("1");
    
            System.out.println(s3 == s3.intern());
    
        
    
    }
    
    
    }

     

    JDK6的执行结果为:false
    JDK7和JDK8的执行结果为:true

     

    JDK6的内存模型如下:

     

            我们都知道JDK6中的常量池是放在永久代的,永久代和Java堆是两个完全分开的区域。而存在变量使用“+”连接而来的的对象存在Java堆中,且并未将对象存于常量池中,当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。所以结果为false。

     

    JDK7JDK8的内存模型如下:

     

            JDK7中,字符串常量池已经被转移至Java堆中,开发人员也对intern 方法做了一些修改。因为字符串常量池和new的对象都存于Java堆中,为了优化性能和减少内存开销,当调用 intern 方法时,如果常量池中已经存在该字符串,则返回池中字符串;否则直接存储堆中的引用,也就是字符串常量池中存储的是指向堆里的对象。所以结果为true。

     

    String典型案例

    关于equals和== :

    (1)对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。

    (2)equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。在Object类中,equals方法是用来比较两个对象的引用是否相等。

    (3)对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。

    public class StringTest {
    
    
    
    public static void main(String[] args) {
    
    // TODO 自动生成的方法存根
    
            /**
    
             * 情景一:字符串池
    
              * JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象;
    
             * 并且可以被共享使用,因此它提高了效率。
    
             * 由于String类是final的,它的值一经创建就不可改变。
    
             * 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。  
    
             */  
    
            String s1 = "abc";     
    
            //↑ 在字符串池创建了一个对象  
    
            String s2 = "abc";     
    
            //↑ 字符串pool已经存在对象“abc”(共享),所以创建0个对象,累计创建一个对象  
    
            System.out.println("s1 == s2 : "+(s1==s2));    
    
            //↑ true 指向同一个对象,  
    
            System.out.println("s1.equals(s2) : " + (s1.equals(s2)));    
    
            //↑ true  值相等  
    
            //↑------------------------------------------------------over  
    
            /**
    
             * 情景二:关于new String("")
    
             *  
    
             */  
    
            String s3 = new String("abc");  
    
            //↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;  
    
            //↑ 还有一个对象引用s3存放在栈中  
    
            String s4 = new String("abc");  
    
            //↑ 字符串池中已经存在“abc”对象,所以只在堆中创建了一个对象  
    
            System.out.println("s3 == s4 : "+(s3==s4));  
    
            //↑false   s3和s4栈区的地址不同,指向堆区的不同地址;  
    
            System.out.println("s3.equals(s4) : "+(s3.equals(s4)));  
    
            //↑true  s3和s4的值相同  
    
            System.out.println("s1 == s3 : "+(s1==s3));  
    
            //↑false 存放的地区多不同,一个栈区,一个堆区  
    
            System.out.println("s1.equals(s3) : "+(s1.equals(s3)));  
    
            //↑true  值相同  
    
            //↑------------------------------------------------------over  
    
            /**
    
             * 情景三:  
    
             * 由于常量的值在编译的时候就被确定(优化)了。
    
             * 在这里,"ab"和"cd"都是常量,因此变量str3的值在编译时就可以确定。
    
             * 这行代码编译后的效果等同于: String str3 = "abcd";
    
             */  
    
            String str1 = "ab" + "cd";  //1个对象  
    
            String str11 = "abcd";   
    
            System.out.println("str1 = str11 : "+ (str1 == str11));  
    
            //↑------------------------------------------------------over  
    
            /**
    
             * 情景四:  
    
             * 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。
    
             *  
    
             * 第三行代码原理(str2+str3):
    
             * 运行期JVM首先会在堆中创建一个StringBuilder类,
    
             * 同时用str2指向的拘留字符串对象完成初始化,
    
             * 然后调用append方法完成对str3所指向的拘留字符串的合并,
    
             * 接着调用StringBuilder的toString()方法在堆中创建一个String对象,
    
             * 最后将刚生成的String对象的堆地址存放在局部变量str4中。
    
             *  
    
             * 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。
    
             * str4与str5地址当然不一样了。
    
             *  
    
             * 内存中实际上有五个字符串对象:
    
             *       三个拘留字符串对象、一个String对象和一个StringBuilder对象。
    
             */  
    
            String str2 = "ab";  //1个对象  
    
            String str3 = "cd";  //1个对象                                         
    
            String str4 = str2+str3;                                        
    
            String str5 = "abcd";    
    
            System.out.println("str4 = str5 : " + (str4==str5)); // false  
    
            //↑------------------------------------------------------over  
    
            /**
    
             * 情景五:
    
             *  JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。
    
             *  运行期的两个string相加,会产生新的对象的,存储在堆(heap)中
    
             */  
    
            String str6 = "b";  
    
            String str7 = "a" + str6;  
    
            String str67 = "ab";  
    
            System.out.println("str7 = str67 : "+ (str7 == str67));  
    
            //↑str6为变量,在运行期才会被解析。  
    
            final String str8 = "b";  
    
            String str9 = "a" + str8;  
    
            String str89 = "ab";  
    
            System.out.println("str9 = str89 : "+ (str9 == str89));  
    
            //↑str8为常量变量,编译期会被优化  
    
            //↑------------------------------------------------------over  
    
    }
    
    }

     

    运行结果:

    s1 == s2 : true

    s1.equals(s2) : true

    s3 == s4 : false

    s3.equals(s4) : true

    s1 == s3 : false

    s1.equals(s3) : true

    str1 = str11 : true

    str4 = str5 : false

    str7 = str67 : false

    str9 = str89 : true

     

     

    String被设计成不可变和不能被继承的原因

            String是不可变和不能被继承的(final修饰),这样设计的原因主要是为了设计考虑、效率和安全性。

     

    字符串常量池的需要:

            只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段。

     

    String对象缓存HashCode:

            上面解析String类的源码的时候已经提到了HashCode。Java中的String对象的哈希码被频繁地使用,字符串的不可变性保证了hash码的唯一性。

     

    安全性

            首先String被许多Java类用来当参数,如果字符串可变,那么会引起各种严重错误和安全漏洞。

            再者String作为核心类,很多的内部方法的实现都是本地调用的,即调用操作系统本地API,其和操作系统交流频繁,假如这个类被继承重写的话,难免会是操作系统造成巨大的隐患。

            最后字符串的不可变性使得同一字符串实例被多个线程共享,所以保障了多线程的安全性。而且类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。

     

     

    学习参考资料:

    https://www.cnblogs.com/xiaoxi/p/6036701.html

    https://tech.meituan.com/in_depth_understanding_string_intern.html

     

     

     

    展开全文
  • String用法详解

    万次阅读 多人点赞 2018-04-05 16:15:51
    string类声明string类本不是STL的容器,但是它与STL容器有着很多相似的操作,因此,把string放在这里一起进行介绍。 之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为他和前者比较起来,不必担心...
  • 在Java语言中,没有内置的字符串类型,而是在标准准Java类库中提供了一个预定义类(即String类),这个类是对基本数据类型中的char的一个封装。String类型声明时需要用“”将字符串内容引起来。一、常用方法及相关...
  • 本文将介绍String、StringBuilder类的常用方法。 > 在java中**String**类不可变的,创建一个**String**对象后不能更改它的值。所以如果需要对原字符串进行一些改动操作,就需要用**StringBuilder**类或者**...
  • String的方法 1.字符串拼接: String s1 = "hello"; s1 = s1.concat(",."); 2.s1是否包含指定字符子串 boolean isCon = s1.contains("ll"); 3.是否以指定字符串结尾 boolean isEnds = s1.endsWith("@126.com"); 4...
  • String和StringBuilder常用方法 String的用法: String.length() //获取字符串的长度 String.charAt(i) //获取第i个字符的内容 String.subString(start) //获取[start,)的字符串 String.subString(start,end) //...
  • 本人是个IT初学者,一直对编程很感兴趣,每当了解到新的知识都会有想弄明白的冲动,但愿我能保持这股冲劲,几年后还能说出这句话吧(ง •̀_•́)ง String类和StringBuilder类的常用方法 String
  • C++中的string类用法简介

    万次阅读 多人点赞 2019-08-30 15:20:35
    本文主要介绍C++中的string类的常见用法。 1. 概述 string是C++标准库的一个重要的部分,主要用于字符串处理。可以使用输入输出流方式直接进行string操作,也可以通过文件等手段进行string操作。同时,C++的算法库...
  • 1 String转换为int类型的方法

    万次阅读 2018-03-02 11:17:53
    Object obj = getObject();if(obj instanceof Integer) int value = ... 1 String转换为int类型的方法: 2 1. Integer.parseInt([String]) 3 2.Integer.valueOf([String]).intValue(); 4 3.Integer.decode([S...
  • String...的用法

    万次阅读 多人点赞 2019-01-08 11:10:32
    一、前言 在项目中,在检查参数是否为空的工具类中,出现了一个函数的参数列表是这种写法,于是学习了一下这种用法的使用。个人觉得这种处理还是很不错的,使得代码更加简洁... 例如我们有一个方法叫做test(String…...
  • String类的20种常见方法

    万次阅读 多人点赞 2017-02-05 16:32:46
    String类里面常用的20个方法 和长度有关的方法返回类型 方法名 作用 int length() 得到一个字符串的字符个数(一个中文是一个字符,一个英文是一个字符,一个转义字符是一个字符) 和数组有关的方法返回类型 方法...
  • String和List<String>间的相互转换

    万次阅读 2018-06-06 15:13:08
    public void test() { //字符串转list&lt;String&gt; String str = "asdfghjkl"; List&...String&... lis = Arrays.asList(str.split("... for (String string : lis) { ...
  • 记录一下用到过的int转换成string的两种方法 第一种是to_string函数,这是C++11新增的,使用非常方便,简单查了下:C++11标准增加了全局函数std::to_string,以及std::stoi/stol/stoll等等函数(这几个就是string转...
  • String转化为String数组

    万次阅读 2018-09-26 14:21:56
    今天做项目遇到个问题,前台传给我一个String数组,但是我这儿接收的时候都是是String类型,那么就需要将String转为String数组 public class test { public static void main(String[] args) { String pcArray ...
  • String [] 和 String互相转换

    万次阅读 2018-04-09 16:04:45
    1、StringString[ ] String[] ids = perms.split(","); 2、String [ ] 转 String 逗号隔开 String [ ] = lytype; StringBuilder sb = new StringBuilder();  if (lytype != null && lytype.length > 0)
  • String数组转List,List转String数组

    万次阅读 2011-12-09 09:17:41
    List list = new ArrayList(); list.add("a1"); list.add("a2"); String[] toBeStored = list.toArray(new String[list.size()]); for(String s : toBeStored) { System.out.pri
  • int与String互相转换

    万次阅读 2018-11-09 21:54:02
    一、String转为int int i=Integer.parseInt(string); int i=Integer.valueOf(s).intValue(); 二、int转为String String s = String.valueOf(i); String s = Integer.toString(i); String s = “” + i; ...
1 2 3 4 5 ... 20
收藏数 7,918,602
精华内容 3,167,440
关键字:

string