文档章节

Java String 中一不留神就中招的坑

GotaX
 GotaX
发布于 2017/02/28 00:05
字数 976
阅读 24
收藏 2
点赞 0
评论 0

String 类型应该算是 Java 中最常用的类型之一了, 不过其中有一些函数用不好就容易中招.

我们就以分割字符串的函数举例, 一般我们都会这么写:

String[] arr = "a,b,c".split(",");

非常简洁, 并且没什么问题. 不过一旦上下文变得复杂起来事情恐怕就没这么简单了.

比如说, 现在我们的任务是要对一本 (英文) 书进行分词, 分割符是标点和空格. 一般来说我们可以这么写:

for (String line : lines) {
    line.split("[,.!+@#$%^&*()\\- ]");
}

这种写法虽说看起来 OK, 但其实并非如此. 实际上, 这样写的话一旦遇到调用频率高或是需要分割大文本的情况就会出现内存占用大及运行耗时长的问题. 至于为什么会这样, 我们可以来看一下该函数是如何实现的:

public String[] split(String regex, int limit) {
    /* fastpath if the regex is a
     (1)one-char String and this character is not one of the
        RegEx's meta characters ".$|()[{^?*+\\", or
     (2)two-char String and the first char is the backslash and
        the second is not the ascii digit or ascii letter.
     */
    char ch = 0;
    if (((regex.value.length == 1 &&
         ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
         (regex.length() == 2 &&
          regex.charAt(0) == '\\' &&
          (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
          ((ch-'a')|('z'-ch)) < 0 &&
          ((ch-'A')|('Z'-ch)) < 0)) &&
        (ch < Character.MIN_HIGH_SURROGATE ||
         ch > Character.MAX_LOW_SURROGATE))
    {
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
                list.add(substring(off, next));
                off = next + 1;
            } else {    // last one
                //assert (list.size() == limit - 1);
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // If no match was found, return this
        if (off == 0)
            return new String[]{this};

        // Add remaining segment
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0) {
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
                resultSize--;
            }
        }
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    return Pattern.compile(regex).split(this, limit);
}

也就是说在大部分情况下, split(String regex) 函数实际上是新建了一个 Pattern 对象, 再去调用它的 split(CharSequence input, int limit) 函数, 仅有两种情况例外:

  • 传入的 regex 参数仅有一个字符, 且非正则表达式中的 ".$|()[{^?*+\" 字符
  • 传入的 regex 参数仅有两个字符, 且第一个字符为反斜杠, 第二个字符不能是数字或字母

这样事情就很明朗了, 我们在分词的时候调用了多少次 split 函数就等于新建了多少 Pattern 对象, 自然会慢. 因此只要对原来的实现稍加改动就能解决这个问题:

Pattern pattern = Pattern.compile("[,.!+@#$%^&*()\\- ]");
for (String line : lines) {
    pattern.split(line);
}

下面附上实际测试的一些数据, 对 "The_Thousand_and_One_Nights_.txt" (1.42 MB) 进行分词, 两种调用方式各跑 100 遍, 取平均值后结果如下:

String#splitPattern#split
24.32 ms3.03 ms

从这个结果可以看出比较明显的差异, Pattern 要比 String 的切分快 8 倍左右.

需要注意的是 String 中除了 split 还有一些函数也会在内部生成 Pattern 对象, 包括:

  • matches(String regex)
  • replaceFirst(String regex, String replacement)
  • replaceAll(String regex, String replacement)
  • split(String regex, int limit)

所以使用这些函数的时候就要小心了, 如果是被反复调用的情况, 最好是声明成一个 Pattern 常量, 再去调用对应的函数.

最后附上测试代码:

import com.google.common.base.Stopwatch;
import com.google.common.io.Resources;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

/**
 * Created by gota on 2017/2/27.
 */
public class StringTest {

  private static final String REGEX = "[,.!+@#$%^&*()\\- ]";

  private static List<String> lines = new LinkedList<>();

  @BeforeClass
  public static void before() throws IOException {
    URL resource = Resources.getResource(StringTest.class, "The_Thousand_and_One_Nights_.txt");
    lines.addAll(Resources.readLines(resource, Charset.defaultCharset()));
  }

  /**
   * 使用 String 的 split 函数分割字符串
   */
  @Test
  public void test() {
    split(lines, line -> line.split(REGEX));
  }

  /**
   * 使用 Pattern 的 split 函数分割字符串
   */
  @Test
  public void test2() {
    Pattern pattern = Pattern.compile(REGEX);
    split(lines, line -> pattern.split(REGEX));
  }

  private void split(List<String> lines,
                     Function<String, String[]> function) {
    IntStream
        .range(0, 100)
        .mapToLong($ -> {
          Stopwatch stopwatch = Stopwatch.createStarted();
          lines.forEach(function::apply);
          return stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);
        })
        .average()
        .ifPresent(ms -> System.out.printf("Use: %s ms\n", ms));
  }
}

© 著作权归作者所有

共有 人打赏支持
GotaX
粉丝 9
博文 4
码字总数 2811
作品 0
程序员
Java开发每日复盘2018_0514

今天主要跟大家分享3个部分: 一、「Java的核心机制」 二、「Java命名规范」 三、「Java数据类型相关」 下面我们来一个一个的说: 「Java核心机制」 Java是一种强制面向对象的解释型语言(O...

ZeroOSTalk ⋅ 05/14 ⋅ 0

JDBC中Class.forName()的作用

使用JDBC时,我们都会很自然得使用下列语句: java 代码 Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://127.0.0.1/test?useUnicode=true&characterEncoding=utf-8"; ......

村长大神 ⋅ 2014/05/09 ⋅ 0

Python和Java的硬盘夜话

点击上方“程序员小灰”,选择“置顶公众号” 有趣有内涵的文章第一时间送达! 本文转载自公众号 码农翻身 这是一个程序员的电脑硬盘,在一个叫做“学习”的目录下曾经生活着两个小程序,一个...

bjweimengshu ⋅ 05/16 ⋅ 0

Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀 ⋅ 04/16 ⋅ 0

gradle/Groovy语法

Groovy官网的介绍(http://www.groovy-lang.org/download.html#gvm) Gradle API 文档: https://docs.gradle.org/current/dsl/org.gradle.api.invocation.Gradle.html 深入理解Android(一)......

shareus ⋅ 04/27 ⋅ 0

你不知道 Java 10 的 5 件事

局部变量类型推断是有争议的热点,但Java 10在JVM中的垃圾收集和容器识别上带来了可喜的变化。 关于本系列 所以你认为你了解Java编程? 事实是,大多数开发人员只是浮于Java平台的表面上,仅...

ismdeep ⋅ 04/24 ⋅ 0

Java开发每日复盘2018_0515

今天跟大家分享几个Java小细节: 【1】「for each循环的写法」 【2】「常量 final 写法细节」 【3】「类型转换之String转其他」 下面我们来一个一个说: 【1】「for each循环的写法」 Java中:...

ZeroOSTalk ⋅ 05/16 ⋅ 0

Python 爬取 11 万 Java 程序员信息竟有这些重大发现!

一提到程序猿,我们的脑子里就会出现这样的画面: 或者这样的画面: 心头萦绕的字眼是:秃头、猝死、眼镜、黑白 T 恤、钢铁直男…… 而真实的程序猿们,是每天要和无数数据,以及数十种编程语...

p柯西 ⋅ 昨天 ⋅ 0

Maven中指定Java的编译版本和源文件编码方式

使用Maven编译Java项目,默认的编译版本是1.5. 参考官网的说明-》https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html source String 2.0 The -source argument for......

孟飞阳 ⋅ 05/04 ⋅ 0

从Java到Kotlin,然后又回到Java!

最近Java与kotlin语言之争又有点小热,大概是因为某位当初吹捧Java的大神来华兜售其kotlin新书有关,但是与此同时相反观点也是不断涌现,Allegro团队就在他们的博客发表这篇文章,从Java到K...

欧阳海阳 ⋅ 05/28 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

关于“幂等”操作

一个幂等(idempotent)操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同. 开发中, 我们经常考虑幂等操作的场景有“接口调用”、“MQ消费”、“自动任务”等 接口调用, 可能出现...

零二一七 ⋅ 30分钟前 ⋅ 0

Dubbo服务服务暴露之ProxyFactory Invoker

Dubbo服务暴露过程中有涉及到调用ProxyFactory 中方法获取Invoker对象的过程,现在我们来深究下源码,来看下这个过程是在做些什么,返回的Invoker 对象是什么,我们来看一下代码的切入点: ...

哲别0 ⋅ 44分钟前 ⋅ 0

GP两种连接方式性能测试

GP两种连接方式性能测试 Pivotal import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class GPQueryStrin......

仔仔1993 ⋅ 48分钟前 ⋅ 0

jsonrpc-4j代码解析

解析文件 AutoJsonRpcServiceImplExporter JsonServiceExporter AutoJsonRpcServiceImplExporter 路径:com.googlecode.jsonrpc4j.spring.AutoJsonRpcServiceImplExporter AutoJsonRpcServi......

郭恩洲_OSC博客 ⋅ 今天 ⋅ 0

百度搜索

from selenium import webdriver import time brower=webdriver.Firefox() brower.get('http://www.baidu.com') input=brower.find_element_by_id('kw') input.send_keys('中南大学') time.s......

南桥北木 ⋅ 今天 ⋅ 0

tomcat 日志记录器

1、日志记录器是记录消息的组件 日志记录器需要与某个servlet 容器相关联 2、Logger 接口 共定义了5种日志级别:FATAL、ERROR、WARNING、INFORMATION、DEBUGGER setVerbosity 设置级别 setC...

职业搬砖20年 ⋅ 今天 ⋅ 0

Thrift RPC实战(三) Thrift序列化机制

1.Thrift基础架构 Thrift是一个客户端和服务端的架构体系,数据通过socket传输; 具有自己内部定义的传输协议规范(TProtocol)和传输数据标准(TTransports); 通过IDL脚本对传输数据的数据结构...

lemonLove ⋅ 今天 ⋅ 0

网站建设就要像2018世界杯的俄罗斯队大杀四方[图]

今天心情不错,因为昨天晚上观看了世界杯比赛,尤其是对俄罗斯队的大杀四方感到十分霸气侧漏啊,因此我联想到了自己的博客网站,我的博客是去年年底上线的,一直想建设一个关于读书和读后感作...

原创小博客 ⋅ 今天 ⋅ 0

linux 信号机制

signal(SIGPIPE, SIG_IGN); TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协...

xxdd ⋅ 今天 ⋅ 0

my.cnf, my-small.cnf, my-medium.cnf, my-large.cnf

1. my-small.cnf # Example MySQL config file for small systems.## This is for a system with little memory (<= 64M) where MySQL is only used# from time to time and it's importa......

周云台 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部