DosLin's Blog

什么都略懂一点,生活更美好一些

0%


说明和前提

1、由于通过命令react-native init初始化的React Native(以下简称RN)项目结构与原生项目有区别,所以不能单纯直接在已有项目的根目录下运行该命令。

2、RN需要Android4.1或以上的环境,故集成RN的Android项目的minSdkVersion需设置为API16或以上。

3、本机安装NPM支持:
新版的RN依赖都将发布在NPM(包管理工具,如同Maven仓库)中,故需安装NPM。

4、本文安装环境为MAC,Windows或Linux上的命令差异请参考官方文档

集成步骤

1. 安装NPM

之后于同时需要NodeJS服务器,故只需安装NodeJS,NPM会随同安装。

1
2
brew install node
brew install watchman

2. 添加NPM依赖

进入Android项目根目录,新建文件package.json,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "react-native-module",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"dependencies": {
"react": "15.3.2",
"react-native": "0.34.1"
}
}

之后拉取NPM组件,在package.json同目录位置下运行

1
npm install

完成后将出现node_modules文件夹。

3.添加页面文件

在根目录下,新建文件index.android.js,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';

class RN_Demo extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.android.js
</Text>
<Text style={styles.instructions}>
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu
</Text>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

AppRegistry.registerComponent('RN_Demo', () => RN_Demo);

记住上面的组件名RN_Demo

4.添加RN依赖

编辑app目录下的build.gradle,添加

1
compile 'com.facebook.react:react-native:0.34.1'  // From node_modules

之所以此处不根据官方文档的配置(compile ‘com.facebook.react:react-native:+’),你会发现通过Gradle Sync进行同步后会报错,所以需要在项目下的build.gradle中添加

1
2
3
4
5
6
7
8
9
allprojects {
repositories {
jcenter()
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$projectDir/../node_modules/react-native/android"
}
}
}

否则未写明版本的RN依赖可能是jcenter中的旧版本。

5.新建Activity与Application

新建一个继承自ReactActivityactivity,内容如下:

1
2
3
4
5
6
7
8
9
import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {

@Override
protected String getMainComponentName() {
return "RN_Demo";
}
}

注意此处实现的getMainComponentName方法返回的字符串与index.android.js中注册的Component名的要一致。

之后编辑自定义Application,实现ReactApplication接口,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainApplication extends Application implements ReactApplication {

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
// 表示是否启用开发者模式
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}

@Override
// 模块列表,可添加原生Java模块
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
};

@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}

6.编辑AndroidManifest.xml

添加应用的网络访问权限,以及刚刚新建的ActivityApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.doslin.rndemo">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="false"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

</manifest>

7.添加NDK支持

gradle.properties中添加如下内容:

1
android.useDeprecatedNdk=true

app下的build.gradle中添加如下内容:

1
2
3
ndk {
abiFilters 'armeabi', 'armeabi-v7a'
}

如果你的app需要在x86下运行,请额外进行配置x86

8.启动Packager Server

package.json同级目录下运行

start```
1
之后为了防止出现`error “Could not get BatchedBridge, make sure your bundle is packaged properly” on start of app`的异常,先运行

curl “http://localhost:8081/index.android.bundle?platform=android" -o “./app/src/main/assets/index.android.bundle”

1
2
3
4

将在**assets**目录下下载一份`index.android.bundle`文件。

如果你是用的手机而非模拟器测试,还需要运行如下命令设置反向代理:

adb reverse tcp:8081 tcp:8081


最后执行**AS**的绿色**RUN**按钮等待**apk**上传并运行。
在主界面摇晃手机会出现调试界面,注意开启**显示悬浮窗**权限。

------
## 参考链接

[1] [http://facebook.github.io/react-native/docs/native-components-android.html](http://facebook.github.io/react-native/docs/native-components-android.html)

[2] [http://gold.xitu.io/entry/57a0d03ed342d300572e9ffc](http://gold.xitu.io/entry/57a0d03ed342d300572e9ffc)

[3] [http://www.imbeta.cn/react-native-for-android-huan-jing-da-jian-ji-cai-keng.html](http://www.imbeta.cn/react-native-for-android-huan-jing-da-jian-ji-cai-keng.html)

[4] [http://acgtofe.com/posts/2016/06/react-native-embedding-android](http://acgtofe.com/posts/2016/06/react-native-embedding-android)

[5] [http://stackoverflow.com/questions/38870710/error-could-not-get-batchedbridge-make-sure-your-bundle-is-packaged-properly](http://stackoverflow.com/questions/38870710/error-could-not-get-batchedbridge-make-sure-your-bundle-is-packaged-properly)

## 相关资料

1.[React-Native学习指南](https://github.com/reactnativecn/react-native-guide)

2.[react-native-android-guide](https://github.com/xujinyang/react-native-android-guide#react-native-android-guide)

有时候会出现github客户端在Mac下点击Clone in Desktop时会带你去GitHub客户端的下载页面。

根据官方写明的调用原理,只要确保
在浏览器中访问https://ghconduit.com:25035/status能正常返回
{"capabilities":["status","unique_id","url-parameter-filepath"],"running":false,"server_version":"5"}
类似的信息即可。

如果访问失败,可以通过以下几种形式定位问题:

  • 进程清单中是否有Github Conduit
  • 浏览器是否开了代理;
  • 尝试在/etc/hosts中加入127.0.0.1 ghconduit.com后重新访问。

当链接正常时刷新页面重新点击Clone就能生效了。

原文地址-Sam Hughes

翻译水平有限,如有谬误,欢迎评论斧正或者Pull Request

正则表达式(“regexes”)即增强查找/字符串替换操作。当在文本编辑器中编辑文字时,正则表达式经常用于:

  • 检查文本是否包含一个给定的模式
  • 查找任何匹配的模式
  • 从文本中拉取信息(比如截断)
  • 修改文本

和文本编辑器一样,绝大多数高级编程语言支持正则表达式。在本文中,“文本”仅仅是一个字符串变量,但是有效的操作却是一致的。某些编程语言(Perl,JavaScript)甚至为正则表达式提供专用的语法。

但是正则表达式是什么?

一个正则表达式仅仅为一个字符串。它没有长度限制,但是通常该字符串很短。下面看几个例子:

  • I had a \S+ day today
  • [A-Za-z0-9\-_]{3,16}
  • \d\d\d\d-\d\d-\d\d
  • v(\d+)(\.\d+)*
  • TotalMessages="(.*?)"
  • <[^<>]>

这个字符串实际上是一个极小的计算程序,并且正则表达式是一门语法小而简洁,领域特定的编程语言。牢记以下几点,它们不该在学习过程中让你感到惊讶:

  • 每个正则表达式都能分解成一串指令。“找到这个,再找到那个,然后找到其中一个…”
  • 一个正则表达式拥有输入(文本)和输出(模式匹配,和有些时候的自定义文本)。
  • 存在语法错误——不是每个字符串都是合法的正则表达式!
  • 语法有些怪异,也可以说是恐怖。
  • 一个正则表达式有时候可以被编译以便更快运行。

正则实现一直有着显著的改变。对于本文,我所关注的是那些几乎每个正则表达式都实现了的核心语法。

练习

获取一个支持正则的文本编辑器。我推荐Notepad++

下载一篇很长的散文故事比如Gutenberg出版社出版的H. G. Wells的《时光机器》然后打开它。

下载一部字典,比如这个,解压然后打开。

一切准备就绪,稍后开始练习。

提示: 正则表达式与文件通配符语法完全不兼容,比如*.xml

##正则表达式基础语法

###字面值(Literals)

正则表达式由只代表自身的字面值和代表特定含义的元字符组成。

这里也有一些例子。我会对元字符进行高亮。

  • I had a \S+ day today
  • [A-Za-z0-9-_]{3,16}
  • \d\d\d\d-\d\d-\d\d
  • v(\d+)(.\d+)*
  • TotalMessages="(.*?)"
  • <[^<>]*>

大部分字符,包括字母数字字符,会以字面值的形式出现。这意味着它们查找的是自身。比如,正则表达式cat代表“先找到c,接着找到a,最后找到t”。

目前为止感觉良好。这的确很像

  • 一个普通的查找对话框
  • Java中的String.indexOf()函数
  • PHP中的strpos()函数
  • 等等

提示:除非特别说明,正则表达式是区分大小写的。然而,绝大多数实现都会提供一个标记来开启不区分大小写的功能。

###句点(dot)

我们第一个元字符是句号(译者注:句点,英文句号),.。一个.表示匹配任何单个字符。下面这个正则表达式c.t代表“先找到c,接着找到任何单个字符,再找到t”。

在一段文本中,这个表达式将会找到catcotczt,甚至字面值为c.t的字符串(c,句点,t),但是不包括ct或者coot

在正则表达式里,空格是有效的。正则表达式 ‘c t’ 代表”先找到 ‘c’,接着找到空格,再找到 ‘t’“。

任何元字符如果用一个反斜杆\进行转义就会变成字面值。所以上述的正则表达式c\.t就代表“先找到c,接着找到句号,再找到t”。

反斜杠是一个元字符,这意味着它也可以使用反斜杠转义。所以正则表达式c\\t代表“先找到c,接着找到反斜杆,再找到t”。

注意! 在一些实现中,. 会匹配除了 换行符 的任意字符。这意味着“换行符”在不同的实现中也会变化。 要查看你的文档。在这篇文章中, 我会确保. 会匹配任意字符。

在其它情况下, 通常会有一个标记来调整这种行为,那就是`DOTALL`或类似的标记

练习

使用你目前所学,在字典中使用正则表达式,匹配一个有两个z的单词,其中这两个z离得越远越好。

练习

《时光机器》这本书中,使用正则表达式来查找以介词收尾的句子。

###字符类(Character classes)

字符类是字符在方括号中的集合。表示“找到集合里任意一个字符”。

  • 正则表达式c[aeiou]t表示“找到c后跟一个元音字母,再找到t”。在一段文本中,将会匹配到catcetcitcotcut
  • 正则表达式[0123456789]表示找到一个数字
  • 正则表达式[a]a意义相同:“找到a

一些转义的例子:

  • \[a\]表示“找到一个左方括号紧跟着一个a,再跟着一个右方括号”。
  • [\[\]ab]表示“匹配一个左方括号或者右方括号或者a或者 b”。
  • [\\\[\]]表示“匹配一个反斜杆或者一个左方括号或者一个右方括号”。(呕!)

在字符类中顺序和重复字符并不重要。[dabaaabcc][abcd]一样。

重要的提示

在字符类内部的“规则”和在字符类内部的规则有所不同。一些字符在字符类内部扮演着元字符的角色,但在字符类外部则充当字面值。还有一些字符做着相反的事。一些字符在两种情形都为元字符,但在各自情形里代表不同的含义。

特别地, .表示“匹配任意字符”,但是[.]表示“匹配句点”。不能并为一谈。

练习

结合目前所学,在字典中,使用正则表达式查找有连续的元音和连续的辅音的单词。

###字符类区间(ranges)

你可以在字符类中使用连字符来表示一个字母或数字的区间

  • [b-f][bcdef]都表示“找到一个bcdef”。
  • [A-Z][ABCDEFGHIJKLMNOPQRSTUVWXYZ]都表示“匹配大写字母”。
  • [1-9][123456789]都表示“匹配一个非零数字”。

连字符在字符类外部使用时并没有特别都含义。正则表达式a-z表示“找到一个a接着跟着一个连字符,然后匹配一个z”。

区间和单独的字符可能会共存于同一个字符类:

  • [0-9.,]表示“匹配一个数字或者一个句点或者一个逗号”。
  • [0-9a-fA-F]表示“匹配一位十六进制数”。
  • [a-zA-Z0-9\-]表示“匹配一个字母数字字符或连字符”。

虽然你可以尝试在区间内以非字母数字字符结束(比如abc[!-/]def),但这在其它实现中的语法不一定对。即使语法正确,但在这个区间内很难看出包含了哪个字符。请谨慎使用(我的意思是不要这么干)。

同样的,区间端点的范围应该一致。即使像[A-z]这种表达式在你选择的实现中合法,但结果可能不如你愿。(补充:可以有Za的区间范围)。

注意。 区间是字符的区间,不是数字的区间。正则表达式[1-31]表示“找到一个1或一个 2或一个3”,不是“找到一个从131的整数"。

练习

使用目前学习,编写一个查找以YYYY-MM-DD为格式的日期的正则表达式。

###字符类的否定(negation)

你可以通过在最开始的位置使用插入符号(译者注:^)来否定一个字符类。

  • [^a]表示“匹配除了a的任意字符”。
  • [^a-zA-Z0-9]表示“找到一个非字母也非数字的字符”。
  • [\^abc]表示“找到一个插入符或者a或者b或者c”。
  • [^\^]表示“找到除了插入符外的任意字符”。(呕!)

练习

在字典中,使用正则表达式去找到这个规则的反例“i位于e 前面并且不出现在c的后面”。

###字符类补充

正则表达式\d含义与[0-9]一致:“匹配一个数字”。(为了匹配一个反斜杆后跟一个d,可以使用\\d。)

\w的含义与[0-9A-Za-z_]一致:“匹配一个单词字符(译者注:字母或数字或下划线或汉字)”。

\s表示“匹配任意空白字符(空格,tab,回车或者换行)”。

此外,

  • \D[^0-9]:“匹配任意非数字的字符”。
  • \W[^0-9A-Za-z_]:“匹配任意非单词字符(译者注:匹配任意不是字母,数字,下划线,汉字的字符)”。
  • \S表示“匹配任意不是空白符的字符”。

这些字符类都很常见,你必须学会。

你可能也注意到了,句点.本质上是一个包含任意字符的字符类

许多实现提供了很多额外的字符类或标记,它们通过扩展现有的字符类来覆盖ASCII之外范围的字符。提示:Unicode包含更多的“数字字符”而不仅仅是09,这一点同样对于“单词”和“空格”也适用。注意你的文档所写。

练习

简化正则表达式[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]

###乘法器(Multipliers)

你可以在一个字面值或者字符类后跟着一个大括号来使用乘法器

  • 正则表达式a{1}a,表示“匹配一个a”。
  • a{3}表示“找到一个a后再跟一个a,最后找到一个a”。
  • a{0}表示“匹配空字符”。就其本身而言,这似乎没有用处。如果你在任何一段文本中使用该表达式,你会在你刚开始搜索的端点处立即得到一个匹配。即使你的文本为空字符串结果也为真。
  • a\{2\}代表“找到一个a,跟着一个左大括号,接着跟匹配一个2,然后跟着一个右大括号”。
  • 在字符类中大括号没有特别的含义。[{}]代表“匹配一个左大括号或者一个右大括号”。

注意。 乘法器没有记忆。该正则表达式[abc]{2}表示“匹配a或者b或者c,接着匹配a或者b或者c。这跟“匹配aaabacbabbbccacbcc”相同。这跟“匹配aabbcc”含义不同

练习

简化以下正则表达式:

  • z.......z
  • \d\d\d\d-\d\d-\d\d
  • [aeiou][aeiou][aeiou][aeiou][aeiou][aeiou]
  • [bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz][bcdfghjklmnpqrstvwxyz]

###乘法器区间

乘法器可能会有区间

  • x{4,4}x{4}一样。
  • colou{0,1}r表示“匹配colourcolor
  • a{3,5}表示“匹配aaaaaaaaaaaa”。

值得注意的是优先选择更长的匹配,因为乘法器是贪婪的。如果你输入的文本是I had an aaaaawful day,该正则表达式就会在aaaaawful中匹配到aaaaa。不会在第三个a后就停止匹配。

乘法器是贪婪的,但它不会忽略一个更好的匹配。如果你的输入文本为I had an aaawful daaaaay,之后这个正则表达式会在第一次的匹配中于aaawful找到aaa。只有在你说“给我找到另一个匹配”的时候,它才会继续搜索然后在daaaaay中找到aaaaa

乘法器区间可能是开区间:

  • a{1,}表示“在一列中找到一个或多个a”。然而你的乘法器将会是贪婪的。在找到第一个a后,它将会尽可能匹配到更多的a
  • .{0,}表示“匹配任何情形”。不管你的输入文本是什么——甚至为空——这个正则表达式都会匹配整个字符串然后返回给你。

练习

编写一个能匹配双引号字符串的正则表达式。同时该字符串可以拥有任意数量的字符。

用你已经学到的之时,修改上面的正则表达式,来找到了双引号字符串,但它们之间没有多余的双引号。

###乘法器补充

?代表的含义与{0,1}相同。比如说,colou?r表示“匹配colourcolor”。

*等于{0,}。比如说,.*表示“匹配一切”,跟上面提到的一样。

+等于{1,}。比如说,\w+表示“匹配一个单词”。这里的“单词”是1个或多个“单词字符”的序列,就像_varAccountName1

这些乘法器都很常见,你必须掌握。还有:

  • \?\*\+表示“匹配一个问号,接着找到一个星号,然后跟着一个加号”。
  • [?*+]表示“找到一个问号或者一个星号或者一个加号”。

练习

简化下面的正则表达式:

  • ".{0,}""[^"]{0,}"
  • x?x?x?
  • y*y*
  • z+z+z+z+

练习

编写一个表达式来查找非单词字符分隔的两个单词。如果改为三个单词或者六个单词又该怎么写?

###惰性(Non-greed)

正则表达式".*"表示“找到一个双引号,接着找到尽可能多的字符,最后再找到一个双引号”。注意一下被.*匹配的内部字符,很可能包含多个双引号。这通常不是非常有用。

乘法器可通过追加问号来实现惰性。这里对优先顺序进行了反转:

  • \d{4,5}?表示“匹配\d\d\d\d\d\d\d\d\d”。其实跟\d{4}行为一致。
  • colou??r就是colou{0,1}?r,表示“找到colorcolour”。和colou?r行为一致。
  • ".*?"表示“匹配一个双引号,跟着一个尽可能少的字符,再跟着一个双引号”。这个不像上面两个例子,实际上很有用。

###分支(Alternation)

你可以使用管道符号来实现匹配多种选择:

  • cat|dog表示“匹配catdog”。
  • red|blue|red||blue以及|red|blue都是同样的意思,“匹配redblue或空字符串”。
  • a|b|c[abc]一样。
  • cat|dog|\|表示“匹配catdog管道符号”。
  • [cat|dog]表示“找到acddgot或一个管道符号”。

练习

尽你所能简化下述正则表达式:

  • s|t|u|v|w
  • aa|ab|ba|bb
  • [abc]|[^abc]
  • [^ab]|[^bc]
  • [ab][ab][ab]?[ab]?

练习

编写一个正则表达式匹配1到31(含)之间的整数。 记住,[1-31]不是正确答案。

###组合(Grouping)

你可以使用圆括号来组合表达式:

  • 在一周中找到一天,使用(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day
  • (\w*)ility等同于\w*ility。都表示“找到以ility结尾的单词”。为什么第一种形式更有用,后面会看到…
  • \(\)表示“匹配一个左圆括号后,再匹配一个右圆括号”。
  • [()]表示“匹配一个左圆括号或一个右圆括号”。

练习

《时光机器》这本书中,使用正则表达式来查找包裹在括号中的句子。接着,修改你的答案来查找外部被括号包裹但内部没有括号的句子。

组合可能会包含空字符串:

  • (red|blue|)表示“匹配redblue空字符串”。
  • abc()def等同于abcdef

可能你会在组合中使用乘法器:

  • (red|blue)?等同于(red|blue|)
  • \w+(\s+\w+)*代表“找到一个或多个单词,它们以空格隔开”。

练习

简化\w+\W+\w+\W+\w+\w+\W+\w+\W+\w+\W+\w+\W+\w+\W+\w+

###单词边界(Word boundaries)

单词边界是一个单词字符和非单词字符之间的位置。记住,一个单词字符是\w,它是[0-9A-Za-z_],一个非单词字符是\W,也就是[^0-9A-Za-z_]

文本的开头和结尾总是当作单词边界。

输入的文本it's a cat有八个单词边界。如果我们在cat后追加一个空格,这里就会有九个单词边界。

  • 正则表达式\b表示“匹配一个单词边界”。
  • \b\w\w\w\b表示“匹配一个三个字母的单词”。
  • a\ba表示“找到a,跟着一个单词边界,接着找到b”。不管输入文本是什么,这个正则表达式永远都不会成功找到一个匹配。

单词边界不是字符。它们宽度为零.下面的正则表达式表示相同的含义:

  • (\bcat)\b
  • (\bcat\b)
  • \b(cat)\b
  • \b(cat\b)

练习

查找字典中最长的单词。

###行边界(Line boundaries)

每一块文本会分解成一个或多个行,用换行符分隔,像这样:

  • 换行
  • 换行
  • 换行

注意文本不是以换行符结束,而是以行结束。然而,任何行,包括最后一行,可以包含零个字符。

起始行位置是在一个换行符和下一行的第一个字符之间。与单词边界一样,在文本的开头也算作一个起始的行。

结束行位置是在行的最后一个字符和换行符之间。与单词边界一样,文本结束也算作行结束。

所以我们都细分为:

  • 起始行,行,结束行
  • 换行
  • 开始行,行,结束行
  • 换行
  • 换行
  • 开始行,行,结束行

在此基础上,有:

  • 正则表达式^表示“匹配开始行”。
  • 正则表达式$表示“匹配结束行”。
  • ^$表示“匹配空行”。
  • ^.*$将会匹配整个文本,因为换行符是一个字符,所以.会匹配它。为了匹配单行,要使用惰性乘法器,^.*?$
  • \^\$表示“匹配尖符号后跟着一个美元符号”。
  • [$]表示“匹配一个美元符”。然而[^]是非法单正则表达式。要记住的是尖符号在方括号中时有不同的特殊含义。把尖符号放在字符类中,这么用[\^]

像单词边界一样,行边界也不是字符。它们宽度为零。下面的正则表达式表示相同的含义:

  • (^cat)$
  • (^cat$)
  • ^(cat)$
  • ^(cat$)

练习

适用正则表达式查找《时光机器》中最长的一行。

###文本边界(Text boundaries)

很多实现提供一个标记,通过改变它来改变^$的含义。从“行开始”和“行结束”变成“文本开始”和“文本结束”。

其它的一些实现提供单独的元字符\A\z来达到这个目的。

##捕获和替换

这里就是正则表达式开始变得异常强大的地方。

###捕获组

你已经知道,括号是用来表示组。它们也可以用来捕获子串。如果正则表达式是一个很小的电脑程序,这个捕获组就是它的输出(的一部分)。

正则表达式(\w*)ility表示“找到一个以ility结束的单词”。捕获组1就是匹配了部分内容的\w*。举个例子,如果我们的文本包含单词accessibility,捕获组1就是accessib。如果我们的文本自身只包含ility,捕获组1就是空字符串。

你可以拥有多个捕获组,它们甚至可以嵌套使用。捕获组从左到右进行编号。只要计算左圆括号。

假设我们到正则表达式是(\w+) had a ((\w+) \w+)。如果我们的输入文本是I had a nice day,那么

  • 捕获组1是I
  • 捕获组2是nice day
  • 捕获组3是nice

在一些实现中,你可能可以访问捕获组0,即完整匹配:I had a nice day

是的,这确实意味着圆括号有些重复。一些实现就提供了一个独立语法来声明“非捕获组”,但是这个语法不符合标准,所以这里我们不涉及。

从一个成功返回的匹配中捕获组数量总是等于原来正则表达式中捕获组的数量。记住这一点,因为它可以帮助你理解一些令人困惑的情形。

正则表达式((cat)|dog)表示“匹配catdog”。这里总是存在两组捕获组。如果我们的输入文本是dog,那么捕获组1是dog,捕获组2是空字符串,因为另一个选择未被使用。

正则表达式a(\w)*表示“匹配一个以a开头的单词”。这里总是只有一个捕获组(译者注:除去捕获组0)

  • 如果输入文本是a,捕获组1是空字符串。
  • 如果输入文本是ad,捕获组1是d
  • 如果输入文本是avocado,捕获组1是v。然而,捕获组0会是整个单词,avocado

###替换

一旦你用了正则表达式来查找字符串,你可以指定另一个字符串来替换它。第二个字符串时替换表达式。首先,就像:

  • 传统的替换对话框
  • Java的String.replace()函数
  • PHP的String.replace()函数
  • 等等

练习

使用r替换《时间机器》中所有的元音字母。确保使用正确的大小写!

然而,你可以在你的替换表达式中引用捕获组。这是你可以在替换表达式唯一能的特殊的事,它是令人难以置信的强大,因为它意味着你不必完全销毁你刚刚发现的东西。

比方说,你尝试去用ISO 8691格式的日期(YYYY-MM-DD)去替换美式日期(MM/DD/YY)。

  • 通过正则表达式(\d\d)/(\d\d)/(\d\d)开始。注意这里有三个捕获组:月,日和两个数字表示的年。
  • 通过使用一个反斜杆和一个捕获组号来引用一个捕获组。所以,你的替换表达式为20\3-\1-\2
  • 如果我们的输入文本是03/04/05(表示 3月4号,2005年),那么
    • 捕获组1是03
    • 捕获组2是04
    • 捕获组3是05
    • 替换字符串为2005-03-04

你可以在替换表达式中多次引用捕获组。

  • 使用正则表达式([aeiou])和替换表达式\1\1来让元音翻倍。

在替换表达式中的反斜杆必须进行转义。举个例子,你有一些在计算机程序的字面值中使用的文本。那就意味着你需要在普通文本中的每个双引号或者反斜杆前放置一个反斜杆。

  • 正则表达式([\\"])中,捕获组1是双引号或者反斜杆。
  • 替换表达式\\\1中,一个字面值反斜杆后跟着一个匹配的双引号或者反斜杆。

###后向引用(Back-references)

你可以在同样的表达式中引用同一个捕获组。这称为后向引用

举个例子,再次调用前面的表达式[abc]{2}表示“匹配aaabac or babbbccacbcc”。但是表达式([abc])\1表示“匹配aabbcc”。

练习

在字典中,找到出现两次相同字符串的最长的单词(比如papacoco)。

##结合正则表达式编程

一些具体的注意事项:

###过度反斜线综合征(Excessive backslash syndrome)

在一些编程语言中,如Java,对于含有正则表达式的字符串没有提供特别的支持。字符串有自己的转义规则,这些规则与正则表达式的转义规则叠加,通常会导致反斜杆过多(overload)。比如(还是Java):

  • 为了匹配一个数字,正则表达式\d在源代码中变成String re = "\\d;"
  • 为了匹配一个双引号字符串,"[^"]*"变成String re = "\"[^\"]*\"";
  • 为了匹配一个反斜杆或者一个左方括号或者一个又方括号,正则表达式[\\\[\]]变成String re = "[\\\\\\[\\]]";
  • String re = "\\s";String re = "[ \t\r\n]";是一样的。注意不同的转义“优先级”。

在其它编程语言里,通过一个特殊标记来标识正则表达式,通常是正斜杆/。这里有一些JavaScript例子:

  • 为了匹配一个数字,\d变成var regExp = /\d/;
  • 匹配一个反斜杆或者一个左方括号或者一个右方括号,var regExp = /[\\\[\]]/;
  • var regExp = /\s/;var regExp = /[ \t\r\n]/;一样。
  • 当然,这意味着必须对正斜杠而不是双引号进行转义。匹配URL的前面部分:var regExp = /https?:\/\//;

基于这一点,我希望你明白为什么我对你反复提及反斜杆。

###偏移量(Offsets)

在文本编辑器中,会在你光标所在处开始搜索。这个编辑器会向前开始搜索文字,然后停在第一个匹配的地方。下一次搜索会在第一次完成搜索的地方的右侧开始。

当编程的时候,文本的偏移量是必须的。这个偏移量会在代码中有明确的支持,或保存在包含文本的对象中(如Perl),或包含正则表达式的对象中(如JavaScirpt)。(在Java里,这是一个由正则表达式和复合对象的字符串。)在任何情况下,默认值为0,表示文本的开始。搜索后,偏移量会自动更新,或者作为输出的一部分返回。

无论什么情况,通常很容易去使用循环来解决这个问题。

注意。正则表达式匹配空字符串是完全可能的。 你可以立马实现的一个简单的例子是a{0}在这种情况下,新的偏移量等于旧偏移量,从而导致死循环。

一些实现可能保护你避免发生这些情况,但要查下对应的文档。

###动态正则表达式

动态地构造一个正则表达式字符串时一定要小心。如果你使用的字符串不是固定的,那么它可能包含意想不到的元字符。这会导致语法错误。更糟糕的是,它可能产生一个语法正确,但行为不可预期的正则表达式。

有bug的Java代码:

String sep = System.getProperty("file.separator");
String[] directories = filePath.split(sep);

这个bug就是:String.split()认为sep是一个正则表达式。但是在Windows下,sep是由犯斜杆组成的字符串"\\".这不是一个语法正确的正则表达式。结果是:一个异常PatternSyntaxException

任何一个优秀的编程语言都提供了一种机制,用以转义在一个字符串中出现的所有元字符。在Java中,你可以这么做:

String sep = System.getProperty("file.separator");
String[] directories = filePath.split(Pattern.quote(sep));

###循环内的正则表达式

把正则表达式字符串编译进一个正在运行的“程序”中是一个代价昂贵的操作。如果你能避免在循环内这么做的话能提高程序性能。

##各类建议

###输入验证

正则表达式能用于用户输入验证。但过于严格的验证会让用户感到难受。下面举几个例子:

支付卡号

我在网页上输入我的卡号如1234 5678 8765 4321。会被这个站点拒绝。因为它使用\d{16}来进行验证。

该正则表达式允许出现空格和连字符。

其实,为什么不直接去掉所有非数字字符,然后再进行验证?要做到这一点,使用正则表达式\D和空字符串来替换表达式。

练习

编写一个正则表达式,可以验证我的卡号而不用让我删去非数字字符。

名字

不要使用正则表达式来验证用户的名字。其实,不需要验证名字,你无能无力。

Falsehoods programmers believe about names提到了:

  • 名字不能包含空格。
  • 名字不能包含标点符号。
  • 名字只能使用ASCII字符。
  • 名字会被限制在任何特定的字符集。
  • 名字总是有像M字符那么长。
  • 人总是有且只有一个用的名字。
  • 人总是有且仅有一个中间名。
  • 人总是有且只有一个姓。

邮件地址

不要使用正则表达式来验证邮件地址。

首先,这很难保证正确无误。电子邮件地址确实符合一个正则表达式,但是这个表达式长又复杂地让人联想到世界末日。任何缩略都会可能产生遗漏(false negatives)。(你知道吗?电子邮件地址可以包含注释!)

其次,即使所提供的电子邮件地址符合正则表达式,但也并不能证明它的存在。验证电子邮件地址的唯一方法是发送电子邮件给它。

###标记

在正式的应用中,不要使用正则表达式来解析HTML或XML。解析HTML/XML是

  1. 不可能使用简单的正则
  2. 一般来说很难
  3. 一个已解决了的问题。

不妨找一个已有的解析库来为你搞定这些工作。

##这就是55分钟内容

总结:

  • 字面值:a b c d 1 2 3 4等等。
  • 字符类:. [abc] [a-z] \d \w \s
    • .表示“任何字符”
    • \d表示“一个数字”
    • \w表示“一个单词字符”,[0-9A-Za-z_]
    • \s表示“一个空格,tab,回车或一个换行符”
    • 否定字符类:[^abc] \D \W \S
  • 乘法器:{4} {3,16} {1,} ? * +
    • ?表示“没有或一个”
    • *表示“没有或多个”
    • +表示“一个或多个”
    • 乘法器是贪婪的除非你在之后使用?
  • 分支和组合:(Septem|Octo|Novem|Decem)ber
  • 词、行和文本边界:\b ^ $ \A \z
  • 反向捕获组:\1 \2 \3等等。(在替换表达式和匹配表达式中同时生效)
  • 元字符列表:. \ [ ] { } ? * + | ( ) ^ $
  • 字符类中使用到元字符列表:[ ] \ - ^
  • 你总是可以使用反斜杆对元字符进行转义:\

###感谢阅读
正则表达式无处不在,令人难以置信的有用。那些在编辑文本和写电脑程序方面将花费大量时间的人们应该学会如何使用它们。
到目前为止,我们只接触了冰山一角。

练习

继续阅读你选择的正则表达式实现的对应文档。我保证在我们这里所讨论的部分之外还有更多的特性并未涉及。

安装Homebrew

安装wxWidgets之前,推荐使用Homebrew管理软件包(当然,如果你熟悉MacPorts的话,也可以用它安装wxWidgets)。

在终端中键入以下命令:

ruby -e "$(curl -fsSL https://raw.github.com/Homebrew/homebrew/go/install)"

安装wxWidgets

搞定Homebrew,现在安装wxWidgets库了。
输入:

brew install wxwidgets
//或者brew install wxmac

Homebrew将会自动为你下载好os x环境下的wxWidgets,免去编译源码的步骤。安装路径为/usr/local/Cellar

接着输入:

wx-config --version

终端输出3.0.0说明安装成功。

配置xCode

我们下面要配置xCode中项目的Build Settings

###配置Linking

在终端中输入:

wx-config --libs

返回:

-L/usr/local/Cellar/wxmac/3.0.0.0/lib   -framework IOKit -framework Carbon -framework Cocoa -framework AudioToolbox -framework System -framework OpenGL -framework QuickTime -lwx_osx_cocoau-3.0 

将上面内容填入Linking下的Other Linker Flags

###配置Custom Compiler Flags

在终端中输入:

wx-config --cxxflags

返回:

-I/usr/local/Cellar/wxmac/3.0.0.0/lib/wx/include/osx_cocoa-unicode-3.0 -I/usr/local/Cellar/wxmac/3.0.0.0/include/wx-3.0 -D_FILE_OFFSET_BITS=64 -DwxDEBUG_LEVEL=0 -DWXUSINGDLL -D__WXMAC__ -D__WXOSX__ -D__WXOSX_COCOA__ 

如果输入命令后,终端没有上述内容,请输入cd切换到主目录,再输入上述命令。

将输入内容填入Custom Compiler Flags下的Other C++ Flags

至此,你可以引入#inlcude <wx/wx.h>,编写你的wxWidgets程序了!

Uploadify作为一个比较常用的页面上传控件,最近也是在上面遇到了一个问题。即使用了该控件的页面在IE6下刷新会出现下图错误:

调查结果是因为IE6下该控件的Object未能销毁的缘故引起的页面报错document.getElementById("SWFUpload_0").SetReturnValue("<undefined/>");和内存飙高。所以解决办法就是可以在每次刷新页面前进行对象释放。由于dwz也使用了该控件,我们可以在每次进行标签操作时进行该动作,在dwz.navTab.js文件的_switchTab函数加入如下语句:

_switchTab: function(iTabIndex){
    for(var i=SWFUpload.movieCount; i>=0; i--) { // 手动释放SWFUpload对象,修正IE6下报错问题
        if(SWFUpload.instances["SWFUpload_"+i]){
            SWFUpload.instances["SWFUpload_"+i].destroy();
        }
    }
    // ... 
},

参考资料:
(1)http://blog.csdn.net/zhichao2001/article/details/9468023
(2)http://my.opera.com/justnewbee/blog/fix-swfupload-destroy-ie-js-error

去年写了一个地图组件worldMap-js ,因为用JS实现的,众所周知,IE6-8对JS的执行效率实在让人捉急,而国内浏览器中IE占用率又是个大头,所以萌生了用Flash重现实现这个组件的念头,使用Flex编写,目前还不完善,大体实现了地图的绘制和提示信息的展示,绘制下级地图的功能还没完成,同时MapData目前只有中国地图。

以下是Demo演示:

To view this page ensure that Adobe Flash Player version 11.4.0 or greater is installed.

你可以在这里找到源码:worldMap-flex

需求描述

在实际开发中,我们常常需要应对多类环境,针对不同的环境来更改相应的配置,比如常见开发环境、测试环境以及客户的实际部署环境。

因此我们会遇到下面的场景,在开发环境中数据源的获取方式是直连数据库,部署环境中需要连接的是JNDI,如何避免项目打包完每次人工更改配置文件的繁琐工作呢?

解决方案

通过引入Spring和Maven的profile特性来实现不同环境自动切换不同配置的功能。

###声明Spring profile

1、 定义两个beans,分别对应两个环境下的数据源配置:在Spring的配置文件applicationContext.xml中定义两个profile的beans。

<beans profile="development">
        <bean id="dataSource"  class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName">
            <value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
        </property>
        <property name="url">
            <value>jdbc:sqlserver://x.x.x.x:1433;databaseName=trunk</value>
        </property>
        <property name="username">
            <value>sa</value>
        </property>
        <property name="password">
            <value>123</value>
        </property>
        <property name="maxActive">
            <value>200</value>
        </property>
        <property name="maxIdle">
            <value>30</value>
        </property>
        <property name="maxWait">
            <value>600</value>
        </property>
    </bean>
</beans>

<beans profile="production">
     <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
        <property name="jndiName">
             <value>java:comp/env/jdbc/test</value>
        </property>
    </bean>        
</beans>

###更改web.xml

那么根据Spring的profile特性,我们只要在web.xml文件中定义如下形式配置:

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>development</param-value>
</context-param>

<context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>production</param-value>
</context-param>

就会启用相应的profile配置,Spring根据指定的配置来注入依赖。

###为spring.profiles.active赋值

那么如何在打包时自动更改spring.profiles.active的值呢?这就需要Maven的profile特性。

我们将上述spring.profiles.active的param-value的值更改为{profiles.active},这是Maven的属性值替换的占位符,Maven的资源过滤插件(Maven Resources plugin)将会在构建期间替换该值,为了在打包时启用资源过滤,需要我们配置Maven打包插件(Maven war plugin):

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.4</version>
        <configuration>
            <warName>${project.artifactId}</warName>

                <!-- 激活spring profile -->
                <webResources>
                    <resource>
                        <filtering>true</filtering>
                        <directory>src/main/webapp</directory>
                        <includes>
                            <include>**/web.xml</include>
                        </includes>
                        </resource>
                </webResources>
                <warSourceDirectory>src/main/webapp</warSourceDirectory>
                <webXml>src/main/webapp/WEB-INF/web.xml</webXml>

        </configuration>
</plugin>

###启用Maven profile

最后在项目的pom.xml或m2文件夹下的setting.xml中指定Maven的profile:

<!-- 不同的打包环境 -->
<profiles>
    <!-- 开发环境,默认激活 -->
    <profile>
        <id>development</id>
        <properties>
            <profiles.active>development</profiles.active>
        </properties>

    </profile>

    <!-- 部署环境 -->
    <profile>
        <id>production</id>
        <properties>
            <profiles.active>production</profiles.active>
        </properties>
        <activation>
            <!-- 默认启用的是dev环境配置 -->
            <activeByDefault>true</activeByDefault>
        </activation>
    </profile>
</profiles>

根据上述配置,输入打包命令mvn clean package默认启用的是production,数据源由JNDI提供,如果需要激活另一个profile,只要更改打包命令为mvn clean package -P development

组件版本

替换步骤

###样式文件替换

定位dwz的themes/css下的core.css文件,在184行~216行中注释JTree样式。

提取zTree的样式文件zTreeStyle.css(位于css/zTreeStyle)及图标资源img文件夹。前者代码置于dwz的core.css的底部以防止dwz预定义的样式破坏zTree的样式。同时将img文件夹与core.css放在同一个文件夹下(themes/css/)。

###脚本文件替换与修改

找到DWZ的js文件夹下的dwz.tree.js文件,用zTree的js文件jquery.ztree.all-3.5.js中的内容替换(如果不需要zTree的excheck + exedit扩展,可以使用jquery.ztree.core-3.5.js)。

脚本内容替换完成后,需要修改dwz.ui.jsdwz.tree.js(现在它的脚本语句是zTree的):

  • dwz.ui.js

注释掉77行的调用jTree语句。

$("ul.tree", $p).jTree();
  • dwz.tree.js

1、树节点添加DWZ中的rel属性(添加DWZ中的external属性同理):

找到makeDOMNodeNameBefore方法(当前版本位于1112行),在html.push方法后添加:

if (node.rel) {
    html.push("' rel='", node.rel);
}

2、更改节点的默认Target为navTab

找到makeNodeTarget方法(当前版本位于1178行),return语句修改为return (node.target || "navTab");

3、修改节点属性更新方法updateNode(当前版本位于1639行),追加view.setNodeRel(this.setting, node)

setNodeRel方法定义如下:

setNodeRel: function(setting, node) {
        var aObj = $("#" + node.tId + consts.id.A),
        rel = treeNode.rel;
        if (rel == null || rel.length == 0) {
            aObj.removeAttr("rel");
        } else {
            aObj.attr("rel", rel);
        }
    }

调用方式

原先的jTree调用方式

<ul class="tree treeFolder">
    <li>
        <a href="tabsPage.html" target="navTab">主框架面板</a>
        <ul>
            <li><a href="main.html" target="navTab" rel="main">我的主页</a></li>
            <li><a href="http://www.baidu.com" target="navTab" rel="page1">页面一(外部页面)</a></li>
            <li><a href="demo_page2.html" target="navTab" rel="external" external="true">iframe navTab页面</a></li>
        </ul>
    </li>
</ul>

现在的zTree调用方式:
将上述树DOM内容更改为

<div class="zTreeDemoBackground left">
    <ul id="treeDemo" class="ztree"></ul>
</div>

添加如下调用脚本:

var setting = {
        data: {
            simpleData: {
                enable: true
            }
        }
};

var zNodes =[
        { id:1, pId:0, name:"主框架面板", url:"tabsPage.html", target:"navTab", open:true},
        { id:2, pId:1, name:"我的主页", url:"main.html", target:"navTab", rel:"main"},
        { id:3, pId:1, name:"iframe navTab页面", url:"demo_page2.html", target:"navTab", rel:"external", external:"true"}
    ];

    $(document).ready(function(){
        $.fn.zTree.init($("#treeDemo"), setting, zNodes);
    });

注意:原先超链接中href属性名要更改为url。

数据的存储位置会影响其读取速度,这个问题对于JavaScript来说相对简单,因为它只有四种基本的数据存储位置:

  • 直接量
  • 变量
  • 数组元素
  • 对象成员

一般来说,使用直接量和局部变量的访问速度快于数组项和对象成员的访问速度(除非浏览器内部刻意优化)。

管理作用域

###作用域链和标识符解析

每一个JavaScript函数都是Function对象的一个实例。而该对象拥有可编程访问的属性和不能通过代码访问的内部属性(仅供JavaScript引擎存取)。内部属性中的Scope包含的一个函数被创建的作用域中对象的集合称为函数的作用域链。

执行函数时会创建一个运行期上下文(execution context)的内部对象,每个运行期上下文对象都有自己的作用域链用于标识符解析。正是解析标识符时在作用域链中的搜索过程影响了性能。

###标识符解析的性能

一个标识符在运行期上下文的作用域链中位置越深,它的读写速度就越慢。因为全局变量总是存在于运行期上下文作用域链的最末端,因此读写全局变量较读写局部变量来说是慢的。

###改变作用域链

可通过with语句或try-catch语句中的catch子句临时改变作用域链。

###动态作用域

动态作用域只存在于代码执行过程中,因此无法通过静态分析(查看代码结构)检测出来。with语句、try-catch中的catch子句以及包含eval()的函数,都被认为是动态作用域。

###闭包,作用域和内存

当闭包被创建时,它的Scope属性初始化为上下文中的活动对象和全局对象。

当闭包被执行时,它的’Scope’属性中对了一个为闭包自身所创建的活动对象。所以要访问的标识符存在于作用域链第一个对象之后的位置,访问时导致性能损失。

对象成员

###原型

JavaScript中的对象是基于原型(Prototype)的。原型定义并实现了一个新对象必须包含的成员列表,并为所有对象实例所共享。

对象可以有两种成员类型:实例成员(存在于对象实例中)和原型成员(由对象原型集成而来)。

可以使用hasOwnProperty()方法判断对象是否包含特定的实例成员(只搜索原型)。

使用in操作符判断对象是否包含特定的属性(既搜索实例也搜索原型)。

###原型链

对象的原型决定了实例的原型(所有的对象都是Object的实例)。

访问对象成员(属性(非函数类型成员)或方法(函数类型成员))的过程就是先搜索对象的实例成员,再遍历原型链。对象成员的深度决定了访问它所需的时间。

###嵌套成员

对象成员嵌套越深,访问速度就会越慢。如访问速度:

location.href > widow.location.href > window.location.href.toString()

###缓存成员对象值

在同一个函数中没有必要多次访问同一个对象成员,除非它的值发生了更改。如果需要多次查找成员变量,可以使用局部变量来进行缓存。但这种方法并不适用于使用this来进行判断执行上下文的对象方法。因为把一个方法保存在局部变量中会导致this绑定到window。

小结

  • 访问直接量和局部变量比访问数组元素和对象成员快。
  • 变量在作用域链中的位置越深,访问所需时间越长。属性或方法在原型链中的位置越深,访问它的速度也越慢。
  • 由于局部变量存在于作用域链的起始位置使得访问局部变量比访问跨作用域变量快。
  • 由于全局变量总处在作用域链的最末端,因此访问速度最慢。
  • 避免使用with语句和try-catch语句中的catch子句,因为它们会改变运行期上下文作用域链。
  • 尽量少用影响性能的嵌套的对象成员。
  • 可以把常用的对象成员、数组元素、跨域变量保存在局部变量中来改善JavaScript性能。

配置环境

首先下载最新版的Lucene分发包(如:lucene-4.2.1.tgz),并解压到工作目录,找到以下四个jar包:

core\lucene-core-{version}.jar
queryparser\lucene-queryparser-{version}.jar
analysis\common\lucene-analyzers-common-{version}.jar
demo\lucene-demo-{version}.jar

将上述jar文件放入某个目录(如:D:\lucene)并配置到CLASSPATH中,追加环境变量中的CLASSPATH内容为

;D:\lucene\lucene-core-4.2.1.jar;D:\lucene\lucene-queryparser-4.2.1.jar;D:\lucene\lucene-analyzers-common-4.2.1.jar;D:\lucene\lucene-demo-4.2.1.jar;

运行例子

确保你的CLASSPATH设置无误之后,我们下面使用演示例子创建索引文件。在控制台中键入以下命令:

cd D:\lucene
java org.apache.lucene.demo.IndexFiles -docs {path-to-lucene}/src

这将会对lucene的源码目录创建索引,并将索引文件放在D:\lucene下的index目录中。

之后我们根据创建的索引文件来搜索文件,键入:

java org.apache.lucene.demo.SearchFiles

程序将提示我们输入要查询的字符(Enter query:),输入string回车,将会返回给我们十个匹配结果,输入n回车,会再返回十个结果,输入q退出。

源码解析

下面我们将浏览一遍这个lucene命令行例子,分解一下它的函数以便于理解如何在我们的程序中应用Lucene。
IndexFiles.java是创建索引的代码。
SearchFiles.java是根据索引来检索的代码。

###索引文件

main()函数接受命令行中的参数,之后通过构造一个Directory对象,并实例化StandardAnalyzerIndexWriterConfig作为实例化IndexWriter的准备工作。

命令行中的-index参数接收的是存放索引文件的目录名称。如果在相对路径下用-index参数或者没有-index参数的情况下(将会使用默认的索引文件夹名index)调用IndexFiles,这个索引文件夹将会在当前工作目录下作为子目录创建(如果还没创建的话)。但在有一些平台中,这个索引文件夹的创建路径可能不同(比如会在用户的主目录中创建)。

-docs参数接收的是存放那些要进行创建索引的目标文件的文件夹路径。

-update参数告诉IndexWriter类:不要删除已经存在的索引文件。如果没给出-update参数,IndexFiles将会在创建新的索引文件前清空旧索引。

IndexFiles中使用的Directory类是为了在索引中储存信息。另外这个类的一个实现FSDirectory,它有三个子类分别实现向内存、数据库等等其它介质中写入索引。

Analyzer类也称为将文本拆分为可被索引的词元(tokens)的处理管道。它会对这些词元中的词(Term)进行一些可选操作,比如大小写转换,插入同义词以及过滤不想要的词元等等。我们这里使用的词法分析器是StandardAnalyzer,它使用在Unicode Standard Annex #29中指定的Unicode Text Segmentation算法作为单词拆分规则,接着将词元转换成小写形式,然后过滤出停词(stopword)。停词指的是一般语言中的冠词(a,an,the,etc。)和那些没有检索价值的词元。值得注意的是不同的语言有不同的拆分规则。目前Lucene针对不同的语言提供了许多分析器

IndexWriterConfig的实例保存了IndexWriter类的配置。比如说,我们根据-update参数设置了OpenMode。

往下看,在IndexWriter实例后面,你可以看到indexDocs()代码。这个递归函数爬取指定目录下的所有文件并为它们建立Document对象。Document对象是表示相应文件的文本内容,创建时间以及位置等信息的简单数据对象。这些实例被添加到IndexWriter中。如果给定了-update参数,IndexWriterConfig OpenMode将被设置为OpenMode.CREATE_OR_APPEND,代表IndexWriter将对已经拥有相同标识符(在上述例子中,文件路径作为该标识符)的已建立索引的文档的索引进行更新操作,不存在则创建。反之,不给定-update 参数将表示删除先前已索引的文档,在目录中创建新索引。

###搜索文件

SearchFiles类很简单。它主要通过和IndexSearcherStandardAnalyzer(在IndexFiles中也用到了)以及QueryParser协作。查询语句解析器是由分析器来构建的,和之前文档解析的方式一样用于解析你的查询语句:搜寻单词边界,大小写转换以及移除那些像 ‘a’,‘an’,和‘the’ 的无用单词。Query对象包含了传给扫描器的QuqeryParser的结果。需要注意的是,以编程方式去构造一个富Query对象而不是用查询语句解析器是可行的。因为查询语句解析器仅仅是能够将Lucene query syntax解码为相应的Query对象。

参考文档

http://lucene.apache.org/core/4_2_1/demo/overview-summary.html