一、概述

  大多数Java程序员都曾经使用过java.util.StringTokenizer类。它是一个很方便的字符串分解器,主要用来根据分隔符把字符串分割成标记(Token),然后按照请求返回各个标记。这个过程称为Tokenization,实际上就是把字符序列转换成应用程序能够理解的多个标记。

  虽然StringTokenizer用起来很方便,但它的功能却很有限。这个类只是简单地在输入字符串中查找分隔符,一旦找到了分隔符就分割字符串。它不会检查分隔符是否在子串之中这类条件,当输入字符串中出现两个连续的分隔符时,它也不会返回""(字符串长度为0)形式的标记。

  为了突破这些局限,Java 2平台提供了BreakIterator类,它是在StringTokenizer之上改进的字符串分解器。由于JDK 1.1.x没有提供这个类,为了满足自己的需要,开发者经常花费很多时间从头开始编写分解器。在涉及到数据格式化处理的大型工程中,这类定制的字符串分解器有时随处可见,而且这种情况并不罕见。

  本文的目标是帮助你利用现有的StringTokenizer类,编写一个高级字符串分解器。

二、StringTokenizer的局限

  你可以用以下三种构造函数中的任意一种创建StringTokenizer分解器:

StringTokenizer(String sInput):以空白字符(“ ”,“\t”,“\n”)为分隔符分割字符串。 
StringTokenizer(String sInput, String sDelimiter):以sDelimiter为分隔符分割字符串。 
StringTokenizer(String sInput, String sDelimiter, boolean bReturnTokens):以sDelimiter为分隔符分割字符串,但如果bReturnTokens为true,则分隔符也作为标记返回。 
  第一个构造函数不检查输入字符串是否包含子串。例如,如果以空白字符为分隔符分割“hello. Today \"I am \" going to my home town”,则字符串分解结果是hello.、Today、"I、am、"、going等,而不是hello.、Today、"I am "、going等。

  第二个构造函数不检查两个分隔符连续出现的情况。例如,如果以“,”为分隔符分割“book, author, publication,,,date published”这个字符串,则StringTokenizer返回book、author、publication和date published这四个标记,而不是book、author、publication、""、""和date published这6个标记(其中""表示0长度字符串)。要得到6个标记的答案,你必须把StringTokenizer的bReturnTokens参数设置为true。

  允许设置值为true的bReturnTokens参数是一个重要的功能,因为它考虑到了分隔符连续出现的情况。例如,使用第二个构造函数时,如果数据是动态收集得到而且要用来更新数据库中的表,输入字符串中的标记对应着表里面列的值,那么当我们不能确定哪一个列应该设置为""时,我们就无法把输入串中的标记映射到数据库列。假设我们要把记录插入到一个有6个列的表,而输入数据中包含两个连续的分隔符。此时,StringTokenizer的分解结果是5个标记(两个连续的分隔符代表""标记,它将被StringTokenizer忽略),而我们却有6个字段需要设置。同时,我们也不知道连续分隔符在哪里出现,所以也就不知道哪一个列应该设置成""。

  当标记本身等同于分隔符(无论是长度还是值)且位于子串之内时,第三个构造函数无效。例如,如果我们要以“,”为分隔符分解字符串“book, author, publication,\",\",date published”(这个字符串包含一个“,”标记,它与分隔符一样),结果是book、author、publication、"、"、date published这六个标记,而不是book、author、publication、,(逗号字符)、date published这五个标记。再提醒一下,即使我们把StringTokenizer的bReturnTokens参数设置设置成了true,在这种情况下也没有什么帮助。

三、高级字符串分解器

  在编写代码之前,你必须搞清楚一个好的分解器有哪些基本要求。因为Java开发者已经习惯于使用StringTokenizer类,所以一个好的分解器应该提供StringTokenizer类提供的所有实用方法,比如hasMoreTokens()、nextToken()、countTokens()。

  本文提供的代码很简单,而且大部分代码足以自我解释。在这里,我主要利用了StringTokenizer类(创建类实例时bReturnTokens参数设置为true),并提供了上面提到的几个方法。大多数时候标记与分隔符不同,有些时候分隔符却要作为标记输出(尽管非常罕见),此时如果出现了对标记的请求,分解器要把分隔符作为标记输出。创建PowerfulTokenizer对象时,你只需要提供输入字符串和分隔符这两个参数,PowerfulTokenizer将在内部使用bReturnTokens设置成true的StringTokenizer。(这么做的原因在于,如果不是用bReturnTokens设置成true的方式创建StringTokenizer,那么它将在解决先前提出的问题时受到限制)。为了正确地控制分解器,代码在几个地方(计算标记的总数量以及nextToken())检查bReturnTokens是否设置成了true。

  你可能已经发现,PowerfulTokenizer实现了Enumeration接口,从而也就实现了hasMoreElements()和nextElement()这两个方法,而这两个方法又分别把调用直接委托给hasMoreTokens()和nextToken()。(由于实现了Enumeration接口,PowerfulTokenizer实现了与StringTokenizer的向后兼容。)

  我们来看一个例子,假设输入字符串是“hello, Today,,, \"I, am \", going to,,, \"buy, a, book\"”,分隔符是“,”。用分解器分割这个字符串时返回结果如表1所示:

表1:字符串分解结果

  输入字符串包含11个逗号(,)字符,其中3个在子串里面、4个连续出现(“Today,,,”中包含两个连续逗号,第一个逗号是Today的分隔符)。下面是PowerfulTokenizer计算标记总数的算法:

如果bReturnTokens=true,把子串中的分隔符数量乘以2,再从实际总数量减去该数字,就得到了标记的总数。理由是,对于子串“buy, a, book”,StringTokenizer将返回5个标记(即“buy:,:a:,:book”),而PowerfulTokenizer将返回一个标记(即“buy, a, book”),两者的差值是4(即,2乘以子串中的分隔符数量)。这个公式对于所有包含分隔符的子串都有效。 
类似地,对于bReturnTokens=false的情形,我们从实际总数(19)减去表达式[分隔符总数(11)- 连续分隔符数量(4) + 子串中的分隔符数量(3)]。由于这时我们不返回分隔符,它们(非连续出现或在子串内部)对我们来说没有用,上面的公式为我们返回了标记的总数量(9)。 
  请记住这两个公式,它们是PowerfulTokenizer的核心。这两个公式适用于几乎所有它们各自条件下的情形。但是,如果你有更复杂的要求,不能使用这两个公式,那么你应该在编写代码之前分析各种可能出现的情况,并设计出自己的公式。

// 检查分隔符是否位于子串之内 
for (int i=1; i<aiIndex.length; i++)>/td> 

iIndex = sInput.indexOf(sDelim, iIndex+1); 
if (iIndex == -1) 
break; 
// 如果分隔符位于子串之内,则向前分析直至子串结束 
while (sInput.substring(iIndex-iLen, iIndex).equals(sDelim)) 

iNextIndex = sInput.indexOf(sDelim, iIndex+1); 
if (iNextIndex == -1) 
break; 
iIndex = iNextIndex; 

aiIndex[i] = iIndex; 
//System.out.println("aiIndex[" + i + "] = " + iIndex); 
if (isWithinQuotes(iIndex)) 

if (bIncludeDelim) 
iTokens -= 2; 
else 
iTokens -= 1; 

}

  countTokens()方法检查子串是否包含双引号。如果包含,那么它减少总数并把索引值修改为字符串中下一个双引号出现的位置(如上面的代码片断所示)。如果bReturnTokens是false,那么它从总数减去输入字符串中出现的非连续分隔符的数量。

// 如发现多个连续的分隔符,则返回""作为标记 
if ( (sPrevToken.equals(sDelim)) && (sToken.equals(sDelim)) ) { 
sPrevToken = sToken; 
iTokenNo++; 
return ""; 
}

// 检查标记本身是否等于分隔符 
if ( (sToken.trim().startsWith("\"")) && (sToken.length() == 1) ) { 
// 标记本身等于分隔符的特殊情况 
String sNextToken = oTokenizer.nextToken(); 
while (!sNextToken 
sToken += sNextToken; 
sPrevToken = sToken; 
iTokenNo++; 
return sToken.substring(1, sToken.length()-1); 

// 检查字符串中是否包含子串 
else if ( (sToken.trim().startsWith("\"")) 
&& (!((sToken.trim().endsWith("\"")) 
&& (!sToken.trim().endsWith("\"\"")))) ) 

if (oTokenizer.hasMoreTokens()) 

String sNextToken = oTokenizer.nextToken(); 
// 检查"\"\"" 
while (!((sNextToken.trim().endsWith("\"")) 
&& (!sNextToken.trim().endsWith("\"\""))) ) 

sToken += sNextToken; 
if (!oTokenizer.hasMoreTokens()) 

sNextToken = ""; 
break; 

sNextToken = oTokenizer.nextToken(); 

sToken += sNextToken; 


  nextToken()方法通过StringTokenizer.nextToken方法获取标记,并检查标记中的双引号字符。如果发现了这些字符,它继续获取标记直至不能再找到带有双引号的标记。另外,它还把标记保存到一个变量(sPrevToken,参见本文后面完整的源代码)以检查连续出现的分隔符。如果nextToken()发现等同于分隔符的连续多个标记,那么它返回""(长度为0的字符串)作为标记。

  按照类似的方法,hasMoreTokens()方法检查已经返回的标记数量是否小于标记的总数量。

  【结束语】本文为你介绍了如何轻松地编写一个强大的字符串分解器。根据本文介绍的原理,你能够迅速编写出复杂的字符串分解器,节省大量的开发时间。

详解StringTokenizer函数相关推荐

  1. 一分钟详解initUndistortRectifyMap函数bug修复方法

    本文首发于微信公众号「3D视觉工坊」--一分钟详解initUndistortRectifyMap函数bug修复方法 在上一篇文章OpenCV中initUndistortRectifyMap函数存在bu ...

  2. python命名空间和闭包_Python函数基础实例详解【函数嵌套,命名空间,函数对象,闭包函数等】...

    本文实例讲述了Python函数基础用法.分享给大家供大家参考,具体如下: 一.什么是命名关键字参数? 格式: 在*后面参数都是命名关键字参数. 特点: 1.约束函数的调用者必须按照Kye=value的 ...

  3. 详解虚函数的实现过程之菱形继承(5)

    大家看到标题,会不会菱形继承的虚表会不会是重复的呢?祖父类的虚表会不会在子类会不会是两份相同呢?那么我们一起来探索一下吧,冲冲冲!! 首先我们来分析一下: 它一共定义了四个类,分别为CFurnitur ...

  4. 详解虚函数的实现过程之虚基类(4)

    博客虚函数实现过程3 时提到过虚基类,这里呢,我们来详细讲述一下: 当我们在虚函数的声明结尾处添加"=0",这种虚函数就被称为纯虚函数. 它好似一个没有实现只有声明的函数,它的存在 ...

  5. 详解虚函数的实现过程之多重继承(3)

    下面来一起探索一下多重继承时,有虚函数会怎么继承呢? 这里大家猜一下,SofaBed会占多少个字节呢? 首先我们是不是得猜一下它有几个虚表指针? 4* 4(4个int数据)+2*4(两个虚表指针)=2 ...

  6. 详解虚函数的实现过程之单继承(2)

    从汇编分析一下下面的多态模拟结构 利用 父类指针指向子类的特性,可以间接调用各子类中的虚函数. 虽然指针类型为父类,但由于虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的,因此,只要继承 ...

  7. 详解虚函数的实现过程之初探虚表(1)

    空对象它有一字节的大小,在没有任何成员变量但是却有虚函数的对象里,它的大小是四个字节,这是为什么呢? 因为含有虚函数的对象里,对象的起始地址往后四个字节其实是 一个指针,它指向了一个数组,这个数组的元 ...

  8. 详解Scala函数也是对象的特性

    详解Scala函数也是对象的特性

  9. python函数中可变参数的传递方式是_详解Python函数可变参数定义及其参数传递方式...

    Python函数可变参数定义及其参数传递方式详解 python中 函数不定参数的定义形式如下 1. func(*args) 传入的参数为以元组形式存在args中,如: def func(*args): ...

最新文章

  1. iOS中UITextField 使用全面解析
  2. cytoscape使用方法_信号通路分析工具教程——Cytoscape及OmniPath插件
  3. datatable里添加一个标识列
  4. 70 周岁快乐 | 盛世华诞,每一刻都值得铭记
  5. 生成模型和判别模型的区别
  6. PyTorch基础(15)-- torch.flatten()方法
  7. 【共读Primer】55.[6.4]函数重载--重载与作用域 Page210
  8. python3元类简介(metaclass)
  9. Vue2.0 脚手架代码详解
  10. 记录学习第三天---系统基础
  11. 二分查找 —— 从三分支到二分支
  12. 网络工程师Day1--实验1-4 配置三层交换
  13. Linux下的时间戳
  14. 一份超详细的VTD安装指南(内容含如何安装ubuntu系统及如何在ubuntu中安装显卡驱动)
  15. 即时通讯工具:Output Messenger Server
  16. jq ui autocomplete的使用
  17. 华硕电脑开机 C键和空格建不灵了怎么办
  18. 多传感器融合定位 第七章 基于滤波的融合方法
  19. 国外的一些开源网站汇集以及优秀博客的博客
  20. echarts实现颜色渐变

热门文章

  1. G1 垃圾收集器详解
  2. 运行python程序总是报警告“libpng warning: iCCP: known incorrect sRGB profile”,解决方案:将Anaconda中的libpng版本替换即可
  3. IIS6.0+Tomcat整合(java,C++等教程免费下载)
  4. 【Java虚拟机】浅谈Java虚拟机
  5. 应用SqlHelper例子(userService)
  6. Ipad软件paper破解方法
  7. 验证码——python去除干扰线
  8. 【小程序】之net::ERR_NAME_NOT_RESOLVED的问题
  9. 数据库-jdbc、spring-jdbc、spring-boot-starter-jdbc
  10. 我使用过的Linux命令之usleep - 延迟以微秒为单位的时间