﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-iNeo-随笔分类-Java</title><link>http://www.blogjava.net/iNeo/category/5351.html</link><description /><language>zh-cn</language><lastBuildDate>Wed, 28 Feb 2007 05:21:07 GMT</lastBuildDate><pubDate>Wed, 28 Feb 2007 05:21:07 GMT</pubDate><ttl>60</ttl><item><title>螳螂捕蝉、黄雀在后——从一个成语谈观察家模式[转]</title><link>http://www.blogjava.net/iNeo/archive/2005/12/09/23165.html</link><dc:creator>只牵这只狗</dc:creator><author>只牵这只狗</author><pubDate>Fri, 09 Dec 2005 08:26:00 GMT</pubDate><guid>http://www.blogjava.net/iNeo/archive/2005/12/09/23165.html</guid><wfw:comment>http://www.blogjava.net/iNeo/comments/23165.html</wfw:comment><comments>http://www.blogjava.net/iNeo/archive/2005/12/09/23165.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/iNeo/comments/commentRss/23165.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/iNeo/services/trackbacks/23165.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;&nbsp;观察家模式是一个事件通知模式，被观察者发生某个事件，或者状态发生某个变化，就通知观察者，这样观察者就能采取适当的行动。下面我以一个简单的例子来说明一下这个模式的应用。我们都知道，蜜蜂是勤劳的精灵，它总是四处采蜜。只要花朵的花瓣一张开，她就飞上去采蜜。我们轻易就能想到，在这里，蜜蜂应该是一个观察者，而花朵是一个被观察者。只要花朵发生花瓣张开事件，就通知了观...&nbsp;&nbsp;<a href='http://www.blogjava.net/iNeo/archive/2005/12/09/23165.html'>阅读全文</a><img src ="http://www.blogjava.net/iNeo/aggbug/23165.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/iNeo/" target="_blank">只牵这只狗</a> 2005-12-09 16:26 <a href="http://www.blogjava.net/iNeo/archive/2005/12/09/23165.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Java Map 集合类简介[转]</title><link>http://www.blogjava.net/iNeo/archive/2005/12/06/22703.html</link><dc:creator>只牵这只狗</dc:creator><author>只牵这只狗</author><pubDate>Tue, 06 Dec 2005 05:16:00 GMT</pubDate><guid>http://www.blogjava.net/iNeo/archive/2005/12/06/22703.html</guid><wfw:comment>http://www.blogjava.net/iNeo/comments/22703.html</wfw:comment><comments>http://www.blogjava.net/iNeo/archive/2005/12/06/22703.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/iNeo/comments/commentRss/22703.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/iNeo/services/trackbacks/22703.html</trackback:ping><description><![CDATA[<P>
<TABLE cellSpacing=0 cellPadding=0 align=left border=0>
<TBODY>
<TR>
<TD>
<SCRIPT language=javascript src="/ad/js/edu_left_300-300.js"></SCRIPT>
</TD></TR></TBODY></TABLE>java.util 中的集合类包含 Java 中某些最常用的类。 最常用的集合类是 List 和 Map。 List 的具体实现包括 ArrayList 和 Vector，它们是可变大小的列表，比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。 <BR><BR>Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对（称作“键”和“值”），其中每个键映射到一个值。 从概念上而言，您可以将 List 看作是具有数值键的 Map。 而实际上，除了 List 和 Map 都在定义 java.util 中外，两者并没有直接的联系。本文将着重介绍核心 Java 发行套件中附带的 Map，同时还将介绍如何采用或实现更适用于您应用程序特定数据的专用 Map。 <BR><BR>了解 Map 接口和方法 <BR><BR>Java 核心类中有很多预定义的 Map 类。 在介绍具体实现之前，我们先介绍一下 Map 接口本身，以便了解所有实现的共同点。 Map 接口定义了四种类型的方法，每个 Map 都包含这些方法。 下面，我们从两个普通的方法（表 1）开始对这些方法加以介绍。 <BR><BR>表 1： 覆盖的方法。 我们将这 Object 的这两个方法覆盖，以正确比较 Map 对象的等价性。 equals(Object o) 比较指定对象与此 Map 的等价性 <BR>hashCode() 返回此 Map 的哈希码 <BR><BR><BR><BR>Map 构建 <BR><BR>Map 定义了几个用于插入和删除元素的变换方法（表 2）。 <BR><BR>表 2： Map 更新方法： 可以更改 Map 内容。 clear() 从 Map 中删除所有映射 <BR>remove(Object key) 从 Map 中删除键和关联的值 <BR>put(Object key, Object value) 将指定值与指定键相关联 <BR>clear() 从 Map 中删除所有映射 <BR>putAll(Map t) 将指定 Map 中的所有映射复制到此 map <BR><BR><BR><BR>尽管您可能注意到，纵然假设忽略构建一个需要传递给 putAll() 的 Map 的开销，使用 putAll() 通常也并不比使用大量的 put() 调用更有效率，但 putAll() 的存在一点也不稀奇。 这是因为，putAll() 除了迭代 put() 所执行的将每个键值对添加到 Map 的算法以外，还需要迭代所传递的 Map 的元素。 但应注意，putAll() 在添加所有元素之前可以正确调整 Map 的大小，因此如果您未亲自调整 Map 的大小（我们将对此进行简单介绍），则 putAll() 可能比预期的更有效。 <BR><BR>查看 Map <BR><BR>迭代 Map 中的元素不存在直接了当的方法。 如果要查询某个 Map 以了解其哪些元素满足特定查询，或如果要迭代其所有元素（无论原因如何），则您首先需要获取该 Map 的“视图”。 有三种可能的视图（参见表 3） <BR><BR>所有键值对 — 参见 entrySet() <BR>所有键 — 参见 keySet() <BR>所有值 — 参见 values() <BR><BR>前两个视图均返回 Set 对象，第三个视图返回 Collection 对象。 就这两种情况而言，问题到这里并没有结束，这是因为您无法直接迭代 Collection 对象或 Set 对象。要进行迭代，您必须获得一个 Iterator 对象。 因此，要迭代 Map 的元素，必须进行比较烦琐的编码 <BR><BR><BR><FONT color=#ff0000>Iterator keyValuePairs = aMap.entrySet().iterator(); <BR>Iterator keys = aMap.keySet().iterator(); <BR>Iterator values = aMap.values().iterator(); <BR><BR></FONT><BR>值得注意的是，这些对象（Set、Collection 和 Iterator）实际上是基础 Map 的视图，而不是包含所有元素的副本。 这使它们的使用效率很高。 另一方面，Collection 或 Set 对象的 toArray() 方法却创建包含 Map 所有元素的数组对象，因此除了确实需要使用数组中元素的情形外，其效率并不高。 <BR><BR>我运行了一个小测试（随附文件中的 Test1），该测试使用了 HashMap，并使用以下两种方法对迭代 Map 元素的开销进行了比较： <BR><BR><BR>int mapsize = aMap.size(); <BR><BR>Iterator keyValuePairs1 = aMap.entrySet().iterator(); <BR>for (int i = 0; i &lt; mapsize; i++) <BR>{ <BR>Map.Entry entry = (Map.Entry) keyValuePairs1.next(); <BR>Object key = entry.getKey(); <BR>Object value = entry.getValue(); <BR>... <BR>} <BR><BR>Object[] keyValuePairs2 = aMap.entrySet().toArray(); <BR>for (int i = 0; i &lt; rem; i++) { <BR>{ <BR>Map.Entry entry = (Map.Entry) keyValuePairs2[i]; <BR>Object key = entry.getKey(); <BR><BR><BR>Object value = entry.getValue(); <BR>... <BR>} <BR><BR><BR>此测试使用了两种测量方法： 一种是测量迭代元素的时间，另一种测量使用 toArray 调用创建数组的其他开销。 第一种方法（忽略创建数组所需的时间）表明，使用已从 toArray 调用中创建的数组迭代元素的速度要比使用 Iterator 的速度大约快 30%-60%。 但如果将使用 toArray 方法创建数组的开销包含在内，则使用 Iterator 实际上要快 10%-20%。 因此，如果由于某种原因要创建一个集合元素的数组而非迭代这些元素，则应使用该数组迭代元素。 但如果您不需要此中间数组，则不要创建它，而是使用 Iterator 迭代元素。 <BR><BR>表 3： 返回视图的 Map 方法： 使用这些方法返回的对象，您可以遍历 Map 的元素，还可以删除 Map 中的元素。 entrySet() 返回 Map 中所包含映射的 Set 视图。 Set 中的每个元素都是一个 Map.Entry 对象，可以使用 getKey() 和 getValue() 方法（还有一个 setValue() 方法）访问后者的键元素和值元素 <BR>keySet() 返回 Map 中所包含键的 Set 视图。 删除 Set 中的元素还将删除 Map 中相应的映射（键和值） <BR>values() 返回 map 中所包含值的 Collection 视图。 删除 Collection 中的元素还将删除 Map 中相应的映射（键和值） <BR><BR><BR><BR>访问元素 <BR><BR>表 4 中列出了 Map 访问方法。Map 通常适合按键（而非按值）进行访问。 Map 定义中没有规定这肯定是真的，但通常您可以期望这是真的。 例如，您可以期望 containsKey() 方法与 get() 方法一样快。 另一方面，containsValue() 方法很可能需要扫描 Map 中的值，因此它的速度可能比较慢。 <BR><BR>表 4： Map 访问和测试方法： 这些方法检索有关 Map 内容的信息但不更改 Map 内容。 get(Object key) 返回与指定键关联的值 <BR>containsKey(Object key) 如果 Map 包含指定键的映射，则返回 true <BR>containsValue(Object value) 如果此 Map 将一个或多个键映射到指定值，则返回 true <BR>isEmpty() 如果 Map 不包含键-值映射，则返回 true <BR>size() 返回 Map 中的键-值映射的数目 <BR><BR><BR><BR>对使用 containsKey() 和 containsValue() 遍历 HashMap 中所有元素所需时间的测试表明，containsValue() 所需的时间要长很多。 实际上要长几个数量级！ （参见图 1 和图 2，以及随附文件中的 Test2）。 因此，如果 containsValue() 是应用程序中的性能问题，它将很快显现出来，并可以通过监测您的应用程序轻松地将其识别。 这种情况下，我相信您能够想出一个有效的替换方法来实现 containsValue() 提供的等效功能。 但如果想不出办法，则一个可行的解决方案是再创建一个 Map，并将第一个 Map 的所有值作为键。 这样，第一个 Map 上的 containsValue() 将成为第二个 Map 上更有效的 containsKey()。 <BR><BR><BR>图 1： 使用 JDeveloper 创建并运行 Map 测试类 <BR><BR><BR><BR><BR>图 2： 在 JDeveloper 中使用执行监测器进行的性能监测查出应用程序中的瓶颈 <BR><BR><BR><BR>核心 Map <BR><BR>Java 自带了各种 Map 类。 这些 Map 类可归为三种类型： <BR><BR><BR>通用 Map，用于在应用程序中管理映射，通常在 java.util 程序包中实现 <BR>HashMap <BR>Hashtable <BR>Properties <BR>LinkedHashMap <BR>IdentityHashMap <BR>TreeMap <BR>WeakHashMap <BR>ConcurrentHashMap <BR>专用 Map，您通常不必亲自创建此类 Map，而是通过某些其他类对其进行访问 <BR>java.util.jar.Attributes <BR>javax.print.attribute.standard.PrinterStateReasons <BR>java.security.Provider <BR>java.awt.RenderingHints <BR>javax.swing.UIDefaults <BR>一个用于帮助实现您自己的 Map 类的抽象类 <BR>AbstractMap <BR><BR>内部哈希： 哈希映射技术 <BR><BR>几乎所有通用 Map 都使用哈希映射。 这是一种将元素映射到数组的非常简单的机制，您应了解哈希映射的工作原理，以便充分利用 Map。 <BR><BR>哈希映射结构由一个存储元素的内部数组组成。 由于内部采用数组存储，因此必然存在一个用于确定任意键访问数组的索引机制。 实际上，该机制需要提供一个小于数组大小的整数索引值。 该机制称作哈希函数。 在 Java 基于哈希的 Map 中，哈希函数将对象转换为一个适合内部数组的整数。 您不必为寻找一个易于使用的哈希函数而大伤脑筋： 每个对象都包含一个返回整数值的 hashCode() 方法。 要将该值映射到数组，只需将其转换为一个正值，然后在将该值除以数组大小后取余数即可。 以下是一个简单的、适用于任何对象的 Java 哈希函数 <BR><BR><BR>int hashvalue = Maths.abs(key.hashCode()) % table.length; <BR><BR><BR>（% 二进制运算符（称作模）将左侧的值除以右侧的值，然后返回整数形式的余数。） <BR><BR>实际上，在 1.4 版发布之前，这就是各种基于哈希的 Map 类所使用的哈希函数。 但如果您查看一下代码，您将看到 <BR><BR><BR>int hashvalue = (key.hashCode() &amp; 0x7FFFFFFF) % table.length; <BR><BR><BR>它实际上是使用更快机制获取正值的同一函数。 在 1.4 版中，HashMap 类实现使用一个不同且更复杂的哈希函数，该函数基于 Doug Lea 的 util.concurrent 程序包（稍后我将更详细地再次介绍 Doug Lea 的类）。 <BR><BR><BR>图 3： 哈希工作原理 <BR><BR><BR><BR>该图介绍了哈希映射的基本原理，但我们还没有对其进行详细介绍。 我们的哈希函数将任意对象映射到一个数组位置，但如果两个不同的键映射到相同的位置，情况将会如何？ 这是一种必然发生的情况。 在哈希映射的术语中，这称作冲突。 Map 处理这些冲突的方法是在索引位置处插入一个链接列表，并简单地将元素添加到此链接列表。 因此，一个基于哈希的 Map 的基本 put() 方法可能如下所示 <BR><BR><BR>public Object put(Object key, Object value) { <BR>//我们的内部数组是一个 Entry 对象数组 <BR>//Entry[] table; <BR><BR>//获取哈希码，并映射到一个索引 <BR>int hash = key.hashCode(); <BR>int index = (hash &amp; 0x7FFFFFFF) % table.length; <BR><BR>//循环遍历位于 table[index] 处的链接列表，以查明 <BR>//我们是否拥有此键项 — 如果拥有，则覆盖它 <BR>for (Entry e = table[index] ; e != null ; e = e.next) { <BR>//必须检查键是否相等，原因是不同的键对象 <BR>//可能拥有相同的哈希 <BR>if ((e.hash == hash) &amp;&amp; e.key.equals(key)) { <BR>//这是相同键，覆盖该值 <BR>//并从该方法返回 old 值 <BR>Object old = e.value; <BR>e.value = value; <BR>return old; <BR>} <BR>} <BR><BR>//仍然在此处，因此它是一个新键，只需添加一个新 Entry <BR>//Entry 对象包含 key 对象、 value 对象、一个整型的 hash、 <BR>//和一个指向列表中的下一个 Entry 的 next Entry <BR><BR>//创建一个指向上一个列表开头的新 Entry， <BR>//并将此新 Entry 插入表中 <BR>Entry e = new Entry(hash, key, value, table[index]); <BR>table[index] = e; <BR><BR>return null; <BR>} <BR><BR><BR><BR>如果看一下各种基于哈希的 Map 的源代码，您将发现这基本上就是它们的工作原理。 此外，还有一些需要进一步考虑的事项，如处理空键和值以及调整内部数组。 此处定义的 put() 方法还包含相应 get() 的算法，这是因为插入包括搜索映射索引处的项以查明该键是否已经存在。 （即 get() 方法与 put() 方法具有相同的算法，但 get() 不包含插入和覆盖代码。） 使用链接列表并不是解决冲突的唯一方法，某些哈希映射使用另一种“开放式寻址”方案，本文对其不予介绍。 <BR><BR>优化 Hasmap <BR><BR>如果哈希映射的内部数组只包含一个元素，则所有项将映射到此数组位置，从而构成一个较长的链接列表。 由于我们的更新和访问使用了对链接列表的线性搜索，而这要比 Map 中的每个数组索引只包含一个对象的情形要慢得多，因此这样做的效率很低。 访问或更新链接列表的时间与列表的大小线性相关，而使用哈希函数访问或更新数组中的单个元素则与数组大小无关 — 就渐进性质（Big-O 表示法）而言，前者为 O(n)，而后者为 O(1)。 因此，使用一个较大的数组而不是让太多的项聚集在太少的数组位置中是有意义的。 <BR><BR>调整 Map 实现的大小 <BR><BR>在哈希术语中，内部数组中的每个位置称作“存储桶”(bucket)，而可用的存储桶数（即内部数组的大小）称作容量 (capacity)。 为使 Map 对象有效地处理任意数目的项，Map 实现可以调整自身的大小。 但调整大小的开销很大。 调整大小需要将所有元素重新插入到新数组中，这是因为不同的数组大小意味着对象现在映射到不同的索引值。 先前冲突的键可能不再冲突，而先前不冲突的其他键现在可能冲突。 这显然表明，如果将 Map 调整得足够大，则可以减少甚至不再需要重新调整大小，这很有可能显著提高速度。 <BR><BR>使用 1.4.2 JVM 运行一个简单的测试，即用大量的项（数目超过一百万）填充 HashMap。 表 5 显示了结果，并将所有时间标准化为已预先设置大小的服务器模式（关联文件中的 Test3）。 对于已预先设置大小的 JVM，客户端和服务器模式 JVM 运行时间几乎相同（在放弃 JIT 编译阶段后）。 但使用 Map 的默认大小将引发多次调整大小操作，开销很大，在服务器模式下要多用 50% 的时间，而在客户端模式下几乎要多用两倍的时间！ <BR><BR>表 5： 填充已预先设置大小的 HashMap 与填充默认大小的 HashMap 所需时间的比较 客户端模式 服务器模式 <BR>预先设置的大小 100% 100% <BR>默认大小 294% 157% <BR><BR><BR><BR>使用负载因子 <BR><BR>为确定何时调整大小，而不是对每个存储桶中的链接列表的深度进行记数，基于哈希的 Map 使用一个额外参数并粗略计算存储桶的密度。 Map 在调整大小之前，使用名为“负载因子”的参数指示 Map 将承担的“负载”量，即它的负载程度。 负载因子、项数（Map 大小）与容量之间的关系简单明了： <BR><BR><BR>如果（负载因子）x（容量）&gt;（Map 大小），则调整 Map 大小 <BR><BR>例如，如果默认负载因子为 0.75，默认容量为 11，则 11 x 0.75 = 8.25，该值向下取整为 8 个元素。 因此，如果将第 8 个项添加到此 Map，则该 Map 将自身的大小调整为一个更大的值。 相反，要计算避免调整大小所需的初始容量，用将要添加的项数除以负载因子，并向上取整，例如， <BR><BR><BR>对于负载因子为 0.75 的 100 个项，应将容量设置为 100/0.75 = 133.33，并将结果向上取整为 134（或取整为 135 以使用奇数） <BR><BR>奇数个存储桶使 map 能够通过减少冲突数来提高执行效率。 虽然我所做的测试（关联文件中的Test4）并未表明质数可以始终获得更好的效率，但理想情形是容量取质数。 1.4 版后的某些 Map（如 HashMap 和 LinkedHashMap，而非 Hashtable 或 IdentityHashMap）使用需要 2 的幂容量的哈希函数，但下一个最高 2 的幂容量由这些 Map 计算，因此您不必亲自计算。 <BR><BR>负载因子本身是空间和时间之间的调整折衷。 较小的负载因子将占用更多的空间，但将降低冲突的可能性，从而将加快访问和更新的速度。 使用大于 0.75 的负载因子可能是不明智的，而使用大于 1.0 的负载因子肯定是不明知的，这是因为这必定会引发一次冲突。 使用小于 0.50 的负载因子好处并不大，但只要您有效地调整 Map 的大小，应不会对小负载因子造成性能开销，而只会造成内存开销。 但较小的负载因子将意味着如果您未预先调整 Map 的大小，则导致更频繁的调整大小，从而降低性能，因此在调整负载因子时一定要注意这个问题。 <BR><BR>选择适当的 Map <BR><BR>应使用哪种 Map？ 它是否需要同步？ 要获得应用程序的最佳性能，这可能是所面临的两个最重要的问题。 当使用通用 Map 时，调整 Map 大小和选择负载因子涵盖了 Map 调整选项。 <BR><BR>以下是一个用于获得最佳 Map 性能的简单方法 <BR><BR>将您的所有 Map 变量声明为 Map，而不是任何具体实现，即不要声明为 HashMap 或 Hashtable，或任何其他 Map 类实现。 <BR><BR><BR>Map criticalMap = new HashMap(); //好 <BR><BR>HashMap criticalMap = new HashMap(); //差 <BR><BR><BR>这使您能够只更改一行代码即可非常轻松地替换任何特定的 Map 实例。 <BR><BR>下载 Doug Lea 的 util.concurrent 程序包 (http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html)。 将 ConcurrentHashMap 用作默认 Map。 当移植到 1.5 版时，将 java.util.concurrent.ConcurrentHashMap 用作您的默认 Map。 不要将 ConcurrentHashMap 包装在同步的包装器中，即使它将用于多个线程。 使用默认大小和负载因子。 <BR>监测您的应用程序。 如果发现某个 Map 造成瓶颈，则分析造成瓶颈的原因，并部分或全部更改该 Map 的以下内容： Map 类；Map 大小；负载因子；关键对象 equals() 方法实现。 专用的 Map 的基本上都需要特殊用途的定制 Map 实现，否则通用 Map 将实现您所需的性能目标。 <BR><BR>Map 选择 <BR><BR>也许您曾期望更复杂的考量，而这实际上是否显得太容易？ 好的，让我们慢慢来。 首先，您应使用哪种 Map？ 答案很简单： 不要为您的设计选择任何特定的 Map，除非实际的设计需要指定一个特殊类型的 Map。 设计时通常不需要选择具体的 Map 实现。 您可能知道自己需要一个 Map，但不知道使用哪种。 而这恰恰就是使用 Map 接口的意义所在。 直到需要时再选择 Map 实现 — 如果随处使用“Map”声明的变量，则更改应用程序中任何特殊 Map 的 Map 实现只需要更改一行，这是一种开销很少的调整选择。 是否要使用默认的 Map 实现？ 我很快将谈到这个问题。 <BR><BR>同步 Map <BR><BR>同步与否有何差别？ （对于同步，您既可以使用同步的 Map，也可以使用 Collections.synchronizedMap() 将未同步的 Map 转换为同步的 Map。 后者使用“同步的包装器”）这是一个异常复杂的选择，完全取决于您如何根据多线程并发访问和更新使用 Map，同时还需要进行维护方面的考虑。 例如，如果您开始时未并发更新特定 Map，但它后来更改为并发更新，情况将如何？ 在这种情况下，很容易在开始时使用一个未同步的 Map，并在后来向应用程序中添加并发更新线程时忘记将此未同步的 Map 更改为同步的 Map。 这将使您的应用程序容易崩溃（一种要确定和跟踪的最糟糕的错误）。 但如果默认为同步，则将因随之而来的可怕性能而序列化执行多线程应用程序。 看起来，我们需要某种决策树来帮助我们正确选择。 <BR><BR>Doug Lea 是纽约州立大学奥斯威戈分校计算机科学系的教授。 他创建了一组公共领域的程序包（统称 util.concurrent），该程序包包含许多可以简化高性能并行编程的实用程序类。 这些类中包含两个 Map，即 ConcurrentReaderHashMap 和 ConcurrentHashMap。 这些 Map 实现是线程安全的，并且不需要对并发访问或更新进行同步，同时还适用于大多数需要 Map 的情况。 它们还远比同步的 Map（如 Hashtable）或使用同步的包装器更具伸缩性，并且与 HashMap 相比，它们对性能的破坏很小。 util.concurrent 程序包构成了 JSR166 的基础；JSR166 已经开发了一个包含在 Java 1.5 版中的并发实用程序，而 Java 1.5 版将把这些 Map 包含在一个新的 java.util.concurrent 程序包中。 <BR><BR>所有这一切意味着您不需要一个决策树来决定是使用同步的 Map 还是使用非同步的 Map， 而只需使用 ConcurrentHashMap。 当然，在某些情况下，使用 ConcurrentHashMap 并不合适。 但这些情况很少见，并且应具体情况具体处理。 这就是监测的用途。 <BR><BR><BR>结束语 <BR><BR>通过 Oracle JDeveloper 可以非常轻松地创建一个用于比较各种 Map 性能的测试类。 更重要的是，集成良好的监测器可以在开发过程中快速、轻松地识别性能瓶颈 - 集成到 IDE 中的监测器通常被较频繁地使用，以便帮助构建一个成功的工程。 现在，您已经拥有了一个监测器并了解了有关通用 Map 及其性能的基础知识，可以开始运行您自己的测试，以查明您的应用程序是否因 Map 而存在瓶颈以及在何处需要更改所使用的 Map。 <BR><BR>以上内容介绍了通用 Map 及其性能的基础知识。 当然，有关特定 Map 实现以及如何根据不同的需求使用它们还存在更多复杂和值得关注的事项，这些将在本文第 2 部分中介绍。 <BR><BR><BR><FONT style="BACKGROUND-COLOR: #9acd32">-------------------------------------------------------------------------------- <BR>Jack Shirazi 是 O''Reilly 的“Java 性能调整”的作者，以及受欢迎的 JavaPerformanceTuning.com 网站（提供 Java 性能信息的全球知名站点）的总监。 Jack 在 Java 性能领域提供咨询并著书立说。 他还监督 JavaPerformanceTuning.com 提供的信息，其中包括每年大约发布 1000 条性能技巧以及许多有关性能工具、讨论组等内容的文章。 Jack 早年还曾发布有关蛋白质结构预测以及黑洞热力学方面的文章，而且在其空闲时还对某些 Perl5 核心模块作出了贡献。</FONT><FONT style="BACKGROUND-COLOR: #800080"> </FONT><BR></P><img src ="http://www.blogjava.net/iNeo/aggbug/22703.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/iNeo/" target="_blank">只牵这只狗</a> 2005-12-06 13:16 <a href="http://www.blogjava.net/iNeo/archive/2005/12/06/22703.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>正则表达式之道[转]</title><link>http://www.blogjava.net/iNeo/archive/2005/12/06/22655.html</link><dc:creator>只牵这只狗</dc:creator><author>只牵这只狗</author><pubDate>Tue, 06 Dec 2005 01:25:00 GMT</pubDate><guid>http://www.blogjava.net/iNeo/archive/2005/12/06/22655.html</guid><wfw:comment>http://www.blogjava.net/iNeo/comments/22655.html</wfw:comment><comments>http://www.blogjava.net/iNeo/archive/2005/12/06/22655.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/iNeo/comments/commentRss/22655.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/iNeo/services/trackbacks/22655.html</trackback:ping><description><![CDATA[<DIV class=postTitle><FONT size=2>原著：Steve Mansour <BR>sman@scruznet.com <BR>Revised: June 5, 1999<BR>(copied by jm /at/ jmason.org from http://www.scruz.net/%7esman/regexp.htm, after the original disappeared! ) </FONT></DIV>
<DIV class=postText>
<H1>
<P><FONT size=2>翻译：Neo Lee</FONT><BR>什么是正则表达式</P></H1>
<P>一个正则表达式，就是用某种模式去匹配一类字符串的一个公式。很多人因为它们看上去比较古怪而且复杂所以不敢去使用——很不幸，这篇文章也不能够改变这一点，不过，经过一点点练习之后我就开始觉得这些复杂的表达式其实写起来还是相当简单的，而且，一旦你弄懂它们，你就能把数小时辛苦而且易错的文本处理工作压缩在几分钟（甚至几秒钟）内完成。正则表达式被各种文本编辑软件、类库（例如Rogue Wave的tools.h++）、脚本工具（像awk/grep/sed）广泛的支持，而且像Microsoft的Visual C++这种交互式IDE也开始支持它了。 </P>
<P>我们将在如下的章节中利用一些例子来解释正则表达式的用法，绝大部分的例子是基于<B><TT>vi</TT></B>中的文本替换命令和<B><TT>grep</TT></B>文件搜索命令来书写的，不过它们都是比较典型的例子，其中的概念可以在sed、awk、perl和其他支持正则表达式的编程语言中使用。你可以看看<A href="http://net.pku.edu.cn/~yhf/tao_regexps_zh.html#Regular_Expressions_In_Various_Tools">不同工具中的正则表达式</A>这一节，其中有一些在别的工具中使用正则表达式的例子。还有一个关于vi中文本替换命令（s）的<A href="http://net.pku.edu.cn/~yhf/tao_regexps_zh.html#ViSubstitutionCommandSyntax">简单说明</A>附在文后供参考。</P>
<H2>正则表达式基础</H2>
<P>正则表达式由一些普通字符和一些<I>元字符（metacharacters）</I>组成。普通字符包括大小写的字母和数字，而元字符则具有特殊的含义，我们下面会给予解释。 </P>
<P>在最简单的情况下，一个正则表达式看上去就是一个普通的查找串。例如，正则表达式"testing"中没有包含任何元字符，，它可以匹配"testing"和"123testing"等字符串，但是不能匹配"Testing"。</P>
<P>要想真正的用好正则表达式，正确的理解元字符是最重要的事情。下表列出了所有的元字符和对它们的一个简短的描述。</P>
<P>
<TABLE cellSpacing=2 cellPadding=2>
<TBODY>
<TR vAlign=baseline>
<TH align=left><B><I>元字符</I></B></TH>
<TD>&nbsp;</TD>
<TH align=left><B><I>描述</I></B></TH></TR>
<TR>
<TD>
<HR width="100%">
</TD>
<TD></TD>
<TD>
<HR width="100%">
</TD></TR>
<TR>
<TD vAlign=top align=middle>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>.</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配任何单个字符。例如正则表达式<B><TT>r.t</TT></B>匹配这些字符串：<I>rat</I>、<I>rut</I>、<I>r t</I>，但是不匹配<I>root</I>。&nbsp;</TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>$</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配行结束符。例如正则表达式<B><TT>weasel$</TT></B> 能够匹配字符串"<I>He's a weasel</I>"的末尾，但是不能匹配字符串"<I>They are a bunch of weasels.</I>"。&nbsp;</TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><FONT size=+1>^</FONT></B> </CENTER></TD>
<TD></TD>
<TD>匹配一行的开始。例如正则表达式<B><TT>^When in</TT></B>能够匹配字符串"<I>When in the course of human events</I>"的开始，但是不能匹配"<I>What and When in the"。</I></TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>*</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配0或多个正好在它之前的那个字符。例如正则表达式<B><TT></TT></B><B><TT>.*</TT></B>意味着能够匹配任意数量的任何字符。</TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>\</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>这是引用府，用来将这里列出的这些元字符当作普通的字符来进行匹配。例如正则表达式<B><TT>\$</TT></B>被用来匹配美元符号，而不是行尾，类似的，正则表达式<TT><STRONG>\.</STRONG></TT>用来匹配点字符，而不是任何字符的通配符。</TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>[ ]&nbsp;</FONT></FONT></TT></B> <BR><B><TT><FONT face="Courier New"><FONT size=+1>[c</FONT><FONT size=-1>1</FONT><FONT size=+1>-c</FONT><FONT size=-1>2</FONT><FONT size=+1>]</FONT></FONT></TT></B> <BR><B><TT><FONT face="Courier New"><FONT size=+1>[^c</FONT><FONT size=-1>1</FONT><FONT size=+1>-c</FONT><FONT size=-1>2</FONT><FONT size=+1>]</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配括号中的任何一个字符。例如正则表达式<B><TT>r[aou]t</TT></B>匹配<I>rat</I>、<I>rot</I>和<I>rut</I>，但是不匹配<I>ret</I>。可以在括号中使用连字符-来指定字符的区间，例如正则表达式<B><TT>[0-9]</TT></B>可以匹配任何数字字符；还可以制定多个区间，例如正则表达式<B><TT>[A-Za-z]</TT></B>可以匹配任何大小写字母。另一个重要的用法是“排除”，要想匹配<I>除了</I>指定区间之外的字符——也就是所谓的补集——在左边的括号和第一个字符之间使用^字符，例如正则表达式<B><TT>[^269A-Z]</TT></B> 将匹配除了2、6、9和所有大写字母之外的任何字符。</TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>\&lt; \&gt;</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配词（<EM>word</EM>）的开始（\&lt;）和结束（\&gt;）。例如正则表达式<B><TT><FONT face="Courier New">\&lt;the</FONT></TT></B>能够匹配字符串"<I>for the wise</I>"中的"the"，但是不能匹配字符串"<I>otherwise</I>"中的"the"。<STRONG>注意</STRONG>：这个元字符不是所有的软件都支持的。</TD></TR>
<TR>
<TD vAlign=top>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>\( \)</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>将 \( 和 \) 之间的表达式定义为“组”（<EM>group</EM>），并且将匹配这个表达式的字符保存到一个临时区域（一个正则表达式中最多可以保存9个），它们可以用 <B><TT>\1</TT></B> 到<B><TT>\9</TT></B> 的符号来引用。</TD></TR>
<TR>
<TD vAlign=baseline>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>|</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>将两个匹配条件进行逻辑“或”（<EM>Or</EM>）运算。例如正则表达式<B><TT><FONT face="Courier New">(him|her)</FONT></TT></B> 匹配"<I>it belongs to him</I>"和"<I>it belongs to her</I>"，但是不能匹配"<I>it belongs to them.</I>"。<STRONG>注意</STRONG>：这个元字符不是所有的软件都支持的。</TD></TR>
<TR vAlign=baseline>
<TD>
<CENTER><B><TT><FONT face="Courier New"><FONT size=+1>+</FONT></FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配1或多个正好在它之前的那个字符。例如正则表达式<B><TT></TT></B><B><TT></TT></B><B><TT>9+</TT></B>匹配9、99、999等。<STRONG>注意</STRONG>：这个元字符不是所有的软件都支持的。</TD></TR>
<TR vAlign=baseline>
<TD>
<CENTER><B><TT><FONT size=+1>?</FONT></TT></B> </CENTER></TD>
<TD></TD>
<TD>匹配0或1个正好在它之前的那个字符。<STRONG>注意</STRONG>：这个元字符不是所有的软件都支持的。</TD></TR>
<TR vAlign=baseline>
<TD>
<CENTER><B><FONT size=+1><TT><FONT face="Courier New">\{</FONT></TT><I>i</I><TT><FONT face="Courier New">\}</FONT></TT></FONT></B> <BR><B><FONT size=+1><TT><FONT face="Courier New">\{</FONT></TT><I>i</I><TT><FONT face="Courier New">,</FONT></TT><I>j</I><TT><FONT face="Courier New">\}</FONT></TT></FONT></B> </CENTER></TD>
<TD></TD>
<TD vAlign=baseline>匹配指定数目的字符，这些字符是在它之前的表达式定义的。例如正则表达式<B><TT><FONT face="Courier New">A[0-9]\{3\}</FONT></TT></B> 能够匹配字符"A"后面跟着正好3个数字字符的串，例如A123、A348等，但是不匹配A1234。而正则表达式<B><TT><FONT face="Courier New">[0-9]\{4,6\}</FONT></TT></B> 匹配连续的任意4个、5个或者6个数字字符。<STRONG>注意</STRONG>：这个元字符不是所有的软件都支持的。</TD></TR></TBODY></TABLE></P>
<P></P>
<HR width="100%">

<P>最简单的元字符是点，它能够匹配任何单个字符（注意<STRONG>不</STRONG>包括新行符）。假定有个文件test.txt包含以下几行内容：</P>
<UL><TT>he is a rat</TT><BR><TT>he is in a rut</TT><BR><TT>the food is Rotten</TT><BR><TT>I like root beer</TT> </UL>
<P>我们可以使用grep命令来测试我们的正则表达式，grep命令使用正则表达式去尝试匹配指定文件的每一行，并将至少有一处匹配表达式的所有行显示出来。命令 </P>
<UL><TT>grep r.t test.txt</TT> </UL>
<P>在test.txt文件中的每一行中搜索正则表达式<B><TT>r.t</TT></B>，并打印输出匹配的行。正则表达式<B><TT>r.t</TT></B>匹配一个<B><TT>r</TT></B>接着任何一个字符再接着一个<B><TT>t</TT></B>。所以它将匹配文件中的<B><TT>rat</TT></B>和<B><TT>rut</TT></B>，而不能匹配<B><TT>Rotten</TT></B>中的<B><TT>Rot</TT></B>，因为正则表达式是大小写敏感的。要想同时匹配大写和小写字母，应该使用字符区间元字符（方括号）。正则表达式<B><TT>[Rr]</TT></B>能够同时匹配<B><TT>R</TT></B>和<B><TT>r</TT></B>。所以，要想匹配一个大写或者小写的<B><TT>r</TT></B>接着任何一个字符再接着一个<B><TT>t</TT></B>就要使用这个表达式：<B><TT>[Rr].t</TT></B>。 </P>
<P>要想匹配行首的字符要使用抑扬字符（<EM>^</EM>）——又是也被叫做插入符。例如，想找到text.txt中行首"he"打头的行，你可能会先用简单表达式<B><TT>he</TT></B>，但是这会匹配第三行的<B><TT>the</TT></B>，所以要使用正则表达式<B><TT>^he</TT></B>，它只匹配在行首出现的<B><TT>h</TT></B>。 </P>
<P>有时候指定“除了×××都匹配”会比较容易达到目的，当抑扬字符（<EM>^</EM>）出现在方括号中是，它表示“排除”，例如要匹配<B><TT>he</TT></B> ，但是排除前面是<B><TT>t</TT></B> or <B><TT>s</TT></B>的情性（也就是<B><TT>the</TT></B>和<B><TT>s</TT></B><B><TT>he</TT></B>），可以使用：<B><TT>[^st]he</TT></B>。 </P>
<P>可以使用方括号来指定多个字符区间。例如正则表达式<B><TT>[A-Za-z]</TT></B>匹配任何字母，包括大写和小写的；正则表达式<B><TT>[A-Za-z][A-Za-z]*</TT></B> 匹配一个字母后面接着0或者多个字母（大写或者小写）。当然我们也可以用元字符<B><TT>+</TT></B>做到同样的事情，也就是：<B><TT>[A-Za-z]+</TT></B> ，和<B><TT>[A-Za-z][A-Za-z]*</TT></B>完全等价。但是要注意元字符<B><TT>+</TT></B> 并不是所有支持正则表达式的程序都支持的。关于这一点可以参考后面的<A href="http://net.pku.edu.cn/~yhf/tao_regexps_zh.html#Regular%20Expressions%20Syntax">正则表达式语法支持情况</A>。</P>
<P>要指定特定数量的匹配，要使用大括号（注意必须使用反斜杠来转义）。想匹配所有<B><TT>100</TT></B>和<B><TT>1000</TT></B>的实例而排除<B><TT>10</TT></B>和<B><TT>10000</TT></B>，可以使用：<B><TT>10\{2,3\}</TT></B>，这个正则表达式匹配数字1后面跟着2或者3个0的模式。在这个元字符的使用中一个有用的变化是忽略第二个数字，例如正则表达式<B><TT>0\{3,\}</TT></B> 将匹配至少3个连续的0。</P>
<H2><A name=SimpleCommands></A>简单的例子</H2>
<P>这里有一些有代表性的、比较简单的例子。 </P>
<P>
<TABLE cellSpacing=2 cellPadding=2>
<TBODY>
<TR>
<TD><B><I>vi 命令</I></B></TD>
<TD><B><I>作用</I></B></TD></TR>
<TR>
<TD>
<HR width="100%">
</TD>
<TD>
<HR width="100%">
</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New"><FONT size=+1>:%s/ */ /g</FONT></FONT></TT></B></TD>
<TD>把一个或者多个空格替换为一个空格。</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New"><FONT size=+1>:%s/ *$//</FONT></FONT></TT></B></TD>
<TD>去掉行尾的所有空格。</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New"><FONT size=+1>:%s/^/ /</FONT></FONT></TT></B></TD>
<TD>在每一行头上加入一个空格。</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New"><FONT size=+1>:%s/^[0-9][0-9]* //</FONT></FONT></TT></B></TD>
<TD>去掉行首的所有数字字符。</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New"><FONT size=+1>:%s/b[aeio]g/bug/g</FONT></FONT></TT></B></TD>
<TD>将所有的<I>bag</I>、<I>beg</I>、<I>big</I>和<I>bog</I>改为<I>bug</I>。&nbsp;</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New"><FONT size=+1>:%s/t\([aou]\)g/h\1t/g</FONT></FONT></TT></B></TD>
<TD>将所有<I>tag</I>、<I>tog</I>和<I>tug</I>分别改为<I>hat</I>、<I>hot</I>和<I>hug</I>（注意用group的用法和使用\1引用前面被匹配的字符）。</TD></TR>
<TR>
<TD></TD>
<TD></TD></TR></TBODY></TABLE></P>
<H2><A name=MediumDifficultyExamples></A>中级的例子（神奇的咒语）</H2>
<H3>例1</H3>
<P>将所有方法foo(<I>a,b,c</I>)的实例改为foo(<I>b,a,c</I>)。这里a、b和c可以是任何提供给方法foo()的参数。也就是说我们要实现这样的转换： </P>
<P>
<TABLE cellSpacing=4 cellPadding=0>
<TBODY>
<TR>
<TD><B>之前</B></TD>
<TD>&nbsp;</TD>
<TD><B>之后</B></TD></TR>
<TR>
<TD><TT>foo(10,7,2)</TT></TD>
<TD></TD>
<TD><TT>foo(7,10,2)</TT></TD></TR>
<TR>
<TD><TT>foo(x+13,y-2,10)</TT></TD>
<TD></TD>
<TD><TT>foo(y-2,x+13,10)</TT></TD></TR>
<TR>
<TD><TT>foo( bar(8), x+y+z, 5)</TT></TD>
<TD></TD>
<TD><TT>foo( x+y+z, bar(8), 5)</TT></TD></TR></TBODY></TABLE></P>
<P>下面这条替换命令能够实现这一魔法：</P>
<UL><B><TT><FONT face="Courier New">:%s/foo(\([^,]*\),\([^,]*\),\([^)]*\))/foo(\2,\1,\3)/g</FONT></TT></B> </UL>
<P>现在让我们把它打散来加以分析。写出这个表达式的基本思路是找出foo()和它的括号中的三个参数的位置。第一个参数是用这个表达式来识别的：：<B><TT><FONT face="Courier New">\([^,]*\)</FONT></TT></B>，我们可以从里向外来分析它：&nbsp; </P>
<P>
<TABLE>
<TBODY>
<TR>
<TD><B><TT><FONT face="Courier New">[^,]</FONT></TT></B></TD>
<TD>&nbsp;</TD>
<TD>除了逗号之外的任何字符</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New">[^,]*</FONT></TT></B></TD>
<TD></TD>
<TD>0或者多个非逗号字符</TD></TR>
<TR>
<TD><B><TT><FONT face="Courier New">\([^,]*\)</FONT></TT></B></TD>
<TD></TD>
<TD>将这些非逗号字符标记为<B><TT>\1</TT></B>，这样可以在之后的替换模式表达式中引用它</TD></TR>
<TR vAlign=baseline>
<TD><B><TT><FONT face="Courier New">\([^,]*\),</FONT></TT></B></TD>
<TD></TD>
<TD>我们必须找到0或者多个非逗号字符后面跟着一个逗号，并且非逗号字符那部分要标记出来以备后用。</TD></TR></TBODY></TABLE></P>
<P>现在正是指出一个使用正则表达式常见错误的最佳时机。为什么我们要使用<B><TT><FONT face="Courier New">[^,]*</FONT></TT></B>这样的一个表达式，而不是更加简单直接的写法，例如：<B><TT><FONT face="Courier New">.*</FONT></TT></B>，来匹配第一个参数呢？设想我们使用模式<B><TT><FONT face="Courier New">.*</FONT></TT></B>来匹配字符串"10,7,2"，它应该匹配"10,"还是"10,7,"？为了解决这个两义性（ambiguity），正则表达式规定一律按照最长的串来，在上面的例子中就是"10,7,"，显然这样就找出了两个参数而不是我们期望的一个。所以，我们要使用<B><TT><FONT face="Courier New">[^,]*</FONT></TT></B>来强制取出第一个逗号之前的部分。</P>
<P>这个表达式我们已经分析到了：<B><TT><FONT face="Courier New">foo(\([^,]*\)</FONT></TT></B>，这一段可以简单的翻译为“当你找到<B><TT>foo(</TT></B>就把其后直到第一个逗号之前的部分标记为<B><TT><FONT face="Courier New">\1</FONT></TT></B>”。然后我们使用同样的办法标记第二个参数为<B><TT><FONT face="Courier New">\2</FONT></TT></B>。对第三个参数的标记方法也是一样，只是我们要搜索所有的字符直到右括号。我们并没有必要去搜索第三个参数，因为我们不需要调整它的位置，但是这样的模式能够保证我们只去替换那些有三个参数的foo()方法调用，在foo()是一个重载（overoading）方法时这种明确的模式往往是比较保险的。然后，在替换部分，我们找到foo()的对应实例，然后利用标记好的部分进行替换，是的第一和第二个参数交换位置。</P>
<H3>例2</H3>
<P>假设有一个CSV（comma separated value）文件，里面有一些我们需要的信息，但是格式却有问题，目前数据的列顺序是：姓名，公司名，州名缩写，邮政编码，现在我们希望讲这些数据重新组织，以便在我们的某个软件中使用，需要的格式为：姓名，州名缩写-邮政编码，公司名。也就是说，我们要调整列顺序，还要合并两个列来构成一个新列。另外，我们的软件不能接受逗号前后面有任何空格（包括空格和制表符）所以我们还必须要去掉逗号前后的所有空格。 </P>
<P>这里有几行我们现在的数据：</P>
<UL><TT>Bill Jones,&nbsp;&nbsp;&nbsp;&nbsp; HI-TEK Corporation ,&nbsp; CA, 95011</TT> <BR><TT><FONT face="Courier New">Sharon Lee Smith,&nbsp; Design Works Incorporated,&nbsp; CA, 95012</FONT></TT> <BR><TT><FONT face="Courier New">B. Amos&nbsp;&nbsp; ,&nbsp; Hill Street Cafe,&nbsp; CA, 95013</FONT></TT> <BR><TT><FONT face="Courier New">Alexander Weatherworth,&nbsp; The Crafts Store,&nbsp; CA, 95014</FONT></TT> <BR><TT><FONT face="Courier New">...</FONT></TT> </UL>
<P>我们希望把它变成这个样子： </P>
<UL><TT>Bill Jones,CA 95011,HI-TEK Corporation</TT> <BR><TT><FONT face="Courier New">Sharon Lee Smith,CA 95012,Design Works Incorporated</FONT></TT> <BR><TT><FONT face="Courier New">B. Amos,CA 95013,Hill Street Cafe</FONT></TT> <BR><TT><FONT face="Courier New">Alexander Weatherworth,CA 95014,The Crafts Store</FONT></TT> <BR><TT><FONT face="Courier New">...</FONT></TT> </UL>
<P>我们将用两个正则表达式来解决这个问题。第一个移动列和合并列，第二个用来去掉空格。 </P>
<P>下面就是第一个替换命令：</P>
<UL><B><TT><FONT face="Courier New">:%s/\([^,]*\),\([^,]*\),\([^,]*\),\(.*\)/\1,\3 \4,\2/</FONT></TT></B> </UL>
<P>这里的方法跟例1基本一样，第一个列（姓名）用这个表达式来匹配：<B><TT><FONT face="Courier New">\([^,]*\)</FONT></TT></B>，即第一个逗号之前的所有字符，而姓名内容被用<B><TT><FONT face="Courier New">\1</FONT></TT></B>标记下来。公司名和州名缩写字段用同样的方法标记为<B><TT><FONT face="Courier New">\2</FONT></TT></B>和<B><TT><FONT face="Courier New">\3</FONT></TT></B>，而最后一个字段用<B><TT><FONT face="Courier New">\(.*\)</FONT></TT></B>来匹配（"匹配所有字符直到行末"）。替换部分则引用上面标记的那些内容来进行构造。 </P>
<P>下面这个替换命令则用来去除空格：</P>
<UL><B><TT><FONT face="Courier New">:%s/[ \t]*,[ \t]*/,/g</FONT></TT></B> </UL>
<P>我们还是分解来看：<B><TT><FONT face="Courier New">[ \t]</FONT></TT></B>匹配空格/制表符，<B><TT><FONT face="Courier New">[ \t]*</FONT></TT></B> 匹配0或多个空格/制表符，<B><TT>[ \t]*</TT></B>,匹配0或多个空格/制表符后面再加一个逗号，最后，<B><TT><FONT face="Courier New">[ \t]*,[ \t]*</FONT></TT></B>匹配0或多个空格/制表符接着一个逗号再接着0或多个空格/制表符。在替换部分，我们简单的我们找到的所有东西替换成一个逗号。这里我们使用了结尾的可选的<B><TT>g</TT></B>参数，这表示在每行中对所有匹配的串执行替换（而不是缺省的只替换第一个匹配串）。 </P>
<H3>例3</H3>
<P>假设有一个多字符的片断重复出现，例如： </P>
<BLOCKQUOTE><TT>Billy tried really hard</TT> <BR><TT>Sally tried really really hard</TT> <BR><TT>Timmy tried really really really hard</TT> <BR><TT>Johnny tried really really really really hard</TT></BLOCKQUOTE>
<P>而你想把"really"、"really really"，以及任意数量连续出现的"really"字符串换成一个简单的"very"（simple is good!），那么以下命令： </P>
<BLOCKQUOTE><B><TT>:%s/\(really \)\(really \)*/very /</TT></B></BLOCKQUOTE>
<P>就会把上述的文本变成： </P>
<BLOCKQUOTE><TT>Billy tried very hard</TT> <BR><TT>Sally tried very hard</TT> <BR><TT>Timmy tried very hard</TT> <BR><TT>Johnny tried very hard</TT></BLOCKQUOTE>
<P>表达式<B><TT>\(really \)*</TT></B>匹配0或多个连续的"really "（注意结尾有个空格），而<B><TT>\(really \)\(really \)*</TT></B> 匹配1个或多个连续的"really "实例。 </P>
<H2><A name=HardExamples></A>困难的例子（不可思议的象形文字）</H2>
<P><I>Coming soon</I>. </P>
<P></P>
<HR>

<H1><A name=Regular_Expressions_In_Various_Tools></A>不同工具中的正则表达式</H1>
<P>OK，你已经准备使用RE（regular expressions，正则表达式），但是你并准备使用vi。所以，在这里我们给出一些在其他工具中使用RE的例子。另外，我还会总结一下你在不同程序之间使用RE可能发现的区别。 </P>
<P>当然，你也可以在Visual C++编辑器中使用RE。选择Edit-&gt;Replace，然后选择"Regular expression"选择框，Find What输入框对应上面介绍的vi命令<B><TT>:%s/pat1/pat2/g</TT></B>中的pat1部分，而Replace输入框对应pat2部分。但是，为了得到vi的执行范围和<B><TT>g</TT></B>选项，你要使用Replace All或者适当的手工Find Next and Replace（译者按：知道为啥有人骂微软弱智了吧，虽然VC中可以选中一个范围的文本，然后在其中执行替换，但是总之不够vi那么灵活和典雅）。</P>
<H2>sed</H2>
<P>Sed是<B><U>S</U></B>tream <B><U>ED</U></B>itor的缩写，是Unix下常用的基于文件和管道的编辑工具，可以在手册中得到关于sed的详细信息。 </P>
<P>这里是一些有趣的sed脚本，假定我们正在处理一个叫做price.txt的文件。注意这些编辑并不会改变源文件，sed只是处理源文件的每一行并把结果显示在标准输出中（当然很容易使用重定向来定制）： </P>
<P>
<TABLE>
<TBODY>
<TR>
<TD><B><I>sed脚本</I></B></TD>
<TD>&nbsp;</TD>
<TD><B><I>描述</I></B></TD></TR>
<TR>
<TD>
<HR width="100%">
</TD>
<TD></TD>
<TD>
<HR width="100%">
</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>sed 's/^$/d' price.txt</TT></B></TD>
<TD></TD>
<TD>删除所有空行</TD></TR>
<TR>
<TD><B><TT>sed 's/^[ \t]*$/d' price.txt</TT></B></TD>
<TD></TD>
<TD>删除所有只包含空格或者制表符的行</TD></TR>
<TR>
<TD><B><TT>sed 's/"//g' price.txt</TT></B></TD>
<TD></TD>
<TD>删除所有引号</TD></TR></TBODY></TABLE></P>
<H2>awk</H2>
<P>awk是一种编程语言，可以用来对文本数据进行复杂的分析和处理。可以在手册中得到关于awk的详细信息。这个古怪的名字是它作者们的姓的缩写（Aho，Weinberger和Kernighan）。 </P>
<P>在Aho，Weinberger和Kernighan的书<U>The AWK Programming Language</U>中有很多很好的awk的例子，请不要让下面这些微不足道的脚本例子限制你对awk强大能力的理解。我们同样假定我们针对price.txt文件进行处理，跟sed一样，awk也只是把结果显示在终端上。&nbsp; </P>
<P>
<TABLE>
<TBODY>
<TR>
<TD><B><I>awk脚本</I></B></TD>
<TD>&nbsp;</TD>
<TD><B><I>描述</I></B></TD></TR>
<TR>
<TD>
<HR width="100%">
</TD>
<TD></TD>
<TD>
<HR width="100%">
</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>awk '$0 !~ /^$/' price.txt</TT></B></TD>
<TD></TD>
<TD>删除所有空行</TD></TR>
<TR>
<TD><B><TT>awk 'NF &gt; 0' price.txt</TT></B></TD>
<TD></TD>
<TD>awk中一个更好的删除所有行的办法</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>awk '$2 ~ /^[JT]/ {print $3}' price.txt</TT></B></TD>
<TD></TD>
<TD>打印所有第二个字段是'J'或者'T'打头的行中的第三个字段</TD></TR>
<TR vAlign=baseline>
<TD noWrap><B><TT>awk '$2 !~ /[Mm]isc/ {print $3 + $4}' price.txt</TT></B></TD>
<TD></TD>
<TD>针对所有第二个字段不包含'Misc'或者'misc'的行，打印第3和第4列的和（假定为数字）</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>awk '$3 !~ /^[0-9]+\.[0-9]*$/ {print $0}' price.txt</TT></B></TD>
<TD></TD>
<TD>打印所有第三个字段不是数字的行，这里数字是指<TT>d.d</TT>或者<TT>d这样的形式，其中</TT><TT>d</TT>是0到9的任何数字</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>awk '$2 ~ /John|Fred/ {print $0}' price.txt</TT></B></TD>
<TD></TD>
<TD>如果第二个字段包含'John'或者'Fred'则打印整行</TD></TR></TBODY></TABLE></P>
<H2>grep</H2>
<P>grep是一个用来在一个或者多个文件或者输入流中使用RE进行查找的程序。它的name编程语言可以用来针对文件和管道进行处理。可以在手册中得到关于grep的完整信息。这个同样古怪的名字来源于vi的一个命令，<B><TT>g/</TT></B><I>re</I><B><TT>/p</TT></B>，意思是<B>g</B>lobal <B>r</B>egular <B>e</B>xpression <B>p</B>rint。 </P>
<P>下面的例子中我们假定在文件phone.txt中包含以下的文本，——其格式是姓加一个逗号，然后是名，然后是一个制表符，然后是电话号码：</P>
<UL>
<P><TT>Francis, John&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5-3871</TT> <BR><TT>Wong, Fred&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4-4123</TT> <BR><TT>Jones, Thomas&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1-4122</TT> <BR><TT>Salazar, Richard&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5-2522</TT></P></UL>
<P>
<TABLE>
<TBODY>
<TR>
<TD><B><I>grep命令</I></B></TD>
<TD><B><I>&nbsp;</I></B></TD>
<TD><B><I>描述</I></B></TD></TR>
<TR>
<TD>
<HR width="100%">
</TD>
<TD></TD>
<TD>
<HR width="100%">
</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>grep '\t5-...1' phone.txt</TT></B></TD>
<TD></TD>
<TD>把所有电话号码以5开头以1结束的行打印出来，注意制表符是用<B><TT>\t</TT></B>表示的</TD></TR>
<TR vAlign=baseline>
<TD noWrap><B><TT>grep '^S[^ ]* R' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有姓以S打头和名以R打头的行</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>grep '^[JW]' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有姓开头是J或者W的行</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>grep ', ....\t' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有姓是4个字符的行，注意制表符是用<B><TT>\t</TT></B>表示的</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>grep -v '^[JW]' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有不以J或者W开头的行</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>grep '^[M-Z]' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有姓的开头是M到Z之间任一字符的行</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>grep '^[M-Z].*[12]' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有姓的开头是M到Z之间任一字符，并且点号号码结尾是1或者2的行</TD></TR></TBODY></TABLE></P>
<H2>egrep</H2>
<P>egrep是grep的一个扩展版本，它在它的正则表达式中支持更多的元字符。下面的例子中我们假定在文件phone.txt中包含以下的文本，——其格式是姓加一个逗号，然后是名，然后是一个制表符，然后是电话号码： </P>
<UL><TT>Francis, John&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5-3871</TT> <BR><TT>Wong, Fred&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4-4123</TT> <BR><TT>Jones, Thomas&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1-4122</TT> <BR><TT>Salazar, Richard&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 5-2522</TT> </UL>
<P>
<TABLE>
<TBODY>
<TR>
<TD><B><I>egrep command</I></B></TD>
<TD><B><I>&nbsp;</I></B></TD>
<TD><B><I>Description</I></B></TD></TR>
<TR>
<TD>
<HR width="100%">
</TD>
<TD></TD>
<TD>
<HR width="100%">
</TD></TR>
<TR vAlign=baseline>
<TD><B><TT>egrep '(John|Fred)' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有包含名字<I>John</I>或者<I>Fred</I>的行</TD></TR>
<TR vAlign=baseline>
<TD noWrap><B><TT>egrep 'John|22$|^W' phone.txt</TT></B></TD>
<TD></TD>
<TD>打印所有包含<I>John</I> 或者以22结束或者以<I>W</I>的行</TD></TR>
<TR>
<TD><B><TT>egrep 'net(work)?s' report.txt</TT></B></TD>
<TD></TD>
<TD>从report.txt中找到所有包含<I>networks</I>或者<I>nets</I>的行</TD></TR></TBODY></TABLE></P>
<H2>
<HR width="100%">
</H2>
<H1><A name="Regular Expressions Syntax"></A>正则表达式语法支持情况</H1>
<P>
<TABLE cellSpacing=0 border=1>
<TBODY>
<TR>
<TD><B>命令或环境</B></TD>
<TD><B><TT><FONT face="Courier New">.</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">[ ]</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">^</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">$</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">\( \)</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">\{ \}</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">?</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">+</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">|</FONT></TT></B></TD>
<TD><B><TT><FONT face="Courier New">( )</FONT></TT></B></TD></TR>
<TR>
<TD>vi</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD></TR>
<TR>
<TD>Visual C++</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD></TR>
<TR>
<TD>awk</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD></TR>
<TR>
<TD>sed</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD></TR>
<TR>
<TD>Tcl</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD></TR>
<TR>
<TD>ex</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD></TR>
<TR>
<TD>grep</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD></TR>
<TR>
<TD>egrep</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD></TR>
<TR>
<TD>fgrep</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;X&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;</TD></TR>
<TR>
<TD>perl</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD>
<TD>&nbsp;X</TD></TR></TBODY></TABLE></P>
<P>&nbsp;</P>
<HR>

<H1><A name=ViSubstitutionCommandSyntax></A>vi替换命令简介</H1>
<P>Vi的替换命令： </P>
<UL><B><TT>:</TT></B><I>range</I><B><TT>s/</TT></B><I>pat1</I><B><TT>/</TT></B><I>pat2</I><B><TT>/g</TT></B> </UL>
<P>其中 </P>
<UL><B><TT>:</TT></B> 这是Vi的命令执行界面。 </UL>
<UL><I>range </I>是命令执行范围的指定，可以使用百分号（%）表示所有行，使用点（.）表示当前行，使用美元符号（$）表示最后一行。你还可以使用行号，例如<B><TT>10,20</TT></B>表示第10到20行，<B><TT>.,$</TT></B>表示当前行到最后一行，<B><TT>.+2,$-5</TT></B>表示当前行后两行直到全文的倒数第五行，等等。 
<P><B><TT>s</TT></B> 表示其后是一个替换命令。</P>
<P><I>pat1 </I>这是要查找的一个正则表达式，这篇文章中有一大堆例子。</P></UL>
<UL><I>pat2 </I>这是希望把匹配串变成的模式的正则表达式，这篇文章中有一大堆例子。 
<P><B><TT>g</TT></B> 可选标志，带这个标志表示替换将针对行中每个匹配的串进行，否则则只替换行中第一个匹配串。</P></UL>
<P>网上有很多vi的在线手册，你可以访问他们以获得更加完整的信息。 </P><BR><BR>
<P id=TBPingURL>Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=539524</P></DIV><img src ="http://www.blogjava.net/iNeo/aggbug/22655.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/iNeo/" target="_blank">只牵这只狗</a> 2005-12-06 09:25 <a href="http://www.blogjava.net/iNeo/archive/2005/12/06/22655.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>