在Cetia4 REST 后继的讨论中又列出了下面两个不错的 REST 框架:
XX Framework (see www.xxframework.org)
http://www.restlet.org/ 这是个老牌的 REST 框架,这个框架的设计目的不是完全面向Web Application的,学习曲线比较高。
restlet 和 cetia4 的最大区别是 restlet 不是基于 java servlet 的,而后者是。当然 restlet 也可以在 servlet 容器中运行
I look, touch and think
在Cetia4 REST 后继的讨论中又列出了下面两个不错的 REST 框架:
XX Framework (see www.xxframework.org)
http://www.restlet.org/ 这是个老牌的 REST 框架,这个框架的设计目的不是完全面向Web Application的,学习曲线比较高。
restlet 和 cetia4 的最大区别是 restlet 不是基于 java servlet 的,而后者是。当然 restlet 也可以在 servlet 容器中运行
conn sys/oracle@xmlindex as sysdba
grant plustrace to xdb;
set autot trace
set autot off
1.thin:
jdbc:oracle:thin:@xedo 其中xedo在tns中configuration
jdbc:oracle:thin:@//152.69.90.48:1521/orcl
2.oci:
jdbc:oracle:oci8:@xedo 其中xedo在tns中configuration
jdbc:oracle:oci8:@//152.69.90.48:1521/orcl
3.sqlplus
sqlplus xedo/xedo@xedo 其中xedo在tns中configuration
1.建立一个目录别名
create DIRECTORY 'tmpdir' AS '/tmp';
GRANT READ ON DIRECTORY bfile_dir1 TO scott;
2.
建立一个含有bfile字段的表
create table bfiletest(id number(3), fname bfile);
建立一个含有BLOB字段的表
create table blobtest(id number(3),ablob blob);
3.插入数据
INSERT INTO bfiletest VALUES (1, BFILENAME ('XMLDIR', 'tmptest'));
4.bfile转blob
CREATE OR REPLACE PROCEDURE loadLOBFromBFILE_proc(
TID IN NUMBER,rfilename in varchar2,upmessage out varchar2)
AS
Dest_loc BLOB;
Src_loc BFILE;
BEGIN
INSERT INTO BLOBTEST(ID,ABLOB) VALUES(TID,EMPTY_BLOB()) RETURN ABLOB INTO DEST_LOC;
Src_loc := BFILENAME('XMLDIR',rfilename);
IF (DBMS_LOB.FILEEXISTS(Src_loc) != 0)
THEN
DBMS_LOB.OPEN(Src_loc, DBMS_LOB.LOB_READONLY);
DBMS_LOB.OPEN(Dest_loc, DBMS_LOB.LOB_READWRITE);
DBMS_LOB.LOADFROMFILE(Dest_loc, Src_loc,DBMS_LOB.GETLENGTH(Src_loc));
DBMS_LOB.CLOSE(Dest_loc);
DBMS_LOB.CLOSE(Src_loc);
COMMIT;
upmessage := '0';
END IF;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
upmessage := '操作失败';
END;
5.bfile转clob
create or replace function getDocument(filename varchar2)
return CLOB deterministic
is
file bfile := bfilename('XMLDIR',filename);
charContent CLOB := ' ';
targetFile bfile;
warning number;
begin
targetFile := file;
DBMS_LOB.fileopen(targetFile, DBMS_LOB.file_readonly);
DBMS_LOB.loadfromFile(charContent,targetFile, DBMS_LOB.getLength(targetFile),1,1);
DBMS_LOB.fileclose(targetFile);
return charContent;
end;
/
6.Select到LOB字段的定位器
对某一LOB字段进行选择,则返回的不是LOB的值,而是该LOB字段的定位器
DELCARE
AUDIO_INFO BLOB;
BENGIN
SELECT ablob INTO AUDIO_INFO FROM blobtest WHERE id=100;
END;
1.运行远程控制
如果是本机安装,运行xhost +x
如果是远程控制,运行export DISPLAY=152.69.90.92:0.0
测试运行xeyes,xclock,gnome-session,应该出现图形界面
1.4.创建用户和组
# su root
# /usr/sbin/groupadd oinstall
# /usr/sbin/groupadd dba
# /usr/sbin/useradd -g oinstall -G dba oracle
# passwd oracle
如果存在oracle用户: /usr/sbin/userdel oracle
可以通过查看或直接修改/etc/passwd和/etc/group来看是否生效
su oracle
unzip 10201_database_linux32.zip
使用oracle用户upload和unzip有好处,因为oracle将拥有10201_database_linux32.zip的访问限制
如果没有权限:
chown -R oracle:dba database (optional)
1.7 安装路径设置
推荐安装在/home/oracle下面,因为oracle对该目录有访问权限,无须其它操作,如果想换地方,例如:
创建安装目录 mkdir /opt/oracle
注意如果待安装目录dir对于oracle用户而言没有权限,可以修改目录权限,使用如下命令:chown -R oracle:dba /opt/oracle (optional)
这样oracle就拥有了/opt/oracle的访问权限
2.修改核心参数
su root
vi /etc/sysctl.conf
kernel.shmall = 2097152
kernel.shmmax = 2147483648
kernel.shmmni = 4096
kernel.sem = 250 32000 100 128
fs.file-max = 65536
net.ipv4.ip_local_port_range = 1024 65000
net.core.rmem_default=262144
net.core.wmem_default=262144
net.core.rmem_max=262144
net.core.wmem_max=262144
命令操作来使我们所做的变更生效
/sbin/sysctl -p
vi /home/oracle/.bash_profile
添加如下内容,你的具体值应该不会和这个完全相同.
export ORACLE_BASE=/home/oracle
export ORACLE_HOME=$ORACLE_BASE/product/10.2.0/db_1
export ORACLE_SID=ORCL
export PATH=$PATH:$HOME/bin:$ORACLE_HOME/bin
export LD_LIBRARY_PATH=$ORACLE_HOME/lib:/usr/lib
export LC_CTYPE=en_US.UTF-8
su - oracle
在安装文件目录下找到runInstaller文件
./runInstaller
之后就会出现安装向导,sid最好用小写,安装前把domain改成有意义的域名
用默认值下一步下一步就可以了
注意上面开始的检测提示,如果有不对的地方要退出进行修正,我在安装的时候就遇到glibc的版本不对,这时应该下载安装需要的版本进行升级,rpm命令详见rpm文章。
在Oracle数据库中,用户的权限分为两种(在这里我们不讨论dba或dbopr的权限,只考虑普通用户的权限),分别是System Privilege系统权限 和User Table Privilege用户数据表权限.
1.首先,创建用户,以下几条命令可以创建一个用户,前提是必须以DBA的身份登录(如果你不是DBA,不要看下去了):
create user DB_USER identified by DB_USER_PW '创建用户DB_USER,密码为DB_USER_PW
grant create session to DB_USER '给用户创建会话的权限
grant resource to DB_USER
2.当用户建立后,会自动在Oracle数据库系统中生成属于该用户的Scheme (可以理解为所有属于该用户的表,视图....等对象的集合).
该用户可以将对这些对象的访问权限赋予其它的系统用户.
3.该用户用sqlplus登录后,以下命令可以看到该用户的权限(该部分取自于CNOUG网站):
本用户读取其他用户对象的权限:
select * from user_tab_privs;
本用户所拥有的系统权限:
select * from user_sys_privs;
drop user username cascade;
create user username identified by my_password;
grant create any directory, drop any directory to username;
grant connect, resource to username;
grant dba to username;
grant all on emp to username; --把emp用户的权限都给username
ALTER USER scott ACCOUNT LOCK -- lock a user account
ALTER USER scott ACCOUNT UNLOCK; -- unlocks a locked users account
ALTER USER scott PASSWORD EXPIRE; -- Force user to choose a new password
ALTER USER <username> IDENTIFIED BY <new_password> --改密码
在Oracle9i和10g中,一个新的文件被引入spfile,spfile用于服务器端,管理初始化参数。
在9i以前,Oracle使用pfile存储初始化参数设置,这些参数在实例启动时被读取,任何修改需要重起实例才能生效;使用spfile你可以使用ALTER SYSTEM或者ALTER SESSION来动态修改那些可动态修改的参数,所有更改可以立即生效
1.pfile和spfile 的系统缺省目录
Unix: $ORACLE_HOME/dbs; NT: $ORACLE_HOME\database
Oralce在缺省目录中搜索参数文件,先找spfile,再找pfile
2.创建SPFILE(spfile${ORACLE_SID}.ora)
SQL> CREATE SPFILE='E:\ora9i\database\SPFILEEYGLE.ORA' FROM PFILE='E:\ora9i\admin\eygle\pfile\init.ora';
2.创建pfile(init${ORACLE_SID}.ora)
SQL> CREATE SPFILE='E:\ora9i\database\SPFILEEYGLE.ORA' FROM PFILE='E:\ora9i\admin\eygle\pfile\init.ora';
3.使用pfile启动数据库
SQL> startup pfile='E:\Oracle\admin\eyglen\pfile\init.ora';
4.使用spfile启动数据库
你不能以同样的方式指定spfile,但是可以创建一个包含spfile参数的pfile文件,指向spfile.
我们修改PFILE文件内容如下:
#Pfile link to SPFILE
SPFILE= 'E:\Oracle\Ora9iR2\database\SPFILEEYGLEN.ORA'
log_archive_start = false
4.查看当前使用的spfile
SQL> SHOW parameter spfile
结果value列返回空值,那么说明你在使用pfile
5.导出SPFILE文件后手动编辑
SQL> create pfile='c:\initeyglen.ora' from spfile;
SQL> SHUTDOWN immediate
手动编辑c:\initeyglen.ora
SQL>STARTUP pfile='e:\initeyglen.ora'
SQL> CREATE spfile FROM pfile='e:\initeyglen.ora';
SQL> STARTUP
6.在线修改系统参数
ALTER SYSTEM SET {parameter}=TRUE SCOPE=BOTH;
其中{parameter}可以是select name from v$parameter;的其中一个name值
SCOPE参数有三个可选值:MEMORY(只改变当前实例运行) 、SPFILE (只改变SPFILE的设置)、BOTH(改变实例及SPFILE)。
设立封锁机制主要是为了对并发操作进行控制,对干扰进行封锁,保证数据的一致性和准确性
数据库锁方式有三种:共享锁,独占锁,共享更新锁
共享锁SHARE
特点:可在一个表上共存多个
效果: 所有用户只可查询
独占锁EXCLUSIVE
特点:在一个表上只能单独存在,在用户执行DML语句INSERT、UPDATE、DELETE时隐含获得
效果: 独占的用户可查可更新,其他用户只可查
共享更新锁(行锁)
特点:对一个表的一行或多行进行封锁
效果: 查询也可以更新被封锁的数据行,其它用户只能查询但不能更新被封锁的数据行.
释放锁: 执行COMMIT或ROLLBACK语句,退出数据库
ORACLE里锁有以下几种模式:
0:none
1:null 空
2:Row-S 行共享(RS):共享表锁
3:Row-X 行专用(RX):用于行的修改
4:Share 共享锁(S):阻止其他DML操作
5:S/Row-X 共享行专用(SRX):阻止其他事务操作
6:exclusive 专用(X):独立访问使用
数字越大锁级别越高, 影响的操作越多。
一般的查询语句如select ... from ... ;是小于2的锁, 有时会在v$locked_object出现。
select ... from ... for update; 是2的锁。
当对话使用for update子串打开一个游标时,
所有返回集中的数据行都将处于行级(Row-X)独占式锁定,
其他对象只能查询这些数据行,不能进行update、delete或select...for update操作。
insert / update / delete ... ; 是3的锁。
没有commit之前插入同样的一条记录会没有反应,
因为后一个3的锁会一直等待上一个3的锁, 我们必须释放掉上一个才能继续工作。
创建索引的时候也会产生3,4级别的锁。
locked_mode为2,3,4不影响DML(insert,delete,update,select)操作,
但DDL(alter,drop等)操作会提示ora-00054错误。
有主外键约束时 update / delete ... ; 可能会产生4,5的锁。
DDL语句时是6的锁。
1.查看当前数据库里锁的情况
select s.username,s.sid,s.serial#,object_name from v$locked_object l,v$session s ,dba_objects o
where l.session_id=s.sid and o.object_id=l.object_id order by s.logon_time;
select /*+ RULE */ ls.osuser os_user_name, ls.username user_name,
decode(ls.type, 'RW', 'Row wait enqueue lock', 'TM', 'DML enqueue lock', 'TX',
'Transaction enqueue lock', 'UL', 'User supplied lock') lock_type,
o.object_name object, decode(ls.lmode, 1, null, 2, 'Row Share', 3,
'Row Exclusive', 4, 'Share', 5, 'Share Row Exclusive', 6, 'Exclusive', null)
lock_mode, o.owner, ls.sid, ls.serial# serial_num, ls.id1, ls.id2
from sys.dba_objects o, ( select s.osuser, s.username, l.type,
l.lmode, s.sid, s.serial#, l.id1, l.id2 from v$session s,
v$lock l where s.sid = l.sid ) ls where o.object_id = ls.id1 and o.owner
<> 'SYS' order by o.owner, o.object_name
select *from v$lock;
2.杀掉不正常的锁
alter system kill session 'sid,serial#';
select * from all_directories;
create or replace directory XMLDIR as '/home/oracle/rain'
drop directory XMLDIR
conn / as sysdba
grant read on directory XMLDIR to public;
grant write on directory XMLDIR to public;
--从OS文件系统中读入文件到CLOB
create or replace function getDocument(filename varchar2)
return CLOB deterministic
is
file bfile := bfilename('XMLDIR',filename);
charContent CLOB := ' ';
targetFile bfile;
warning number;
begin
targetFile := file;
DBMS_LOB.fileopen(targetFile, DBMS_LOB.file_readonly);
DBMS_LOB.loadfromFile(charContent,targetFile, DBMS_LOB.getLength(targetFile),1,1);
DBMS_LOB.fileclose(targetFile);
return charContent;
end;
/
create or replace function getFileContent(file bfile)
return CLOB deterministic
is
charContent CLOB := ' ';
targetFile bfile;
warning number;
begin
targetFile := file;
DBMS_LOB.fileopen(targetFile, DBMS_LOB.file_readonly);
DBMS_LOB.loadfromFile(charContent,targetFile,
DBMS_LOB.getLength(targetFile),1,1);
DBMS_LOB.fileclose(targetFile);
return charContent;
end;
/
drop user username cascade;
create user username identified by my_password;
grant create any directory, drop any directory to username;
grant connect, resource to username;
grant dba to username;
grant all on emp to username; --把emp用户的权限都给username
ALTER USER scott ACCOUNT LOCK -- lock a user account
ALTER USER scott ACCOUNT UNLOCK; -- unlocks a locked users account
ALTER USER scott PASSWORD EXPIRE; -- Force user to choose a new password
ALTER USER <username> IDENTIFIED BY <new_password> --改密码
1.VNC 客户端
下载一个vnc-3.3.7-x86_win32_viewer.exe
2.在Linux上启动VNC服务
/>vncserver
然后设置vnc登陆密码
3.使用客户端访问
RedHat AS中预装的是vsftpd
vsftpd 是一个基于GPL发布的类Unix类操作系统上运行的服务器的名字(是一种守护进程),它可以运行在诸如Linux、BSD、Solaris、HP-UX 以及IRIX上面。它支持很多其他传统的FTP服务器所不支持的特征。它具有如下特点:非常高的安全性、带宽限制、良好的扩展性、支持创建虚拟用户、支持IPv6、支持虚拟IP、高速、稳定。
启动vsftpd目前我知道一种方法:
在GNome中 开始->系统->服务设置->服务
在服务中选中vsftpd,点击启动便可以了
rpm -ivh xorg-version.rpm // 安装
rpm -ivh --replacepkgs xorg-version.rpm // 强行重新安装
rpm -ivh --nodeps xorg-version.rpm // 强行安装
rpm -q xorg // 查询
rpm -e xorg // 删除
rpm -Uvh xorg-version.rpm // 升级安装
zip all.zip *.jpg
这条命令是将所有.jpg的文件压缩成一个zip包
unzip all.zip
这条命令是将all.zip中的所有文件解压出来
tar -cf all.tar *.jpg
这条命令是将所有.jpg的文件打成一个名为all.tar的包。-c是表示产生新的包,-f指定包的文件名。
tar -rf all.tar *.gif
这条命令是将所有.gif的文件增加到all.tar的包里面去。-r是表示增加文件的意思。
tar -uf all.tar logo.gif
这条命令是更新原来tar包all.tar中logo.gif文件,-u是表示更新文件的意思。
tar -tf all.tar
这条命令是列出all.tar包中所有文件,-t是列出文件的意思
tar -xf all.tar
这条命令是解出all.tar包中所有文件,-t是解开的意思
tar调用gzip
# tar -czf all.tar.gz *.jpg
这条命令是将所有.jpg的文件打成一个tar包,并且将其用gzip压缩,生成一个gzip压缩过的包,包名为all.tar.gz
# tar -xzf all.tar.gz
这条命令是将上面产生的包解开。
tar调用bzip2
# tar -cjf all.tar.bz2 *.jpg
这条命令是将所有.jpg的文件打成一个tar包,并且将其用bzip2压缩,生成一个bzip2压缩过的包,包名为all.tar.bz2
# tar -xjf all.tar.bz2
这条命令是将上面产生的包解开。
tar调用compress
# tar -cZf all.tar.Z *.jpg
这条命令是将所有.jpg的文件打成一个tar包,并且将其用compress压缩,生成一个uncompress压缩过的包,包名为all.tar.Z
# tar -xZf all.tar.Z
这条命令是将上面产生的包解开
对于.tar结尾的文件
tar -xf all.tar
对于.gz结尾的文件
gzip -d all.gz
gunzip all.gz
对于.tgz或.tar.gz结尾的文件
tar -xzf all.tar.gz
tar -xzf all.tgz
对于.bz2结尾的文件
bzip2 -d all.bz2
bunzip2 all.bz2
对于tar.bz2结尾的文件
tar -xjf all.tar.bz2
对于.Z结尾的文件
uncompress all.Z
对于.tar.Z结尾的文件
tar -xZf all.tar.z
Rar文件
# rar a all *.jpg
这条命令是将所有.jpg的文件压缩成一个rar包,名为all.rar,该程序会将.rar 扩展名将自动附加到包名后。
# unrar e all.rar
这条命令是将all.rar中的所有文件解压出来
procedure:
1.enter /usr/src/linux
2.make mrproper
3.make dep
4.make clean
5.make bzImage
6.make modules
7.make module_install
8.make install
1.delete the line
dd
2.copy
Y
3.exit and save
:wq
4.exit and not save
:q!
5.import mode
i
6.find forward
/string
7.find backward
?stirng
8.copy line
yy
9.copy the position where the cursor is
p
10.replace
:s/old/new/g
基本命令
C-x C-c : 退出Emacs
C-x C-f : 打开一个文件,如果文件不存在,则创建一个文件
C-g : 取消未完成的命令
编辑
C-z (redefined): Undo;原来C-z是挂起Emacs(然后用fg命令调出);C-x u 是默认的命令; 移动一下光标,再C-z就可以redo
M-d : 删除光标后的词语
移动光标
C-v : 向前翻页
M-v : 向后翻页
M-r : 将光标移动到屏幕中间那行
C-a : 移到行首
M-a : 移到句首,从行首到句首之间可能有空格
C-e : 移到行尾
M-e : 移到句尾
M-{ : 向上移动一段
M-} : 向下移动一段
C-right : 向前移动一个单词
C-left : 向后移动一个单词
C-up : 向前移动一段
C-down : 向后移动一段
M-< : 移到整个文本开头
M-> : 移到整个文本末尾
C-u 数字 命令 : 执行多次(数字表示次数)该命令;“M-数字 命令” 也可以
M-x goto-line : 移动到某一行
C-l : 重绘屏幕,效果就是当前编辑行移动窗口中央
Buffer 相关
C-x k : 关闭当前buffer
C-x b : 切换到前一个编辑的buffer
C-x C-b : 列出当前所有buffer
C-x C-s : 保存当前buffer
C-x s : 保存所有未保存的buffer,会提示你是否需要保存
C-x C-w : 文件另存为
拷贝与粘贴
M-space (redefined): 设置mark; C-@ 是默认命令
C-w (redefined) : 剪切一块区域;如果没有设置mark,则是剪切一行
M-w (redefined) : 拷贝一块区域;如果没有设置mark, 则是拷贝一行
C-k : 从当前位置剪切到行尾
C-y : 粘贴
M-y : 用C-y拉回最近被除去的文本后,换成 M-y可以拉回以前被除去的文本。键入多次的M-y可以拉回更早以前被除去的文本。
C-x r k : 执行矩形区域的剪切
C-x r y : 执行矩形区域的粘贴
窗口操作
C-x 0 : 关闭当前窗口
C-x 1 : 将当前窗口最大化
C-x 2 : 垂直分割窗口
C-x 3 : 水平分割窗口
M-o (redefined) : 在窗口之间切换; C-x o 是默认命令
C-x 5 1/2/3/0 : 对frame类似的操作
C-x < : 窗口内容右卷
C-x > : 窗口内容左卷(这两个命令在垂直分割窗口后比较有用)
(C-u) C-x ^ : 加高当前窗口,如果有C-u,则每次加高4行
(C-u) C-x } : 加宽当前窗口
(C-u) C-x { : 压窄当前窗口
ESC C-v : 在其它窗口进行卷屏操作
搜索和替换
C-s : 向前搜索(增量式搜索);连续C-s,跳到下一个搜索到的目标
C-s RET : 普通搜索
C-r : 向前搜索
C-s RET C-w : 按单词查询
M-% : 查询替换,也就是替换前会询问一下
M-x replace-string : 普通替换
Tags
M-! etags .c .h : 创建TAGS文件
M-. : 跳到tag所在位置
M-x list-tags : 列出tags
Bookmark
C-x r m : 设置书签bookmark
C-x r b : 跳到bookmark处
帮助
C-h ? : 查看帮助信息
C-h f : 查看一个函数
C-h v : 查看一个变量
C-h k : 查看一个键绑定 (C-h c 也是查看键绑定,但是信息较简略)
C-h C-f : 查看一个函数的Info,非常有用
C-h i : 看Info
其它
C-M-\ : 对选中区域,按照某种格式(比如C程序)进行格式化
C-x h : 全部选中
M-! : 执行外部shell命令
M-x shell : 模拟shell的buffer
M-x term : 模拟terminal, C-c k 关闭terminal
C-x C-q : 修改buffer的只读属性
1、安装Xmanager或XWin32软件。
2、启动telnet登陆到待远程控制的主机
1)在用户的目录下找到文件.bash_profile或profile,用vi对其进行编辑。加入下列命令行:
DISPLAY=192.168.0.11:0.0;export DISPLAY
2)保存,退出。
3)如果只想临时在客户端用一下图形界面,无需进行第4、5步,直接在xterm界面的命令行中输入:
export DISPLAY=192.168.0.11:0.0
注:192.168.0.11是你本机IP
3、然后就可以在命令行中运行图形界面程序了,测试。
运行xeyes,应该出现图形界面。
4、redhat下运行gnome-session,启动图形用户界面
if you want to run jdev in the current directory
./jdev& : if the shell is closed, the jdev application will be closed followed by the shell being closed.
nohup ./jdev : if the shell is closed, the jdev application won't be closed followed by the shell being closed.
Samba: http://www.e2web.cn/temp/0005/20066142358339294.htm
mount -t smbfs -o username=abc,password=abc //lily/download /mnt/download
Cifs : sudo mount -t cifs //10.182.112.107/public /mnt/rhost/arctic/pub -o uid=1000,gid=1000,username=spsz,password=showmethemoney,codepage=utf8,iocharset=utf8
本文主要談一下密碼學中的加密和數字簽名,以及其在java中如何進行使用。對密碼學有興趣的夥伴,推薦看Bruce Schneier的著作:Applied Crypotography。在jdk1.5的發行版本中安全性方面有了很大的改進,也提供了對RSA算法的直接支持,現在我們從實例入手解決問題(本文僅是作爲簡單介紹):
一、密碼學上常用的概念
1)消息摘要:
這是一種與消息認證碼結合使用以確保消息完整性的技術。主要使用單向散列函數算法,可用于檢驗消息的完整性,和通過散列密碼直接以文本形式保存等,目前廣泛使用的算法有MD4、MD5、SHA-1,jdk1.5對上面都提供了支持,在java中進行消息摘要很簡單, java.security.MessageDigest提供了一個簡易的操作方法:
/**
*MessageDigestExample.java
*Copyright 2005-2-16
*/
import java.security.MessageDigest;
/**
*單一的消息摘要算法,不使用密碼.可以用來對明文消息(如:密碼)隱藏保存
*/
public class MessageDigestExample{
public static void main(String[] args) throws Exception{
if(args.length!=1){
System.err.println("Usage:java MessageDigestExample text");
System.exit(1);
}
byte[] plainText=args[0].getBytes("UTF8");
//使用getInstance("算法")來獲得消息摘要,這裏使用SHA-1的160位算法
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
System.out.println("\n"+messageDigest.getProvider().getInfo());
//開始使用算法
messageDigest.update(plainText);
System.out.println("\nDigest:");
//輸出算法運算結果
System.out.println(new String(messageDigest.digest(),"UTF8"));
}
}
還可以通過消息認證碼來進行加密實現,javax.crypto.Mac提供了一個解決方案,有興趣者可以參考相關API文檔,本文只是簡單介紹什麽是摘要算法。
2)私鑰加密:
消息摘要只能檢查消息的完整性,但是單向的,對明文消息並不能加密,要加密明文的消息的話,就要使用其他的算法,要確保機密性,我們需要使用私鑰密碼術來交換私有消息。
這種最好理解,使用對稱算法。比如:A用一個密鑰對一個文件加密,而B讀取這個文件的話,則需要和A一樣的密鑰,雙方共享一個私鑰(而在web環境下,私鑰在傳遞時容易被偵聽):
使用私鑰加密的話,首先需要一個密鑰,可用javax.crypto.KeyGenerator産生一個密鑰 (java.security.Key),然後傳遞給一個加密工具(javax.crypto.Cipher),該工具再使用相應的算法來進行加密,主要對稱算法有:DES(實際密鑰只用到56位),AES(支持三種密鑰長度:128、192、256位),通常首先128位,其他的還有DESede等, jdk1.5種也提供了對對稱算法的支持,以下例子使用AES算法來加密:
/**
*PrivateExmaple.java
*Copyright 2005-2-16
*/
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import java.security.Key;
/**
*私?加密,保證消息機密性
*/
public class PrivateExample{
public static void main(String[] args) throws Exception{
if(args.length!=1){
System.err.println("Usage:java PrivateExample ");
System.exit(1);
}
byte[] plainText=args[0].getBytes("UTF8");
//通過KeyGenerator形成一個key
System.out.println("\nStart generate AES key");
KeyGenerator keyGen=KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key=keyGen.generateKey();
System.out.println("Finish generating DES key");
//獲得一個私?加密類Cipher,ECB是加密方式,PKCS5Padding是填充方法
Cipher cipher=Cipher.getInstance("AES/ECB/PKCS5Padding");
System.out.println("\n"+cipher.getProvider().getInfo());
//使用私?加密
System.out.println("\nStart encryption:");
cipher.init(Cipher.ENCRYPT_MODE,key);
byte[] cipherText=cipher.doFinal(plainText);
System.out.println("Finish encryption:");
System.out.println(new String(cipherText,"UTF8"));
System.out.println("\nStart decryption:");
cipher.init(Cipher.DECRYPT_MODE,key);
byte[] newPlainText=cipher.doFinal(cipherText);
System.out.println("Finish decryption:");
System.out.println(new String(newPlainText,"UTF8"));
}
}
3)公鑰加密:
上面提到,私鑰加密需要一個共享的密鑰,那麽如何傳遞密鑰呢?web環境下,直接傳遞的話很容易被偵聽到,幸好有了公鑰加密的出現。公鑰加密也叫不對稱加密,不對稱算法使用一對密鑰對,一個公鑰,一個私鑰,使用公鑰加密的數據,只有私鑰能解開(可用于加密);同時,使用私鑰加密的數據,只有公鑰能解開(簽名)。但是速度很慢(比私鑰加密慢100到1000倍),公鑰的主要算法有RSA,還包括Blowfish,Diffie-Helman 等,jdk1.5種提供了對RSA的支持,是一個改進的地方:
/**
*PublicExample.java
*Copyright 2005-2-16
*/
import java.security.Key;
import javax.crypto.Cipher;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
/**
*一個簡單的公?加密例子,Cipher類使用KeyPairGenerator生成的公?和私?
*/
public class PublicExample{
public static void main(String[] args) throws Exception{
if(args.length!=1){
System.err.println("Usage:java PublicExample ");
System.exit(1);
}
byte[] plainText=args[0].getBytes("UTF8");
//構成一個RSA密鑰
System.out.println("\nStart generating RSA key");
KeyPairGenerator keyGen=KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair key=keyGen.generateKeyPair();
System.out.println("Finish generating RSA key");
//獲得一個RSA的Cipher類,使用公?加密
Cipher cipher=Cipher.getInstance("RSA/ECB/PKCS1Padding");
System.out.println("\n"+cipher.getProvider().getInfo());
System.out.println("\nStart encryption");
cipher.init(Cipher.ENCRYPT_MODE,key.getPublic());
byte[] cipherText=cipher.doFinal(plainText);
System.out.println("Finish encryption:");
System.out.println(new String(cipherText,"UTF8"));
//使用私?解密
System.out.println("\nStart decryption");
cipher.init(Cipher.DECRYPT_MODE,key.getPrivate());
byte[] newPlainText=cipher.doFinal(cipherText);
System.out.println("Finish decryption:");
System.out.println(new String(newPlainText,"UTF8"));
}
}
4)數字簽名:
數字簽名,它是確定交換消息的通信方身份的第一個級別。上面A通過使用公鑰加密數據後發給B,B利用私鑰解密就得到了需要的數據,問題來了,由于都是使用公鑰加密,那麽如何檢驗是A發過來的消息呢?上面也提到了一點,私鑰是唯一的,那麽A就可以利用A自己的私鑰進行加密,然後B再利用A的公鑰來解密,就可以了;數字簽名的原理就基于此,而通常爲了證明發送數據的真實性,通過利用消息摘要獲得簡短的消息內容,然後再利用私鑰進行加密散列數據和消息一起發送。java中爲數字簽名提供了良好的支持,java.security.Signature類提供了消息簽名:
/**
*DigitalSignature2Example.java
*Copyright 2005-2-16
*/
import java.security.Signature;
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.security.SignatureException;
/**
*數字簽名,使用RSA私鑰對對消息摘要簽名,然後使用公?驗證 測試
*/
public class DigitalSignature2Example{
public static void main(String[] args) throws Exception{
if(args.length!=1){
System.err.println("Usage:java DigitalSignature2Example ");
System.exit(1);
}
byte[] plainText=args[0].getBytes("UTF8");
//形成RSA公鑰對
System.out.println("\nStart generating RSA key");
KeyPairGenerator keyGen=KeyPairGenerator.getInstance("RSA");
keyGen.initialize(1024);
KeyPair key=keyGen.generateKeyPair();
System.out.println("Finish generating RSA key");
//使用私?簽名
Signature sig=Signature.getInstance("SHA1WithRSA");
sig.initSign(key.getPrivate());
sig.update(plainText);
byte[] signature=sig.sign();
System.out.println(sig.getProvider().getInfo());
System.out.println("\nSignature:");
System.out.println(new String(signature,"UTF8"));
//使用公?驗證
System.out.println("\nStart signature verification");
sig.initVerify(key.getPublic());
sig.update(plainText);
try{
if(sig.verify(signature)){
System.out.println("Signature verified");
}else System.out.println("Signature failed");
}catch(SignatureException e){
System.out.println("Signature failed");
}
}
}
5)數字證書。
還有個問題,就是公鑰問題,A用私鑰加密了,那麽B接受到消息後,用A提供的公鑰解密;那麽現在有個討厭的C,他把消息攔截了,然後用自己的私鑰加密,同時把自己的公鑰發給B,並告訴B,那是A的公鑰,結果....,這時候就需要一個中間機構出來說話了(相信權威,我是正確的),就出現了 Certificate Authority(也即CA),有名的CA機構有Verisign等,目前數字認證的工業標准是:CCITT的X.509:
數字證書:它將一個身份標識連同公鑰一起進行封裝,並由稱爲認證中心或 CA 的第三方進行數字簽名。
密鑰庫:java平台爲你提供了密鑰庫,用作密鑰和證書的資源庫。從物理上講,密鑰庫是缺省名稱爲 .keystore 的文件(有一個選項使它成爲加密文件)。密鑰和證書可以擁有名稱(稱爲別名),每個別名都由唯一的密碼保護。密鑰庫本身也受密碼保護;您可以選擇讓每個別名密碼與主密鑰庫密碼匹配。
使用工具keytool,我們來做一件自我認證的事情吧(相信我的認證):
1、創建密鑰庫keytool -genkey -v -alias feiUserKey -keyalg RSA 默認在自己的home目錄下(windows系統是c:\documents and settings\<你的用戶名> 目錄下的.keystore文件),創建我們用 RSA 算法生成別名爲 feiUserKey 的自簽名的證書,如果使用了-keystore mm 就在當前目錄下創建一個密鑰庫mm文件來保存密鑰和證書。
2、查看證書:keytool -list 列舉了密鑰庫的所有的證書
也可以在dos下輸入keytool -help查看幫助。
二、JAR的簽名
我們已經學會了怎樣創建自己的證書了,現在可以開始了解怎樣對JAR文件簽名,JAR文件在Java中相當于 ZIP 文件,允許將多個 Java 類文件打包到一個具有 .jar 擴展名的文件中,然後可以對這個jar文件進行數字簽名,以證實其來源和真實性。該 JAR 文件的接收方可以根據發送方的簽名決定是否信任該代碼,並可以確信該內容在接收之前沒有被篡改過。同時在部署中,可以通過在策略文件中放置訪問控制語句根據簽名者的身份分配對機器資源的訪問權。這樣,有些Applet的安全檢驗訪問就得以進行。
使用jarsigner工具可以對jar文件進行簽名:
現在假設我們有個Test.jar文件(可以使用jar命令行工具生成):
jarsigner Test.jar feiUserKey (這裏我們上面創建了該別名的證書) ,詳細信息可以輸入jarsigner查看幫助
驗證其真實性:jarsigner -verify Test.jar(注意,驗證的是jar是否被修改了,但不檢驗減少的,如果增加了新的內容,也提示,但減少的不會提示。)
使用Applet中: 然後浏覽器就會提示你:准許這個會話-拒絕-始終准許-查看證書等。
三、安全套接字層(SSL Secure Sockets Layer)和傳輸層安全性(TLS Transport Layer Security)
安全套接字層和傳輸層安全性是用于在客戶機和服務器之間構建安全的通信通道的協議。它也用來爲客戶機認證服務器,以及(不太常用的)爲服務器認證客戶機。該協議在浏覽器應用程序中比較常見,浏覽器窗口底部的鎖表明 SSL/TLS 有效:
1)當使用 SSL/TLS(通常使用 https:// URL)向站點進行請求時,從服務器向客戶機發送一個證書。客戶機使用已安裝的公共 CA 證書通過這個證書驗證服務器的身份,然後檢查 IP 名稱(機器名)與客戶機連接的機器是否匹配。
2)客戶機生成一些可以用來生成對話的私鑰(稱爲會話密鑰)的隨機信息,然後用服務器的公鑰對它加密並將它發送到服務器。服務器用自己的私鑰解密消息,然後用該隨機信息派生出和客戶機一樣的私有會話密鑰。通常在這個階段使用 RSA 公鑰算法。
3)客戶機和服務器使用私有會話密鑰和私鑰算法(通常是 RC4)進行通信。使用另一個密鑰的消息認證碼來確保消息的完整性。
java中javax.net.ssl.SSLServerSocketFactory類提供了一個很好的SSLServerSocker 的工廠類,熟悉Socket編程的讀者可以去練習。當編寫完服務器端之後,在浏覽器上輸入https://主機名:端口就會通過SSL/TLS進行通話了。注意:運行服務端的時候要帶系統環境變量運行:javax.net.ssl.keyStore=密鑰庫(創建證書時,名字應該爲主機名,比如localhost)和javax.net.ssl.keyStorePassword=你的密碼。
问题描述:
1 表单提交的数据,用request.getParameter(“xxx”)返回的字符串为乱码或者??
2 Tomcat5下直接通过url如http://localhost/a.jsp?name=中国,这样的get请求在服务端用request. getParameter(“name”)时返回的是乱码;按tomcat4的做法设置Filter也没有用或者用 request.setCharacterEncoding("GBK");也不管用
原因:
1 tomcat5的j2ee实现对表单提交即post方式提示时处理参数采用缺省的iso-8859-1来处理
2 tomcat5对get方式提交的请求对query-string 处理时采用了和post方法不一样的处理方式。(与tomcat4不一样,所以设置setCharacterEncoding(“gbk”))不起作用。
1 post方式的解决办法
1)首先所有的jsp文件都加上:
<%@ page contentType="text/html; charset=GBK"%>
2)实现一个Filter.设置处理字符集为GBK。(在tomcat的webapps/servlet-examples目录有一个完整的例子。请参考web.xml和SetCharacterEncodingFilter的配置。)
把%TOMCAT安装目录%/ webapps\servlets-examples\WEB-INF\classes\filters\SetCharacterEncodingFilter.class 文件拷到你的webapp目录/filters下,如果没有filters目录,就创建一个。
3)在你的web.xml里加入如下几行:
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2 get方式的解决办法
1) 打开tomcat的server.xml文件,找到区块,加入如下一行:
URIEncoding=”GBK”
完整的应如下:
<Connector port="80" maxThreads="150" minSpareThreads="25" maxSpareThreads="75" enableLookups="false" redirectPort="8443" acceptCount="100" debug="0" connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="GBK"/>
當 Tomcat 發現 QueryString 並沒有設定 encode 時,並非像文件中所說預設採用 ISO-8859-1 的編碼,而是用一段 fast conversion 來處理,才會造成中文問題,所以,還是必須在 Server.xml 中,加上 URLEncoding 的參數設定才行哦。
Ajax是一种提高web站点吸引力和实用性的书写web页面的方法。它从服务器端更新web页面的特殊区域,从而增强用户的交互性。它允许信息在短时间的延迟或不用刷新页面的情况下更新。
DWR减少了开发时间,也减少了一些可能的错误,这些错误是在提供常用的方法函数并消除一些与高交互性web站点有关的重复性代码的时候产生的。
DWR是作为开源软件(ASL verssion 2.0)而可以免费得到的。它凭借它的广阔的库、例子和指南非常易于实现。把它结合到一个现有的站点是非常简单的,同样它也可以简单地与大多数java框架结合。
util.js
util.js包含了一些使用的方法,从而帮助你利用j avascript(可能)从服务器端更新你的web数据。
你可以在DWR之外的地方使用它,因为它并不依赖与DWR而实现。
它包含四个页面处理函数:getValue[s]()、setValue[s]()作用于除tables、lists和images以外的大多数html元素。getText()作用于select lists。
addRows()和removeAllRows()用于编辑tables。addOptions()和removeAllOptions()用于编辑lists(如:select lists、ul、ol)。
$()
$函数(在j avascript中,他的名字是合法的)的思想是从prototype引进的。一般说来,$ = document.getElementById。在以后你花大量时间进行ajax编程的时候,在合适的地方使用这种格式是很有益的。
'$'通过给定的ID在当前HTML页面找到元素,如果多于一个的参数被提交,它就会返回一个包含已找到元素的数组。这个函数从prototype的library中得到的灵感,而且,它还能更好的工作在不同的浏览器中。
Generating Lists
DWR的一个功能可以给一个下拉列表(select list)添加选项,只需使用DWRUtil.addOptions()。
如果你在更新列表之前,希望保留一些选项,你需要写以下一些代码:
var sel = DWRUtil.getValue(id);
DWRUtil.removeAllOptions(id);
DWRUtil.addOptions(id, ...);
DWRUtil.setValue(id, sel);
如果你想有个初始化选项,如:“please select”,你可以直接使用:
DWRUtil.addOptions(id, ["Please select"]);
DWRUtil.addOptions 有5种调用方法:Array: DWRUtil.addOptions(selectid, array) 。selectid为目标ID,array为每一项的text。
Array of objects (option text = option value): DWRUtil.addOptions(selectid, data, prop) 用text和value的集合来为每一个数组元素创建一个选项,pro参数指定text和value的值。
Array of objects (with differing option text and value): DWRUtil.addOptions(selectid, array, valueprop, textprop) 用text和value的集合来为每一个数组元素创建一个选项,valueprop确定value,textprop确定text。
Object: DWRUtil.addOptions(selectid, map, reverse) 为map中每一个属性(property)创建一个选项,属性名作为选项的value,属性的value作为选项的text。这样做看起来是错的,但实际上这种做法的确是正确的。如果reverse参数被设置为true,则属性的value还是被用做选项的value。
Map of objects: DWRUtil.addOptions(selectid, map, valueprop, textprop) 为map中的每一个对象创建一个选项,valueprop指定选项的value,textprop指定选项的text。
Generating Tables
DWRUtil.addRows() 从一个数组(第二个参数)取得值,创建table的每一行。从另一个数组(第三个参数)去得值,为table的每一行创建若干列。
DWRUtil.addRows()的用法:
DWRUtil.addRows(”items”,items,cellFunctions);
第一个items,应该是table的id,第2个items,是远程返回的结果集,cellFunctions,对表格行填充的回调函数一类的。
var cellFunctions = [ function(item) { return item.name; },
function(item) { return item.description; },
function(item) { return item.formattedPrice; },
function(item) { var btn = document.createElement(”button”); btn.innerHTML = “Add to cart”; btn.itemId = item.id; btn.onclick = addToCartButtonHandler; return btn; }
应该是一个function(item)就代表一列,return的就是传入的结果集的某列的内容。那么这个表格就应该有4列。最后一列是静态的html代码了。不过没研究一下,return回去动静混合的内容,就是一个字串会如何。我猜应该也可以吧。
DWRUtil.getText(id)
可以根据id取得text的值,这个方法只能用于select list
DWRUtil.getValue(id)
可以根据id取得value,使用这个方法,你不必在意div和select list的不同。
DWRUtil.getValues()
getValues() is similar to getValue() except that the input is a Javascript object that contains name/value pairs. The names are assumed to be the IDs of HTML elements, and the values are altered to reflect the contents of those IDs. This method does not return the object in question, it alters the value that you pass to it.
这个方法和getValue()一样,只是它传入的是一个包含名字和数值的j avascript对象.这个名字就是HTML元素的ID。这个方法不会返回任何对象,它只会将ID的value映射给传入的value。例:
function doGetValues() {
var text= "{
div:null,
textarea:null,
select:null,
text:null,
password:null,
formbutton:null,
button:null
}";
var object = objectEval(text); //j avascript对象
DWRUtil.getValues(object);
var reply = DWRUtil.toDescriptiveString(object, 2); //toString
reply = reply.replace(/n/g, "<br/>"); //转意
DWRUtil.setValue("getvaluesret", reply); //显示
}
DWRUtil.onReturn
贴一段代码,暂时不理解,用onReturn和不用有什么区别
<script>
function submitFunction()
{
$("alert").style.display = "inline";
setTimeout("unsubmitFunction();", 1000);
}
function unsubmitFunction()
{
$("alert").style.display = "none";
}
</script>
<p><input type="text" onkeydown="DWRUtil.onReturn(event, submitFunction)"/>
<input type="button" onclick="submitFunction()" value="GO"/>
<span id="alert" style="display:none; background:#FFFFDD; font-weight:bold;">submitFunction called</span>
</p>
DWRUtil.selectRange
在一个input box里选一个范围
DWRUtil.selectRange("sel-test", $("start").value, $("end").value);
DWRUtil.setValue(id, value)
用ID找到元素,并更新value
DWRUtil.setValues()
和setValue(id,value)一样,只是它需要的参数是个j avascript对象,如:
DWRUtil.setValues({
div: "new div content",
password: "1234567890"
});
DWRUtil.toDescriptiveString
带debug信息的toString,第一个为将要debug的对象,第二个参数为处理等级。等级如下:
0: Single line of debug 单行调试
1: Multi-line debug that does not dig into child objects 不分析子元素的多行调试
2: Multi-line debug that digs into the 2nd layer of child objects 最多分析到第二层子元素的多行调试
And so on. Level 2 and greater probably produce too much output.
总结:DWR不但屏蔽了许多client与server交互的重复且复杂的代码,而且还提供了一些常用的方法,一些思想还是从prototype继承而来,并有一定的改进。同时,它也考虑到了与struts、hibernate、spring的结合问题。
需要注意的是,DWR是一种把服务器端的java代码通过j avascript直接从浏览器调用的方法(DWR is a way of calling Java code on the server directly from Javascript in the browser.),而不是一个j avascript的库(Generally speaking DWR is not a generic JavaScript library so it does not attempt to provide fill this need. However this is one of these really useful functions to have around if you are doing Ajax work.)能做到怎么多,已经很难得了。
DWR自04年11月草案提出到现在的Version 1.1 beta 3(2005-12-29),已经更新发布了20多次了,但愿在ajax的发展大路上,能始终看见DWR的身影。
程序代码: |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans public "-//SPRING/DTD BEAN/EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"> <value>com.mysql.jdbc.Driver</value> </property> <property name="url"> <value>jdbc:mysql://localhost:3306/TestDB</value> </property> <property name="username"> <value>root</value> </property> <property name="password"> <value></value> </property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean> <bean id="userDAO" class="onlyfun.caterpillar.UserDAO"> <property name="dataSource"> <ref bean="dataSource"/> </property> <property name="transactionManager"> <ref bean="transactionManager"/> </property> </bean> </beans> |
程序代码: |
package onlyfun.caterpillar; import javax.sql.DataSource; import org.springframework.jdbc.core.*; import org.springframework.transaction.*; import org.springframework.transaction.support.*; import org.springframework.dao.*; public class UserDAO { private DataSource dataSource; private PlatformTransactionManager transactionManager; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } public void insertUser(User user) { .... } } |
程序代码: |
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("INSERT INTO USER VALUES('Spring008', 'caterpillar', 'M', 29)"); jdbcTemplate.update("INSERT INTO USER VALUES('Spring009', 'momor', 'F', 26)"); jdbcTemplate.update("INSERT INTO USER VALUES('Spring010, 'beckyday', 'F', 35)"); } catch (DataAccessException ex) { transactionManager.rollback(status); // 也可以執行status.setRollbackOnly(); throw ex; } transactionManager.commit(status); |
程序代码: |
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); transactionTemplate.execute(new TransactionCallbackWithoutResult() { public void doInTransactionWithoutResult(TransactionStatus status) { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("INSERT INTO USER VALUES('Spring008', 'caterpillar', 'M', 29)"); jdbcTemplate.update("INSERT INTO USER VALUES('Spring009', 'momor', 'F', 26)"); jdbcTemplate.update("INSERT INTO USER VALUES('Spring010, 'beckyday', 'F', 35)"); } }); |
程序代码: |
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager); Object result = transactionTemplate.execute( new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { // 作一些操作 // 收集為結果物件 resultObject return resultObject; }); |
您可以使用JdbcTemplate的execute()方法執行SQL陳述,例如:
代碼:
jdbcTemplate.execute("CREATE TABLE USER (user_id integer, name varchar(100))");
如果是UPDATE或INSERT,您可以使用update()方法,一個基本的INSERT例子是:
代碼:
jdbcTemplate.update("INSERT INTO USER VALUES('"+ user.getId() + "', '" + user.getName() + "', '"
+ user.getSex() + "', '"
+ user.getAge() + "')");
使用UPDATE時,您可以在欄位上使用 "?",並指定參數陣列給對應的欄位,例如:
代碼:
jdbcTemplate.update("UPDATE USER SET name = ? WHERE user_id = ?", new Object[] {name, id});
在上例中所給定的name與id是String物件,它會取代之前對應順序的 "?",然後執行SQL更新。
使用參數陣列來指定SQL中的變量相當方便,上例中的INSERT範例也可以這麼作,減輕撰寫SQL與Java物件結合時的負擔:
代碼:
jdbcTemplate.update("INSERT INTO USER VALUES(?, ?, ?, ?)",
new Object[] {user.getId(), user.getName(), user.getSex(), user.getAge()});
使用JdbcTemplate進行查詢時,我們可以使用queryForXXX()等方法,例如下面使用queryForInt()方法傳回USER表格中的資料筆數:
代碼:
int count = jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER");
您也可以使用queryForObject()傳回一個查詢後的結果物件,例如下例傳回一個String物件:
代碼:
String name = (String) jdbcTemplate.queryForObject("SELECT name FROM USER WHERE user_id = ?", new Object[] {id}, java.lang.String.class);
上面兩個例子傳回的都是單一筆資料,如果傳回多筆資料,則可以使用queryForList()方法,例如:
代碼:
List rows = jdbcTemplate.queryForList("SELECT * FROM USER");
傳回的List中包括的是Map物件,每個Map物件代表查詢結果中的一筆資料,每筆資料包括多個欄位內容,要取得欄位中的值,要使用欄位名稱作為key值,例如:
代碼:
List rows = jdbcTemplate.queryForList("SELECT * FROM USER");
Iterator it = rows.iterator();
while(it.hasNext()) {
Map userMap = (Map) it.next();
System.out.print(userMap.get("user_id") + "\t");
System.out.print(userMap.get("name") + "\t");
System.out.print(userMap.get("sex") + "\t");
System.out.println(userMap.get("age") + "\t");
}
事實上,JdbcTemplate只是將我們使用JDBC的流程封裝起來而已,包括了例外捕捉、SQL的執行、查詢結果的轉換與傳回等等,之前所用到的參數設定,也只是在方法中幫您作SQL語句組合的動作而已,Spring大量使用Template Method模式來封裝固定流程的動作,XXXTemplate等類別都是基於這種方式實現的。
除了大量使用Template Method來封裝一些低層操作細節,Spring也大量使用callback方式來呼叫相關類別之方法以提供傳統JDBC相關類別的功能,使得傳統JDBC的使用者也能清楚瞭解Spring所提供的相關封裝類別方法之使用。
例如JDBC的PreparedStatement,我們可以實作PreparedStatementSetter介面來提供:
代碼:
final String id = user.getId();
final String name = user.getName();
final String sex = user.getSex() + "";
final int age = user.getAge();
jdbcTemplate.update("INSERT INTO USER VALUES(?, ?, ?, ?)",
new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, id);
ps.setString(2, name);
ps.setString(3, sex);
ps.setInt(4, age);
}
});
您可以實作RowCallbackHandler介面,在查詢到資料之後先作一些處理再傳回,例如下面的例子實現了手動的O/R Mapping:
代碼:
final User user = new User();
jdbcTemplate.query("SELECT * FROM USER WHERE user_id = ?",
new Object[] {id},
new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setId(rs.getString("user_id"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex").charAt(0));
user.setAge(rs.getInt("age"));
}
});
您更可以實作RowMapper介面將手動的O/R Mapping程式碼獨立撰寫,共用在不同的DAO方法之中:
代碼:
class UserRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int index) throws SQLException {
User user = new User();
user.setId(rs.getString("user_id"));
user.setName(rs.getString("name"));
user.setSex(rs.getString("sex").charAt(0));
user.setAge(rs.getInt("age"));
return user;
}
}
在findAllByRowMapperResultReader()裡面使用UserRowMapper:
代碼:
public List findAllByRowMapperResultReader() {
String sql = "SELECT * FROM USER";
return jdbcTemplate.query(sql, new RowMapperResultReader(new UserRowMapper()));
}
在getUser(id)裡面使用UserRowMapper:
代碼:
public User getUser(final String id) throws DataAccessException {
String sql = "SELECT * FROM USER WHERE user_id=?";
final Object[] params = new Object[] { id };
List list = jdbcTemplate.query(sql, params, new RowMapperResultReader(new UserRowMapper()));
return (User) list.get(0);
}
<c:if test="${product.price >= customer.limit}">
...
</c:if>
<c:forEach var="current">
<c:out value="Product-${current}"/>
</c:forEach>
<jsp:useBean id="customer" type="sample.Customer" scope="request"/> ...
Customer Name: <%=customer.getName()%>
...
<% if (customer.getState().equals("CO")){ %>
...
<%}%>
Customer Name: ${ customer. name}
<c:if test="${customer. state == param. state}">
...
</c:if>
${user.address.city}
${products.[product.id]}
${products.["Part-010"]}
<c:set var="city" value="${user.address.city}" default="N/A" />
<c:out value="${customer.name}" default="N/A" />
<c:if test="${user.visitCount == 1}"
Welcome back!
</c:if>
<c:choose>
<c:when test="${count == 0}">
No records matched your selection.
</c:when>
<c:otherwise>
<c:out value="${count}"/> records matched your selection.
</c:otherwise>
</c:choose>
<table>
<c:forEach var="product"
items="${products}"
varStatus="status">
<tr>
<td><c:out value="${status.count}"/></td>
<td><c:out value="${product.name}"/></td>
</tr>
</c:forEach>
</table>
<c:url=http://mysite.com/register var="myUrl">
<c:param name="name" value="${param.name}"/>
</c:url>
<a href='<c:out value="${myUrl}"/>'>Register</a>
<fmt:message key="welcome">
<fmt:param value="${visitCount}" />
<fmt:message/>
<sql:setDataSource var="datasource" driver="org.gjt.mm.mysql.driver" url="jdbc:mysql://localhost/db" />
<sql:query var="customer" datasource="${datasource}"
SELECT * FROM customers WHERE state = 'CO' ORDER BY city
</sql:query>
<table>
<c:forEach var="row" items="${customers.row}">
<tr>
<td><c:out value="${row.custName}" /></td>
<td><c:out value="${row.address}" /></td>
</tr>
</c:forEach>
</table>
<sql:transaction dataSource="${dataSource}">
<sql:update>
UPDATE account SET Balance =Balance -? WHERE accountNo = ?
<sql:param value="${transferAmount}"/>
<sql:param value="${accountFrom}"/>
</sql:update>
</sql:transaction>
<c:import url="http://oreilly.com/book?id=1234" var="xml"/>
<x:parse source="${xml}" var="bookInfo"/>
<x:out select="$bookInfo/title"/>
<x:out select="$bookInfo/author"/>
<c:import url="/books" var="xml"/>
<c:import url="/WEB-INF/xslt/bookDisplay.xsl" var="xslt"/>
<x:transform source="${xml}" xslt="${xslt}"/>
页面page:
<af:poll interval="500" id="pollid"/>
<af:progressIndicator id="progressid"
action="progressEnd" value="#{progress.progressModel}" partialTriggers="pollid" >
<af:outputFormatted styleUsage="instruction"
value="#{progress.message}"/>
</af:progressIndicator>
解释:af:poll是timer,每隔0.5秒刷新一次。af:progressIndicator是进度条,设置partialTriggers为af:poll的id,这样,poll每刷新一次就会调用progressIndicator, progressIndicator被调用时会取value指定的#{progress.progressModel},#{}是EL语言,progress是后台的managed bean,在adf-config.xml文件配置。
ManagedBean文件:
One example:
Logger logger = Logger.getLogger(SZ800BusHandler.class);
PropertyConfigurator.configure("conf/log4j.properties");
logger.debug("debug"); //the lowest level
logger.info("info");
logger.warn("warn");
logger.error("error");
logger.fatal("fatal"); //the highest level
log4j.properties:
log4j.rootLogger=info, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=log\\sinker.log
log4j.appender.logfile.MaxFileSize=512KB
# Keep three backup files.
log4j.appender.logfile.MaxBackupIndex=3
# Pattern to output: date priority [category] - message
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
note that: If you set "info" in the log4j properties, the log will write info,warn,error,fatal log. in the other words, log will record higher level's log.
connect by prior start with 经常会被用到一个表中存在递归关系的时候。比如我们经常会将一个比较复杂的目录树存储到一个表中。或者将一些部门存储到一个表中,而这些部门互相有隶属关系。这个时候你就会用到connect by prior start with。
典型的使用方法就是:
select * from table connect by prior cur_id=parent_id start with cur_id=???
例如:
a b
1 0
2 1
3 1
4 2
5 3
如果想查找a=2及其下面的所有数据,则:
select * from table connect by prior a=b start with a=2
a b
2 1
4 2
这些只是基础,皮毛。其实只要你灵活的构造查询语句。可以得出意想不到的结果。比如生成树每一个路径。
但是这些记录组成的树必须正常才可以。如果有互为父子的情况,就会出现循环错误!
WebDB cycle. WebDB is a persistent custom database that tracks every known page and relevant link. It maintains a small set of facts about each, such as the last-crawled date. WebDB is meant to exist for a long time, across many months of operation.
Since WebDB knows when each link was last fetched, it can easily generate a set of fetchlists. These lists contain every URL we're interested in downloading. WebDB splits the overall workload into several lists, one for each fetcher process. URLs are distributed almost randomly; all the links for a single domain are fetched by the same process, so it can obey politeness constraints.
The fetchers consume the fetchlists and start downloading from the Internet. The fetchers are "polite," meaning they don't overload a single site with requests, and they observe the Robots Exclusion Protocol. (This allows Web-site owners to mark parts of the site as off-limits to automated clients such as our fetcher.) Otherwise, the fetcher blindly marches down the fetchlist, writing down the resulting downloaded text.
Fetchers output WebDB updates and Web content. The updates tell WebDB about pages that have appeared or disappeared since the last fetch attempt. The Web content is used to generate the searchable index that users will actually query.
Note that the WebDB-fetch cycle is designed to repeat forever, maintaining an up-to-date image of the Web graph.
Indexing and Querying. Once we have the Web content, Nutch can get ready to process queries. The indexer uses the content to generate an inverted index of all terms and all pages. We divide the document set into a set of index segments, each of which is fed to a single searcher process.
We can thus distribute the current set of index segments over an arbitrary number of searcher processes, allowing us to scale easily with the query load. Further, we can copy an index segment to multiple machines and run a searcher over each one; that allows more good scaling behavior and reliability in case one or more of the searcher machines fail.
Each searcher also draws upon the Web content from earlier, so it can provide a cached copy of any Web page.
Finally, a pool of Web servers handle interactions with users and contact the searchers for results. Each Web server interacts with many different searchers to learn about the entire document set. In this way, the Web server is simultaneously acting as an HTTP server and a Nutch-search client.
Web servers contain very little state and can be easily reproduced to handle increased load. They need to be told only about the existing pool of searcher machines. The only state they do maintain is a list of which searcher processes are available at any time; if a given segment's searcher fails, the Web server will query a different one instead.
我们知道在Windows操作系统下文件名是不区分大小写;另外在不同的操作系统下可以用 . 来表示当前目录,或者直接只写文件名也表示默认为当前目录,例如当前目录是D:\work,那么文件D:\work\aaa.txt和.\Aaa.txt 实际上指的是磁盘上的同一个文件,但是程序怎么来判断这种情况呢?请看下面代码
/**
* 判断两个File对象是否指向同一个文件
* @throws IOException
*/
protected static void testCanonicalFile() throws IOException{
File f1 = new File("D:\work\AAA.txt");
File f2 = new File("./aaa.txt");
boolean sameFile = f1.getCanonicalFile().
equals(f2.getCanonicalFile());
System.out.println(sameFile);
}
通过getCanonicalFile方法来获取某个文件在当前操作系统下对应的目标文件,只要两个路径指向同一个文件,则两个文件对象的getCanonicalFile返回的对象一定相等,因此上面的例子打印的值是 true.
nutch中每个page就是webdb中page database的一行(row), page中含有如下代码内容:
Page 1: Version: 4
URL: http://keaton/tinysite/A.html
ID: fb8b9f0792e449cda72a9670b4ce833a
Next fetch: Thu Nov 24 11:13:35 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 1
Score: 1.0
NextScore: 1.0
其中:
The ID field is the MD5 hash of the page contents, the same contents have same MD5 hash
Nutch的工作分为两个阶段:抓取和搜索。抓取阶段取得网页并把他们处理成倒排的索引,后面的搜索阶段的工作基于这些索引来进行。
我按照Nutch Tutorial中对Intranet搜索的介绍,抓取了两个网站上的数据。生成了如下的文件结构:
Nutch的抓取系统使用Nutch的“CrawlTool(CrawTool.java)”工具以及一些列相关工具来建立和维护上面这几类数据结构,包括“web database”、一系列“segment”以及“index”。
Web 数据库(web database,或者WebDB),是用来保存抓取得Web图(Web Graph)的持久化的数据结构。只要被抓取的Web页面还存在,WebDB就会一直存在。WebDB只在抓取过程中起作用,并不参与搜索的过程。 WebDB保存两类实体:页面(page)和链接(link)。一个页面就相对于Web上的一个网页,网页的URL和内容的MD5哈希码被存储在页面中,同时,网页上的链接数、抓取信息以及计算的网页的权重(score)都被存储在页面中。而一个链接则表示两个网页之间链接。WebDB中的Web图就是由页面和链接组成,页面是节点,链接是边。
一个片断(Segment)是抓取器在一次抓取中取回和索引的网页。如上图所示,一个fetchlist是从WebDB中生成的URL列表,将由抓取器去抓取。
而索引则是系统所抓回的所有网页内容的倒排索引,是由多个片断融合而成。Nutch使用Lucene进行索引,所以所有的Lucene工具和API都可以在 Nutch生成的索引上工作。要注意的是,Lucene中也有片断(segment)这种说法,不过他和Nutch片断是不一样的。Lucene片断是 Lucene索引的一部分,而Nutch片断只是在WebDB的抓取和索引中用到,并不是最后的索引。
抓取是一个循环的过程:抓取蜘蛛从WebDB中生成了一个 fetchlist 集合;抽取工具根据fetchlist从网络上下载网页内容;蜘蛛程序根据抽取工具发现的新链接更新WebDB;然后再生成新的fetchlist;周而复始。(注:蜘蛛是分两个部分的。有一次在公司的一个讨论会上还就此争论了一番,google也是如此,以后会给出例子。)这个抓取循环在nutch中经常指: generate/fetch/update 循环。
一般来说同一域名下的 url 链接会被合成到同一个 fetchlist。这样做的考虑是:当同时使用多个蜘蛛抓取的时候,不会产生重复抓取的现象。Nutch 遵循 Robots Exclusion Protocol, 你可以用robots.txt 定义保护私有网页数据不被抓去。
上面这个抓取工具的组合是Nutch的最外层的,你也可以直接使用更底层的工具,自己组合这些底层工具的执行顺序达到同样的结果。这就是Nutch吸引人的地方吧。下面把上述过程分别详述一下,括号内就是底层工具的名字:
在创建了一个新的WebDB后,抓取循环 generate/fetch/update 就根据 最先第二步指定的根 url 在一定周期下自动循环了。当抓取循环结束后,就会生成一个最终的索引。从第7步到第10步。
需要说明的是:上面第 8 步中每个 segment 的索引都是单独建立的,之后才消重(第9步)。第10步就是大功告成,合并单独的索引到一个大索引库。
Dedup 工具可以从 segment 的索引中去除重复的url。因为 WebDB 中不允许重复的url , 也就是说 fetchlist 中不会有重复的url,所以不需要对 fetchlist 执行 dedup 操作。上文说过,默认的抓取周期是30天,如果已经生成的旧 fetch 没有删除,而又生成了新的fetch 这是还是会出现重复的url的。当只有一个抓取程序运行的时候是不会发生上述情况的。
从上面的介绍可以看出,一般情况下我们只要从头执行的程序就可以了,不需要接触底层的工具。但是搜索引擎有很多“意外”,很多的时间需要花费在维护上,所以底层的工具也是需要掌握的。我将会在下文给你演示如何运行上述过程。
开篇说过,本文是面向一个中型的搜索引擎的,如果做像百度这样的抓取互联网数据的引擎,你就需要参考下面的资源。
资源列表:
1、Nutch project page Nutch项目的大本营,想必大家都知道。
2、邮件列表: nutch-user 和 nutch-dev
CrawlTool.java文件是整个Nutch的入口, 从这里着手分析可以从宏观上掌握nutch的结构. 为了加强可读性, 如下代码进行过删减.
String db = new File(dir + "/db").getCanonicalPath();
String segments = new File(dir + "/segments").getCanonicalPath();
NutchFileSystem nfs = NutchFileSystem.parseArgs(args, 0);
// 创建一个新的WebDB
WebDBAdminTool.main(prependFileSystem(fs, nameserver, new String[] { db, "-create"}));
//把开始抓取的根Url放入WebDb
WebDBInjector.main(prependFileSystem(fs, nameserver, new String[] { db, "-urlfile", rootUrlFile }));
for (int i = 0; i < depth; i++) {
//从WebDb的新segment中生成fetchlist
FetchListTool.main(prependFileSystem(fs, nameserver, new String[] { db, segments } ));
String segment = getLatestSegment(nfs, segments);
//根据 fetchlist 列表抓取网页的内容
Fetcher.main(prependFileSystem(fs, nameserver, new String[] { "-threads", ""+threads, segment } ));
// 根据抓取回来的网页链接url更新 WebDB
UpdateDatabaseTool.main(prependFileSystem(fs, nameserver, new String[] { db, segment } ));
}
//用计算出来的网页url权重scores更新segments
UpdateSegmentsFromDb updater = new UpdateSegmentsFromDb(nfs, db, segments, dir);
updater.run();
File workDir = new File(dir, "workdir");
File[] segmentDirs = nfs.listFiles(new File(segments));
//对抓取回来的网页建立索引,每个segment的索引都是单独建立的
for (int i = 0; i < segmentDirs.length; i++) {
IndexSegment.main(prependFileSystem(fs, nameserver, new String[] { segmentDirs[i].toString(), "-dir", workDir.getPath() } ));
}
//在segment的索引中消除重复的内容和重复的url
DeleteDuplicates.main(prependFileSystem(fs, nameserver, new String[] { segments }));
//合并多个segment的索引到一个大索引,为搜索提供索引库
IndexMerger merger = new IndexMerger(nfs, segmentDirs, new File(dir + "/index"), workDir);
merger.merge();
KooXoo(http://www.kooxoo.com)的侧重点在于搜索, 提供了很多不同行业的搜索
其中有几点有特色:
1.把更新时间作为一个重要参数提供在搜索结果中, 并以为default的排序方法, 这对于有时效性的搜索很有帮助
2.地图搜索, 和mapbar合作, 可以拉框选择搜索, 很直观, 有想法
3.冒泡, 实时refresh客户查询结果
I created a contrived example with just four pages to understand the steps involved in the crawl process. Figure 1 illustrates the links between pages. C and C-dup (C-duplicate) have identical content.
Before we run the crawler, create a file called urls that contains the root URLs from which to populate the initial fetchlist. In this case, we'll start from page A.
echo 'http://keaton/tinysite/A.html' > urls
The crawl
tool uses a filter to decide which URLs go into the WebDB (in steps 2 and 5 in the breakdown of crawl
above). This can be used to restrict the crawl to URLs that match any given pattern, specified by regular expressions. Here, we just restrict the domain to the server on my intranet (keaton
), by changing the line in the configuration file conf/crawl-urlfilter.txt from
+^http://([a-z0-9]*\.)*MY.DOMAIN.NAME/
to
+^http://keaton/
Now we are ready to crawl, which we do with a single command:
bin/nutch crawl urls -dir crawl-tinysite -depth 3 >& crawl.log
The crawl uses the root URLs in urls to start the crawl, and puts the results of the crawl in the directory crawl-tinysite. The crawler logs its activity to crawl.log. The -depth
flag tells the crawler how many generate/fetch/update cycles to carry out to get full page coverage. Three is enough to reach all of the pages in this example, but for real sites it is best to start with five (the default), and increase it if you find some pages aren't being reached.
We shall now look in some detail at the data structures crawl
has produced.
The first thing to look at is the number of pages and links in the database. This is useful as a sanity check to give us some confidence that the crawler did indeed crawl the site, and how much of it. The readdb
tool parses the WebDB and displays portions of it in human-readable form. We use the -stats
option here:
bin/nutch readdb crawl-tinysite/db -stats
which displays:
Number of pages: 4
Number of links: 4
As expected, there are four pages in the WebDB (A, B, C, and C-duplicate) and four links between them. The links to Wikipedia are not in the WebDB, since they did match the pattern in the URL filter file. Both C and C-duplicate are in the WebDB since the WebDB doesn't de-duplicate pages by content, only by URL (which is why A isn't in twice). Next, we can dump all of the pages, by using a different option for readdb
:
bin/nutch readdb crawl-tinysite/db -dumppageurl
which gives:
Page 1: Version: 4
URL: http://keaton/tinysite/A.html
ID: fb8b9f0792e449cda72a9670b4ce833a
Next fetch: Thu Nov 24 11:13:35 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 1
Score: 1.0
NextScore: 1.0
Page 2: Version: 4
URL: http://keaton/tinysite/B.html
ID: 404db2bd139307b0e1b696d3a1a772b4
Next fetch: Thu Nov 24 11:13:37 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 3
Score: 1.0
NextScore: 1.0
Page 3: Version: 4
URL: http://keaton/tinysite/C-duplicate.html
ID: be7e0a5c7ad9d98dd3a518838afd5276
Next fetch: Thu Nov 24 11:13:39 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 0
Score: 1.0
NextScore: 1.0
Page 4: Version: 4
URL: http://keaton/tinysite/C.html
ID: be7e0a5c7ad9d98dd3a518838afd5276
Next fetch: Thu Nov 24 11:13:40 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 0
Score: 1.0
NextScore: 1.0
Each page appears in a separate block, with one field per line. The ID field is the MD5 hash of the page contents: note that C and C-duplicate have the same ID. There is also information about when the pages should be next fetched (which defaults to 30 days), and page scores. It is easy to dump the structure of the web graph, too:
bin/nutch readdb crawl-tinysite/db -dumplinks
which produces:
from http://keaton/tinysite/B.html
to http://keaton/tinysite/A.html
to http://keaton/tinysite/C-duplicate.html
to http://keaton/tinysite/C.html
from http://keaton/tinysite/A.html
to http://keaton/tinysite/B.html
For sites larger than a few pages, it is less useful to dump the WebDB in full using these verbose formats. The readdb
tool also supports extraction of an individual page or link by URL or MD5 hash. For example, to examine the links to page B, issue the command:
bin/nutch readdb crawl-tinysite/db -linkurl http://keaton/tinysite/B.html
to get:
Found 1 links.
Link 0: Version: 5
ID: fb8b9f0792e449cda72a9670b4ce833a
DomainID: 3625484895915226548
URL: http://keaton/tinysite/B.html
AnchorText: B
targetHasOutlink: true
Notice that the ID is the MD5 hash of the source page A.
There are other ways to inspect the WebDB. The admin
tool can produce a dump of the whole database in plain-text tabular form, with one entry per line, using the -textdump
option. This format is handy for processing with scripts. The most flexible way of reading the WebDB is through the Java interface. See the Nutch source code and API documentation for more details. A good starting point is org.apache.nutch.db.WebDBReader
, which is the Java class that implements the functionality of the readdb
tool (readdb
is actually just a synonym for org.apache.nutch.db.WebDBReader
).
使用innerHTML可以动态改变网页的样子和行为, 把下例代码建立一个html文件可以试一试:
<DIV ID="swappable" onClick="setValue()">test</DIV>
<SCRIPT LANGUAGE="JavaScript">
function setValue(){
document.getElementById("swappable").innerHTML="<input name='' type='text' value='test'><br><input name='' type='button' value='save' onClick='save()'>";
}
function save(){
document.getElementById("swappable").innerHTML="test";
}
</SCRIPT>
The crawl created three segments in timestamped subdirectories in the segments directory, one for each generate/fetch/update cycle. The segread
tool gives a useful summary of all of the segments:
bin/nutch segread -list -dir crawl-tinysite/segments/
giving the following tabular output (slightly reformatted to fit this page):
PARSED? STARTED FINISHED COUNT DIR NAME
true 20051025-12:13:35 20051025-12:13:35 1 crawl-tinysite/segments/20051025121334
true 20051025-12:13:37 20051025-12:13:37 1 crawl-tinysite/segments/20051025121337
true 20051025-12:13:39 20051025-12:13:39 2 crawl-tinysite/segments/20051025121339
TOTAL: 4 entries in 3 segments.
The PARSED?
column is always true
when using the crawl
tool. This column is useful when running fetchers with parsing turned off, to be run later as a separate process. The STARTED
and FINISHED
columns indicate the times when fetching started and finished. This information is invaluable for bigger crawls, when tracking down why crawling is taking a long time. The COUNT
column shows the number of fetched pages in the segment. The last segment, for example, has two entries, corresponding to pages C and C-duplicate.
Sometimes it is necessary to find out in more detail what is in a particular segment. This is done using the -dump
option for segread
. Here we dump the first segment (again, slightly reformatted to fit this page):
s=`ls -d crawl-tinysite/segments/* | head -1`
bin/nutch segread -dump $s
Recno:: 0
FetcherOutput::
FetchListEntry: version: 2
fetch: true
page: Version: 4
URL: http://keaton/tinysite/A.html
ID: 6cf980375ed1312a0ef1d77fd1760a3e
Next fetch: Tue Nov 01 11:13:34 GMT 2005
Retries since fetch: 0
Retry interval: 30 days
Num outlinks: 0
Score: 1.0
NextScore: 1.0
anchors: 1
anchor: A
Fetch Result:
MD5Hash: fb8b9f0792e449cda72a9670b4ce833a
ProtocolStatus: success(1), lastModified=0
FetchDate: Tue Oct 25 12:13:35 BST 2005
Content::
url: http://keaton/tinysite/A.html
base: http://keaton/tinysite/A.html
contentType: text/html
metadata: {Date=Tue, 25 Oct 2005 11:13:34 GMT, Server=Apache-Coyote/1.1,
Connection=close, Content-Type=text/html, ETag=W/"1106-1130238131000",
Last-Modified=Tue, 25 Oct 2005 11:02:11 GMT, Content-Length=1106}
Content:
Alligators live in freshwater environments such as ponds,
marshes, rivers and swamps. Although alligators have
heavy bodies and slow metabolisms, they are capable of
short bursts of speed that can exceed 30 miles per hour.
Alligators' main prey are smaller animals that they can kill
and eat with a single bite. Alligators may kill larger prey
by grabbing it and dragging it in the water to drown.
Food items that can't be eaten in one bite are either allowed
to rot or are rendered by biting and then spinning or
convulsing wildly until bite size pieces are torn off.
(From
the
Wikipedia entry for Alligator.)
ParseData::
Status: success(1,0)
Title: 'A' is for Alligator
Outlinks: 2
outlink: toUrl: http://en.wikipedia.org/wiki/Alligator
anchor: the Wikipedia entry for Alligator
outlink: toUrl: http://keaton/tinysite/B.html anchor: B
Metadata: {Date=Tue, 25 Oct 2005 11:13:34 GMT,
CharEncodingForConversion=windows-1252, Server=Apache-Coyote/1.1,
Last-Modified=Tue, 25 Oct 2005 11:02:11 GMT, ETag=W/"1106-1130238131000",
Content-Type=text/html, Connection=close, Content-Length=1106}
ParseText::
'A' is for Alligator Alligators live in freshwater environments such
as ponds, marshes, rivers and swamps. Although alligators have heavy
bodies and slow metabolisms, they are capable of short bursts of
speed that can exceed 30 miles per hour. Alligators' main prey are
smaller animals that they can kill and eat with a single bite.
Alligators may kill larger prey by grabbing it and dragging it in
the water to drown. Food items that can't be eaten in one bite are
either allowed to rot or are rendered by biting and then spinning or
convulsing wildly until bite size pieces are torn off.
(From the Wikipedia entry for Alligator .) B
There's a lot of data for each entry--remember this is just a single entry, for page A--but it breaks down into the following categories: fetch data, raw content, and parsed content. The fetch data, indicated by the FetcherOutput
section, is data gathered by the fetcher to be propagated back to the WebDB during the update part of the generate/fetch/update cycle.
The raw content, indicated by the Content
section, contains the page contents as retrieved by the fetcher, including HTTP headers and other metadata. (By default, the protocol-httpclient
plugin is used to do this work.) This content is returned when you ask Nutch search for a cached copy of the page. You can see the HTML page for page A in this example.
Finally, the raw content is parsed using an appropriate parser plugin--determined by looking at the content type and then the file extension. In this case, parse-html
was used, since the content type is text/html. The parsed content (indicated by the ParseData
and ParseText
sections) is used by the indexer to create the segment index.
1、上步国商那边有家韩国料理叫“相约烤吧”还不错价格也挺实惠。
2、华强北民间瓦罐旁有一家四川小吃店,店不大但东东挺不错价格也还可以,口水鸡好象是10元一份。
3、地王附近的工商局后面一条小巷有家湘菜馆也不错,好象叫“湖南人家”那的腊肠做的不错。
4、草原情餐厅,电话:26974816.地点:南山区中山园路188号.正宗蒙古羊肉,价格便宜。
5、南山区政府对面的富豪城,好像主要是粤菜吧,不记得了。只记得在一楼大厅吃饭,啤酒不要钱任喝。
6、南圆路加洲红对面的那家顺德蛇城喝茶挺便宜的,大、中、小都是3:00元,味道最正的是那里的烤乳鸽,又香又便宜。
7、佳和城大酒楼,中航路与深南中路交界的地方.喜欢喝酒的朋友们可以上那里去,菜也不太贵,每次下来每人20元左右。
8、八登街有一家西安菜,做的十分不错,价钱又便宜,强烈推荐:羊肉拌面,10块银子,天上绝色。
9、八登街的鱼香米坊,便宜而且最主要那二楼的环境顺德人开的,广东风味。
10、八卦三路有个明记烧鹅王还不错,新开的,送餐电话82450696
11、著名的潮州打冷,这里只提供最正宗且干净的地点,就在晒布路的深运大酒店。
12、南油酒店中午喝茶是很便宜啊,半价!还有盐插虾8元/斤,片皮鸭18元/只,但二者只能选一。
13、蛇口风华剧院那有家陕西风味不错哦~~价格巨便宜,土豆饼才3元/张,绿豆稀饭2元一大碗,撑死了别找我~~~
14、银湖车站有个金湖大酒楼,门前车总是满满的,基围虾1元一斤,炒菜9元,酒免费,但会收服务费若干,如果聚会这里也是个不错的选择,吃完后可上银湖公园遛遛。
15、振兴路自上步路口起,到华富路口止,大小食肆林立。自东向西依次是:稻香村 军分区旁边的小巴站处 有海鲜,含部分徽菜口味 中午吃饭花上18元吃一条清蒸鲈鱼,真是很不错耶。
16、醉翁亭 在稻香村后面 这是总店,徽菜口味。诚意推荐菜式:野鸭,鱼头煲,炒菜心,鸭黄豆腐,炒猪肝,炒腰花。物廉价美,尤其适合安徽,江浙一带,非常下饭,每次都吃的很饱才走。
17、巴蜀风 在燕南路和振兴路的交界处 深圳最火爆的川菜馆,其中的牛杆菌炒双椒,大漠风沙牛肉,冒菜什么的都很好吃,小心它的泡椒鱼头煲啊,我的嘴就是这样吃肿的。主食里的西红柿鸡蛋面也是不错的,就是要提前一点点,否则等好久。
18、永和大王 巴蜀风西侧 不用我说了,一般无所事事,会去小吃一顿,干净卫生。
19、家常饭 永和大王西侧 江西口味。不要点它的汤喝,反正我不喜欢。井冈豆皮,黄豆猪蹄,干焗大肠,糯米排骨或牛肉都是上好的东东。
20、乌江活鱼一条街 在巴蜀风后面的一条路上,没有路名。上面有很多家专吃鱼的店家,什么都一处啊,正宗乌江活鱼啊。四五个人进去选条三斤多的鱼,吃的很开心了。
21、外婆桥1 沿着活鱼一条街,往前走点,就可以看到一家外婆桥。这一家是专吃小吃的,没有炒菜,只有煲啊什么的。里面的蒜泥白肉和手撕饼味道很不错,糖醋排骨也是美味的, 就是没什么肉,而且我觉得做的焦了一点。
22、外婆桥2 这里外婆桥有两家,往南走一些,左拐到底就是另一家。这一家主要做炒菜,我喜欢吃它的鸭血煲,说实话,这是我吃过的最鲜美的鸭血煲。!!!三星推荐。还有,千万不要吃它的全家福,味道还可以,就是太咸。
23、潮州大碗粥 沿着振兴路往西走,过了招商银行,就有一家粥城。里面随便点吧,反正很便宜就是了。潮州的粥味道很不错的,他们用大米炒过之后才下锅,配一条红鱼,吃起来有 滋有味的。以后我会介绍更好的一个专门吃粥的地方给大家。
24、香辣蟹 过了大碗粥,拐进去就到了振兴食街,有三家做香辣蟹的。三家香辣蟹味道都不错,不过我喜欢中间那家,虽然右边那家最早味道也最好,但是中间那家态度好,吃的舒服。还可以点黄骨鱼吃,就是浑身黄色斑点的一种鱼,大小大概三个手指这么大,烫火锅吃真的很美味。
25、东北春饼店 在香辣蟹对面,进去就是死吃活吃。不过我最怕葱,蒜,菜,忌口,所以很少去。记得份量不要点太多,否则吃不完。
26、名典及热带雨林 回到振兴路,就可以看到了。是两家咖啡屋,名典里面的生果沙拉和菠萝饭我还是马虎爱吃,但是跟好的西餐比起来就差劲多了。几个朋友聚会一起,倒是不错 的地方,海阔天空的聊。热带雨林挺差劲的,虽然当初上当有了它的贵宾卡,不如不要。过了华强北,就是振兴西路了,我这里把中航路也给包括进去一起说了。
27、沿着振兴路西行,逐渐就超出单位500米圆范围了,所以就不是很熟悉了。路的中段西侧有一个南昌菜馆,吃过两次,具体吃的什么也就忘记了。但是这里是一个很重要的交界口,因为有好几家好吃的地方。
28、华神火锅那个辣哟!天!店名好像应该是华神川菜馆,但是最著名的该是它的火锅。 下了班要是晚到了,就只有等位的份。店有两层,一群人围炉来个火锅,最是惬意不过。 只不过俺也算是逐渐成长起来的一个辣子手,到了那里就只有甘拜下风。不过同样的是, 几个四川、湖南的同事同样是对开锅汤不敢下嘴,冰冻可乐倒是去的飞快。反而是一个东北妹妹,刺溜刺溜吃个利索。我们是战战兢兢等她把汤面的辣油几乎扫光了,才开始呲牙 咧嘴的吃。星级推荐:猪肝,烫火锅从来没有这么好吃过。
29、东北人 一句:翠花,上酸菜。着实把生意本来就红火的东北人,又给捧了一下。里面的姑娘小伙都是民族服装,啥民族服装,就是翠花和翠华那汉子呗。里面东西吃起来自然是大鱼大肉,吃的就是一个俗字,味道呢,个人觉得,酱骨架还是面点王的好吃。不过只要人多,随便点个什么吃,都是非常香。而且姑娘们一句一个大哥,叫得人心里暖呼呼的。
30、面点王 始终觉得面点王还是开办的很成功的,无论如何一个石油企业,竟然开办起一个风牛马不相及的饮食行业,还是心生佩服。面点王的酱骨架,水饺,鸡蛋炒面,桂圆红枣粥,都是闲来吃饭的好选择。经常会在值班的时候去打包一份回来。华强北地区有两家,一家在燕南路和振兴路交界沿着燕南路往南走20米左右,一家在红荔路和华强北交界处,友情提醒,午饭或晚饭时间,请估计好具体人数再去,否则等位。
31、家家鸡煲 在振华路和燕南路交界口往南走10米。原来有两家鸡煲店,后来家家味道好,服务也不错,就把另一家给吞并了。进去自然是吃它的传统奇香鸡煲,配上炸的金黄的腐竹,金针菇,味道很好。尤其是它的炸腐竹,是腐竹泡水后又沥干水分之后炸的,不像有些火锅店,不泡水就干炸,怎么煮都煮不开腐竹的皮,真是太没专业精神了。
32、天天渔港 振中路上,铜锣湾量贩的对面。说实话,这是我们老百姓最能体现物廉价美的海鲜吃处。环境好,服务好,海鲜味道做的也很不错。其他不说了,就推荐它的点心--菠萝包, 里面的馅心真的有菠萝的喔,全鹏城独此一家。我每次有同学来要吃海鲜,都是带他们去那里吃。
33、海上皇 振中路和华发北路交界处。是吃早茶,午茶,下午茶的绝佳去处。最喜欢吃的是它的牛杂,皮蛋瘦肉粥,白灼菜心(吃的时候淋上调好的蚝油)。其实还有一个很好吃,就是它的点心拼盘。不过一般不是熟客它不卖,你可以理直气壮的点,虽然菜牌上不写,坚持坚持,它就给做了,20元一份,每份是八种点心一起上笼蒸。在这里吃晚餐也不错,推荐它的芝士焗生蚝,才8元一个,每次都去吃两个,HOHO.
34、小肥羊 振华路与燕南路交界,不过实在振华路口。羊的火锅。之所以这么说,是因为里面几乎都是羊的天下,看名字就知道路。什么羊肉,羊肝,羊蛋什么的,不过我坚决只 吃羊肉。这里吃火锅,没有小碗调料的。火锅的汤味道很好,不知道放了什么东西调味的,据说还不准打包,免得外泄。吃东西就是点羊肉吃咯,外加火锅通常都有的,不过味道真是很不错,可以理解我两星期内竟然去吃了四次,要预订。
35、漓江又一轩 沿着小肥羊西行,就看见远远的一块绿色招牌,就是它了。这里是广西干锅口味,鸡煲鱼煲和鸭煲,不过我还是爱吃鸡煲,每种都试过了。两三个人去吃点个鸡煲就够了,青菜和豆腐是送的。它的吃饭是先吃干锅,然后快吃完了才加入汤水煮锅。千万不要吃它的任何鱼,反正我从来没有爱吃过。点心里面倒是有很多好的,对了它的糯米牛肉不错的喔,还有土豆片,香芋饼,可以一顿吃好几个。要是上火,还可以点个龟苓膏吃 ,降火的,据说是梧州的。其实我吃过正宗的梧州龟苓膏,苦死了,我还是喜欢吃甜点的。
36、北海渔村 深圳这个渔村那个渔村是铺天盖地,我喜欢来这家吃。随便点些海鲜吃,但 是最主要的是我爱吃这里的鲍鱼粥。不是那种大的,而是特价三元一个地,点30个小鲍鱼,然后煮粥喝,要半个多小时才能上桌。上次献血完元气大伤,狠狠吃了一顿鲍鱼粥,仿佛心理上才恢复过来。
37、穆斯林 去了很多次,还是不知道叫什么名字,反正那里就是穆斯林吃的地方。羊肉,羊肉。位置在南园路和爱华路的交界处二楼。里面都是新疆姑娘喔,好吃东西很多啦,比 如大盘鸡,羊肉汤等等。最好吃的当属羊腿,15元一个,这么便宜,吃的时候人手一腿,抓起来大啃特啃,不是一般的好。友情提醒:别点多了,吃个羊腿已经打倒一半了。
38、巴香鱼头 沿着穆斯林店,往爱华路南走几步,就看见了。里面的鱼头做法很有特色,是炸过之后再放进汤里煮,配火锅吃的汤料也是干货,吃的时候勺火锅的汤进去搅和,然后再吃。味道奇特,值得一吃。
39、图们烧烤 就在巴香鱼头的马路对面拉。爱吃它的烤鸡架子,就是爪子的筋,吃起来嘎本嘎本特别脆。其他的爱吃啥就点啥吧,可以让店员帮你们烤,免得自己回去都灰头土脸的。
40、山天 沿着穆斯林店,朝南园路的东面走100米吧,就看见山天了。我们喊它三个一, 因为商标就是这样的。里面也是吃火锅的,但是是吃菌类的,进去不用人指点,爱吃啥就点啥吃吧,火锅的汤底都是菌汤熬的,很鲜。菌丝炒饭是很不错的主食。最喜欢菌汤煮的鱼头了,一顿可以吃四五个,还意犹未尽。牛杆菌是我的最爱。
41、沅绿回转寿司店,在振兴东路靠近华强北的地方,对面就是紫荆城。寿司倒不是非常感兴趣,爱吃的是它的鳗鱼饭,可惜我的肚子有限,每次都只能吃一份就饱了。不过配上的豆豉汤,真不是一般的难喝,不过有同事每次去都是帮我包喝的。
42、江之鹤 振兴东路中段有个建设银行,旁边一拐就看见了。哇塞,好贵的说。晚餐每个人如果是吃自助,要168。吃过三次,最好吃的就是烤鳗鱼条,味道比沅绿好多了。尤其 是它的银鳕鱼西京烧,天哪,我有一回疯狂的点了四份吃,小姐肯定对我有意见了。第二次去的时候,他们就开始限制吃饭时间了,只给三个小时的点菜时间,其他随便。到第三个小时的时候,我已经饱的不能动了。
43、舞鹤 又是一个广告贼多的料理店,在上海宾馆。不过说实话,自从上次自费去过一次之后,打死我都不会再去了,估计都是补偿广告费去了,不过骗妹妹不错喔。贵,而且我觉得味道不如江之鹤的好。不过料理吃过了,也就这么个样。罗湖商业城下面有一家料理店,是深圳味道最好的地方,有空大家可以去试一下,名字忘记了。
44、罐罐鸡 这是一家小吃。就在我刚才说过的海上皇旁边,就吃的是一个一个砂锅的面或者粉。自然主打的是鸡,可以配猪肝,或者其他的菜牌上有的东西。吃的时候,从罐子里捞面条出来到小碗里,配以咸菜花生,汤本就是鲜的,很是爽口的说。
45、家家长沙米粉店 唉,挤死了,每次去都是人山人海的,空调又不足,还这么多人爱吃。华强北地区就有三家,两家在华发北路上,靠近红荔路的地方。老板一定发财了,本来就只有一家的,一年之内在深圳市内连开四家分店。里面卖粉,也卖面条。什么大肉面啊,冬菇肉饼蒸鸡蛋面啊,酸辣面啊,什么什么的,都挂墙上,大家爱吃什么点什么。粉是宽粉,我不爱吃粉。面是碱水面,很筋斗的,唉,热就热吧,谁让你爱吃呢。每次吃完都是大汗淋漓。吃的时候还可以点红烧猪蹄,豆干或者绿豆汤喝,都是不错的。
46、深运粥城 名字是不是叫这个忘记了,位置在红荔路靠近华富路的地方。反正你沿着红荔路往华富路走就是了,在路的北侧。这里的粥集合了潮州粥的大全,不会点不要紧, 吃几次就总结出经验来了。
47、香积世界素菜馆 在华强北丽人世界的背后,振兴西路靠近华强北的路口。里面都是吃素的,偶尔换个口味,去吃一下,也是挺不错滴,就是吃完了容易饿,毕竟现在的人都是肉食动物。
48、深井烧鹅 据说是香港同胞过来开的。门面不大,不注意还找不着。在家家长沙米粉的对面,注意,这里说的家家是在华发北路东侧的那家,也就是说烧鹅店在华发北西侧。 进去如果一个人的话,可以点个叉鹅饭,意思就是烧鹅和叉烧,如果两个人以上,可以来个叉鹅例盘,吃个痛快。味道很正宗的说,的确如此。不过价格也不菲喔,简单一个饭要15,如果是吃一个烧鹅腿,要25。它用来蘸烧鹅的小碟汁,真是爽死了。
49、谭鱼头:新华宾馆对面,那个鲜、那个辣、那个麻真是不摆了
50、人民公社:西丽转盘往白芒关走,不到5分钟车程地方(路痴女人,交代不清,见
谅),所有服务生和店面装扮都是文化大革命时期。一律土锅、土碗,有很多鸟可以吃,干锅的,吃完做火锅,青菜免费提供,最绝的是楼上还有免费电影看,什么小兵张噶、渡江侦察记、、、很是过瘾川菜:
51、干锅系列:八卦三路平安大厦附近有一家佳和长沙米粉店,湘菜馆,服务不错,最爱吃他们的干锅肥肠,分量十足,蒸饭很香。
52、湘鄂情: 我弟弟狂喜那儿的瓦缸猪手
53、车工庙丹桂轩: 精致粤菜, 喜欢座在露台看高尔夫美景
54、上步吉之岛对正南面小巷一小店,有很正宗的双皮奶,拆鱼粥,陈村粉
55、湖南人家——皇岗中学附近,湖南人家在深圳开了6家分店了,但唯有皇岗店最好,是我吃过的认为最好吃最实惠的湘菜馆,推荐:香菜野山椒炒牛肉丝,干锅鸡杂,干锅田鸡,剁椒鱼头...价格不贵,就是份量少点。怕辣的人最好不要去吃
56、老四川——科技园深南花园的后边,专做川菜和粤菜,吃过一次川菜,12元一份的凉拌猪耳,28元的干锅鸡杂,份量足,狂辣,好吃。
57、喝早茶去格兰晴天二楼,小吃非常精致,猪肚、鸡爪、炒饭...好吃...
58、在深南路设计院 后面的凤凰路上, 有一家老成都(在沃尔玛附近),四楼的火锅也还可以,价格合理, 重点推荐的是三楼的自助餐--粗粮坊,各种小点,等,品种丰富,每位18元,有肚量大的朋友, 可以吃到很舒服。每次我去吃着吃着就不行了, 朋友门直惋惜。 很超值!
59、在春风路穆斯林宾馆的对面, 名字叫撒哈拉一千零一夜。是深圳最正宗的中东风味的穆斯林餐厅。我和朋友常常去,羊肉茄子、哈达丝
MapReduce是Google 的一项重要技术,它是一个编程模型,用以进行大数据量的计算。对于大数据量的计算,通常采用的处理手法就是并行计算。至少现阶段而言,对许多开发人员来说,并行计算还是一个比较遥远的东西。MapReduce就是一种简化并行计算的编程模型,它让那些没有多少并行计算经验的开发人员也可以开发并行应用。在我看来,这也就是MapReduce的价值所在,通过简化编程模型,降低了开发并行应用的入门门槛。相对于现在普通的开发而言,并行计算需要更多的专业知识,有了MapReduce,并行计算就可以得到更广泛的应用。
MapReduce的名字源于这个模型中的两项核心操作:Map和 Reduce。也许熟悉Functional Programming的人见到这两个词会倍感亲切。简单的说来,Map是把一组数据一对一的映射为另外的一组数据,其映射的规则由一个函数来指定,比如对[1, 2, 3, 4]进行乘2的映射就变成了[2, 4, 6, 8]。Reduce是对一组数据进行归约,这个归约的规则由一个函数指定,比如对[1, 2, 3, 4]进行求和的归约得到结果是10,而对它进行求积的归约结果是24。
Map操作是独立的对每个元素进行操作,在FP中,操作是没有副作用的,换句话说,Map操作将产生一组全新的数据,而原来的数据保持不变。因此,它是高度并行的。Reduce操作虽然不如Map操作并行性那么好,但是它总会得到一个相对简单的结果,大规模运算也相对独立,因此也是比较适合并行的。
无论是Map还是Reduce都是以另外的函数作为参数,在 FP中,这样的函数被称为高阶函数(high-order function)。正是因为它们可以同其它函数相结合,所以,我们只要把Map和Reduce这两个高阶函数进行并行化处理,而无需面面俱到的把所有的函数全部考虑到。这样便形成了一个以Map和Reduce为基础的框架,具体应用相关代码写在用户代码中,之后与MapReduce结合获得并行处理的能力。当然,这么做的前提是按照这个框架的要求,把计算归结为Map和Reduce操作。为什么是Map和Reduce?从前面的内容我们可以看出,在 Map过程中,我们将数据并行,也就是将数据分开,而Reduce则把分开的数据合到了一起,换句话说,Map是一个分的过程,Reduce则对应着合,这一分一合便在不知不觉中完成了计算。所以,站在计算的两端来看,与我们通常熟悉的串行计算没有任何差别,所有的复杂性都在中间隐藏了。
所有这些并行化能力的获得都与FP有着密不可分的关系。事实上,不仅仅是MapReduce从FP中获得了灵感,其它一些并行编程模型也走上了同样的道路。 FP中有很多的好东西,比如自动内存管理,比如动态类型。在遥远的年代里,因为机器性能的原因,它们无法得到广泛应用,当机器性能不再是瓶颈,这些东西便逐渐复活了。
前面提到过,并行计算对于普通开发人员来说,有一个比较高的门槛。从前我们或许可以不理会并行计算,但是随着Intel开始将多核带入人们的日常生活,并行计算将会变得更加平民化,毕竟谁也不希望自己机器里面的多核只有一个在干活。现在的许多操作系统会把多核视为多处理器,但那也得有多任务才能在CPU处多分得一杯羹。对于服务器端应用来说,拥有多任务的能力是一个正常的现象。但对于很多桌面应用来说,一条道跑到黑的情况比较多见。而且,多任务并非为并行计算专门准备的,所以,控制粒度是很大的。如果需要更细粒度的并行计算,至少从表达能力上来说,多任务就有些麻烦了。
并行计算进入日常开发的难度就在于编程模型,太复杂的东西会被人唾弃的,CORBA在这方面已经是个反面教材了。MapReduce已经为我们演示了一种可以接受的编程模型,接下来,变化还会有,Intel和AMD都在努力。不过,具体的进程得取决于多核CPU占领市场的进度。