这里介绍一下String和MessageFormat中的format方法的差异以及实现原理。

String与MessageFormat的说明

一、两者的使用场景

String.format:for layout justification and alignment, common formats for numeric, string, and date/time data, and locale-specific output.

MessageFormat.format:to produce concatenated messages in language-neutral way.

二、两者的性能比较

MeesageFormat由于是一个在先分析的指定位置插入相应的值,性能要好于采用正则表达式查找占位符的String.format方法。MessageFormat > String

三、以下是异常的情况

String message = MessageFormat.format("name={0}, age={}", , "huhx"); // java.lang.IllegalArgumentException: can't parse argument number:
String string = String.format("name=%s, age=%d", "huhx"); // java.util.MissingFormatArgumentException: Format specifier '%d'

两者的实现原理

我们通过下面的简单的例子来分析两者的原理:

public void messageFormat() {
String string = String.format("name=%s, age=%d", "huhx", );
String message = MessageFormat.format("name={1}, age={0}, {1}", , "huhx");
System.out.println(string);
System.out.println(message);
}
// name=huhx, age=25
// name=huhx, age=25, huhx

一、String.format的实现原理

String.format内部的实现是一个Formatter,使用了正则表达式来查找占位数据的。我们在这里贴出它实现的源代码。

 public Formatter format(Locale l, String format, Object ... args) {
ensureOpen();
// index of last argument referenced
int last = -1;
// last ordinary index
int lasto = -1; FormatString[] fsa = parse(format);
for (int i = 0; i < fsa.length; i++) {
FormatString fs = fsa[i];
int index = fs.index();
try {
switch (index) {
case -2: // fixed string, "%n", or "%%"
fs.print(null, l);
break;
case -1: // relative index
if (last < 0 || (args != null && last > args.length - 1))
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
case 0: // ordinary index
lasto++;
last = lasto;
if (args != null && lasto > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[lasto]), l);
break;
default: // explicit index
last = index - 1;
if (args != null && last > args.length - 1)
throw new MissingFormatArgumentException(fs.toString());
fs.print((args == null ? null : args[last]), l);
break;
}
} catch (IOException x) {
lastException = x;
}
}
return this;
}

以下是Formatter内部的正则表达式:

private static final String formatSpecifier = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";

使用formatSpecifier的正则表达式应用于name=%s, age=%d,会生成一个列表,也就是上述第9行代码的执行结果。里面大概记录了以下的内容,大小为4。

、类型为FixedString,内容为name=
、类型为FormatSpecifier,内容为%s
、类型为FixedString,内容为, age=
、类型为FormatSpecifier,内容为%d

这里对FixedString和FormatSpecifier做一个简单的说明。两者都是实现了FormatString接口。其中FormatString暴露了以下的三个方法。

private interface FormatString {
int index();
void print(Object arg, Locale l) throws IOException;
String toString();
}

如果是FixedString类型的,index为-2。如果是FormatSpecifier类型的,index为0。

、类型为FixedString:使用的fs.print函数是把string内容写到Formatter类里面StringBuilder里。
、类型为FormatSpecifier:使用fs.print里面的实现比较复杂,处理各种精度、对齐、布局调整等问题。

最后调用Formatter的toString方法,返回内容维护的StringBuilder内容。

public String toString() {
ensureOpen();
return a.toString();
}

二、MessageFormat.format的实现原理

MessageFormat的原理简单来说就是遍历第一个字符,维护一个{}数组,并且记录了{}的各个位置,各个位置还对应着index(参数的下标)。还是以下面的代码做分析

String message = MessageFormat.format("name={1}, age={0}, {1}", , "huhx");

首先它会调用一个applyPattern方法,这里我们先贴出代码。这一行代码执行完,会生成以下有用的信息。

其中offset是一个int数据,里面目前的数据是5,11,13分别代表{0}、{1}和{1}的位置。maxOffset为2代表上面的{n}有3个。argumentNumbers里面的1、0、1代表regex里面的{n}的n的值。这个过程具体可以看下面的代码。

 public void applyPattern(String pattern) {
StringBuilder[] segments = new StringBuilder[];
// Allocate only segments[SEG_RAW] here. The rest are
// allocated on demand.
segments[SEG_RAW] = new StringBuilder(); int part = SEG_RAW;
int formatNumber = ;
boolean inQuote = false;
int braceStack = ;
maxOffset = -;
for (int i = ; i < pattern.length(); ++i) {
char ch = pattern.charAt(i);
if (part == SEG_RAW) {
if (ch == '\'') {
if (i + < pattern.length()
&& pattern.charAt(i+) == '\'') {
segments[part].append(ch); // handle doubles
++i;
} else {
inQuote = !inQuote;
}
} else if (ch == '{' && !inQuote) {
part = SEG_INDEX;
if (segments[SEG_INDEX] == null) {
segments[SEG_INDEX] = new StringBuilder();
}
} else {
segments[part].append(ch);
}
} else {
if (inQuote) { // just copy quotes in parts
segments[part].append(ch);
if (ch == '\'') {
inQuote = false;
}
} else {
switch (ch) {
case ',':
if (part < SEG_MODIFIER) {
if (segments[++part] == null) {
segments[part] = new StringBuilder();
}
} else {
segments[part].append(ch);
}
break;
case '{':
++braceStack;
segments[part].append(ch);
break;
case '}':
if (braceStack == ) {
part = SEG_RAW;
makeFormat(i, formatNumber, segments);
formatNumber++;
// throw away other segments
segments[SEG_INDEX] = null;
segments[SEG_TYPE] = null;
segments[SEG_MODIFIER] = null;
} else {
--braceStack;
segments[part].append(ch);
}
break;
case ' ':
// Skip any leading space chars for SEG_TYPE.
if (part != SEG_TYPE || segments[SEG_TYPE].length() > ) {
segments[part].append(ch);
}
break;
case '\'':
inQuote = true;
// fall through, so we keep quotes in other parts
default:
segments[part].append(ch);
break;
}
}
}
}
if (braceStack == && part != ) {
maxOffset = -;
throw new IllegalArgumentException("Unmatched braces in the pattern.");
}
this.pattern = segments[].toString();
}

后面做format工作,根据上述applyPattern分析出来的重要信息。大概的过程就是循环maxOffset,得到对应的offset下标。然后把参数插入到对应的位置。比如第一个的参数数字25会插入到pattern的第12位置,而huhx字符串会插入到pattern的第6和第14的位置。组装的一个string返回。以下是format的源码。

 private StringBuffer subformat(Object[] arguments, StringBuffer result,
FieldPosition fp, List<AttributedCharacterIterator> characterIterators) {
// note: this implementation assumes a fast substring & index.
// if this is not true, would be better to append chars one by one.
int lastOffset = ;
int last = result.length();
for (int i = ; i <= maxOffset; ++i) {
result.append(pattern.substring(lastOffset, offsets[i]));
lastOffset = offsets[i];
int argumentNumber = argumentNumbers[i];
if (arguments == null || argumentNumber >= arguments.length) {
result.append('{').append(argumentNumber).append('}');
continue;
}
// int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
if (false) { // if (argRecursion == 3){
// prevent loop!!!
result.append('\uFFFD');
} else {
Object obj = arguments[argumentNumber];
String arg = null;
Format subFormatter = null;
if (obj == null) {
arg = "null";
} else if (formats[i] != null) {
subFormatter = formats[i];
if (subFormatter instanceof ChoiceFormat) {
arg = formats[i].format(obj);
if (arg.indexOf('{') >= ) {
subFormatter = new MessageFormat(arg, locale);
obj = arguments;
arg = null;
}
}
} else if (obj instanceof Number) {
// format number if can
subFormatter = NumberFormat.getInstance(locale);
} else if (obj instanceof Date) {
// format a Date if can
subFormatter = DateFormat.getDateTimeInstance(
DateFormat.SHORT, DateFormat.SHORT, locale);//fix
} else if (obj instanceof String) {
arg = (String) obj; } else {
arg = obj.toString();
if (arg == null) arg = "null";
} // At this point we are in two states, either subFormatter
// is non-null indicating we should format obj using it,
// or arg is non-null and we should use it as the value. if (characterIterators != null) {
// If characterIterators is non-null, it indicates we need
// to get the CharacterIterator from the child formatter.
if (last != result.length()) {
characterIterators.add(
createAttributedCharacterIterator(result.substring
(last)));
last = result.length();
}
if (subFormatter != null) {
AttributedCharacterIterator subIterator =
subFormatter.formatToCharacterIterator(obj); append(result, subIterator);
if (last != result.length()) {
characterIterators.add(
createAttributedCharacterIterator(
subIterator, Field.ARGUMENT,
Integer.valueOf(argumentNumber)));
last = result.length();
}
arg = null;
}
if (arg != null && arg.length() > ) {
result.append(arg);
characterIterators.add(
createAttributedCharacterIterator(
arg, Field.ARGUMENT,
Integer.valueOf(argumentNumber)));
last = result.length();
}
}
else {
if (subFormatter != null) {
arg = subFormatter.format(obj);
}
last = result.length();
result.append(arg);
if (i == && fp != null && Field.ARGUMENT.equals(
fp.getFieldAttribute())) {
fp.setBeginIndex(last);
fp.setEndIndex(result.length());
}
last = result.length();
}
}
}
result.append(pattern.substring(lastOffset, pattern.length()));
if (characterIterators != null && last != result.length()) {
characterIterators.add(createAttributedCharacterIterator(
result.substring(last)));
}
return result;
}

友情链接

java基础---->String和MessageFormat的format方法的更多相关文章

  1. java基础---->String中replace和replaceAll方法

    这里面我们分析一下replace与replaceAll方法的差异以及原理. replace各个方法的定义 一.replaceFirst方法 public String replaceFirst(Str ...

  2. Java基础String的方法

    Java基础String的方法 字符串类型写法格式如下: 格式一: String 变量名称; 变量名称=赋值(自定义或传入的变量值); 格式二: String 变量名称=赋值(自定义或传入的变量值); ...

  3. Java基础系列-equals方法和hashCode方法

    原创文章,转载请标注出处:<Java基础系列-equals方法和hashCode方法> 概述         equals方法和hashCode方法都是有Object类定义的. publi ...

  4. Java基础 String 裸暴力算法- 五个小练习

      之间的博客,承上启下:    Java基础 String/StringBuff 常用操作方法复习/内存分析 Java数组直接选择排序.sort()排序 Java基础 String 算法 - 五个练 ...

  5. Java基础—String构造方法

    Java基础--String构造方法 public String(): 创建一个空表字符串对象,不包含任何内容 public String(char[]chs): 根据字符数组的内容,来创建字符串对象 ...

  6. java基础---->String中的split方法的原理

    这里面主要介绍一下关于String类中的split方法的使用以及原理. split函数的说明 split函数java docs的说明: When there is a positive-width m ...

  7. Java基础-String、StringBuffer、StringBuilder

    看下面这段代码: public class Main { public static void main(String[] args) { String string = ""; ...

  8. java基础(十六)----- equals()与hashCode()方法详解 —— 面试必问

    本文将详解 equals()与hashCode()方法 概述 java.lang.Object类中有两个非常重要的方法: public boolean equals(Object obj) publi ...

  9. Java基础——String类(二)

    今天做了几道String常见操作.先来几个代码实例: 例一:此方法,仅把字符串前后出现的空格去掉了,中间部分不会. class TestTrim { public static void main(S ...

随机推荐

  1. 小甲鱼Python第十一讲课后习题

    0. 注意,这道题跟上节课的那道题有点儿不同,回答完请上机实验或参考答案. old = [1, 2, 3, 4, 5]new = oldold = [6]print(new) 如果不上机操作,你觉得会 ...

  2. 前端工程化系列[02]-Grunt构建工具的基本使用

    本文主要介绍前端开发中常用的构建工具Grunt,具体包括Grunt的基本情况.安装.使用和常见插件的安装.配置和使用等内容. 1.1 Grunt简单介绍 Grunt是一套前端自动化构建工具.对于需要反 ...

  3. Jetpack 架构组件 LiveData ViewModel MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  4. ASP.NET 使用 plupload 上传大文件时出现“blob”文件的Bug

    最近在一个ASP.NET 项目中使用了plupload来上传文件,结果几天后客户发邮件说上传的文件不对,说是文件无法打开 在进入系统进行查看后发现上传的文件竟然没有后缀,经过一番测试发现如果文件上传的 ...

  5. JAVA获取程序(打成jar或classpath)所在目录

    一.简述 JAVA获取程序(打成jar或classpath)所在目录. 二.代码 package dearcloud.utils.context; import dearcloud.utils.Str ...

  6. Hibernate 离线对象构建通用查询

    1.业务场景 当下主系统衍生子业务系统已经成为常态,像京东的物流和金融,阿里的支付宝和淘宝. 子业务系统需要对主系统的资源进行访问,这里的资源我具体化为数据库数据,但日常业务中可能不只是数据. 抽象服 ...

  7. exception The valid characters are defined in RFC 7230 and RFC 3986

      1.情景展示 当你使用浏览器进行问号传参与后台进行交互时,会报这个异常. tomcat控制台报错信息如下: The valid characters are defined in RFC 7230 ...

  8. 分析轮子(六)- LinkedList.java

    注:玩的是JDK1.7版本 一:先上类的继承结构图 二:再看一下他的底层实现数据结构 三:然后从源码中找点好玩的东西 1)双向链表的结构构成元素,头指针.尾指针.节点信息(前向指针.后向指针.节点信息 ...

  9. Python 爬虫实例(7)—— 爬取 新浪军事新闻

    我们打开新浪新闻,看到页面如下,首先去爬取一级 url,图片中蓝色圆圈部分 第二zh张图片,显示需要分页, 源代码: # coding:utf-8 import json import redis i ...

  10. VNC Viewer 设置屏幕分辨率

    1.第一种方法:使用geometry参数进行调整 vncserver -geometry 1280x1024即可,之后通过window下vnc连接后的ubuntu分辨率即为1280x1024了,注意这 ...