字符串——读《编写高质量代码:改善Java程序的151个建议》(四)

读书,收获,分享
建议后面的五角星仅代表笔者个人需要注意的程度。
Talk is cheap.Show me the code

建议52:推荐使用String直接量赋值★☆☆☆☆

示例代码:

public class Client {

    public static void main(String[] args) {
        String s1 = "谨以书为马";
        String s2 = "谨以书为马";
        System.out.println(s1 == s2);
        //运行结果:true
        String s3 = new String("谨以书为马");
        System.out.println(s1 == s3);
        //运行结果:false
    }
}

两种赋值的区别:

  • 直接赋值

    因为String字符串是程序中最常使用的类型,所以Java为了避免在一个系统中大量产生String对象,于是就设计了一个字符串池(字符串常量池)

    它的机制是这样的:创建一个字符串时,首先检查池中是否有字面值相等的字符串,如果有,则不再创建,直接返回池中该对象的引用,若没有则创建之,然后放到池中,并返回新建对象的引用。

  • new String("")赋值

    new String("")直接声明的String对象是不检查字符串池的,也不会把对象放到池中。

对象放到池中会不会产生线程安全?

String类是一个不可变(Immutable)对象,有两层意思:一是String类是final类,不可继承,不可能产生一个String的子类;二是在String类提供的所有方法中,如果有String返回值,就会新建一个String对象,不对原对象进行修改,这也就保证了原对象是不可改变的。

注意:字符串池比较特殊,它在编译期已经决定了其存在JVM的常量池(ConstantPool),垃圾回收器是不会对它进行回收的。

建议53:注意方法中传递的参数要求★☆☆☆☆

replacereplaceAll方法举例说明:

public class Client {

    public static void main(String[] args) {
        String s1 = "谨以书为马谨以";
        System.out.println(s1.replace("谨以", ""));
        //运行结果:书为马
        String s2 = "谨以书为马谨以";
        System.out.println(s2.replaceAll("谨以", ""));
        //运行结果:书为马
    }
}

以上例子中,虽然使用replace与replaceAll的运行结果一致,但是他们是有区别的,源码如下:

/** 
 * Replaces each substring of this string that matches the literal target sequence 
 * with the specified literal replacement sequence ...
 * 翻译:使用指定的文字替换序列 替换此字符串中与文字目标序列匹配的每个子字符串
 */
public String replace(CharSequence target, CharSequence replacement) {
        return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
                this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}
/** 
 * Replaces each substring of this string that matches the given regular expression 
 * with the given replacement...
 * 翻译:替换此字符串中 与给定正则表达式匹配的每个子字符串 为给定的替换字符串
 */
public String replaceAll(String regex, String replacement) {
        return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

原来replaceAll方法传递的第一个参数是正则表达式,但是在参数类型上只要是String就行,所以很容易让使用者误解。

  • replace 的参数是 char 和 CharSequence,即可以支持字符的替换,也支持字符串的替换。

  • replaceAll 的参数是 regex,即基于正则表达式的替换,如果是正则,执行正则替换,如果是字符串,执行字符串替换。

注意:虽然有时确实得到了预期的结果,但我们要真正了解方法的参数要求

建议54:正确使用String、StringBuffer、StringBuilder★☆☆☆☆

String类是不可改变的,String类的操作都是产生新的String对象,赋值操作表面看似值变了,不过是引用指向了新创建的字符串对象而已。

StringBuilder与StringBuffer基本相同,都是可变字符序列,StringBuffer的方法前都有synchronized关键字,所以StringBuffer是线程安全的,这也是StringBuffer在性能上远低于StringBuilder的原因。

性能比较(由低到高):String < StringBuffer < StringBuilder

三者的使用场景:

  • 使用String类的场景

    在字符串不经常变化的场景中可以使用String类,例如常量的声明、少量的变量运算等。

  • 使用StringBuffer类的场景

    在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在多线程的环境中,则可以考虑使用StringBuffer,例如XML解析、HTTP参数解析和封装等。

  • 使用StringBuilder类的场景

    在频繁进行字符串的运算(如拼接、替换、删除等),并且运行在单线程的环境中,则可以考虑使用StringBuilder,如SQL语句的拼装、JSON封装等。

建议55:注意字符串的位置★☆☆☆☆

示例代码:

public class Client {

    public static void main(String[] args) {
        String s1 = 1 + 2 + "谨以书为马";
        System.out.println(s1);
        //运行结果:3谨以书为马
        String s2 = "谨以书为马" + 1 + 2;
        System.out.println(s2);
        //运行结果:谨以书为马12
    }
}

Java对加号的处理机制:在使用加号进行计算的表达式中,只要遇到String字符串,则所有的数据都会转换为String类型进行拼接,如果是原始数据,则直接拼接,如果是对象,则调用toString方法的返回值然后拼接。

注意在“+”表达式中,String字符串具有最高优先级。

建议56:自由选择字符串拼接方法★★☆☆☆

字符串进行拼接有三种方法:加号、concat方法及StringBuilder(StringBuffer,两者是一样的)的append方法。

三者之间的区别:append方法最快,concat方法次之,加号最慢。具体看如下示例:

  1. “+”方法拼接字符串,示例代码:
//“+”方法拼接字符串
String str = "a";
str += "c";
str += "d";
//内部实现与以下代码相同
str = new StringBuilder(str).append("c").toString();
str = new StringBuilder(str).append("d").toString();

它与纯粹使用StringBuilder的append方法是不同的:一是每次会创建一个StringBuilder对象,二是每次执行完毕都要调用toString方法将其转换为字符串,它的执行时间就是耗费在这里了。

注意:

String str = "a";
str += "b" + "c" ;
//内部实现与以下代码相同
str = new StringBuilder(str).append("b").append("b").toString();
//所以“+”拼接字符串,尽量“一行”写,避免循环中使用“+”拼接字符串
  1. concat方法拼接字符串源码:
public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        //将原始字符串放到新增长的char数组中
        char buf[] = Arrays.copyOf(value, len + otherLen);
        //将新字符串添加到buf中
        str.getChars(buf, len);
        //返回新的字符串
        return new String(buf, true);
}

其整体看上去就是一个数组拷贝,虽然在内存中的处理都是原子性操作,速度非常快,不过,注意看最后的return语句,每次的concat操作都会新创建一个String对象,这就是concat速度慢下来的真正原因。

  1. append方法拼接字符串,StringBuilder的append方法直接由父类AbstractStringBuilder实现,其代码如下:
public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        //加长数组,并做copy
        ensureCapacityInternal(count + len);
        //将字符串复制到目标数组
        str.getChars(0, len, value, count);
        count += len;
        return this;
}
private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0) {
            value = Arrays.copyOf(value,
                    newCapacity(minimumCapacity));
        }
}

整个append方法都在做字符数组处理,加长,然后数组拷贝,这些都是基本的数据处理,没有新建任何对象,所以速度也就最快了。

注意:StringBuilder的实现性能最优,但并不表示我们一定要使用StringBuilder,因为“+”非常符合我们的编码习惯,适合人类阅读。在大多数情况下我们都可以使用加号操作,只有在系统性能临界(如在性能“增之一分则太长”的情况下)的时候才可以考虑使用concat或append方法。而且,很多时候系统80%的性能是消耗在20%的代码上的,我们的精力应该更多的投入到算法和结构上。

建议57:推荐在复杂字符串操作中使用正则表达式★☆☆☆☆

正则表达式在字符串的查找、替换、剪切、复制、删除等方面有着非凡的作用,特别是面对大量的文本字符需要处理(如需要读取大量的LOG日志)时,使用正则表达式可以大幅地提高开发效率和系统性能,但是正则表达式是一个恶魔(Regular Expressions is evil),它会使程序难以读懂,想想看,写一个包含^、$、\A、\s、\Q、+、?、()、[]、{}等符号的正则表达式,然后告诉你这是一个“这样,这样……”的字符串查找,你是不是要崩溃了?这代码只有上帝才能看懂了!

注意:正则表达式是恶魔,威力巨大,但难以控制。

建议58:强烈建议使用UTF编码★☆☆☆☆

Java程序涉及的编码包括两部分:

  1. Java文件编码
    如果我们使用记事本创建一个.java后缀的文件,则文件的编码格式就是操作系统默认的格式。如果是使用IDE工具创建的,则依赖于IDE的设置。
  2. Class文件编码
    通过javac命令生成的后缀名为.class的文件是UTF-8编码的UNICODE文件,这在任何操作系统上都是一样的,只要是class文件就会是UNICODE格式。需要说明的是,UTF是UNICODE的存储和传输格式,它是为了解决UNICODE的高位占用冗余空间而产生的,使用UTF编码就标志着字符集使用的是UNICODE。

注意:为了避免在应用开发中出现乱码或者需要额外进行转码操作,最好的解决办法就是使用统一的编码格式。

建议59:对字符串排序持一种宽容的心态★☆☆☆☆

Java中一涉及中文处理就会冒出很多问题来,其中排序也是一个让人头疼的课题,中国的汉字博大精深,Java是否都能精确的排序呢?最主要的一点是汉字中有象形文字,音形分离,是不是每个汉字都能按照拼音的顺序排列好呢?

答案是:并不能。

示例代码:

public class Client {

    public static void main(String[] args) {
        String[] strs = {"犇(B)", "鑫(X)"};
        Arrays.sort(strs, Collator.getInstance(Locale.CHINA));
        int i = 0;
        for (String str : strs) {
            System.out.println((++i) + "、" + str);
        }
        //运行结果:
        //1、鑫(X)
        //2、犇(B)
    }
}

输出结果乱了!不要责怪Java,它已经尽量为我们考虑了,只是因为我们的汉字文化太博大精深了,要做好这个排序确实有点难为它。

更深层次的原因是Java使用的是UNICODE编码,而中文UNICODE字符集是来源于GB18030的,GB18030又是从GB2312发展起来,GB2312是一个包含了7000多个字符的字符集,它是按照拼音排序,并且是连续的,之后的GBK、GB18030都是在其基础上扩充出来的,所以要让它们完整排序也就难上加难了。

注意:如果排序不是一个关键算法,使用Collator类即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容