2009年3月26日星期四

ORA-12737错误

今天群里一个哥们碰见ORA-12737错误,情况是这样的:服务器装在win2003上,客户端使用了instant client(linux平台)。

ORA-12737: Instant Client Light: unsupported server character set ZHS16GBK

看来是即时客户端不支持服务器的ZHS16GBK字符集。

ORACLE给的解释如下:

ORA-12737: Instant Client Light: unsupported server character set string
Cause: The character set specified is not allowed for this operation or is invalid. Instant Client Light has only minimal character sets.
Action: Do not use Instant Client Light for this character set

翻译过来就是说即时客户端支持的字符集有限,不要用这个东西。
感觉ORACLE有时候就是这么操蛋,给这种解释。

2009年3月24日星期二

新版的下载类

import java.io.*;
import java.net.*;
import java.util.*;

// This class downloads a file from a URL.
class Download extends Observable implements Runnable {
// Max size of download buffer.
private static final int MAX_BUFFER_SIZE = 1024;

// These are the status names.
public static final String STATUSES[] = {"Downloading",
"Paused", "Complete", "Cancelled", "Error"};

// These are the status codes.
public static final int DOWNLOADING = 0;
public static final int PAUSED = 1;
public static final int COMPLETE = 2;
public static final int CANCELLED = 3;
public static final int ERROR = 4;

private URL url; // download URL
private int size; // size of download in bytes
private int downloaded; // number of bytes downloaded
private int status; // current status of download

// Constructor for Download.
public Download(URL url) {
this.url = url;
size = -1;
downloaded = 0;
status = DOWNLOADING;

// Begin the download.
download();
}

// Get this download's URL.
public String getUrl() {
return url.toString();
}

// Get this download's size.
public int getSize() {
return size;
}

// Get this download's progress.
public float getProgress() {
return ((float) downloaded / size) * 100;
}

// Get this download's status.
public int getStatus() {
return status;
}

// Pause this download.
public void pause() {
status = PAUSED;
stateChanged();
}

// Resume this download.
public void resume() {
status = DOWNLOADING;
stateChanged();
download();
}

// Cancel this download.
public void cancel() {
status = CANCELLED;
stateChanged();
}

// Mark this download as having an error.
private void error() {
status = ERROR;
stateChanged();
}

// Start or resume downloading.
private void download() {
Thread thread = new Thread(this);
thread.start();
}

// Get file name portion of URL.
private String getFileName(URL url) {
String fileName = url.getFile();
return fileName.substring(fileName.lastIndexOf('/') + 1);
}

// Download file.
public void run() {
RandomAccessFile file = null;
InputStream stream = null;

try {
// Open connection to URL.
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();

// Specify what portion of file to download.
connection.setRequestProperty("Range",
"bytes=" + downloaded + "-");

// Connect to server.
connection.connect();

// Make sure response code is in the 200 range.
if (connection.getResponseCode() / 100 != 2) {
error();
}

// Check for valid content length.
int contentLength = connection.getContentLength();
if (contentLength < size ="="" size =" contentLength;" file =" new" stream =" connection.getInputStream();" status ="=""> MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[size - downloaded];
}

// Read from server into buffer.
int read = stream.read(buffer);
if (read == -1)
break;

// Write buffer to file.
file.write(buffer, 0, read);
downloaded += read;
stateChanged();
}

/* Change status to complete if this point was
reached because downloading has finished. */
if (status == DOWNLOADING) {
status = COMPLETE;
stateChanged();
}
} catch (Exception e) {
error();
} finally {
// Close file.
if (file != null) {
try {
file.close();
} catch (Exception e) {}
}

// Close connection to server.
if (stream != null) {
try {
stream.close();
} catch (Exception e) {}
}
}
}

// Notify observers that this download's status has changed.
private void stateChanged() {
setChanged();
notifyObservers();
}

}

想扩展一下这个类实现多线程下载,FTP下载和断点续传。

2009年3月23日星期一

编程的首要原则

转自云风的blog,原文请看这里

刘未鹏的 blog 上写了一篇 编程的首要原则(s)是什么? ,这段时间在我的 google reader 上被许多人分享。

我问自己,我目前的首要原则是什么?

其实想说的,那篇里都有人说了。如果非要说首要,我也认可最多人认可的:

KISS - Keep It Simple Stupid

不过对 DRY - Don’t Repeat Yourself 我反而认为是次要的,当然是在和 KISS 相冲突的时候。

如果换一句和 KISS 原则相当分量的话,我会说:不要用愚蠢的方法做事。很矛盾?Repeat Yourself 往往代表了一些愚蠢的方案,且并不 simple ,至少会付出更多的体力。我想,KISS 的最后一个 S 指的是大智若愚的愚,而自做聪明则是另一种愚蠢。

在 KISS 的大原则下,我想其实可以分出一些细节的东西,也是别人都提过的:

最近两年我对同事说的最多的几句话,“弄清你的问题是什么”,“你不一定需要解决这个问题” 。

因为什么都不做才是最简单的。要知道什么可以不做,必须了解你的问题。

面向对象以及复杂软件技术的滥用,或是找不到更 Simple 的方案解决问题(以性能、以需求等为借口去实现更复杂的方案)往往都是对需求了解不清,或者眼光太短。把手段当成了目的。(以为达到目的,必须采用某种手段,而如何应用这种手段就变成了目的)

同时,我觉得过度抽象也来源于对问题的认识不清。我还没想好后面要写什么,实现些什么,所以先利用“抽象” 把其它的部分搭起来。久而久之,不分析具体问题,先做抽象就变成了惯性。而抽象层本身往往是软件中最复杂的部分,离 KISS 原则最远的一块。

以下是个人的想法:
我想,KISS 的最后一个 S 指的是大智若愚的愚,而自做聪明则是另一种愚蠢。其实我现在手头就有一个项目,仅仅为了实现下载功能,就反反复复包装了17层,把抽象用到了令人乍舌的地步。记得有句话说,如果清楚自己要做什么,3层就够了,如果不清楚,17层也不够。个人感觉3层足够了,UI一层,逻辑一层,数据一层。

KISS原则说起来容易,做起来真的不容易。

2009年3月19日星期四

用wget备份网站

wget -nc --convert-links -r http://fanng.blogspot.com
今天发现wget的这个功能很好用,用来备份个人博客很方便。
将这个命令加入一个job,隔一段时间定时运行即可。

顺便附上一篇介绍wget的好文章。
wget是一个从网络上自动下载文件的自由工具。它支持HTTP,HTTPS和FTP协议,可以使用HTTP代理. 
所谓的自动下载是指,wget可以在用户退出系统的之后在后台执行。这意味这你可以登录系统,启动一个wget下载任务,然后退出系统,wget将在后台执行直到任务完成,相对于其它大部分浏览器在下载大量数据时需要用户一直的参与,这省去了极大的麻烦。 
wget 可以跟踪HTML页面上的链接依次下载来创建远程服务器的本地版本,完全重建原始站点的目录结构。这又常被称作"递归下载"。在递归下载的时候,wget 遵循Robot Exclusion标准(/robots.txt). wget可以在下载的同时,将链接转换成指向本地文件,以方便离线浏览。 
wget 非常稳定,它在带宽很窄的情况下和不稳定网络中有很强的适应性.如果是由于网络的原因下载失败,wget会不断的尝试,直到整个文件下载完毕。如果是服务 器打断下载过程,它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。 
wget的常见用法
wget虽然功能强大,但是使用起来还是比较简单的,
基本的语法是:wget [参数列表] "URL" 用""引起来可以避免因URL中有特殊字符造成的下载出错。
下面就结合具体的例子来说明一下wget的用法。
1、下载整个http或者ftp站点。
wget http://place.your.url/here
这个命令可以将http://place.your.url/here 首页下载下来。使用-x会强制建立服务器上一模一样的目录,如果使用-nd参数,那么服务器上下载的所有内容都会加到本地当前目录。 

wget -r http://place.your.url/here
这个命令会按照递归的方法,下载服务器上所有的目录和文件,实质就是下载整个网站。这个命令一定要小心使用,因为在下载的时候,被下载网站指向的所有地址 同样会被下载,因此,如果这个网站引用了其他网站,那么被引用的网站也会被下载下来!基于这个原因,这个参数不常用。可以用-l number参数来指定下载的层次。例如只下载两层,那么使用-l 2。

要是您想制作镜像站点,那么可以使用-m参数,例如:wget -m http://place.your.url/here
这时wget会自动判断合适的参数来制作镜像站点。此时,wget会登录到服务器上,读入robots.txt并按robots.txt的规定来执行。

2、断点续传。
当文件特别大或者网络特别慢的时候,往往一个文件还没有下载完,连接就已经被切断,此时就需要断点续传。wget的断点续传是自动的,只需要使用-c参数,例如:
wget -c http://the.url.of/incomplete/file
使用断点续传要求服务器支持断点续传。-t参数表示重试次数,例如需要重试100次,那么就写-t 100,如果设成-t 0,那么表示无穷次重试,直到连接成功。-T参数表示超时等待时间,例如-T 120,表示等待120秒连接不上就算超时。

3、批量下载。
如果有多个文件需要下载,那么可以生成一个文件,把每个文件的URL写一行,例如生成文件download.txt,然后用命令:wget -i download.txt
这样就会把download.txt里面列出的每个URL都下载下来。(如果列的是文件就下载文件,如果列的是网站,那么下载首页)

4、选择性的下载。
可以指定让wget只下载一类文件,或者不下载什么文件。例如:
wget -m --reject=gif http://target.web.site/subdirectory
表示下载http://target.web.site/subdirectory,但是忽略gif文件。--accept=LIST 可以接受的文件类型,--reject=LIST拒绝接受的文件类型。

5、密码和认证。
wget只能处理利用用户名/密码方式限制访问的网站,可以利用两个参数:
--http-user=USER设置HTTP用户
--http-passwd=PASS设置HTTP密码
对于需要证书做认证的网站,就只能利用其他下载工具了,例如curl。

6、利用代理服务器进行下载。
如果用户的网络需要经过代理服务器,那么可以让wget通过代理服务器进行文件的下载。此时需要在当前用户的目录下创建一个.wgetrc文件。文件中可以设置代理服务器:
http-proxy = 111.111.111.111:8080
ftp-proxy = 111.111.111.111:8080
分别表示http的代理服务器和ftp的代理服务器。如果代理服务器需要密码则使用:
--proxy-user=USER设置代理用户
--proxy-passwd=PASS设置代理密码 
这两个参数。
使用参数--proxy=on/off 使用或者关闭代理。
wget还有很多有用的功能,需要用户去挖掘。



wget的使用格式 
Usage: wget [OPTION]... [URL]...
* 用wget做站点镜像: 
wget -r -p -np -k http://dsec.pku.edu.cn/~usr_name/
# 或者
wget -m http://dsec.pku.edu.cn/~usr_name/
* 在不稳定的网络上下载一个部分下载的文件,以及在空闲时段下载 
wget -t 0 -w 31 -c http://dsec.pku.edu.cn/BBC.avi -o down.log &
# 或者从filelist读入要下载的文件列表
wget -t 0 -w 31 -c -B ftp://dsec.pku.edu.cn/linuxsoft -i filelist.txt -o down.log &
上面的代码还可以用来在网络比较空闲的时段进行下载。我的用法是:在mozilla中将不方便当时下载的URL链接拷贝到内存中然后粘贴到文件filelist.txt中,在晚上要出去系统前执行上面代码的第二条。
* 使用代理下载 
wget -Y on -p -k https://sourceforge.net/projects/wvware/
代理可以在环境变量或wgetrc文件中设定 
# 在环境变量中设定代理
export PROXY=http://211.90.168.94:8080/
# 在~/.wgetrc中设定代理
http_proxy = http://proxy.yoyodyne.com:18023/
ftp_proxy = http://proxy.yoyodyne.com:18023/


wget各种选项分类列表
* 启动 
-V, --version 显示wget的版本后退出
-h, --help 打印语法帮助
-b, --background 启动后转入后台执行
-e, --execute=COMMAND 执行`.wgetrc'格式的命令,wgetrc格式参见/etc/wgetrc或~/.wgetrc
* 记录和输入文件 
-o, --output-file=FILE 把记录写到FILE文件中
-a, --append-output=FILE 把记录追加到FILE文件中
-d, --debug 打印调试输出
-q, --quiet 安静模式(没有输出)
-v, --verbose 冗长模式(这是缺省设置)
-nv, --non-verbose 关掉冗长模式,但不是安静模式
-i, --input-file=FILE 下载在FILE文件中出现的URLs
-F, --force-html 把输入文件当作HTML格式文件对待
-B, --base=URL 将URL作为在-F -i参数指定的文件中出现的相对链接的前缀
--sslcertfile=FILE 可选客户端证书
--sslcertkey=KEYFILE 可选客户端证书的KEYFILE
--egd-file=FILE 指定EGD socket的文件名
* 下载 
--bind-address=ADDRESS 指定本地使用地址(主机名或IP,当本地有多个IP或名字时使用)
-t, --tries=NUMBER 设定最大尝试链接次数(0 表示无限制).
-O --output-document=FILE 把文档写到FILE文件中
-nc, --no-clobber 不要覆盖存在的文件或使用.#前缀
-c, --continue 接着下载没下载完的文件
--progress=TYPE 设定进程条标记
-N, --timestamping 不要重新下载文件除非比本地文件新
-S, --server-response 打印服务器的回应
--spider 不下载任何东西
-T, --timeout=SECONDS 设定响应超时的秒数
-w, --wait=SECONDS 两次尝试之间间隔SECONDS秒
--waitretry=SECONDS 在重新链接之间等待1...SECONDS秒
--random-wait 在下载之间等待0...2*WAIT秒
-Y, --proxy=on/off 打开或关闭代理
-Q, --quota=NUMBER 设置下载的容量限制
--limit-rate=RATE 限定下载输率
* 目录 
-nd --no-directories 不创建目录
-x, --force-directories 强制创建目录
-nH, --no-host-directories 不创建主机目录
-P, --directory-prefix=PREFIX 将文件保存到目录 PREFIX/...
--cut-dirs=NUMBER 忽略 NUMBER层远程目录
* HTTP 选项 
--http-user=USER 设定HTTP用户名为 USER.
--http-passwd=PASS 设定http密码为 PASS.
-C, --cache=on/off 允许/不允许服务器端的数据缓存 (一般情况下允许).
-E, --html-extension 将所有text/html文档以.html扩展名保存
--ignore-length 忽略 `Content-Length'头域
--header=STRING 在headers中插入字符串 STRING
--proxy-user=USER 设定代理的用户名为 USER
--proxy-passwd=PASS 设定代理的密码为 PASS
--referer=URL 在HTTP请求中包含 `Referer: URL'头
-s, --save-headers 保存HTTP头到文件
-U, --user-agent=AGENT 设定代理的名称为 AGENT而不是 Wget/VERSION.
--no-http-keep-alive 关闭 HTTP活动链接 (永远链接).
--cookies=off 不使用 cookies.
--load-cookies=FILE 在开始会话前从文件 FILE中加载cookie
--save-cookies=FILE 在会话结束后将 cookies保存到 FILE文件中
* FTP 选项 
-nr, --dont-remove-listing 不移走 `.listing'文件
-g, --glob=on/off 打开或关闭文件名的 globbing机制
--passive-ftp 使用被动传输模式 (缺省值).
--active-ftp 使用主动传输模式
--retr-symlinks 在递归的时候,将链接指向文件(而不是目录)
* 递归下载 
-r, --recursive 递归下载--慎用!
-l, --level=NUMBER 最大递归深度 (inf 或 0 代表无穷).
--delete-after 在现在完毕后局部删除文件
-k, --convert-links 转换非相对链接为相对链接
-K, --backup-converted 在转换文件X之前,将之备份为 X.orig
-m, --mirror 等价于 -r -N -l inf -nr.
-p, --page-requisites 下载显示HTML文件的所有图片
* 递归下载中的包含和不包含(accept/reject) 
-A, --accept=LIST 分号分隔的被接受扩展名的列表
-R, --reject=LIST 分号分隔的不被接受的扩展名的列表
-D, --domains=LIST 分号分隔的被接受域的列表
--exclude-domains=LIST 分号分隔的不被接受的域的列表
--follow-ftp 跟踪HTML文档中的FTP链接
--follow-tags=LIST 分号分隔的被跟踪的HTML标签的列表
-G, --ignore-tags=LIST 分号分隔的被忽略的HTML标签的列表
-H, --span-hosts 当递归时转到外部主机
-L, --relative 仅仅跟踪相对链接
-I, --include-directories=LIST 允许目录的列表
-X, --exclude-directories=LIST 不被包含目录的列表
-np, --no-parent 不要追溯到父目录
问题
在递归下载的时候,遇到目录中有中文的时候,wget创建的本地目录名会用URL编码规则处理。如"天网防火墙"会被存为"%CC%EC%CD%F8%B7%C0%BB%F0%C7%BD",这造成阅读上的


wget 使用举例
wget -r -p -np -k http://www.linuxdiyf.com
· -r:在本机建立服务器端目录结构;
· -p: 下载显示HTML文件的所有图片;
· -np:只下载目标站点指定目录及其子目录的内容;
· -k: 转换非相对链接为相对链接。

2009年3月18日星期三

判断表空间是否还可以压缩的脚本

 declare
    cursor c_dbfile is
          select  tablespace_name
                  ,file_name
                  ,file_id
                  ,bytes
          from    sys.dba_data_files
          where   status !='INVALID'
          order   by tablespace_name,file_id;
    cursor c_space(v_file_id in number) is
          select block_id,blocks
          from   sys.dba_free_space
          where  file_id=v_file_id
          order  by block_id desc;
  blocksize       number;
  filesize        number;
  extsize         number;
  
  begin
    select value
    into   blocksize
    from   v$parameter
    where  name = 'db_block_size';
    for c_rec1 in c_dbfile
    loop
      filesize := c_rec1.bytes;
      <>
      for c_rec2 in c_space(c_rec1.file_id)
      loop
        extsize := ((c_rec2.block_id - 1)*blocksize + c_rec2.blocks*blocksize);
        if extsize = filesize
        then
          filesize := (c_rec2.block_id - 1)*blocksize;
        else
          exit outer;
        end if;
      end loop outer;
      if filesize = c_rec1.bytes
      then
        dbms_output.put_line('Tablespace: '
        ||' '||c_rec1.tablespace_name||' Datafile: '||c_rec1.file_name);
        dbms_output.put_line('Can not be resized, no free space at end of file.');
        dbms_output.put_line('.');
      else
        if filesize <>
        then
          dbms_output.put_line('Tablespace: '
          ||' '||c_rec1.tablespace_name||' Datafile: '||c_rec1.file_name);
          dbms_output.put_line('Can be resized to: '||2*blocksize
          ||' Bytes, Actual size: '||c_rec1.bytes||' Bytes');
          dbms_output.put_line('.');
        else
          dbms_output.put_line('Tablespace: '
          ||' '||c_rec1.tablespace_name||' Datafile: '||c_rec1.file_name);
          dbms_output.put_line('Can be resized to: '||filesize
          ||' Bytes, Actual size: '||c_rec1.bytes);
          dbms_output.put_line('.');
        end if;
      end if;
    end loop;
  end;
  /

群里WU叔写的代码,用于判定一个表空间能被压缩到什么程度。知道resize的程度之后,只需要运行alter database datafile '......' resize 15000M就可以了。

摸鱼儿

元好问的所有词里面,可能就是这首摸鱼儿最广为流传,究其原因,当然是老金的神雕了。

摸鱼儿
              元好问

  序:泰和五年乙丑岁,赴试并州,道逢捕雁者云;“今旦获一雁,杀之矣。其脱网者悲鸣不能去,竟自投于地而死。”予因买得之,葬之汾水之上,累石为识,号曰雁丘。时同行者多为赋诗,予亦有《雁丘词》。旧所作无宫商,今改定之。

  问世间、情是何物,直教生死相许?
  天南地北双飞客,老翅几回寒暑。
  欢乐趣,离别苦,就中更有痴儿女。
  君应有语,渺万里层云,千山暮雪,只影向谁去?

  横汾路,寂寞当年箫鼓,荒烟依旧平楚。
  招魂楚些何嗟及,山鬼暗啼风雨。
  天也妒,未信与,莺儿燕子俱黄土。
  千秋万古,为留待骚人,狂歌痛饮,来访雁丘处。

这首词前面写的很是不错,后面那部分在我看来,就是完全掉书袋,引用了一大堆典籍。要解说这首词,还是金庸解说的最好。

黄蓉道:“杨过既在谷底,只有差雕儿再去救他。”当下作哨招雕。但连吹数声,双雕竟毫不理睬。黄蓉好生奇怪,数十年来,双雕闻唤即至,从不违命,何以今日对自己的口哨直似不闻?

  她又一声长哨,只见那雌雕双翅一振,高飞入云,盘旋数圈,悲声哀啼,猛地里从空中疾冲而下。黄蓉心道:“不好!”大叫:“雕儿!”只见雌雕一头撞在山 石之上,脑袋碎裂,折翼而死。众人见了都吃了一惊,奔过去看时,原来那雄鹰早已气绝多时。众人见这雌雕如此深情重义,无不慨叹。黄蓉自幼和双雕为伴,更是 伤痛,不禁流下泪来。

  陆无双耳边,忽地似乎响起了师父李莫愁细若游丝的歌声:“问世间,情是何物,直教生死相许?天南地北双飞客,老翅几回寒暑?欢乐趣,离别苦,就中更有 痴儿女。君应有语,渺万里层云,千山暮雪,只影向谁去?”她幼时随着李莫愁学艺,午夜梦回,常听到师父唱着这首曲子,当日未历世情,不明曲中深意,此时眼 见雄雕毙命后雌雕殉情,心想:“这头雌雕假若不死,此后万里层云,千山暮雪,叫它孤单只影,如何排遣?”触动心怀,眼眶儿竟也红了。

在射雕英雄传里,双雕的父母也是这么死的,看来老金很喜欢这首词啊。

2009年3月16日星期一

一个简单的HTML解析器

这个解析器还不能很好的工作,但是架子大概已经有了,慢慢完善吧。

import URLTest.URLUtil;

public class HtmlParser {
public String leader;
public String tag;
public String body;
public String end;
public String trailer;

public HtmlParser more;
public HtmlParser parts;

static String tags[]={"html","body","table","tr","td"};

public HtmlParser(String text){
this (text,tags,0,0);
}

public HtmlParser(String text, String[] tags2){
this (text,tags2,0,0);
}

public HtmlParser(String text, String[] tags2, int level, int offset) {
String lc=text.toLowerCase();
int startTag=lc.indexOf("<"+tags[level]);
int endTag=lc.indexOf(">",startTag)+1;
int startEnd=lc.indexOf(" int endEnd=lc.indexOf(">",startEnd)+1;
int startMore=lc.indexOf("<"+tags[level],endEnd);

if(startTag<0||endTag<0||startEnd<0||endEnd<0){
return;
}


leader=text.substring(0,startTag);
tag=text.substring(startTag,endTag);
body=text.substring(endTag,startEnd);
end=text.substring(startEnd,endEnd);
trailer=text.substring(endEnd);
System.out.println("leader is :"+leader);
System.out.println("tag is :"+tag);
System.out.println("body is :"+body);
System.out.println("end is :"+end);
System.out.println("trailer is :"+trailer);

if(level+1 parts=new HtmlParser(body,tags,level+1,offset+endTag);
body=null;
}

if(startMore>=0){
more=new HtmlParser(body,tags,level,offset+endEnd);
trailer=null;
}
}

public static void main(String[] args) {
HtmlParser hp=new HtmlParser(URLUtil.getHtml("http://www.google.com"));
}

}

2009年3月15日星期日

用JAVA实现下载

import java.io.*;
import java.net.*;
import java.util.*;

/**
 * 

Title: 个人开发的API


 * 

Description: 将指定的HTTP网络资源在本地以文件形式存放


 * 

Copyright: Copyright (c) 2004


 * 

Company: NewSky


 * @author MagicLiao
 * @version 1.0
 */
public class HttpGet {

  public final static boolean DEBUG = true;//调试用
  private static int BUFFER_SIZE = 8096;//缓冲区大小
  private Vector vDownLoad = new Vector();//URL列表
  private Vector vFileList = new Vector();//下载后的保存文件名列表

  /**
   * 构造方法
   */
  public HttpGet() {

  }

  /**
   * 清除下载列表
   */
  public void resetList() {
    vDownLoad.clear();
    vFileList.clear();
  }

  /**
   * 增加下载列表项
   *
   * @param url String
   * @param filename String
   */
  public void addItem(String url, String filename) {
    vDownLoad.add(url);
    vFileList.add(filename);
  }

  /**
   * 根据列表下载资源
   */
  public void downLoadByList() {
    String url = null;
    String filename = null;
    
    //按列表顺序保存资源
    for (int i = 0; i < vDownLoad.size(); i++) {
      url = (String) vDownLoad.get(i);
      filename = (String) vFileList.get(i);

      try {
        saveToFile(url, filename);
      }
      catch (IOException err) {
        if (DEBUG) {
          System.out.println("资源[" + url + "]下载失败!!!");
        }
      }
    }

    if (DEBUG) {
      System.out.println("下载完成!!!");

    }
  }

  /**
   * 将HTTP资源另存为文件
   *
   * @param destUrl String
   * @param fileName String
   * @throws Exception
   */
  public void saveToFile(String destUrl, String fileName) throws IOException {
    FileOutputStream fos = null;
    BufferedInputStream bis = null;
    HttpURLConnection httpUrl = null;
    URL url = null;
    byte[] buf = new byte[BUFFER_SIZE];
    int size = 0;
    
    //建立链接
    url = new URL(destUrl);
    httpUrl = (HttpURLConnection) url.openConnection();
    //连接指定的资源
    httpUrl.connect();
    //获取网络输入流
    bis = new BufferedInputStream(httpUrl.getInputStream());
    //建立文件
    fos = new FileOutputStream(fileName);

    if (this.DEBUG) 
  System.out.println("正在获取链接[" + destUrl + "]的内容...\n将其保存为文件[" + fileName + "]");

    //保存文件
    while ( (size = bis.read(buf)) != -1) 
      fos.write(buf, 0, size);
    
    fos.close();
    bis.close();
    httpUrl.disconnect();
  }

  /**
   * 设置代理服务器
   *
   * @param proxy String
   * @param proxyPort String
   */
  public void setProxyServer(String proxy, String proxyPort) {
    //设置代理服务器
    System.getProperties().put("proxySet", "true");
    System.getProperties().put("proxyHost", proxy);
    System.getProperties().put("proxyPort", proxyPort);

  }

  /**
   * 设置认证用户名与密码
   *
   * @param uid String
   * @param pwd String
   */
  //public void setAuthenticator(String uid, String pwd) {
   // Authenticator.setDefault(new MyAuthenticator(uid, pwd));
 // }

  /**
   * 主方法(用于测试)
   *
   * @param argv String[]
   */
  public static void main(String argv[]) {

    HttpGet oInstance = new HttpGet();
try {
//增加下载列表(此处用户可以写入自己代码来增加下载列表)
       oInstance.addItem("
","");

//开始下载
oInstance.downLoadByList();
    }
    catch (Exception err) {
      System.out.println(err.getMessage());
    }

  }

}

使用JAVA抓取网页内容

今天碰见一个哥们问,怎么使用JAVA抓取网页内容,查了查资料,写了个demo。基本想法是,通过URL打开HTTPconnection,然后可以拿到一个输入流,然后把输入流读入到一个StringBuffer 。

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class URLUtil {

public static String getHtml(String urlString) {
try {
StringBuffer html = new StringBuffer();
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStreamReader isr = new InputStreamReader(conn.getInputStream());
BufferedReader br = new BufferedReader(isr);
String temp;
while ((temp = br.readLine()) != null) {
html.append(temp).append("\n");
}
br.close();
isr.close();
return html.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public static void main(String[] args) {
System.out.println(URLUtil.getHtml("http://www.sina.com.cn"));
}
}

其中的问题是不能处理多媒体信息。

生成etags的脚本

周末学习到了一些emacs的使用技巧。

关于如何配置emacs,使其在linux下可以正常显示汉字,而不出现方框,可以参考博士的文章

这里给出一个有效生成tag文件的脚本,非常好用(make_etags)。

#!/bin/sh

trap "rm -f /tmp/$$" 0 1 2 3 15
rm -f ./TAGS
find `pwd`/ -type f -name '*.[chyl]' -print | \
xargs etags --append -o TAGS

find . -type d -print | \
while read DIR; do
[ "$DIR" != "." ] && ln -f -s `pwd`/TAGS $DIR
done

将该文件拷贝到工程所在目录,运行该文件即可。

2009年3月11日星期三

ORACLE sql调整(三)

使用SPM

SQL PLAN MANAGEMENT是ORACLE11g中引入的新功能。SQL PROFILE实际上一些hint和统计信息,来帮助优化器得到更好的执行计划;stored outline是将稳定的执行计划存储起来,供以后使用;而SPM则是将生成的执行计划放在一个库中进行管理,使用了spm,优化器就不会突然选择效率很 低的查询。

capture SQL plan baseline:
捕捉sql plan baseline在默认情况下是关闭的,所以需要打开:
alter session
set optimizer_capture_sql_plan_baselines = true;

运行以下语句:
select * /* ARUP */ from sales
where quantity_sold > 1 order by cust_id;
-- Change the optimizer mode
alter session set optimizer_mode = first_rows;
-- Second execution. Opt Mode changed
select * /* ARUP */ from sales
where quantity_sold > 1 order by cust_id;
-- Gather stats now
begin
dbms_stats.gather_table_stats (
ownname => 'SH',
tabname => 'SALES',
cascade => TRUE,
no_invalidate => FALSE,
method_opt => 'FOR ALL INDEXED COLUMNS SIZE AUTO',
granularity => 'GLOBAL AND PARTITION',
estimate_percent => 10,
degree => 4
);
end;
/
-- Third execution. After stats
select * /* ARUP */ from sales
where quantity_sold > 1 order by cust_id;

/* ARUP */并不是sql hint主要是方便我们比较容易的表示这条语句。

然后可以在EM里点击 Server tab -> SQL Plan Control->SQL Plan Baseline,enable之。

Using Baselines:

alter session set
optimizer_use_sql_plan_baselines = true;


SQL> explain plan for select * /* ARUP */ from sales
2 where quantity_sold > 1 order by cust_id;
Explained.

SQL> select * from table(dbms_xplan.display(null, null, 'basic'));
PLAN_TABLE_OUTPUT
---------------------------
Plan hash value: 143117509
--------------------------------------------------------------
| Id | Operation | Name |
--------------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT ORDER BY | |
| 2 | PARTITION RANGE ALL | |
| 3 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES |
| 4 | BITMAP CONVERSION TO ROWIDS | |
| 5 | BITMAP INDEX FULL SCAN | SALES_TIME_BIX |
--------------------------------------------------------------

-- disable baselines

SQL> alter session set optimizer_use_sql_plan_baselines = false;
Session altered.

SQL> explain plan for select * /* ARUP */ from sales
2 where quantity_sold > 1 order by cust_id;
Explained.

SQL> select * from table(dbms_xplan.display(null, null, 'basic'));

PLAN_TABLE_OUTPUT
----------------------------
Plan hash value: 3803407550

--------------------------------------
| Id | Operation | Name |
--------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT ORDER BY | |
| 2 | PARTITION RANGE ALL | |
| 3 | TABLE ACCESS FULL | SALES |
--------------------------------------

其实很多操作完全可以在EM里完成,具体可参照:
http://www.oracle.com/technology/obe/11gr1_db/manage/spm/spm.htm

ORACLE sql调整(二)

Stored Outlines

stored outline实际上是ORACLE存储的比较稳定的执行计划。
第一步:
Creating Outlines
修改系统/会话参数
-- Switch on automatic creation of stored outlines.
ALTER SYSTEM SET create_stored_outlines=TRUE;
ALTER SESSION SET create_stored_outlines=TRUE;

-- Switch on automatic creation of stored outlines.
ALTER SYSTEM SET create_stored_outlines=FALSE;
ALTER SESSION SET create_stored_outlines=FALSE;
赋予用户权限
-- Grant the necessary privileges.
CONN sys/password AS SYSDBA
GRANT CREATE ANY OUTLINE TO SCOTT;
GRANT EXECUTE_CATALOG_ROLE TO SCOTT;

CONN scott/tiger

-- 为特定的sql创建outline.
CREATE OUTLINE emp_dept FOR CATEGORY scott_outlines
ON SELECT e.empno, e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno;

--检查outline是否正确创建.
COLUMN name FORMAT A30
SELECT name, category, sql_text FROM user_outlines WHERE category = 'SCOTT_OUTLINES';

NAME CATEGORY
------------------------------ ------------------------------
SQL_TEXT
--------------------------------------------------------------------------------
EMP_DEPT SCOTT_OUTLINES
SELECT e.empno, e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno


1 row selected.

-- 列出所有的hint列表.
COLUMN hint FORMAT A50
SELECT node, stage, join_pos, hint FROM user_outline_hints WHERE name = 'EMP_DEPT';

NODE STAGE JOIN_POS HINT
---------- ---------- ---------- --------------------------------------------------
1 1 0 NO_EXPAND(@"SEL$1" )
1 1 0 PQ_DISTRIBUTE(@"SEL$1" "E"@"SEL$1" NONE NONE)
1 1 0 USE_MERGE(@"SEL$1" "E"@"SEL$1")
1 1 0 LEADING(@"SEL$1" "D"@"SEL$1" "E"@"SEL$1")
1 1 0 NO_STAR_TRANSFORMATION(@"SEL$1" )
1 1 0 NO_FACT(@"SEL$1" "E"@"SEL$1")
1 1 0 NO_FACT(@"SEL$1" "D"@"SEL$1")
1 1 2 FULL(@"SEL$1" "E"@"SEL$1")
1 1 1 INDEX(@"SEL$1" "D"@"SEL$1" ("DEPT"."DEPTNO"))
1 1 0 NO_REWRITE(@"SEL$1" )
1 1 0 NO_REWRITE(@"SEL$1" )

11 rows selected.
下面的方式使用
DBMS_OUTLN.CREATE_OUTLINE的方式建立outline
-- 运行语句
SELECT e.empno, e.ename, d.dname, e.job FROM emp e, dept d WHERE e.deptno = d.deptno AND d.dname = 'SALES';

EMPNO ENAME DNAME JOB
---------- ---------- -------------- ---------
7499 ALLEN SALES SALESMAN
7698 BLAKE SALES MANAGER
7654 MARTIN SALES SALESMAN
7900 JAMES SALES CLERK
7844 TURNER SALES SALESMAN
7521 WARD SALES SALESMAN

6 rows selected.

-- 获取hash_value
SELECT hash_value, child_number, sql_text FROM v$sql WHERE sql_text LIKE 'SELECT e.empno, e.ename, d.dname, e.job%';

HASH_VALUE CHILD_NUMBER
---------- ------------
SQL_TEXT
----------------------------------------------------------------------------------------------------
3909283366 0
SELECT e.empno, e.ename, d.dname, e.job FROM emp e, dept d WHERE e.deptno = d.deptno AND d.dname = '
SALES'


1 row selected.

-- Create outline .
BEGIN
DBMS_OUTLN.create_outline(
hash_value => 3909283366,
child_number => 0,
category => 'SCOTT_OUTLINES');
END;
/

-- 检查.
COLUMN name FORMAT A30
SELECT name, category, sql_text FROM user_outlines WHERE category = 'SCOTT_OUTLINES';

NAME CATEGORY
------------------------------ ------------------------------
SQL_TEXT
--------------------------------------------------------------------------------
SYS_OUTLINE_05092314510581419 SCOTT_OUTLINES
SELECT e.empno, e.ename, d.dname, e.job FROM emp e, dept d WHERE e.deptno = d.de

EMP_DEPT SCOTT_OUTLINES
SELECT e.empno, e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno


2 rows selected.

-- list
COLUMN hint FORMAT A50
SELECT node, stage, join_pos, hint FROM user_outline_hints WHERE name = 'SYS_OUTLINE_05092314510581419';


NODE STAGE JOIN_POS HINT
---------- ---------- ---------- --------------------------------------------------
1 1 0 NO_EXPAND(@"SEL$1" )
1 1 0 PQ_DISTRIBUTE(@"SEL$1" "E"@"SEL$1" NONE NONE)
1 1 0 USE_MERGE(@"SEL$1" "E"@"SEL$1")
1 1 0 LEADING(@"SEL$1" "D"@"SEL$1" "E"@"SEL$1")
1 1 0 NO_STAR_TRANSFORMATION(@"SEL$1" )
1 1 0 NO_FACT(@"SEL$1" "E"@"SEL$1")
1 1 0 NO_FACT(@"SEL$1" "D"@"SEL$1")
1 1 2 FULL(@"SEL$1" "E"@"SEL$1")
1 1 1 INDEX(@"SEL$1" "D"@"SEL$1" ("DEPT"."DEPTNO"))
1 1 0 NO_REWRITE(@"SEL$1" )

10 rows selected.

Using Outlines

--检查outline是否已经被使用
SELECT name, category, used FROM user_outlines;

NAME CATEGORY USED
------------------------------ ------------------------------ ------
SYS_OUTLINE_05092314510581419 SCOTT_OUTLINES UNUSED
EMP_DEPT SCOTT_OUTLINES UNUSED

2 rows selected.

-- 对下面的语句建大纲
SELECT e.empno, e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno;
SELECT e.empno, e.ename, d.dname, e.job FROM emp e, dept d WHERE e.deptno = d.deptno AND d.dname = 'SALES';

-- 检查outline的使用情况
SELECT name, category, used FROM user_outlines;

NAME CATEGORY USED
------------------------------ ------------------------------ ------
SYS_OUTLINE_05092314510581419 SCOTT_OUTLINES UNUSED
EMP_DEPT SCOTT_OUTLINES UNUSED

2 rows selected.

-- 启用outline
ALTER SESSION SET query_rewrite_enabled=TRUE;
ALTER SESSION SET use_stored_outlines=SCOTT_OUTLINES;

-- 运行语句
SELECT e.empno, e.ename, d.dname FROM emp e, dept d WHERE e.deptno = d.deptno;
SELECT e.empno, e.ename, d.dname, e.job FROM emp e, dept d WHERE e.deptno = d.deptno AND d.dname = 'SALES';

-- 检查使用情况
SELECT name, category, used FROM user_outlines;

NAME CATEGORY USED
------------------------------ ------------------------------ ------
SYS_OUTLINE_05092314510581419 SCOTT_OUTLINES USED
EMP_DEPT SCOTT_OUTLINES USED

2 rows selected.


Dropping Outlines

BEGIN
DBMS_OUTLN.drop_by_cat (cat => 'SCOTT_OUTLINES');
END;
/

2009年3月9日星期一

几种SQL模式

小结果集,直接条件(access by index)
小结果集,间接条件 (join)
多个宽泛条件的交集是小结果集
多个宽泛条件的交集(不一定小,考虑使用sql hint)

大结果集(full table scan+并行查询)

基于一个表的自连接(考虑使用分析函数)

通过聚合获取结果集(尽量减少聚合数据量)

基于日期的搜索(考虑使用子查询优化)

ORACLE sql调整(一)

sql profile:
基本使用方法:
fy@ORCL> drop table t;

Table dropped.

fy@ORCL> create table t(id constraint t_pk primary key,pad) as
2 select rownum,lpad('*',4000,'*')
3 from all_objects
4 where rownum<=10000; Table created. sys@ORCL> grant advisor to fy;

fy@ORCL> var tn varchar2(30)
fy@ORCL> declare
2 l_sqltext clob:='select count(*) from t where id+42=126';
3 begin
4 :tn:=dbms_sqltune.create_tuning_task(sql_text=>l_sqltext);
5 dbms_sqltune.execute_tuning_task(:tn);
6 end;
7 /

PL/SQL procedure successfully completed.

fy@ORCL> select dbms_sqltune.report_tuning_task(:tn) from dual;


DBMS_SQLTUNE.REPORT_TUNING_TASK(:TN)
-------------------------------------------------------------------------------

GENERAL INFORMATION SECTION
-------------------------------------------------------------------------------
Tuning Task Name : TASK_122
Tuning Task Owner : FY
Workload Type : Single SQL Statement
Scope : COMPREHENSIVE
Time Limit(seconds): 1800
Completion Status : COMPLETED
Started at : 03/10/2009 10:34:02
Completed at : 03/10/2009 10:34:02

-------------------------------------------------------------------------------
Schema Name: FY
SQL ID : 8m7h2qakgcajz
SQL Text : select count(*) from t where id+42=126

-------------------------------------------------------------------------------
FINDINGS SECTION (3 findings)
-------------------------------------------------------------------------------

1- SQL Profile Finding (see explain plans section below)
--------------------------------------------------------
A potentially better execution plan was found for this statement.

Recommendation (estimated benefit<=10%) --------------------------------------- - Consider accepting the recommended SQL profile. execute dbms_sqltune.accept_sql_profile(task_name => 'TASK_122',
task_owner => 'FY', replace => TRUE);

Validation results
------------------
The SQL profile was tested by executing both its plan and the original plan
and measuring their respective execution statistics. A plan may have been
only partially executed if the other could be run to completion in less time.

Original Plan With SQL Profile % Improved
------------- ---------------- ----------
Completion Status: COMPLETE COMPLETE
Elapsed Time(ms): 0 0
CPU Time(ms): 0 0
User I/O Time(ms): 0 0
Buffer Gets: 23 21 8.69%
Disk Reads: 0 0
Direct Writes: 0 0
Rows Processed: 1 1
Fetches: 1 1
Executions: 1 1

2- Index Finding (see explain plans section below)
--------------------------------------------------
The execution plan of this statement can be improved by creating one or more
indices.

Recommendation (estimated benefit: 95.23%)
------------------------------------------
- Consider running the Access Advisor to improve the physical schema design
or creating the recommended index.
create index FY.IDX$$_007A0001 on FY.T("ID"+42);

Rationale
---------
Creating the recommended indices significantly improves the execution plan
of this statement. However, it might be preferable to run "Access Advisor"
using a representative SQL workload as opposed to a single statement. This
will allow to get comprehensive index recommendations which takes into
account index maintenance overhead and additional space consumption.

3- Restructure SQL finding (see plan 1 in explain plans section)
----------------------------------------------------------------
The predicate "T"."ID"+42=126 used at line ID 2 of the execution plan
contains an expression on indexed column "ID". This expression prevents the
optimizer from selecting indices on table "FY"."T".

Recommendation
--------------
- Rewrite the predicate into an equivalent form to take advantage of
indices. Alternatively, create a function-based index on the expression.

Rationale
---------
The optimizer is unable to use an index if the predicate is an inequality
condition or if there is an expression or an implicit data type conversion
on the indexed column.

-------------------------------------------------------------------------------
EXPLAIN PLANS SECTION
-------------------------------------------------------------------------------

1- Original With Adjusted Cost
------------------------------
Plan hash value: 454320086

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 4 | 7 (0)| 00:00:01 |
| 1 | SORT AGGREGATE | | 1 | 4 | | |
|* 2 | INDEX FAST FULL SCAN| T_PK | 13 | 52 | 7 (0)| 00:00:01 |
------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("ID"+42=126)

2- Original With Adjusted Cost
------------------------------
Plan hash value: 454320086

------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Tim


使用以下语句可以生成类似EM的建议:
select a.command as type,
f.message as findings,
a.message as recommendation,
t.message as rationable
from dba_advisor_actions a,
dba_advisor_recommendations r,
dba_advisor_findings f,
dba_advisor_rationale t
where
a.task_id=122
and a.task_id=r.task_id
and a.rec_id=r.rec_id
and a.task_id=t.task_id
and a.rec_id=t.rec_id
and f.task_id=r.task_id
and f.finding_id=r.finding_id;

fy@ORCL> alter session set sqltune_category=TEST;

Session altered.

fy@ORCL> begin
2 dbms_sqltune.accept_sql_profile(
3 task_name=>'TASK_122',
4 name=>'frist_rows.sql',
5 category=>'TEST',
6 force_match=>true
7 );
8 end;
9 /

PL/SQL procedure successfully completed.

dbms_sqltune.alter_sql_profile(
name=>'fisrt_rows.sql',
attribute_name=>'CATEGORY',
value=>'DEFAULT'
)

关于sql profile的基本操作,可以看看dbms_sqltune这个包(好像可以直接导入),明天研究一下sqlprofile的机理。

操作系统的革命

前两天看过了操作系统的革命,英文名字是Revolution OS。虽然很早就听别人说过这部电影,但是知道最近才看了看,同时看的还有《代码》,感觉后者比前者差远了。

映象最深的是linus说linux的很多灵感来自sunOS,而不是minix,当然我也不知道他是不是说的真的,只是我觉得像sunOS(后来改名 solaris)这种大的操作系统,模仿起来因该不会很容易,反而minix因为代码量比较小,而且专门有书来解释这个系统是怎么设计的,因此学习和模仿应该更容易一些。

通过这个电影,我第一次知道微内核和单内核的差别。后来查了linus和Andrew Tanenbaum的那段著名论战和若干年后的后续论战,才对这些基本概念了解了一些。其实也看得出来linus也承认微内核比单内核要好一些,个人觉得 minix之所以没有成为主流,主要是因为Andrew Tanenbaum要保证minix的个头足够小,可以让学生一学期就学明白。不过stallman的hurd到现在也不稳定,的确让人觉得很奇怪。

不过这部电影没有夸大linux kernel的作用,虽然我觉得linus写出系统内核非常重要,但是没有那些gnu工具的话,内核一样也没什么用处。

比较linus和stallman,其实我更喜欢stallman一点,可能主要是因为我自己更理想主义一点,linus和eric太实用主义了。而且像stallman那样,为了自己的理想奋斗一辈子的人,其实挺少见的,颇有些不为五斗米折腰的意思。

电影对于linux发展过程中的重要事件交代的其实挺少的,这是美中不足的地方。像apache web server还重点讲了讲,像ORACLE对于linux的支持就一带而过。其实就我知道的,linux之所以能在server端取得这么大的进步,跟 ORACLE在98年对linux的支持密不可分,毕竟linux必须赢得企业的认可,才能做大。

2009年3月8日星期日

使用ORACLE高级查询重写

在无法修改sql的情况下,有以下几种方法可以调整sql:
sql outline,
sql profile,
advanced rewrite

这里主要说明高级查询重写的功能。
首先建立测试表格和数据:
create table t1(c1, c2, c3, c4)
as
select to_char(level), to_char(level), to_char(level), to_char(level)
from dual
connect by level <= 1000000
;

create index t1_n1 on t1(c1); -- c1 has index;

这时查看下列语句的执行计划,可以发现是走了全表扫描,
fy@ORCL> set autot traceonly explain
fy@ORCL> select * from t1 where c1 like '%11%';

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50000 | 1318K| 1257 (2)| 00:00:16 |
|* 1 | TABLE ACCESS FULL| T1 | 50000 | 1318K| 1257 (2)| 00:00:16 |
--------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

1 - filter("C1" LIKE '%11%')

将查询重写为:

fy@ORCL> select /*+ leading(x) use_nl(x t1) */ t1.* from (select /*+ index_ffs(t
1) */ rowid as row_id, c1 from t1 where c1 like '%11%') x,t1 where t1.rowid = x.
row_id;

Execution Plan
----------------------------------------------------------
Plan hash value: 2525156207

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50000 | 2246K| 50704 (1)| 00:10:09 |
| 1 | NESTED LOOPS | | 50000 | 2246K| 50704 (1)| 00:10:09 |
|* 2 | INDEX FAST FULL SCAN | T1_N1 | 50000 | 927K| 688 (2)| 00:00:09 |
| 3 | TABLE ACCESS BY USER ROWID| T1 | 1 | 27 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("C1" LIKE '%11%')

这时可以看出,这个查询走了索引,虽然效果并不好。

赋予相应的权限和修改会话属性:

grant execute on dbms_advanced_rewrite to fy;
grant create materialized view to fy;
alter session set query_rewrite_integrity = trusted;

建立高级查询重写:
begin
sys.dbms_advanced_rewrite.declare_rewrite_equivalence (
name => 'rewrite2',
source_stmt =>
'select * from t1 where c1 like ''%11%''',
destination_stmt =>
'select /*+ leading(x) use_nl(x t1) */ t1.* from (select /*+ index_ffs(t1) */ rowid as row_id, c1 from t1 where c1 like ''%11%'') x,t1 where t1.rowid = x.row_id',
validate => false,
rewrite_mode => 'text_match');
end;
/

然后再执行第一条语句:
fy@ORCL> select * from t1 where c1 like '%11%';

Execution Plan
----------------------------------------------------------
Plan hash value: 2525156207

-------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time
|
-------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 50000 | 1660K| 50704 (1)| 00:10:09 |
| 1 | NESTED LOOPS | | 50000 | 1660K| 50704 (1)| 00:10:09 |
|* 2 | INDEX FAST FULL SCAN | T1_N1 | 50000 | 341K| 688 (2)| 00:00:09 |
| 3 | TABLE ACCESS BY USER ROWID| T1 | 1 | 27 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

2 - filter("C1" LIKE '%11%')

最后,删除查询重写:
begin
sys.dbms_advanced_rewrite.drop_rewrite_equivalence( name=> 'rewrite2');
end;
/

不过我本来是想将查询重写成全文索引的,但是如果重写成全文索引,就会报错:
ERROR at line 1:
ORA-03113: end-of-file on communication channel
Process ID: 5280
Session ID: 170 Serial number: 8


不知道是为什么,准备问问OTN上的高人。

2009年3月6日星期五

上网本的未来在哪里?

从apple4us转过来的,原文请看这里。顺便说说,我不同意作者的观点,毕竟很多新的上网本在性能和体验方面更好一些。

虽然你很难从业界人士口中得到这样的确认(尤其是Intel),但如果你足够敏锐,就会觉察到,上网本目前已经发展到了一个相当尴尬的境地——体积越来越大、性能差强人意,甚至价格上曾经的优势,也正在失去。



第一台真正意义上的上网本——EeePC,最初是被设计作为一台廉价、小型化、弱性能、能够快速开启的“亚笔记本”而上市销售的,它的设计者ASUS显然对之后的上网本风潮缺乏准备——这从他们之后的混乱的产品结构就可以知道——以至于如今他们对整个市场的发展几乎不再具有影响力。



然而将上网本目前的尴尬境地归咎于ASUS的缺乏前瞻,却未免有些有失公允——如果要找出一个应该为此负责的公司,那毫无疑问应该是Intel。



上网本流行起来最大意义在于,它暂时将笔记本从“配置疯狂”的发展路线中拖出来,开启了一条以应用体验为基础的产品路线——一台“够用”的笔记本——但或许是这股热浪来得太快,无论是制造商还是消费者,都还没有准备好,没有人能够清楚地认识到这是与传统的以性能为准绳的发展路线分道扬镳的路线,所以当打着Intel金字招牌的Atom芯片出现时,这一脆弱的体系很快被妥协、同化,又被拖回了“摩尔定律”的旋涡中。



如今,已经有上百款“上网本”在货架上售卖时,我们回过头来看,却发现最能够体现“上网本”精髓的,竟然依然是初代的EeePC——7寸屏幕;仅仅是“够用”的、无法升级的配置;搭载能够快速启动的定制版Linux;以及2799元的售价。而现在的所谓“上网本”,主流尺寸已经提升至10寸,无一不是鸡肋的Atom芯片搭配上百G的低效能硬盘与臃肿的Windows系统,甚至还出现了DELL MINI 12与SAMSUNG NC20(VIA Nano芯片)这样12英寸的“怪物”——上网本,已经变得和传统的笔记本电脑没什么不同,它的价格,也随着Win-tel组合水涨船高,从299美元、399美元到499美元,现在599美元的“新产品”已经没什么值得奇怪了——说真的,这些电脑的效能还不如一代的迅驰,一台9寸的Fujitsu P5120或是12寸的IBM X40加一根内存条就可以轻易击败这些“新产品”,但它们在二手市场上的价格只有这些新款上网本的一半,甚至更便宜。至于Intel在Atom发布时曾许下的诺言——无外接电源24小时不间断工作,现在也被证明只不过是“皇帝的新衣”,采用Atom芯片的上网本的实际使用时间始终徘徊在5小时左右——除非插上一块令机身体积陡增1/3的巨大电池。



要打破目前的尴尬境地,上网本制造商们不“刮骨疗毒”恐怕是不行的,x86架构与Windows操作系统的组合,已经不再是上网本的最大卖点,反倒成了最大的桎梏。而希望,却来自于一种5年前就被我们所抛弃、所遗忘的设备——Handheld PC。



最后一代Handheld PC(简称HPC),诞生与2003年——没错,就是那台因为Netbook名称之争而重新回到我们视野中的PSION Netbook PRO——一台采用400MHz ARM处理器、Windows CE.NET系统和笔记本电脑外形的7.7英寸机器。从性能而言,它很弱,甚至还及不上现在大多数的智能手机,但如果你不用它来看电影和玩3D游戏,仅仅是上网、处理文本和玩玩ZUMA、珠宝奇侠之类的“杀时机”,那你会惊奇地发现,它比大多数的上网本运行得更为流畅,并且它提供了x86架构永远也做不到的事情——即开即用——硬件基础的不同决定了x86架构永远无法做到这一点。Windows CE.NET这样的嵌入式系统完全运行于内存中,而x86则必须依靠硬盘,两者的传输速率的巨大差距与运行状态的差异意味着即使Intel的芯片再快,也无法让Windows(或是x86架构下的MAC OS、Linux等其他操作系统)做到即时启动——即使是待机状态也不行(Windows的待机状态事实上要经历两次内存与硬盘的数据交换,它的原理就是在C盘中辟出一块与内存同样大小的缓存来存储待机前的内存数据,于是违反我们直觉的事发生了——机器的内存越大,进入待机状态和回复到桌面的时间就越长)。SONY的VAIO P就因为Windows Vista的启动过于缓慢而另外搭载了一个类似于PSP界面的“轻省系统”,但即使是这个“轻省系统”,从按下电源开关到进入桌面也需要26秒,与Netbook PRO的0.1秒相比差得实在是太远了。而更令人诧异的是,用一块普通的三芯锂电池,就可以维持Netbook PRO运行8小时以上以及待机两周——这一对于上网本来说已经是非常优秀的成绩,在HPC中仅仅是“合格”而已,更早一代的产品,HP的Jornada 728,仅靠一颗1960毫安的电池就可以运行14个小时——这或许就是停产5年之后,许多报道奥运会的前线记者依然钟情于他们的老Netbook PRO的缘故。



我毫不怀疑,ARM架构(MIPS架构)与嵌入式系统的组合将是上网本的解毒剂。看看微软新版的Windows Mobile 6.5,在上网本的主要职责:日常应用方面,有哪一点及不上Windows系统了呢?至于多媒体方面(主要是流媒体的播放和3D性能)的缺陷,则很容易通过在系统中加入DSP芯片来弥补——TI的DA VINCI芯片、RMI的Alchemy芯片,或是nVidia和ATI专为手机开发的移动显示芯片,甚至是ADI的解码芯片,选择有很多——即使不另外加装DSP,新一代的ARM架构芯片也足够快了,Qualcomm的SnapDragon处理器主频已经突破了1GHz(它已经被用在了TOSHIBA的新款WM手机TG01上,09年第三季度上市),而它将要应付的,是比Windows XP这样的庞然大物轻几个数量级的Windows Mobile/Symbian/Android。



你或许会担心摆脱了Win-tel的上网本又为自己树立了新的敌人——智能手机和MID,但相信我,那是完全不同的东西——即使配置相同,一台能够用10个手指打字的笔记本,与一台用两个拇指操纵的设备,给人的心理暗示也是完全不同的,对待一台“笔记本电脑”(或至少是形似“笔记本电脑”的设备),人们总是会更认真一点,而智能手机和MID,不到迫不得已,没人会用它来处理文档,它们更适合用来做那些不涉及大量输入的工作——在写文章时,我宁愿用那台屏幕晦暗的HP Jornada 720,也不愿用我的HP 6965手机——而文档处理正是上网本的主要职责之一。



回到上网方面,运行相同操作系统的上网本与智能手机/MID相比也拥有天生的优势。因为体积的限制,智能手机/MID的屏幕能够做到5英寸差不多已经是极限了,再大,就无法“口袋化”,而失去一个重要的竞争砝码——在如此细小的屏幕上,分辨率受到了限制,屏幕能够显示的往往只是网页的一部分,用户不得不放大/缩小或是不断左右拖动才能完整地浏览网页。但上网本不存在这一限制——我相信7英寸是个相当合理的尺寸,长边分辨率达到1024,或是更进一步的1280,不必左右拖动就能完整显示大部分的网页,字体也不至于太小,上网的体验相当舒适。



软件扩展,也不必太担心,至少在Windows Mobile平台上,我们已经拥有了不少现成的软件,APP STORE的上线也将进一步扩展应用软件的数目——或是干脆重新开启被微软放弃的Windows CE。不要小看似乎已经被“山寨化”了的Windows CE,事实上,Windows CE从界面上更接近Windows——多窗口,通过任务栏切换,还拥有十分有用的“远程桌面”——而不像Windows Mobile,明明是一个多任务系统,却人为地设置成单任务的界面。至于兼容性,如果你足够了解系统,那么拷贝或替换几个DLL文件就能够运行大部分的Windows Mobile程序,如果你对系统不够了解,也会有Redgear这样的软件扩展帮你的忙。我为什么一直在谈Windows Mobile——APPLE和GOOGLE的粉丝大概要不高兴了,不过鉴于Jobs大人说过“我们不做500美元以下的垃圾笔记本”,我们只好暂且把Mac OS X移动版排除在外;至于Android,他们才刚刚搞定复制/粘贴、蓝牙立体声和软键盘,以本届MWC上Cup Cake软件包的进步来看,最好还是不要对GOOGLE寄予太大希望为好——如果你坚持认为Linux环境下的开发者就是比Window CE/Mobile环境的程序员牛B,那我看我也没法说服你。不过顺便说一句,现在的Windows CE,已经处于半开源状态(开放所有源代码,但不免费)——如果这消息能够令你舒服一点的话。



江湖中的传闻是,不单是TI、RMI,ARM、NOKIA和Freescale都要来做上网本,我期待着第一台机器的上市,期待着HPC的复兴——那才是一台“上网本”所应该有的表现——或许很快,Intel就要后悔他们把PXA Xscale芯片研发部门卖给MARVEL这幢生意了。


最后,我们应当记住,曾经有一个公司,曾经有一台机器,比任何人都更早地看清了这一切,但却没有更多的人意识到它的价值——过于先知先觉,或许未必是件好事——这就是PALM的Foleo,它的胎死腹中,实在是令人相当地惋惜,但或许要等“上网本”被重新定义之后,我们才会知道在2007年的9月,我们失去了一台怎样的机器。

2009年3月4日星期三

性能问题来自什么

最近,看了看公司产品的源代码,从第一天看的时候,我就清醒的认识到这个产品会有很严重的性能问题,主要问题在以下几个方面:

1 documentum的dql语句会被转化为SQL语句在ORACLE中执行,根据我对这个产品的研究,所有的dql都没有对应的绑定变量方式,因此可能造成严重的性能问题和共享池碎片。

2 所有的全文检索都是使用like '*string*'型的查询来完成,这样的话数据库根本没有办法使用索引,完全使用蛮力来完成工作。

3 对很多方法都进行了过度包装,我曾经分析下载功能,发现居然包装了17层,每次看到这里,就想起《Unix编程思想》里的那句话,如果你很清楚你要做什么,那么分3层足够了,如果你不清楚自己要做什么,17层也没什么帮助。

4 历史代码问题,公司产品里积压了好多年的历史代码,不慢才怪。

解决方法:

1 继续寻找使用绑定变量的方法。
2 使用ORACLE全文检索技术来替代dql,或者看看like操作能不能被查询重写成contains操作。
3 重写代码。

2009年3月1日星期日

上网本的冲击

转自Apple4us,原文请看这里

译完这篇 Clive Thompson(克莱夫·汤普森)在《连线》发表的关于时下最热门的上网本(Netbook)的文章,很大程度上改变了我对上网本前途的看法。

回想起了英特尔前 CEO 安迪·格鲁夫在《只有偏执狂才能成功》一书中提到的故事:

他们那一代人对计算机的感知并不像我们大多数,“计算机=个人电脑”,因为他们经历了 1980 年代之前传统的大型机阶段,所以当大型机逐渐被“微机”(一个已经被遗忘很久的对个人电脑的称呼,也是“微软-Microsoft” 名字的来源)取代后,他们也在担心,计算机是否会朝着更加小型化的方向发展,因为对未来的误判可能会造成公司战略上的错误——正是“个人电脑”的趋势让英特尔果断决定把主要业务从存储器切换到处理器。

他们无法预测未来,所以采取的验证未来的办法——安迪·格鲁夫让英特尔在内部自己尝试制造更加小型的计算机设备,以验证超小型计算机是否有前途,后来我们也听到不少关于英特尔制造的长条形的上网设备的消息。英特尔的最大竞争对手 AMD 自然也不能坐视不管,他们也在实验自己的 PIC 上网设备,为未来探路。据说同一时期,甲骨文公司的拉里·埃里森也对超小型上网设备的前途着迷,也进行类似的尝试。

不过,或许是这些先见之明早于了时代的环境,无论是硬件水平,还是网络技术,这些小型上网设备的尝试都在一个尚未成熟的大环境中进行,所以最终的结果可想而知。

上网本无疑是当初的那些失败尝试的复活,由大型机(集中计算)- 微型机(分布计算)- 超小型机(云计算)这条演化之路终究还是来了。硬件水平是重要的一方面,1.6 GHz 的低功耗单核 Atom 芯片在五年前也是难以做到的,当前上网本的小容量或许也会随着存储器的摩尔定律而消失。但更重要的“云计算”技术让上网的能力不再受制于硬件水平——这个统治 PC 产业几十年的基础正在变得薄弱。亚马逊开始把云计算服务当作产品来卖,计算机硬件的利润也越来越薄,或者有一天“云计算”就像我们去缴手机费一样成为 PC 产业的主要利润来源。

当然了,我无法预测常规电脑是否会消失,这让人觉得有些不可思议。毕竟,几天前上网本在我眼里还是鸡肋的“小朋友的玩具”,但当我们真的只需要一块“显示屏+无线芯片”就能够让强大的云端帮我们完成所有操作的时候,那时我们再来讨论这个问题吧。

Mary Lou Jepsen 并没有打算发明一种叫上网本(netbook)的电脑,然后让电脑行业朝着颠倒的方向发展。她只不过是想创造一种超级便宜的笔记本电脑。2005 年,作为 LCD 液晶显示屏技术的先驱,Jepsen 接手了一项负责领导和研发一款新的机器,也就是后来被称为“One Laptop per Child(每个孩子一台电脑)”的 OLPC 项目。这个项目是由 MIT 媒体实验室的梦想家尼古拉·尼葛洛庞蒂(Nicholas Negroponte)牵的头,Jepsen 作为项目的首席技术官,目的是为发展中国家的孩子们开发出一种廉价的笔记本。这种笔记本可接入 Wi-Fi 网络、拥有彩色显示屏和标准全键盘——售价大约 100 美元左右。以这个价格,第三世界的政府们能够采购数百万台这样的电脑,将它们免费分发给农村地区的孩子们。此外,它还需要体积比较小、对极端环境适应力非常强、而且能够在耗电很小的情况下运行。“世界上,有一半左右的孩子们不能够正常有规律的获得电力”,Jepsen 指出。

这些苛刻的条件限制强迫她必须想出各种办法。她选用闪存取代通常的硬盘作为电脑的存储设备,就是你的 U 盘里的那种,因为它不仅耗电很小,而且当它坠落到地上的时候也不容易损坏。在软件方面,她选择 Linux 和其他免费或开源的软件,以节省从微软购买的费用。她选择 AMD Geode 处理器,虽然它速度不是很快,但耗电量很小。而且作为显示屏技术专家,她还设计了一种独有的 LCD 面板,能够检测当前屏幕上显示的图像是否是静态的(比如当你在阅读一篇文档的时候),如果是的话,主处理器会自动关闭以节省宝贵的电量。

为了制造这台代号为 XO-1 的笔记本,OLPC 项目雇用了台湾的制造公司广达(Quanta),它可能算不上是家喻户晓的名字,不过广达(Quanta)是全球最大的笔记本制造商。很可能,此刻你桌子上的那台电脑的很多零部件都是广达制造的,甚至包括设计,无论它是来自苹果、戴尔,还是惠普。像大多数台湾的电脑制造商一样,广达雇用了不少这个星球上最顶尖的工程师。他们解决了许多Jepsen 遇到的最有挑战性的工程难题。到 2007 年,OLPC 终于出炉了。这个世界上落后地区的孩子们也有可能拥有他们自己的笔记本——不过价格不只100 美元,比这个价格还要高不少。

很大程度上是从 OLPC 项目获得的灵感,还包括一些对 OLPC 的恐惧,华硕(Asustek) ——广达(Quanta)在台湾的主要竞争对手,也是全球第七大笔记本制造商——开始生产自己的廉价且性能也较低笔记本。与OLPC 一样,这种笔记本也采用廉价的方式使用 Linux 操作系统、闪存和只有 7 英寸大小的显示屏,没有 DVD 光驱,也不足以运行像 Photoshop 这样的大程序。事实上,华硕(Asustek)刻意强调它的主要功能是查看电子邮件、上网冲浪。华硕预计,这种廉价笔记本主要顾客是,儿童、老年人、以及印度或中国那些负担不起1000 美元的电脑的新兴阶层。

但事情发展却并非像原先预计的那样,当华硕 2007 年秋季推出 Eee PC 之后,仅几个月的时间总计 35 万台的库存全部售完,但这些 Eee PC 并不是被那些贫穷国家的人们买走,而是被西欧和美国的中产阶级消费者购买,这些人希望拥有自己的第二台电脑,可以方便地放进手提包里,随时随地地访问 YouTube 或者Facebook 。很快主流的个人电脑品牌,戴尔、惠普和联想纷纷跟上,到了 08 年秋季时,几乎美国的所有电脑制造商都匆忙推出自己的 400 美元左右上网本(Netbook)进入这一市场。

所有的这一切,当你回过头来看时,你会觉得事情前进的路线是令人难以置信的怪异。上网本(Netbook)打乱了电脑硬件生意的所有法则。传统上,个人电脑总是沿着从高端市场向大众市场扩展的路线。PC 制造商们会首先在高端产品上采用新技术和功能,然后再推广到大众市场。但多年之后,这股变革终于波及到了低端产品。

尽管 Jepsen 的设计最终在向上流动,但她在创造一台适合穷人们使用的笔记本的过程中,也向传统 PC 用户展示了一点:其实他们并不想从笔记上获得更多的功能——他们希望少一些。

mf_netbooks2

细节一瞥:笔记本 VS. 上网本

许多上网本放弃了高速的板载处理器和很占空间的硬盘,取而代之的是各种在线软件和容量更小但速度更快的固态硬盘。结果呢?一台强大的机器诞生了,但价格只要原来的三分之一。

netbook_vs_Laptop

截至 2008 年底,华硕已经售出了 500 万台上网本,其他品牌总计售出了1000 万台。(欧洲的消费者对上网本尤其热衷,欧洲市场上网本的销量比美国多出 8 倍。)仅用了一年时间,上网本的发货量已占到全球笔记本发货量的 7% ,明年预计将达到 12% 。

“我们一开始是在为了金字塔的底部的人们而进行创造,”Jepsen 说:“但现在金字塔顶部的人们也想要它们。”上网本,这种上滴(trickle-up)式的创新,如果不是先革了计算机行业的命,最终也会重新塑造计算机行业的面貌。

此刻,我正在使用上网本写这个故事。如果你碰巧从我的背后看一眼,你会发现我的上网本桌面上只有两个图标:一个火狐(Firefox)浏览器和一个回收站,其他的什么都没有。

因为我花在电脑上的 95% 的时间都是在浏览器内进行,更新我的Twitter 和Facebook ,还有写博客。Meebo.com 让我能够同时登录多个即时通讯的账户。Last.fm 给我播放音乐,webmail 处理邮件。我使用 Google Docs 进行文字处理,如果我需要录制视频,我可以直接通过摄像头录制然后上传到 YouTube 。哦,我想起来了,因为我的上网本上几乎从不存储文件,我得考虑一下是不是把回收站的图标也去掉。

上网本已经终结了电脑的性能之战。过去,当你走进电子产品商店购买电脑时,你总是捡你能够买得起的产品中性能最好的买。因为,谁知道呢?也许有一天你需要玩一下尖端的视频游戏,或者编辑一下你拍摄的视频杰作。过去 15 年来,PC 行业通过不断推进产品升级以强迫我们本能地对性能产生怀疑。英特尔和 AMD 从不间断地推出更高速的芯片,硬盘容量的增长如脱缰野马般飞速,内存一再打破新纪录,高端图像显卡让你能在 17 寸的笔记本屏幕上播放蓝光电影。这些梦幻般的机器几乎可以做任何事情。

不过,这里有一个圈套:在绝大部分时间,我们几乎不做任何事情。我们共同的任务只是——处理电子邮件、上网、观看网络视频——只需很小的处理能力即可。只有很少的一部分人,比如图形设计师或核心游戏玩家,他们需要跟结实的机器。多年以来几乎没人意识到,PC 行业的运作已经很像汽车业在销售 SUV :他们不断推出这些愚蠢而强大的机器仅仅因为它们的利润很高,而消费者们则在幻想或许有一天会驾驶它去越野,但事实上等买回家之后他们却从不这样做。而软件开发者们则正好利用这些剩余的计算能力,编写体积越来越庞大的软件和操作系统。

而上网本所做的,事实上,只不过是把这个时钟拨慢:这些机器的性能只有笔记本四年前的水平,而四年前的水平(或多或少)已经足够了。“常规电脑已经如此之快,以至于你根本分辨不出 1.6 千兆和 2 千兆有什么区别,”微星(MSI)美国区销售副总裁 Andy Tung 表示,这家台湾公司推出了 Wind 上网本。“我们能够区别一秒和两秒,但却不能分辨出万分之一秒和万分之二秒。”对当前大多数的计算任务来说,最大的性能阻力不在机器内部,而是计算机的外部。如果你的 Wi-Fi 信号足够强,Twitter 还会掉线吗?

上网本证明,我们现在开始明白个人电脑到底是用来干什么。换句话说,只有很少的一些事情要做,而且几乎全部是在线操作的。这正是华硕的顿悟,他们创造了一台价格低于 300 美元的笔记本,但是如果不联网,这台设备几乎没有任何意义。考虑一下:Eee 最初的闪存容量只有 4GB ,小到你不得不把所有的照片、视频和文档都存储在网上——还要尽可能少地安装本地软件——因为你的机器内部几乎没有空间容纳。

上网本证明“云计算”并不只是天花乱坠的炒作,设计一台电脑,将它的各种工作外包给“云”已经很现实。云“尾巴”正在摇摆这只硬件“狗”。

top5_laptop_netbooks_1

大多数消费者几乎从未听说过台湾的那些安静的、也不出名的 PC 公司,但过去 30 年以来,他们一直居于那些最重要的硬件产品的幕后。广达(Quanta)最先在 80 年代获得关注,因为他们聪明地采用填鸭式的办法将各种零部件组装成自己的笔记本。然后,到了 2001 年苹果和他们签约,由他们从头到脚代工制造 PowerBook G4 笔记本。G4 获得了惊人的成功,广达(Quanta)也开始为其他主流电脑制造商进行工程设计。华硕(Asustek)和微星(MSI)也是台湾笔记本世界的另外两个巨人,他们的分支业务还从主板扩展到其他各种产品,从液晶电视到手机。这些公司非常的庞大:广达(Quanta)去年的销售收入达到 250 亿美元,比亚马逊(Amazon.com)、德州仪器(Texas Instruments)或电子艺界(Electronic Arts)这样的公司还大。

尽管这些台湾的制造公司在著名 PC 品牌面前还保持低调,但他们这些年来吸取的技术经验和知识足以按吨来计算。比如,1988 年当英特尔造出第一款 X486 芯片后,英特尔自己还没有解决主板兼容性的问题,华硕已赶在他们前面推出了可以兼容的主板。后来,华硕又为苹果制造笔记本零部件。前苹果公司的经理,现在在 DisplaySearch 做 LCD 市场分析师的 John Jacobs 回忆说:“十次有九次,当我们说‘跳’的时候,华硕会说‘需要跳多高?’,这说明华硕学到的东西非常多。”

不过虽然他们很成功,但在戴尔、惠普和苹果这样的大公司眼里,像华硕和微星这样的公司仍是局外人。所以当华硕推出 Eee 上网本时,有好几个月他们都没有做出任何反应。“所有的其他品牌都在想‘哦,这是粪便’,”华硕全球营销主管 Lillian Lin 回忆说。

戴尔和惠普这样的公司不会成为 400 美元笔记本的先驱,因为他们能够以1000 美元的价格卖出笔记本。为什么要把好好的事情弄得一团糟呢?而微星根本没有自己的笔记本业务,华硕自有品牌的笔记本业务也比较小,主要在亚洲和欧洲,后来这家台湾不再沉迷于销售 SUV 级的笔记本,他们能够像本田(Honda)一样突袭小型、高效的款型。多年在利润率极低的主板市场生存的经历也让他们知道如何制造便宜的产品。

在《创新者的困境》(The Innovator's Dilemma)一书中,克莱顿·克里斯汀生(Clayton Christensen)著名的论断是:真正的创新者只会来自于新贵,因为既有的获益公司很少愿意颠倒他们现有的商业模式。“上网本就是发生在 PC 行业的典型的克里斯汀生式的破坏性创新,” Willy Shih 说,作为哈佛商学院的教授,他的研究案例是广达(Quanta)在 OLPC 项目中的工作和华硕(Asustek)开发上网本。

Willy Shih 认为,这些台湾的公司已经在事实上对 PC 行业产生了巨大影响。在美国,人们把品牌和营销看作生意的核心职能部门,因为它们能说服人们去购买什么。但华硕证明,公司真正的杠杆是制造人们想要的产品。这些台湾的公司具有那种曾经用来描述美国的 geek 式聪明,但这种精神已随着我们的产业基础一起逐渐萎缩了。就笔记本制造业而言,台湾实际上占据着几乎整个市场,全球任何其他地方的笔记本制造量都微不足道。

如果你几年前问一家台湾硬件制造公司的 CEO ,他们与戴尔、惠普和苹果的关系怎样,他们会告诉你:美国的公司做品牌和销售,把设计和制造工作外包给台湾公司。但如果你今天问他们同样的问题,他们的说法会完全相反。“当我现在跟他们讨论时,”Willy Shih 笑着说:“他们说‘我们把品牌和销售外包给每美国公司’。”

“那 Photoshop 怎么办呢?”这是那些把上网本看作小孩玩具的人们最标准的反驳。当然,只有 1.6 GHz 的芯片和 Linux 操作系统用来处理电子邮件、观看YouTube 还可以。但要是你需要做一些真正的计算,比如一些复杂的图片编辑?“云”就帮不了你了,他们嘲笑着说。

在狭义上,的确如此:一个真正强大的软件,比如 Adobe Photoshop,需要高速的处理器。不过请看一下我的经历:今年春季,当我的常规 Windows XP 笔记本出现了一天两次崩溃,我把硬盘格式化了。然后重新安装了系统,但我找不到了 Photoshop 安装光盘。我就没管这事儿了——但一个星期之后,我写博客需要修改一张图片,我就在网上找到了 FotoFlexer,基于网页的免费图片编辑工具之一,我把照片上传上去,在一分钟之内我进行了裁切、增加色彩饱和度,还有锐化照片。

自那之后,我就再没有用过 Photoshop 。

请记住,我很喜欢 Photoshop 。我并不想制造任何意识形态的观点,比如我多么前沿,或者我多么讨厌付费盒装软件。只不过是寻找 Photoshop 安装盘的麻烦已经超过了使用 FotoFlexer 的便利。这个基于网页的应用仅 900 KB ,“平均到每个用户而言,它的速度其实很快,”Arbor Labs 实验室的 CEO Sharam Shirazi 告诉我,是他开发了这个应用。

我的 Photoshop 经历只不过是一个例子,用来解释软件行业正在发生改变。过去,软件开发者们被迫让软件的体积越来越大、功能越来越多,因为他们不得不猜想用户可能要做什么。但是,如果你的软件是在“云”上运行,你就能够知道用户此刻正在做什么——你可以实时看到。Shirazi 的公司发现,FotoFlexer 的用户很少做复杂的图片编辑,使用频率最高的工具是在图片上添加文字或者用鼠标涂鸦写字。再看看 Writely 这个应用,它最终被 Google 买走成为 Google Docs 中的文字处理软件:当 Sam Shillace 第一次将其发布上线时,他惊讶地发现,用户们使用最多的功能是多个用户共同编辑一篇文档。

“ 过去的情况是,我购买了一个绘图软件,我就获得了 5000 多种功能,但我根本不知道其中的 2000 多种功能在哪儿,我得到它只是为了以防万一,”Shillace 说:“而今天,哪个软件最容易使用,哪个软件有网络版成了关键。这些软件在功能上平等竞争,但在体积方面绝对算不上平等竞争。”

上网本的价格如此便宜,它正在重新塑造 PC 产业的基本经济学。去年 10 月份,英国移动运营商沃达丰(Vodafone)为其用户提供了一项新选择:如果用户与沃达丰签订为期两年的高速无线数据服务,沃达丰将免费送给用户一台戴尔 Mini 9 上网本。但这并不是一台免费电脑,毕竟,沃达丰从用户那里获得的 1800 美元两年服务费用完全可以负担的起送出一台上网本。(12月份,Radioshack 也提供了类似的选择,任何签订为期两年 AT&T 3G 服务的用户可以获得一台价值 99 美元的 Aspire 上网本。)

这些信号表明,计算机产业正在朝着与手机产业经济学的方向发展:硬件只是个必需品,向它收费很难,而真正有价值的,人们愿意为之付出高价的是——通讯能力。

上网本让 PC 行业打了一个又热又冷的战栗。没错,开辟一个新的产品种类固然很好。但在这个新的产品种类上,他们发现连赚 10 美分都难以置信地困难。300 美元的价格,勉强比零部件的成本高一点——甚至有时候还不够。“这一类产品的利润率几乎不存在,”Paul Goldenberg 轻笑着说,他是 Digital Gadgets 的总经理,该公司推出了一系列 Sylvania 品牌的上网本。“每个人都在说‘我们正在赔钱,不过最终我们会以量取胜的,不是吗?’”

PC 行业的几乎每一家公司,他们既有的策略和计划都被上网本连根拔起。08 年夏季微软决定停止销售 Windows XP ,让用户升级利润更高的 Vista 。但是,当 Linux 咆哮着冲向上网本的大门时,微软立刻改变了主意,决定延长 XP 销售期两年——专门为了上网本。许多专家认为,微软只能向上网本的 XP 操作系统收 15 美元,比之前 XP 售价的 1/4 还要低。(微软公司的副总裁 Brad Brooks 向我担保,他们能够从上网本操作系统上赚到大钱,而且还会确保下一代操作系统,Windows 7 也能够在上网本上运行——上网本运行 Vista 非常困难。)他的伙伴,英特尔已经向上网本制造商售出了数百万块低耗能的 Atom 芯片。“我们认为,这将会成为我们的下一个十亿美元的市场,”英特尔的技术营销主管 Anil Nanduri 表示——不过有一点他没说:一颗 Atom 芯片的利润只有常规笔记本高速芯片利润的很小一部分。

现在 PC 行业内最大的恐惧在于,他们创造的 300 美元的设备如此之好,以至于人们觉得不再需要花 1000 美元去买一台便携电脑。他们祈求上网本仍然是“第二次购买”——当你有了一个常规笔记本之后,再去购买你的第二台上网本。但同样可能的是,下次当你要换笔记本时,你会走进商店并问自己:“我平常只是上上网、看看邮件,为什么要买一台价格那么高的设备呢?”到那天来临时,微软、英特尔、戴尔、惠普或者联想,他们中的一些可能要倒闭了。

这个结果可能并不来自美国之手。事实上,在美国——上网本才刚刚起飞——可能难以领会上网本在欧洲和亚洲的流行程度和对行业面貌的改造。正如 Shih 告诉我的:“我曾与台湾的一家主要笔记本制造商的 CEO 谈话,他说‘这些地方是我的下一个十亿用户的来源’,他没有提到美国,他指的是金砖四国——巴西、俄罗斯、印度和中国——这里有数十亿对价格很敏感的用户。而这些制造商的决定——选择 Windows 还是 Linux 操作系统?微软的软件,还是免费的云应用?——都将对未来几年计算机行业的演化产生巨大的影响。”

上网本可能还会推动价格极低、重量很轻的计算机的诞生。“如果你的每件事情都是在网上完成,最终上网本会变成一块屏幕加一个无线芯片,你还需要主板吗?”OLPC 的设计师 Mary Lou Jepsen 说。“特别是,如果你要想电池的寿命更长,为何不用只用一个屏幕和一块真正便宜的只要 2~5 美元无线芯片制造电脑。”云计算也将强大到让今天的我们觉得不可思议。AMD 正在开发实验性的 3D 图形服务器机群,它可以运行高端视频游戏,并将图形流式传输到移动设备上,让你可以在一台没有板载处理器的设备上玩最尖端的视频游戏。AMD 的营销副总裁 Patrick Moorehead 回忆到,07 年的时候,游戏玩家需要购买特别强大的台式机,升级内存,装载 600 美元的显卡才能玩《孤岛危机》(Crysis):“现在想像一下,有个服务器机群在运行Crysis ,然后将视频流式传输到你的 iPhone 或者上网本,你只需要发送向量(Vectors)就能够控制游戏。”

因为这是硬件的未来:对小部分需要高性能设备的用户,PC 制造商会推出速度更强劲、更酷热——采用水冷却技术、屏幕与宽屏液晶电视一样大,售价 2000 美元的机器;而对于其他的用户——火车上需要找点资料的律师、渴望把所有东西都放进手提包的女士们——上网本将会统治,这是小机器的崛起。

(全文完)