Java编程思想读书笔记——字符串

第十三章 字符串

13.1 不可变String

String对象是不可变的(不能够原地修改),具备只读特性。String类中每一个修改String值的方法,实际上是创建了一个新的String对象。

当把String对象作为方法的参数时,都会复制一份引用,而该引用所指向的对象其实一直呆在单一的物理位置上,从未移动过。

13.2 重载”+”与StringBuilder

String对象的这种只读特性,在进行字符串连接时会产生性能问题。例如:

package com.mzm.chapter13;

/**
 * 
 */
public class Concatenation {

    public static void main(String[] args){
        String mango = "mango";
        String s = "abc" + mango + "def" + 47;
        System.out.println(s);
    }
}

在上述代码中,在进行字符串连接时,首先是”abc”和”mango”这两个字符串常量连接,创建一个”abcmango”字符串常量,之后是”abcmango”和”def”这两个字符串常量连接,创建一个”abcmangodef”字符串常量,最后是”abcmangodef”与数字常量47的连接,最终创建出”abcmangodef47”的字符创常量,可见在使用”+”重载符进行字符串连接时,会产生大量需要垃圾回收的中间对象。

但是在JVM会在底层调用StringBuilder类进行优化。

当为类编写toString()方法时,建议使用StringBuilder。

13.3 无意识的递归
package com.mzm.chapter13;

import java.util.ArrayList;
import java.util.List;

/**
 * 打印对象的内存地址
 */
public class InfiniteRecursion {

    public String toString() {
        //此处不能使用this,因为存在自动类型转换,会调用this的toString()方法,这样就形成了递归调用
        //return " InfiniteRecursion address: " + this + "\n";
        //应该使用Object类的toString(),即super.toString()
        return " InfiniteRecursion address: " + super.toString() + "\n";
    }

    public static void main(String[] args) {
        List<InfiniteRecursion> v = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            v.add(new InfiniteRecursion());
        }
        System.out.println(v);
    }
}
13.4 String上的操作
方法参数,重载版本应用
构造器重载版本:默认版本,String,StringBuilder/StringBuffer,char数组,byte数组创建String对象
length()String中字符的个数
charAt()Int索引取得String中该索引位置上的char
getChars()/getBytes()要复制部分的起点和终点的索引,复制的目标数组,目标数组的起始索引复制char或byte到一个目标数组中
toCharArray()生成一个char[],包含String中的所有字符
equals()/equalsIngoreCase()与之进行比较的String比较两个String的内容是否相同
compareTo()与之进行比较的String按词典顺序比较String的内容,比较结果为负数、零或正数。注意,大小写不等价
contains()要搜索的CharSequence如果该String对象包含参数的内容,则返回true
containEquals()与之进行比较的CharSequence或StringBuffer如果该String与参数的内容完全一致,则返回true
equalsIgnoreCase()与之进行比较的String忽略大小写,如果两个String的内容相同,则返回true
regionMatcher()该String的索引偏移量,另一个String及其索引偏移量,要比较的长度。重载版本增加了”忽略大小写”功能返回boolean结果,以表明所比较区域是否相等
startWith()可能的起始String。重载版本在参数中增加了偏移量返回boolean结果,以表明该String是否以此参数起始
endWith()该String可能的后缀String返回boolean结果,以表明次蚕食是否是该字符串的后缀
indexOf()/lastIndexOf()重载版本包括:char,char与起始索引,String,String与起始索引如果该String并不包含此参数,则返回-1;否则返回此参数在String中的起始索引。lastIndexOf()是从后向前搜索
substring()/subSequence()重载版本:起始索引;起始索引+终点坐标返回一个新的String,以包含参数指定的子字符串
concat()要连接的String返回一个新的String对象,内容为原始String连接上参数String
replace()要替换掉的字符,用来进行替换的新字符。也可以用一个CharSequence来替换另一个CharSequence返回替换后的新String对象。如果没有替换发生,则返回原始的String对象
toLowerCase()/toUpperCase()将字符的大小写改变后,返回一个新的String对象。如果没有改变发生,则返回原始的String对象
trim()将String两端的空白字符删除后,返回原始的String对象
valueOf()重载版本:Object;char[];char[],偏移量,与字符个数;boolean;char;int;long;float;double返回一个表示参数内容的String
intern()为每个唯一的字符序列生成一个且仅生成一个String引用
13.5 格式化输出
13.5.1 printf()

与C语言的printf()类似。
格式修饰符:表明插入数据的位置和插入数据的类型。

13.5.2 System.out.format()

等价于printf()

13.5.3 Formatter类

在Java中,所有新的格式化功能都由java.util.Formatter类处理。创建Formatter对象可以指定输出流,表示格式化后的结果最终输出地。Formatter对象的format()方法即与前面提到的System.out.printf()和System.out.format()用法相同。

13.5.4 格式化说明符

精细复杂的格式化修饰符:

%[argument_index$][flags][width][.precision]conversion

width用来控制最小尺寸,不足时添加空格。
默认是右对齐,可以使用-改变对其方式。

precision用来控制最大尺寸。当precision用于String时,表示打印String时输出字符的最大数量。当precision用于浮点数时,表示小数部分显示的位数,多则舍入,少则补零。但是precision不能用于整数,否则会抛异常。

13.5.5 Formatter转换
类型转换字符
d整数型(十进制)
cUnicode字符
bBoolean值
sString
f浮点数(十进制)
e浮点数(科学计数)
x整数(十六进制)
h散列码(十六进制)
%字符”%”
13.5.6 String.format()

String.format()参照了C语言的sprintf()方法,生成格式化的String对象,其参数与Formatter.format()一样,只是返回一个String对象。

其实在String.format()方法内部,也是创建一个Formatter对象,然后参数传给Formatter对象进行处理。

13.6 正则表达式
13.6.1 基础

String对象自带的match()方法,可以判断该字符串是否匹配指定的正则表达式。
String对象的split()方法,可以传入指定的正则表达式,表示将该字符串按照给定的正则表达式分割,其还有一个重载版本,允许传入第二个参数,来限定字符串分割的次数。
String对象的replace()/replaceFirst()/replaceAll()等方法,第一个参数可以是正则表达式,第二个参数为作为替换的字符串。

13.6.2 创建正则表达式
字符
B指定字符B
\xhh十六进制值为oxhh的字符
\uhhhh十六进制表示为oxhhhh的Unicode字符
\t制表符Tab
\n换行符
\r回车
\f换页
\e转义(Escape)
字符类
.任意字符
[abc]包含a、b和c的任何字符和(a|b|c作用相同)
[^abc]除了a、b和c之外的任何字符(否定)
[a-zA-Z]从a到z或者从A到Z的任何字符(范围)
[abc[hij]]任意a、b、c、h、i和j字符(a|b|c|h|i|j作用相同)(合并)
[a-z&&[hij]]任意h、i或jh(交)
\s空白符(空格、tab、换行、换页和回车)
\S非空白符([^\s])
\d数字[0-9]
\D非数字[^0-9]
\w词字符[a-zA-Z0-9]
\W非词字符[^\w]
逻辑操作符
XYY跟在X后面
X|YX或Y
(X)捕获组(capturing group)。可以在表达式中用\i引用第i个捕获组
边界匹配符
^一行的起始
$一行的结束
\b词的边界
\B非词的边界
\G前一个匹配的结束
13.6.3 量词

量词描述了一个模式吸收文本的方式:

  • 贪婪型:量词总是贪婪的,除非有其他的选项被设置。贪婪表达式会为所有可能的模式发现尽可能多的匹配。
  • 勉强型:用问号指定,这个量词匹配满足模式所需的最少字符数。
  • 占有型:目前只存在于Java。当正则表达式被应用于字符串时,它会产生相当多的状态,以便在匹配失败时可以回溯。不常用。
贪婪型勉强型占有型如何匹配
X?X??X?+一个或零个X
X*X*?X*+零个或多个X
X+X+?X++一个或多个X
X{n}X{n}?X{n}+恰好n次X
X{n,}X{n,}?X{n,}+至少n次X
X{n,m}X{n,m}?X{n,m}+X至少n次,且不超过m次

注意X通常必须用括号括起。

13.6.4 Pattern和Matcher
package com.mzm.chapter13;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 测试正则表达式是否匹配
 * 第一个参数是要匹配的字符串
 * 之后的参数都是正则表达式
 * 
 */
public class TestRegularExpression {

    public static void main(String[] args){
        if(args.length < 2){
            System.out.println("Usage:\njava TestRegularExpression characterSequence regularExpression+");
            System.exit(0);
        }

        System.out.println("Input: \"" + args[0] + "\"");
        for(String arg : args){
            System.out.println("Regular expression: \"" + arg + "\"");
            Pattern p = Pattern.compile(arg);
            Matcher m = p.matcher(args[0]);
            while(m.find()){
                System.out.println("Match \"" + m.group() + "\" at positions " + m.start() + "-" + (m.end() - 1));
            }
        }
    }
}

先使用Pattern.compile()传入要正则表达式来创建Pattern对象,之后使用该Pattern对象的matcher()方法,传入要匹配的字符串,获得Matcher对象。Pattern类还有提供了静态的matches()方法:

static boolean matches(String regex, CharSequence input)

(CharSequence是CharBuffer、String、StringBuffer和StringBuilder这四个类抽象出来的字符序列接口)

该方法检查regex是否匹配整个CharSequence类型的input参数。Pattern对象还有split()方法,与String对象的split()方法类似。

获取Matcher对象后,可以使用Matcher上的方法:

boolean mathes()
boolean lookingAt()
boolean find()
boolean find(int start)

matches()方法判断整个字符串是否匹配正则表达式模式;
lookingAt()方法则表示该字符串的起始部分是否匹配正则表达式,无需整个字符串匹配。
find()方法可用来在CharSequence中查找多个匹配。

package com.mzm.chapter13;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 */
public class Finding {

    public static void main(String[] args){
        Matcher m = Pattern.compile("\\w+").matcher("Evening is full of the linnet's wings");

        while(m.find()){
            System.out.print(m.group()+ " ");
        }
        System.out.println();

        int i = 0;
        while(m.find(i)){
            System.out.print(m.group() + " ");
            i++;
        }
    }
}

模式\w+将字符串划分为单词,find()匹配每一个单词。find(int start)表示从该字符串的start位置开始匹配单词。

组(Group)
组是用括号划分的正则表达式,可以根据组的编号来引用某个组。组号为0表示整个正则表达式,组号为1表示被第一对括号括起的组,依此类推。

Matcher对象关于组的方法:

public int groupCount():返回该匹配器的模式中的分组数目,不包括第0组;
public String group():返回前一次匹配操作的第0组(整个匹配);
public String group(int i):返回在前一次匹配操作期间指定的组号,如果匹配成功,但指定的组没有匹配输入字符串的任何部分,则返回nullpublic int start(int group):返回在前一次匹配操作中寻找到的组的起始索引;
public int end(int group):返回在前一次匹配操作中寻找到的组的最后一个字符索引加一的值。
package com.mzm.chapter13;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 */
public class Groups {

    static public final String POEM =
            "Twas brillig, and the slithy toves\n" +
            "Did gyre and gimble in the wabe.\n" +
            "All mimsy were the borogoves,\n" +
            "And the mome raths outgrabe.\n\n" +
            "Beware the Jabberwock, my son,\n" +
            "The jaws that bite, the claws that catch.\n" +
            "Beware the Jubjub bird, the claws that catch.\n" +
            "The frumious Bandersnatch.";

    public static void main(String[] args){
        //正则表达式匹配的是每一行的最后三个单词
        //?m表示标记模式,指定$表示的是每一行的末尾
        Matcher m = Pattern.compile("(?m)(\\S+)\\s+((\\S+)\\s+(\\S+))$").matcher(POEM);
        while(m.find()){
            for(int j = 0; j <= m.groupCount(); j++){
                System.out.print("[" + m.group(j) + "]");
            }
            System.out.println();
        }
    }
}

start()和end()
在匹配成功后,start()返回先前匹配的起始位置的索引,而end()返回所匹配的最后字符的索引加一的值。而匹配失败,调用start()或end()都会抛出IllegalStateException异常。

package com.mzm.chapter13;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by 蒙卓明 on 2017/11/4.
 */
public class StartEnd {

    public static String input =
            "As long as there is injustice, whenever a\n" +
            "Targathian baby cries out, wherever a distress\n" +
            "signal sounds among the stars ... We'll be there.\n" +
            "This fine ship, and this fine crew ...\n" +
            "Never give up! Never surrender!";

    private static class Display{
        private boolean regexPrinted = false;
        private String regex;

        Display(String regex){
            this.regex = regex;
        }

        void display(String message){
            if(!regexPrinted){
                System.out.println(regex);
                regexPrinted = true;
            }
            System.out.println(message);
        }
    }

    static void examine(String s, String regex){
        Display d = new Display(regex);
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(s);
        while(m.find()){
            d.display("find() '" + m.group() + "' start = " + m.start() + " end = " + m.end());
        }

        if(m.lookingAt()){
            d.display("lookingAt() start = " + m.start() + " end = " + m.end());
        }

        if(m.matches()){
            d.display("matches() start = " + m.start() + " end = " + m.end());
        }
    }

    public static void main(String[] args){
        for(String in : input.split("\n")){
            System.out.println("input : " + in);
            for(String regex : new String[]{"\\w*ere\\w*", "\\w*ever", "T\\w+", "Never.*?!"}){
                examine(in, regex);
            }
        }
    }
}

find()可以在输入的任意位置开始匹配正则表达式;
lookingAt()只能在字符串的开头开始匹配,但不需要整个字符串匹配成功;
matches()也是只能在字符串的开头开始匹配,且需要整个字符串匹配成功。

Pattern标记
Pattern类还有一个complie()方法,接受一个标记参数。

Pattern Pattern.compile(String regex, int flag)

其中flag来自于以下Pattern类常量:

编译标记效果
Pattern.CANON_EQ两个字符当且仅当它们的完全规范分解相匹配时,就认为它们是匹配的。例如,如果我们指定这个标记,表达式a\u030A就会匹配字符串?。默认的情况下,匹配不考虑规范的等价性。
Pattern.CASE_INSENSITIVE(?i)默认情况下,大小写不敏感的匹配假定只有US-ASCII字符集中的字符才能进行。这个标记模式不必考虑大小写。与UNICODE_CASE结合,开启基于Unicode的大小写不敏感的匹配
Pattern.COMMENTS(?x)在这种模式下,空格符将被忽略掉,并且以#开始直到行末的注释也会被忽略掉。通过嵌入标记表达式也可以开启Unix的行模式
Pattern.DOTALL(?s)在dotall模式中,表达式”.”匹配所有字符,包括行终止符。默认情况下,”.”表达式不匹配行终止符
Pattern.MULTILINE(?m)在多行模式下,表达式^和 分 别 匹 配 一 行 的 开 始 和 结 束 , 同 时 还 匹 配 输 入 字 符 串 的 开 始 , 还匹配输入字符串的结束。在默认模式下,^和$分别只匹配字符串的开始和结束
Pattern.UNICODE_CASE(?u)当指定这个标记,并且开启CASE_INSENSITIVE时,大小写不敏感的匹配将按照与Unicode标准相一致的方式进行。默认情况下,大小写不敏感的匹配假定只能在US-ASCII字符集中的字符才能进行
Pattern.UNIX_LINES(?d)在这种情况下,在.、^和$行为中,只识别行终结符\n

Pattern.CASE_INSENSITIVE、Pattern.MULTILINE和Pattern.COMMENTS比较有用。

package com.mzm.chapter13;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 */
public class ReFlags {

    public static void main(String[] args){
        Pattern p = Pattern.compile("^java", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
        Matcher m = p.matcher(
                    "java has regex\nJava has regex\n" +
                          "JAVA has pretty good regular expressions\n" +
                          "Regular expressions are in Java"
        );
        while(m.find()){
            System.out.println(m.group());
        }
    }
}
13.6.5 split()

Pattern类的split()分割字符串,断开边界由正则表达式确定:

String[] split(CharSequence input)
String[] split(CharSequence input, int limit)
13.6.6 替换操作

replaceFirst(String replacement)以参数字符串replacement替换掉第一个匹配成功的部分;
replaceAll(String replacement)以参数字符串replacement替换所有匹配成功的部分;
appendReplacement(StringBuffer sbuf, String replacement)执行渐进式替换,该方法允许调用其他方法来生成或处理replacement。
appendTail(StringBuffer sbuf)在执行一次或多次appendReplacement()后,调用此方法可以将输入的字符串余下的部分复制到sbuf中。

package com.mzm.chapter13;


import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * appendReplacement()的用法
 */
public class TheReplacements {

    public static void main(String[] args){
        String s =
                "/*! Here's a block of text to use as input to\n" +
                "    the regular expression matcher. Note that we'll\n" +
                "    first extract the block of text by looking for\n" +
                "    the special delimiters, then process the \n" +
                "    extracted block. !*/";
        Matcher mInput = Pattern.compile("/\\*!(.*)!\\/", Pattern.DOTALL).matcher(s);
        if(mInput.find()){
            s = mInput.group(1);
        }

        s = s.replaceAll(" {2,}", " ");

        s = s.replaceAll("(?m)^ +", "");

        System.out.println(s);

        s = s.replaceFirst("[aeiou]", "(VOWEL1)");

        StringBuffer sbuf = new StringBuffer();
        Pattern p = Pattern.compile("[aeiou]");
        Matcher m = p.matcher(s);

        while(m.find()){
            //每捕获一个组,用另一个字符串替换(这里是捕获组的大写)
            //每进行一次替换,将结果存入一个StringBuffer对象中
            m.appendReplacement(sbuf, m.group().toUpperCase());
        }
        //将输入字符串的剩余部分复制到StringBuffer对象中
        m.appendTail(sbuf);
        System.out.println(sbuf);
    }
}
13.6.7 reset()方法

reset()方法可以将现有的Matcher对象应用于新的字符序列

13.6.8 正则表达式与Java IO
13.7 扫描输入

Scanner的构造器可接受任何类型的输入对象,包括File、InputStream、String或者Readable对象。所有的输入、分词以及翻译的操作都隐藏在不同类型的next方法中。next()返回下一个String,除char之外,所有的基本类型都有对应的next方法,包括BigInteger和BigDecimal。hasNext()方法判断下一个输入分词是否所需的类型。

Scanner类接受输入时,无需显式的抛出IOException,因为Scanner假设输入结束就会抛出IOExceotion,并将该异常吞掉。但可以通过ioException()方法找到最近发生的异常。

13.7.1 Scanner定界符

在默认的情况下,Scanner根据空白字符对输入进行分词,但可以使用正则表达式指定所需的定界符:

package com.mzm.chapter13;

import java.util.Scanner;

/**
 * 使用逗号作为Scanner类的定界符来进行分词
 * Created by 蒙卓明 on 2017/11/5.
 */
public class ScannerDelimiter {

    public static void main(String[] args) {
        Scanner scanner = new Scanner("12, 42, 78, 99, 42");
        //指定逗号作为定界符
        scanner.useDelimiter("\\s*,\\s*");
        while (scanner.hasNextInt()) {
            System.out.println(scanner.nextInt());
        }
    }
}

Scanner对象还有delimiter()方法,返回当前作为定界符使用的Pattern对象。

13.7.2 用正则表达式进行扫描

除扫描基本类型外,还可以使用正则表达式进行扫描:

package com.mzm.chapter13;

import java.util.Scanner;
import java.util.regex.MatchResult;

/**
 * Scanner使用正则表达式扫描
 * 
 */
public class ThreatAnalyzer {

    static String threatData =
            "58.27.82.161@02/10/2005\n" +
            "204.45.234.40@02/11/2005\n" +
            "58.27.82.161@02/11/2005\n" +
            "58.27.82.161@02/12/2005\n" +
            "58.27.82.161@02/12/2005\n" +
            "[Next log section with different data format]";

    public static void main(String[] args){
        Scanner scanner = new Scanner(threatData);
        String pattern = "(\\d+[.]\\d+[.]\\d+[.]\\d+)@(\\d{2}/\\d{2}/\\d{4})";
        //使用正则表达式扫描
        while(scanner.hasNext(pattern)){
            scanner.next(pattern);
            //调用match()方法匹配
            MatchResult match = scanner.match();
            String ip = match.group(1);
            String date = match.group(2);
            System.out.format("Threat on %s from %s\n", date, ip);
        }
    }
}
13.8 StringTokenizer

在JDK1.4之前,使用StringTokenizer进行分词,但引入正则表达式和Scanner类后,该类已较少使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值