字符串

123 阅读9分钟

字符串

字符串的重要性

    字符串在我们的编程中是非常重要的一种数据类型。

    1. 使用量最大;

    2. 它几乎可以表达所有的我们常见的数据类型的值。或者换句话,对于程序外部的使用者
    
    而言,所有的交互信息(文本)在本质都是字符串,他们是意识不到其他数据类型的存在
    
    的。原因:数据类型是程序内部使用的,告知计算机空间划分,数据存储转换方式,对于外
    
    部是没有意义的。

    3. 字符串操作的场景非常多,所以也需要做一定层度的优化设计。

Java中的字符串类型

    在Java中一共提供了三种类型可以表示字符串:String、StringBuilder、
    
    StringBuffer。

    要谈论为什么有三个字符串类型,我们要先聊聊String的一些特性。
String

1.String是唯一一个设计了字面常量的引用数据类型

        ```java

            String str = "hello";

            System.out.println("请输入:");

        ```

    回顾:

        常量指的是不能修改变化的数据量,与它对应的是变量。

    常量分两种:

        1、符号常量;2、字面常量

    “符号常量”是给常量起一个名字(符号)

        ```java

            final int STUDENT_NUM = 50;

            final double PI = 3.14;

            final double PRICE = 3.14;

        ```
    “字面常量”其实更常用,但也更容易被忽略。

    当我们在代码中出现:5L,-19,3.4,3.14f,'A',"你的年龄是:"。

    所以,再次希望大家注意--字面常量的特点,特别是它的字面书写形式代表了数据类型。

    我们可以很容易的发现几乎所有的字面常量都是基本数据类型,唯有String这个引用数据
    
    类型拥有一个字面常量的设计!
2.String拥有一个“字符串常量池”的设计
    由于String的使用量非常大,所以对于String的字面常量,Java在设计的时候专门提供的
    
    了一个“常量池”来优化。

    所谓池,就是预先在内存当中放置一系列的对象(字符串常量池就是放的字符串常量对
    
    象)。当需要使用的时候,不用临时去创建,而是从池当中取一个来用就可以了。

    JVM会在加载的时候,把加载到的类代码当中,所有书写的字符串常量对象,预存到一个专
    
    门的内容空间---"字符串常量池"。然后开始执行指令语句,当需要用到这个字符串常量对
    
    象的时候,就直接到常量池中去取。

    *演示*

        ```java

            String s0 = "hello";

            String s1 = "hello";

        ```

    此时,在加载期,就会在常量池当中产生一个String类型的对象,里面的值是hello。然后
    
    运行起来以后,s0和s1都会被赋值为这个对象的引用。所以,用"=="比较的时候,我们能
    
    得到true。

        ```java

            String s3 = new String("hello");

        ```

    由于使用了new的语法,那么会在内存的堆当中产生一个全新的String对象,里面的字符值
    
    是hello。
3、String判断非空,应该判断两个条件。
    其他的引用数据类型,只需要判断是否“==”null;但是String有一种特殊性,它可能不为
    
    null,但是指向的String对象里面没有存放字符数据,是一个空串。

    所以String判断非空要用两个条件

    ```java

      //如果输入为空

        if(str == null || str.equals("")){

    }

      //如果输入不为空

        if(str != null && !str.equals("")){

    }      

    ```
4、String对象的值一旦确定,不能改变。
    String的这个特点其实是和String的源代码设计有关系。我们可以把String看成是一个封
    
    装的char[]。

        ```java

            public final class String{

                private final char[] value;

                /*

                    还有其他属性和一大堆的方法。

                */

            }

        ```
        
    而在这个设计当中,我们可以看到字符串中的数据值是被作为属性存在的,而且该属性是私
    
    有的,所以外部不能直接操作,要利用String提供的方法来操作;同时该属性是final的,
    
    所以它的值不能被修改。

    到了新版本的JDK8之后,这个char[]被优化成了byte[]。因为不是所有的字符都需要2个
    
    字节的空间,很多只需要1个字节空间就可以了。

    这种内容不可更改的特性又会照成一个新的问题,当我们大量的在程序中做字符串拼接或
    
    需要修改字符串内容的动作时,就会产生很多字符串对象。所以,Java又设计了新的字
    
    符串类型专门解决这个问题。

StringBuilder和StringBuffer

    StringBuilder 是在JDK5当中,提出来的一个辅助String的字符串类型。它最大的特点
    
    是:内容可变。

    注意:StringBuilder不是String类型,是一种新的类型,所以如下代码都是错的:

        ```java

            StringBuilder sb = "hello";

            System.out.println(sb + "world");

        ```
    由于StringBuilder是为了弥补String在内容不可变上的缺点,所以它提供的方法几乎都
    
    是对内容的修改方法。

    1、append()方法

        它的作用是在字符串的尾部添加内容。为了能够将多种数据类型都方便的添加到尾部,
        
    提供了大量的重载方法。

    2、delete()方法

        它的作用是在字符串中删掉指定从开始下标到结束下标的内容。

    3、insert()方法

        它的作用是在字符串中指定位置插入新的内容。它的第一个参数就是插入位置的下标。
    
    4、replace()方法******

        它的作用是把字符串中指定位置的内容替换为新的内容。      

    StringBuffer也是一个可变的字符串序列,它和StringBuilder的构造、提供的行为完全
    
    一样。唯一的区别就是:StringBuffer是线程安全的。
关于线程安全
    1. 线程安全的,效率低;

    2. 线程不安全的,效率高;

    3. 不是多线程就一定要讲线程安全,多线程的不安全也是在特定条件下才会发生。

String与StringBuffer或StringBuilder的使用场景

    虽然说StringBuffer和StringBuilder具有内容可变的特点,但是对于字符串操作来说它
    
    们两个提供的方法远远不及String的丰富度。

    所以这也就限制了他们两个的使用场景。对于我们来说大部分情况下,我们还是使用的
    
    String。只有当我们需要“大量”的做字符串内容改变的动作,比如:大量的拼接,大量的
    
    插入,大量的删除.....我们才会去考虑另外两个。

    所谓“大量”,也不是程序员的感官感觉,而是要根据是否影响到了执行效率和内存空间,往
    
    往是测试出来的。

    *结论:* 对于StringBuffer和StringBuilder我们主要掌握内容改变的这些方法,以备
    
    不时之需。更迫切的是掌握String当中的那些常用方法。

String当中的常用方法

    与String当中的字符内容有关的方法

        1. length() -- 获取字符串当中有多少个字符。

        2. charAt() -- 根据下标,获取该位置的字符。下标从0开始,如果越界报
        
        “StringIndexOutofBoundsException”。

        3. indexOf() --- 根据内容,返回该内容在字符串当中出现的位置。内容可以
        
        是:char、int、String

        4. lastIndexOf()  --- 根据内容,返回该内容在字符串当中最后一次出现的
        
        位置。34如果找不到,均返回-1,不会报异常。

        5. toCharArray() --- 将字符串的内容构造为一个char[],然后返回出来。

        6. toUpperCase();toLowerCase()  --- 把字符串的内容转换为全大写或全小
        
        写。注意:由于String的内容不可变,所以其实是返回了一个新的字符串。

        7. replace()  --- 用新的字符或字符串,替换字符串中的老字符或子串。

        8. replaceAll() --- 把字符串当中所有 *满足正则* 的内容都全部替换

        9. subString() --- 获取字符串中的部分子串。两个参数代表截取的开始和结
        
        束,但是前闭后开的。

    与String内容比较有关的

        1. equals()

        2. equalsIgnoreCase() --- 忽略大小写比较相等

        3. compareTo() --- 比较两个字符串的大小,返回的是它们的字典顺序。

        4. compareToIgnoreCase()  --- 比较两个字符串的大小,忽略大小写。

        5. startWith() --- 判断字符串是否以某个子串开头(*支持正则*)

        6. endWith() --- 判断字符串是否以某个子串结尾(*支持正则*)

        7. contains() --- 判断字符串是否包含某个子串。

介绍3个特殊方法

    1trim() -- 去掉字符串的前后空格

        当我们在界面上接收了用户的输入以后,很多公司都要求必须要先用这个方法把输入内
        
        容的前后无效空格给去掉,然后才能使用。

    2split() -- 把字符串按分隔符进行拆分,返回一个字符串数组。其中分隔符*
    
    支持正则*;

    3matches() -- 判断一个字符串是否满足正则表达式(regex) 。

正则表达式

    正则表达式是一种判定字符串内容是否符合某种规范的表达式,它本身的形式就是一个字符串。

    要学习正则表达式,我们首先就要学习正则当中的各种符号:

    1、任意一个字符串如果不使用正则表达式的特殊符号,那么它也仍然是正则表达式;只不
    
    过,这个字符串的所有内容没有变化度,全部被指定死了。

    2、如果我们要匹配更多的灵活情况,那么我们必须要去学习正则表达式的特殊符号。  

    正则表达式在各种编程语言当中都已经实现了,且基本上都是遵循了统一的语法规则(全球
    
    标准),只有部分语言自己多提了点特殊语法(用来号称自己更好用而已)。

    虽然符号很多,但是抓住三个关键,就是三种括号:

        [] --- 在正则表达式当中,一个[]代表一个字符,然后把该字符可以有哪些选择,写
        
        在它内部。如果在[]内部出现^,那么表示除了这些符号,其他都可以。比如:[^0-9]

        {} --- 在正则表达式当中,代表它前面的表达式能够出现的次数。

        {m,n} --- 至少出现m次,最多出现n次

        {m,} --- 至少出现m次,最多不限

        {m} --- 只能出现m次

    它也有简写:

        ?代表 0 - 1次

        +代表 至少1次

        *代表 任意次

    () --- 代表可选,可选项之间用“|”号分隔。

     正则表达式中正确性比简洁性要重要得多!

     在实际开发中,经常使用到的正则表达式的地方很多,通常都是输入有效性的验证:

     电话号码、E-mail、邮政编码、身份证......