2006年03月24日

经常有朋友问起,JSP和Servlet之间有什么区别,两者之间又有什么联系?其实Servlet技术的出现时间很早,是当时为了Java的服务器端应用而开发的。大家都知道Applet是应用小程序,Servlet就是服务器端小程序了。但在Microsoft公司的ASP技术出现后,使用Servlet进行响应输出时一行行的输出语句就显得非常笨拙,对于复杂布局或者显示页面更是如此。JSP就是为了满足这种需求在Servlet技术之上开发的。可见,JSP和Servlet之间有着内在的血缘关系,在学习JSP时,如果能够抓住这种联系,就能更深刻地理解JSP的运行机理,达到事半功倍的效果。
本文将通过对一个JSP运行过程的剖析,深入JSP运行的内幕,并从全新的视角阐述一些JSP中的技术要点。
HelloWorld.jsp
我们以Tomcat 4.1.17服务器为例,来看看最简单的HelloWorld.jsp是怎么运行的。
代码清单1:HelloWorld.jsp
HelloWorld.jsp
<%
 String message = "Hello World!";
%>
<%=message%>


  这个文件非常简单,仅仅定义了一个String的变量,并且输出。把这个文件放到Tomcat的webapps\ROOT\目录下,启动Tomcat,在浏览器中访问http://localhost:8080/HelloWorld.jsp,浏览器中的输出为“HelloWorld!”
  让我们来看看Tomcat都做了什么。转到Tomcat的\work\Standalone\localhost\_目录下,可以找到如下的HelloWorld_jsp.java,这个文件就是Tomcat解析HelloWorld.jsp时生成的源文件:
  代码清单2:HelloWorld_jsp.java
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class HelloWorld_jsp extends HttpJspBase {
 ……
public void _jspService(HttpServletRequest request,
HttpServletResponse response)throws java.io.IOException, ServletException
 {
  JspFactory _jspxFactory = null;
  javax.servlet.jsp.PageContext pageContext = null;
  HttpSession session = null;
  ServletContext application = null;
  ServletConfig config = null;
  JspWriter out = null;
  Object page = this;
  JspWriter _jspx_out = null;
  try {
   _jspxFactory = JspFactory.getDefaultFactory();
   response.setContentType("text/html;charset=ISO-8859-1");
   pageContext = _jspxFactory.getPageContext(this, request, response,null, true, 8192, true);
   application = pageContext.getServletContext();
   config = pageContext.getServletConfig();
   session = pageContext.getSession();
   out = pageContext.getOut();
   _jspx_out = out;
   String message = "Hello World!";
   out.print(message);
  } catch (Throwable t) {
   out = _jspx_out;
   if (out != null && out.getBufferSize() != 0)
    out.clearBuffer();
   if (pageContext != null) pageContext.handlePageException(t);
  } finally {
  if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
  }
 }
}

  从上面可以看出,HelloWorld.jsp在运行时首先解析成一个Java类HelloWorld_jsp.java,该类继承于org.apache.jasper.runtime.HttpJspBase基类,HttpJspBase实现了HttpServlet接口。可见,JSP在运行前首先将编译为一个Servlet,这就是理解JSP技术的关键。
  我们还知道JSP页面中内置了几个对象,如pageContext、application、config、page、session、out等,你可能会奇怪,为什么在JSP中的代码片断中可以直接使用这些内置对象。观察_jspService()方法,实际上这几个内置对象就是在这里定义的。在对JSP文件中的代码片断进行解析之前,先对这几个内置对象进行初始化。
  首先,调用JspFactory的getDefaultFactory()方法获取容器实现(本文中指Tomcat 4.1.17)的一个JspFactory对象的引用。JspFactory是javax.servlet.jsp包中定义的一个抽象类,其中定义了两个静态方法set/getDefaultFactory()。set方法由JSP容器(Tomcat)实例化该页面Servlet(即HelloWorld_jsp类)的时候置入,所以可以直接调用JspFactory.getDefaultFactory()方法得到这个JSP工厂的实现类。Tomcat是调用org.apache.jasper.runtime.JspFactoryImpl类。
  然后,调用这个JspFactoryImpl的getPageContext()方法,填充一个PageContext返回,并赋给内置变量pageConext。其它内置对象都经由该pageContext得到。具体过程见上面的代码,这里不再赘述。该页面Servlet的环境设置完毕,开始对页面进行解析。HelloWorld.jsp页面仅仅定义了一个String变量,然后直接输出。解析后的代码如下:
  代码清单3:JSP页面解析后的代码片断
String message = "Hello World!";
out.print(message);

  定制标签的解析过程
  在一个中大型的Web应用中,通常使用JSP定制标签来封装页面显示逻辑。剖析容器对定制标签的解析过程,对我们深入理解定制标签的运行机理非常有帮助。下面我们以Struts1.1b中附带的struts-example应用的主页运行为例加以说明。
  包含定制标签的index.jsp
  Struts1.1b的下载地址是http://jakarta.apache.org/struts/index.html。将下载的包解压,在webapps目录下可以找到struts-example.war。将该War包拷贝到Tomcat的webapps目录下,Tomcat会自动安装此应用包。在浏览器中通过http://localhost:8080/struts-example访问struts-example应用,将显示应用的首页(见图1)。
 
  图一 应用的首页
  代码清单4:index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html:html locale="true">
<head>
<title><bean:message key="index.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
……
</body>
</html:html>

  我们仅以index.jsp中的<bean:message/>标签的解析为例进行分析,看容器是怎样把这个自定义标签解析成HTML输出的。上面代码省略了页面的其它显示部分。首先,查看上面浏览器中页面的源文件:
<html lang="zh">
<head>
<title>MailReader Demonstration Application (Struts 1.0)</title>
</head>
<body bgcolor="white">
……
</body>
</html>

  可见,容器已经把<bean:message key="index.title"/>替换成一个字串,显示为页面的标题。
  解析过程
  那么,JSP容器是怎样完成解析的呢?查看在工作目录jakarta-tomcat-4.1.17\work\Standalone\localhost\struts-example下解析后的index_jsp.java文件:
  代码清单5:index_jsp.java
package org.apache.jsp;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class index_jsp extends HttpJspBase {
 //为所有的定制标签定义处理器池类的引用
 private org.apache.jasper.runtime.TagHandlerPool ;
 _jspx_tagPool_bean_message_key;
 ……
 //页面类构造方法
 public index_jsp() {
  _jspx_tagPool_bean_message_key =
  new org.apache.jasper.runtime.TagHandlerPool();
   ……
 }
 public void _jspService(HttpServletRequest request,
   HttpServletResponse response)
   throws java.io.IOException, ServletException {
  ……
  _jspxFactory = JspFactory.getDefaultFactory();
  response.setContentType("text/html;charset=UTF-8");
  pageContext = _jspxFactory.getPageContext(this,
    request, response,null, true, 8192, true);
  application = pageContext.getServletContext();
  config = pageContext.getServletConfig();
  session = pageContext.getSession();
  out = pageContext.getOut();
  _jspx_out = out;
  ……
  if (_jspx_meth_html_html_0(pageContext))
  return;
  ……
 }
 //页面在处理退出时释放所有定制标签的属性
 public void _jspDestroy() {
  _jspx_tagPool_bean_message_key.release();
  ……
 }
}

  生成的index_jsp.java继承于org.apache. jasper.runtime.HttpJspBase。研究这个文件为我们了解定制标签的运行机理提供了途径。
  从上面可以看出,Tomcat在解析一个JSP页面时,首先为每一个定制标签定义并实例化了一个TagHandlerPool对象。页面的处理方法覆盖父类的_ jspService()方法,_jspService方法首先初始化环境,为内置对象赋值。由于index.jsp页面整体由一个<html:html/>标签包裹,Tomcat对每一个标签都产生一个私有方法加以实现。<html:html/>标签的处理方法是_jspx_meth_html_html_0()。这个方法的命名规范大家也可以从这里看出,就是“_jspx_meth + 标签的前缀 + 标签名 + 该标签在JSP页面同类标签中出现的序号”。其它标签都被包含在该标签中,所以其它标签在_jspx_meth_html_html_0()方法中进行解析。具体的代码实现请参见赛迪网http://linux.ccidnet.com期刊浏览2003年第6期。
  在_jspx_meth_html_html_0()方法中,首先从_jspx_tagPool_html_html_locale池中得到一个org.apache.struts.taglib.html.HtmlTag的实例,然后设置这个tag实例的页面上下文及上级标签,由于html:html标签是页面的最顶层标签,所以它的parent是null。然后对该标签的内容进行解析。HTML代码直接输出,下面主要看看<html:html></html:html>标签之间包含的<bean:message key="index.title"/>标签的解析。对bean:message标签的解析类似于html:html,Tomcat也将其放入一个单独的方法_jspx_meth_bean_message_0()中进行。
  bean:message标签的解析
  代码清单7:_jspx_meth_bean_message_0()方法片断
//对message定制标签的处理方法
private boolean _jspx_meth_bean_message_0(
javax.servlet.jsp.tagext.Tag _jspx_th_html_html_0,
javax.servlet.jsp.PageContext pageContext) throws Throwable {
 JspWriter out = pageContext.getOut();
 /* —-  bean:message —- */
 org.apache.struts.taglib.bean.MessageTag
 _jspx_th_bean_message_0 =
 (org.apache.struts.taglib.bean.MessageTag)
 _jspx_tagPool_bean_message_key.get(
 org.apache.struts.taglib.bean.MessageTag.class);
 _jspx_th_bean_message_0.setPageContext(pageContext);
 _jspx_th_bean_message_0.setParent(_jspx_th_html_html_0);
 _jspx_th_bean_message_0.setKey("index.title");
 int _jspx_eval_bean_message_0 = _jspx_th_bean_message_0.doStartTag();
 if (_jspx_th_bean_message_0.doEndTag()== javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
  return true;
 _jspx_tagPool_bean_message_key.reuse(_jspx_th_bean_message_0);
  return false;
}

  同样,对html:bean也需要从池中得到一个标签类的实例,然后设置环境。这里不再赘述。我们只专注对MessageTag定制标签类特殊的处理部分。定制标签类的开发不在本文讨论范围之内。在index.jsp中定义了一个bean:message标签,并设置了一个属性:<bean:message key="index.title"/>。Tomcat在解析时,调用MessageTag对象的key属性设置方法setKey(),将该属性置入。然后调用MessageTag的doStartTag()和doEndTag()方法,完成解析。如果doEndTag()方法的返回值为javax.servlet.jsp.tagext.Tag. SKIP_PAGE,表明已经完成解析,返回true,Tomcat将立即停止剩余页面代码的执行,并返回。否则把该MessageTag的实例放回池中。
  标签类对象实例的池化
  为了提高运行效率,Tomcat对所有的定制标签类进行了池化,池化工作由org.apache.jasper. runtime.TagHandlerPool类完成。TagHandlerPool类主要有两个方法,代码如下:
  代码清单8:TagHandlerPool.java
public class TagHandlerPool {
 private static final int MAX_POOL_SIZE = 5;
 private Tag[] handlers;
 public synchronized Tag get(Class handlerClass) throws JspException {……}
 public synchronized void reuse(Tag handler) {……}
}

  TagHandlerPool简单地实现了对标签类的池化,其中MAX_POOL_SIZE是池的初始大小,handlers是一个Tag的数组,存储标签类的实例。get(Class handlerClass)得到一个指定标签类的实例,如果池中没有可用实例,则新实例化一个。reuse(Tag handler)把handler对象放回池中。
  至此,我们对JSP在容器中的运行过程已经了然于胸了。虽然每种JSP容器的解析结果会有差异,但其中的原理都雷同。对于编写JSP应用,我们并不需要干涉容器中的运行过程,但如果你对整个底层的运行机制比较熟悉,就能对JSP/Servlet技术有更深的认识。

2006年03月23日

  现在JDK1.4里有了自己的正则表达式API包,JAVA程序员可以免去找第三方提供的正则表达式库的周折了,我们现在就马上来了解一下这个SUN提供的迟来恩物- -对我来说确实如此。

1.简介:
java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包。
它包括两个类:Pattern和Matcher Pattern 一个Pattern是一个正则表达式经编译后的表现模式。
Matcher 一个Matcher对象是一个状态机器,它依据Pattern对象做为匹配模式对字符串展开匹配检查。 首先一个Pattern实例订制了一个所用语法与PERL的类似的正则表达式经编译后的模式,然后一个Matcher实例在这个给定的Pattern实例的模式控制下进行字符串的匹配工作。
以下我们就分别来看看这两个类:

2.Pattern类:
Pattern的方法如下: static Pattern compile(String regex)
将给定的正则表达式编译并赋予给Pattern类
static Pattern compile(String regex, int flags)
同上,但增加flag参数的指定,可选的flag参数包括:CASE INSENSITIVE,MULTILINE,DOTALL,UNICODE CASE, CANON EQ
int flags()
返回当前Pattern的匹配flag参数.
Matcher matcher(CharSequence input)
生成一个给定命名的Matcher对象
static boolean matches(String regex, CharSequence input)
编译给定的正则表达式并且对输入的字串以该正则表达式为模开展匹配,该方法适合于该正则表达式只会使用一次的情况,也就是只进行一次匹配工作,因为这种情况下并不需要生成一个Matcher实例。
String pattern()
返回该Patter对象所编译的正则表达式。
String[] split(CharSequence input)
将目标字符串按照Pattern里所包含的正则表达式为模进行分割。
String[] split(CharSequence input, int limit)
作用同上,增加参数limit目的在于要指定分割的段数,如将limi设为2,那么目标字符串将根据正则表达式分为割为两段。

一个正则表达式,也就是一串有特定意义的字符,必须首先要编译成为一个Pattern类的实例,这个Pattern对象将会使用matcher()方法来生成一个Matcher实例,接着便可以使用该 Matcher实例以编译的正则表达式为基础对目标字符串进行匹配工作,多个Matcher是可以共用一个Pattern对象的。

现在我们先来看一个简单的例子,再通过分析它来了解怎样生成一个Pattern对象并且编译一个正则表达式,最后根据这个正则表达式将目标字符串进行分割:
import java.util.regex.*;
public class Replacement{
public static void main(String[] args) throws Exception {
// 生成一个Pattern,同时编译一个正则表达式
Pattern p = Pattern.compile("[/]+");
//用Pattern的split()方法把字符串按"/"分割
String[] result = p.split(
"Kevin has seen《LEON》seveal times,because it is a good film."
+"/ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部"
+"好电影。/名词:凯文。");
for (int i=0; i<result.length; i++)
System.out.println(result[i]);
}
}

输出结果为:

Kevin has seen《LEON》seveal times,because it is a good film.
凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。
名词:凯文。

很明显,该程序将字符串按"/"进行了分段,我们以下再使用 split(CharSequence input, int limit)方法来指定分段的段数,程序改动为:
tring[] result = p.split("Kevin has seen《LEON》seveal times,because it is a good film./ 凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。",2);

这里面的参数"2"表明将目标语句分为两段。

输出结果则为:

Kevin has seen《LEON》seveal times,because it is a good film.
凯文已经看过《这个杀手不太冷》几次了,因为它是一部好电影。/名词:凯文。

由上面的例子,我们可以比较出java.util.regex包在构造Pattern对象以及编译指定的正则表达式的实现手法与我们在上一篇中所介绍的Jakarta-ORO 包在完成同样工作时的差别,Jakarta-ORO 包要先构造一个PatternCompiler类对象接着生成一个Pattern对象,再将正则表达式用该PatternCompiler类的compile()方法来将所需的正则表达式编译赋予Pattern类:

PatternCompiler orocom=new Perl5Compiler();

Pattern pattern=orocom.compile("REGULAR EXPRESSIONS");

PatternMatcher matcher=new Perl5Matcher();

但是在java.util.regex包里,我们仅需生成一个Pattern类,直接使用它的compile()方法就可以达到同样的效果:
Pattern p = Pattern.compile("[/]+");

因此似乎java.util.regex的构造法比Jakarta-ORO更为简洁并容易理解。

3.Matcher类:
Matcher方法如下: Matcher appendReplacement(StringBuffer sb, String replacement)
将当前匹配子串替换为指定字符串,并且将替换后的子串以及其之前到上次匹配子串之后的字符串段添加到一个StringBuffer对象里。
StringBuffer appendTail(StringBuffer sb)
将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。
int end()
返回当前匹配的子串的最后一个字符在原目标字符串中的索引位置 。
int end(int group)
返回与匹配模式里指定的组相匹配的子串最后一个字符的位置。
boolean find()
尝试在目标字符串里查找下一个匹配子串。
boolean find(int start)
重设Matcher对象,并且尝试在目标字符串里从指定的位置开始查找下一个匹配的子串。
String group()
返回当前查找而获得的与组匹配的所有子串内容
String group(int group)
返回当前查找而获得的与指定的组匹配的子串内容
int groupCount()
返回当前查找所获得的匹配组的数量。
boolean lookingAt()
检测目标字符串是否以匹配的子串起始。
boolean matches()
尝试对整个目标字符展开匹配检测,也就是只有整个目标字符串完全匹配时才返回真值。
Pattern pattern()
返回该Matcher对象的现有匹配模式,也就是对应的Pattern 对象。
String replaceAll(String replacement)
将目标字符串里与既有模式相匹配的子串全部替换为指定的字符串。
String replaceFirst(String replacement)
将目标字符串里第一个与既有模式相匹配的子串替换为指定的字符串。
Matcher reset()
重设该Matcher对象。
Matcher reset(CharSequence input)
重设该Matcher对象并且指定一个新的目标字符串。
int start()
返回当前查找所获子串的开始字符在原目标字符串中的位置。
int start(int group)
返回当前查找所获得的和指定组匹配的子串的第一个字符在原目标字符串中的位置。


(光看方法的解释是不是很不好理解?不要急,待会结合例子就比较容易明白了)

一个Matcher实例是被用来对目标字符串进行基于既有模式(也就是一个给定的Pattern所编译的正则表达式)进行匹配查找的,所有往Matcher的输入都是通过CharSequence接口提供的,这样做的目的在于可以支持对从多元化的数据源所提供的数据进行匹配工作。

我们分别来看看各方法的使用:

★matches()/lookingAt ()/find():
一个Matcher对象是由一个Pattern对象调用其matcher()方法而生成的,一旦该Matcher对象生成,它就可以进行三种不同的匹配查找操作:

matches()方法尝试对整个目标字符展开匹配检测,也就是只有整个目标字符串完全匹配时才返回真值。
lookingAt ()方法将检测目标字符串是否以匹配的子串起始。
find()方法尝试在目标字符串里查找下一个匹配子串。

以上三个方法都将返回一个布尔值来表明成功与否。

★replaceAll ()/appendReplacement()/appendTail():
Matcher类同时提供了四个将匹配子串替换成指定字符串的方法:

replaceAll()
replaceFirst()
appendReplacement()
appendTail()

replaceAll()与replaceFirst()的用法都比较简单,请看上面方法的解释。我们主要重点了解一下appendReplacement()和appendTail()方法。

appendReplacement(StringBuffer sb, String replacement) 将当前匹配子串替换为指定字符串,并且将替换后的子串以及其之前到上次匹配子串之后的字符串段添加到一个StringBuffer对象里,而appendTail(StringBuffer sb) 方法则将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里。

例如,有字符串fatcatfatcatfat,假设既有正则表达式模式为"cat",第一次匹配后调用appendReplacement(sb,"dog"),那么这时StringBuffer sb的内容为fatdog,也就是fatcat中的cat被替换为dog并且与匹配子串前的内容加到sb里,而第二次匹配后调用appendReplacement(sb,"dog"),那么sb的内容就变为fatdogfatdog,如果最后再调用一次appendTail(sb),那么sb最终的内容将是fatdogfatdogfat。

还是有点模糊?那么我们来看个简单的程序:
//该例将把句子里的"Kelvin"改为"Kevin"
import java.util.regex.*;
public class MatcherTest{
public static void main(String[] args)
throws Exception {
//生成Pattern对象并且编译一个简单的正则表达式"Kelvin"
Pattern p = Pattern.compile("Kevin");
//用Pattern类的matcher()方法生成一个Matcher对象
Matcher m = p.matcher("Kelvin Li and Kelvin Chan are both working in Kelvin Chen’s KelvinSoftShop company");
StringBuffer sb = new StringBuffer();
int i=0;
//使用find()方法查找第一个匹配的对象
boolean result = m.find();
//使用循环将句子里所有的kelvin找出并替换再将内容加到sb里
while(result) {
i++;
m.appendReplacement(sb, "Kevin");
System.out.println("第"+i+"次匹配后sb的内容是:"+sb);
//继续查找下一个匹配对象
result = m.find();
}
//最后调用appendTail()方法将最后一次匹配后的剩余字符串加到sb里;
m.appendTail(sb);
System.out.println("调用m.appendTail(sb)后sb的最终内容是:"+ sb.toString());
}
}

最终输出结果为:
第1次匹配后sb的内容是:Kevin
第2次匹配后sb的内容是:Kevin Li and Kevin
第3次匹配后sb的内容是:Kevin Li and Kevin Chan are both working in Kevin
第4次匹配后sb的内容是:Kevin Li and Kevin Chan are both working in Kevin Chen’s Kevin
调用m.appendTail(sb)后sb的最终内容是:Kevin Li and Kevin Chan are both working in Kevin Chen’s KevinSoftShop company.

看了上面这个例程是否对appendReplacement(),appendTail()两个方法的使用更清楚呢,如果还是不太肯定最好自己动手写几行代码测试一下。

★group()/group(int group)/groupCount():
该系列方法与我们在上篇介绍的Jakarta-ORO中的MatchResult .group()方法类似(有关Jakarta-ORO请参考上篇的内容),都是要返回与组匹配的子串内容,下面代码将很好解释其用法:
import java.util.regex.*;

public class GroupTest{
public static void main(String[] args)
throws Exception {
Pattern p = Pattern.compile("(ca)(t)");
Matcher m = p.matcher("one cat,two cats in the yard");
StringBuffer sb = new StringBuffer();
boolean result = m.find();
System.out.println("该次查找获得匹配组的数量为:"+m.groupCount());
for(int i=1;i<=m.groupCount();i++){
System.out.println("第"+i+"组的子串内容为: "+m.group(i));
}
}
}

输出为:
该次查找获得匹配组的数量为:2
第1组的子串内容为:ca
第2组的子串内容为:t

Matcher对象的其他方法因比较好理解且由于篇幅有限,请读者自己编程验证。

4.一个检验Email地址的小程序:
最后我们来看一个检验Email地址的例程,该程序是用来检验一个输入的EMAIL地址里所包含的字符是否合法,虽然这不是一个完整的EMAIL地址检验程序,它不能检验所有可能出现的情况,但在必要时您可以在其基础上增加所需功能。
import java.util.regex.*;
public class Email {
public static void main(String[] args) throws Exception {
String input = args[0];
//检测输入的EMAIL地址是否以 非法符号"."或"@"作为起始字符
Pattern p = Pattern.compile("^\\.|^\\@");
Matcher m = p.matcher(input);
if (m.find()){
System.err.println("EMAIL地址不能以’.'或’@'作为起始字符");
}
//检测是否以"www."为起始
p = Pattern.compile("^www\\.");
m = p.matcher(input);
if (m.find()) {
System.out.println("EMAIL地址不能以’www.’起始");
}
//检测是否包含非法字符
p = Pattern.compile("[^A-Za-z0-9\\.\\@_\\-~#]+");
m = p.matcher(input);
StringBuffer sb = new StringBuffer();
boolean result = m.find();
boolean deletedIllegalChars = false;
while(result) {
//如果找到了非法字符那么就设下标记
deletedIllegalChars = true;
//如果里面包含非法字符如冒号双引号等,那么就把他们消去,加到SB里面
m.appendReplacement(sb, "");
result = m.find();
}
m.appendTail(sb);
input = sb.toString();
if (deletedIllegalChars) {
System.out.println("输入的EMAIL地址里包含有冒号、逗号等非法字符,请修改");
System.out.println("您现在的输入为: "+args[0]);
System.out.println("修改后合法的地址应类似: "+input);
}
}
}

例如,我们在命令行输入:java Email www.kevin@163.net

那么输出结果将会是:EMAIL地址不能以’www.’起始

如果输入的EMAIL为@kevin@163.net

则输出为:EMAIL地址不能以’.'或’@'作为起始字符

当输入为:cgjmail#$%@163.net

那么输出就是:

输入的EMAIL地址里包含有冒号、逗号等非法字符,请修改
您现在的输入为: cgjmail#$%@163.net
修改后合法的地址应类似: cgjmail@163.net

5.总结:
本文介绍了jdk1.4.0-beta3里正则表达式库–java.util.regex中的类以及其方法,如果结合与上一篇中所介绍的Jakarta-ORO API作比较,读者会更容易掌握该API的使用,当然该库的性能将在未来的日子里不断扩展,希望获得最新信息的读者最好到及时到SUN的网站去了解。



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=426562

1  Torque简介

Torque是Apache的公开源代码项目,最开始是Turbine框架的组成部分,后面被独立出来作为一个单独的组件,归入了Apache的DB项目下。目前最新的版本是3.1。Torque的主要功能是实现对数据库的访问,方式是通过生成访问数据库的资源(包括创建数据库、表和初始化表的sql语句)和java代码、提供使用这些代码访问数据库的运行时(runtime)环境。关于Torque的详细介绍请大家参考http://db.apache.org/torque/

目前Torque支持的数据库包括mysql、oracle、sqlserver、db2等,还包括对weblogic的数据源的支持,详细的支持列表大家可以到http://db.apache.org/torque/查找到,你还可以参考http://db.apache.org/torque/db-adapters.html编写你自己的数据库支持类。

1.1  Torque和传统数据库访问方式的差异

下面是两种方式访问数据库时的代码片段,相信大家可以很清晰的感觉到他们之间的差异。

1.1.1  传统数据库访问

下面这段代码实现向数据库中插入一条记录:

Class.forName("%Database_Driver%").newInstance();
java.sql.Connection connection;
connection = DriverManager.getConnection(url,userid,pwd);
Statement statement = connection.createStatement();  
String sql = "";

statement.ExecuteUpdate("insert into tablename
    values(field_value1, field_value1…)");

statement.close();
connection.close();
1.1.2  使用Torque

下面这段代码演示了使用Torque后我们插入一条记录的过程:

TableOBJ  tableObj = new TableObj();
tableObj.SetFieldName1(fieldvalue1);
tableObj.SetFieldName2(fieldvalue2);

tableObj.Save();
下面的章节,作者将详细的描述如何配置使用Torque的全过程以及需要注意的内容。作者选择了mysql作为目的数据库进行操作(中间有一些内容提到了SqlServer,主要是因为使用SqlServer或者其它如oracle、db2等作为目的数据库在某些操作上和使用mysql作为目的数据库有很大的差异,这些地方作者做了特别说明)。

[注] 作者的演示过程使用ant1.5.1和Torque3.1,如果按照作者的过程行不通,请确认工具的版本是否正确。

2  环境配置

2.1  组件下载安装

为了使用Ant和Torque,你必须下载安装Sun提供的J2SE,可以到http://java.sun.com下载J2SE的最新版本。

在Torque的使用过程中,很多的工作都是通过ant来完成的,所以你需要先下载ant工具包,由于Torque的build-torque.xml中使用了ant中的option特性,所以最好下载ant1.5.1版本,你可以到http://ant.apache.org/下载最新的ant版本。作者下载后解压缩到c:/ant(后面章节将使用%ant_home%来表示这个目录)目录中。

然后请下载Torque的3.1版本(包括http://db.apache.org/builds/torque/release/3.1/torque-3.1.ziphttp://db.apache.org/builds/torque/release/3.1/torque-gen-3.1.zip),然后解压缩到同一个目录中,作者使用了E:\test\torque目录(后面章节将使用%Torque_home%来表示这个目录)。

安装完成后,请设置JAVA_HOME环境变量指向安装J2SE的目录,以便各项ant工作能够顺利完成。

2.2  演示例子说明

作者的演示例子使用了一个简单的数据库,它包括三个表:author, publisher和 book表。Author表保存书的作者信息,publisher表保存书的出版者信息,而book表保存书的相关信息如title、ISBN信息等。

2.3  修改配置文件

安装好Torque后,为了让它成功执行,必须配置以下配置文件build.properties、Torque.properties、mybookstore-schema.xml、id-table-schema.xml,下面的章节详细的说明了他们的作用和需要添加的内容。

2.3.1  build.properties

build.properties文件中设置了生成访问数据库需要的资源文件和java代码需要用到的环境变量。下面这段是作者使用mysql数据库时的文件内容:

#工程名称
torque.project = bookstore
#数据库类型
torque.database = mysql
#生成java代码的包特性
torque.targetPackage = com.chinacreator
torque.database.createUrl = jdbc:mysql://127.0.0.1:3306/mysql
#数据库URL
#注意:数据库名应该和工程名保持一致
torque.database.buildUrl = jdbc:mysql://127.0.0.1:3306/bookstore
torque.database.url = jdbc:mysql://127.0.0.1:3306/bookstore
#数据库驱动类
torque.database.driver = org.gjt.mm.mysql.Driver
#连接数据库使用的用户名
torque.database.user = root
#连接数据库使用的密码
torque.database.password = ioffice
#数据库的主机IP
torque.database.host = 127.0.0.1
如果使用其它的数据库,那么请修改里面的相关信息如数据库驱动类、数据库URL、访问数据库的用户名/密码等,下面是作者使用microsoft 的SqlServer的配置文件内容:

torque.project = mybookstore
torque.database = mssql
torque.targetPackage = com.chinacreator
torque.database.createUrl =
    jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mysms
torque.database.buildUrl =
    jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mybookstore
torque.database.url =
    jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=mybookstore
torque.database.driver =
    com.microsoft.jdbc.sqlserver.SQLServerDriver
torque.database.user = sa
torque.database.password = sa
torque.database.host = 127.0.0.1
2.3.2  Torque.properties

这个配置文件是使用Toorque访问数据库时初始化环境需要用到的一些配置内容,下面是作者使用MySql作为目的数据库时的配置文件内容:

#Torque应用的根目录
torque.applicationRoot = .
#Torque中的日志信息的设置,不需要修改
log4j.category.org.apache.torque = ALL, org.apache.torque
log4j.appender.org.apache.torque = org.apache.log4j.FileAppender
log4j.appender.org.apache.torque.file = ${torque.applicationRoot}/logs/torque.log
log4j.appender.org.apache.torque.layout = org.apache.log4j.PatternLayout
log4j.appender.org.apache.torque.layout.conversionPattern = %d [%t] %-5p %c – %m%n
log4j.appender.org.apache.torque.append = false
torque.defaults.pool.logInterval = 0

# 等待数据库连接的时间
torque.defaults.pool.connectionWaitTimeout = 10

# 数据库连接池中的最大连接数
torque.defaults.pool.defaultMaxConnections = 80

# 数据库连接在连接池中的最大存在时间
torque.defaults.pool.maxExpiryTime = 3600

# 数据源的连接驱动类
torque.defaults.connection.driver = org.gjt.mm.mysql.Driver

# 数据源的URL
torque.defaults.connection.url = jdbc:mysql://localhost:3306/bookstore
#访问数据的用户名和密码
torque.defaults.connection.user = root
torque.defaults.connection.password = ioffice
#默认数据库其中及本文件中剩下的bookstore都应该和build.peoperties中的工程名保持一致
torque.database.default=bookstore
torque.database.bookstore.adapter=mysql
 
torque.dsfactory.bookstore.factory =
    org.apache.torque.dsfactory.SharedPoolDataSourceFactory

torque.dsfactory.bookstore.pool.defaultMaxActive=10
torque.dsfactory.bookstore.pool.testOnBorrow=true
torque.dsfactory.bookstore.pool.validationQuery=SELECT 1
torque.dsfactory.bookstore.connection.driver = org.gjt.mm.mysql.Driver
torque.dsfactory.bookstore.connection.url =
    jdbc:mysql://localhost:3306/bookstore
torque.dsfactory.bookstore.connection.user = root
torque.dsfactory.bookstore.connection.password = ioffice

torque.idbroker.cleverquantity=true
torque.manager.useCache = true
2.3.3  project-schema.xml

这个文件位于%Torque_home%/schema目录下,文件名中的project应该和build.properties中的工程名保持一致,它主要是描述工程中使用的数据库信息,包括数据库、表、字段的特性等。这里应该是bookstore-schema.xml,下面的信息是作者使用mysql时对应的bookstore-schema.xml的内容:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE database SYSTEM
 "http://db.apache.org/torque/dtd/database_3_1.dtd">

<database
  name="bookstore"
  defaultIdMethod="idbroker">

  <table name="book" description="Book Table">
    <column
      name="book_id"
      required="true"
      primaryKey="true"
      type="INTEGER"
      description="Book Id"/>
    <column
      name="title"
      required="true"
      type="VARCHAR"
      size="255"
      description="Book Title"/>
    <column
      name="isbn"
      required="true"
      type="VARCHAR"
      size="24"
      javaName="ISBN"
      description="ISBN Number"/>
    <column
      name="publisher_id"
      required="true"
      type="INTEGER"
      description="Foreign Key Publisher"/>
    <column
      name="author_id"
      required="true"
      type="INTEGER"
      description="Foreign Key Author"/>
    <foreign-key foreignTable="publisher">
      <reference
        local="publisher_id"
        foreign="publisher_id"/>
    </foreign-key>
    <foreign-key foreignTable="author">
      <reference
        local="author_id"
        foreign="author_id"/>
    </foreign-key>
  </table>
  <table name="publisher" description="Publisher Table">
    <column
      name="publisher_id"
      required="true"
      primaryKey="true"
      type="INTEGER"
      description="Publisher Id"/>
    <column
      name="name"
      required="true"
      type="VARCHAR"
      size="128"
      description="Publisher Name"/>
  </table>
  <table name="author" description="Author Table">
    <column
      name="author_id"
      required="true"
      primaryKey="true"
      type="INTEGER"
      description="Author Id"/>
    <column
      name="first_name"
      required="true"
      type="VARCHAR"
      size="128"
      description="First Name"/>
    <column
      name="last_name"
      required="true"
      type="VARCHAR"
      size="128"
      description="Last Name"/>
  </table>
</database>
关于如何使用该文件描述数据库以及如何编写该文档,请大家参考Torque的帮助文档或者到Torque的网站(http://db.apache.org/torque/)查询相关内容

2.3.4  id-table-schema.xml

这个文件用于描述project-schema.xml中相关ID内容的增长特性,它用于指导Torque生成id_table表,他保存了project-schema.xml中相关ID自动增长的相关内容,实现类似于Oracle中的Sequence的功能。下面是演示实例的对应文件:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<!DOCTYPE database SYSTEM
 "http://db.apache.org/torque/dtd/database_3_1.dtd">

<database name="bookstore">
  <table name="ID_TABLE" idMethod="idbroker">
    <column
      name="ID_TABLE_ID"
      required="true"
      primaryKey="true"
      type="INTEGER"/>
    <column
      name="TABLE_NAME"
      required="true"
      size="255"
      type="VARCHAR"/>
    <column
      name="NEXT_ID"
      type="INTEGER"/>
    <column
      name="QUANTITY"
      type="INTEGER"/>
    <unique>
      <unique-column name="TABLE_NAME"/>
    </unique>
  </table>
</database>
[注] Torque将根据后面的两个XML文件生成自动建立数据库、表等的相关的SQL数据和访问该数据库的java代码。

2.4  类路径

为了建立使用Torque的相关环境,需要将%Torque_home%/lib下面的所有。jar文件,放入classpath中,其他需要加入classpath中的还包括%Torque_home%,%Torque_home%/templates,还有支持访问数据库的jdbc驱动的.jar文件。

3  生成访问资源

3.1  生成访问数据库的资源

1.生成访问数据库的java代码

您可以使用下面的操作来生成访问数据库的java代码:

ant -f build-torque.xml

这个操作将顺带产生生成数据库、表等的相关SQL语句。

2.自动生成数据库

使用Ant来执行下面的代码,他会帮您自动建立数据库:

ant -f build-torque.xml create-db

[注] 如果是使用Microsoft SqlServer,因为Torque中目前还没有支持创建数据库的相关SQL代码实现,所以需要自己手工建立相对应的数据库。

3.自动生成相关表

使用Ant来执行下面的代码,他会帮您自动建立数据表和一些初始数据内容:

ant -f build-torque.xml id-table-init-sql

ant -f build-torque.xml insert-sql

4  测试一下

好了,现在访问数据库的代码已经自动形成了,所有的代码位于%torque_home%/src/java目录下,写一个简单的例子测试一下吧。

[注] 为了使用我们前面已经定义的环境变量设置,请将%torque_home%/src/java目录下的所有内容拷贝到%torque_home%目录下。

下面是作者写的一个测试用例,它放在%torque_home%目录下:

//引入相关包和类
import com.chinacreator.*;
import org.apache.torque.Torque;

public class Test
{
 public void insert(String s)
  throws Exception{
//使用Torque.properties配置文件初始化Torque
//如果没有这一步,你执行时将会获得一个"Torque was not properly initialized"的违例
  Torque.init("Torque.properties");
//向Publisher中增加一条新的记录
  Publisher addison = new Publisher();
  addison.setName(s);
  addison.save();
 }
 public static void main(String[] args) throws Exception
 {
  Test t = new Test();
  t.insert("xj");

 }
}
4.1  编译执行

请建立一个build-run.cmd(位于%Torque_home%目录下)的批处理文件来编译和执行torque生成的java代码和自己编写的测试类,它的内容如下:

javac com\chinacreator\*.java
javac com\chinacreator\map\*.java
javac Test.java
java Test
建立好了以后,就可以执行这个批处理文件了。

4.2  查看执行结果

执行4.1中建立的那个批处理文件,然后打开数据库,看看publisher表中是否新增加了一条记录。如果你按照上面的配置和执行过程完全成功的话,你就可以看到publisher表中已经有了一条记录,它的publisher_id是1000,name是xj。

5  优点和局限

5.1  优点

Torque最大的优点就是在java访问数据库的过程中,我们可以按照面向对象的习惯和方式处理,而且整个过程我们不再需要使用任何的Sql语句。如果你喜欢的话,你还可以借助Torque来帮助您建立数据库、表和使用它提供的ID自增长特性,而不需要自己来处理这些信息。

5.2  局限性

使用Torque可以很方便的实现访问数据库的OO实现,但是作者使用了Torque后,发觉使用Torque虽然有好处,但是不同的情况下,他的表现还是有很大的差异:

1.如果你的数据库表结构在项目开始时已经设计好,不需要中途修改,那么你可以完全的享受Torque带来的好处

2.如果你的数据库表结构在项目进行中经常需要修改,那么使用Torque就有些局限了,因为Torque生成的访问数据库的java代码都是静态的,如果数据库表结构发生了变化,那么需要重新生成访问数据库的java代码,而且所有调用这些代码的其他应用代码都需要发生相应的改变。这种情况下使用Torque并不适合。

6  总结

Torque是Apache的公开源代码项目,用于生成访问数据库的java代码。这样使用Torque的人就可以不再需要编写SQL语句来访问数据库,而是完全使用面向对象的方式(由Torque生成的java代码)来访问数据库,而且如果你喜欢,你还可以使用Torque来协助您完成建立数据库、表和相关的初始化工作。

本文中作者详细的介绍了如何配置环境变量使Torque能够正常工作,以及如何使用Torque来生成访问数据库所需要的资源(java代码和相关的SQL语句)、如何使用Torque生成的代码来访问数据库。并且给出了使用MySQL作为目的数据库的整个操作过程,另外给出了使用Microsoft SqlServer作为目的数据库时需要注意的一些地方。希望能够帮助大家了解和掌握Torque的作用机制、实现原理和方式,同时希望大家能够结合自己应用的情况和Torque的特性合理的抉择在何种情况下使用Torque,让Torque能够助您轻松的实现数据库访问。

参考资料

Torque 帮助 http://db.apache.org/torque/
Ant 帮助 http://ant.apache.org/
J2SE1.4.2 http://java.sun.com/j2se/1.4.2/index.jsp
Ant1.5.1 http://archive.apache.org/dist/ant/binaries/
Torque http://db.apache.org/builds/torque/release/3.1/

2006年03月22日

 
有感:应聘Java笔试时可能出现问题及其答案
作者:huijunzi

   前段时间因为要参加一个笔试,在准备期间在网上找到了两条关于笔试题目的文章,其中一篇为<<有感:应聘Java笔试时可能出现问题>>,还有一篇忘了名字,读后深受启发。
        在寻找这些答案的过程中,我将相关答案记录下来,就形成了以下这些东西。需要说明的是以下答案肯定有很多不完整甚至错误的地方,需要各位来更正与完善它,千万不要扔我的鸡蛋啊。
       希望本文能够给即将奔赴笔试考场的同仁些许帮助,更希望更多的人加入到收集整理笔试题与完善答案的这些工作中来,为大家更好的获得工作机会做一点贡献。
       在此感谢前面两文的作者的对笔试题目的收集与整理。
       如有任何意见与建议请通过QQ:6045306,Mail:huijunzi@21cn.com与我联系。
Java基础方面:

1、作用域public,private,protected,以及不写时的区别
答:区别如下:
作用域           当前类       同一package  子孙类       其他package
public            √              √                  √             √
protected        √              √                  √             ×
friendly          √              √                   ×            ×
private           √              ×                   ×            ×
不写时默认为friendly

2、ArrayList和Vector的区别,HashMap和Hashtable的区别
答:就ArrayList与Vector主要从二方面来说.
一.同步性:Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步的
二.数据增长:当需要增长时,Vector默认增长为原来一培,而ArrayList却是原来的一半
就HashMap与HashTable主要从三方面来说。
一.历史原因:Hashtable是基于陈旧的Dictionary类的,HashMap是Java 1.2引进的Map接口的一个实现
二.同步性:Hashtable是线程安全的,也就是说是同步的,而HashMap是线程序不安全的,不是同步的
三.值:只有HashMap可以让你将空值作为一个表的条目的key或value

3、char型变量中能不能存贮一个中文汉字?为什么?
答:是能够定义成为一个中文的,因为java中以unicode编码,一个char占16个字节,所以放一个中文是没问题的

4、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?
答:多线程有两种实现方法,分别是继承Thread类与实现Runnable接口
同步的实现方面有两种,分别是synchronized,wait与notify

5、继承时候类的执行顺序问题,一般都是选择题,问你将会打印出什么?
答:父类:
package test;
public class  FatherClass
{
    public FatherClass()
 {
  System.out.println("FatherClass Create");
 }
}
子类:
package test;
import test.FatherClass;
public class  ChildClass extends FatherClass
{
 public ChildClass()
 {
  System.out.println("ChildClass Create");
 }
 public static void main(String[] args)
 {
  FatherClass fc = new FatherClass();
  ChildClass cc = new ChildClass();
 }
}
输出结果:
C:\>java test.ChildClass
FatherClass Create
FatherClass Create
ChildClass Create

6、内部类的实现方式?
答:示例代码如下:
package test;
public class  OuterClass
{
 private class InterClass
 {
  public InterClass()
  {
   System.out.println("InterClass Create");
  }
 }
 public OuterClass()
 {
  InterClass ic = new InterClass();
  System.out.println("OuterClass Create");
 }
 public static void main(String[] args)
 {
  OuterClass oc = new OuterClass();
 }
}
输出结果:
C:\>java test/OuterClass
InterClass Create
OuterClass Create
再一个例题:
public class OuterClass {
  private double d1 = 1.0;
    //insert code here
}
You need to insert an inner class declaration at line 3. Which two inner class declarations are

valid?(Choose two.)
A. class InnerOne{
     public static double methoda() {return d1;}
   }
B. public class InnerOne{
     static double methoda() {return d1;}
   }
C. private class InnerOne{
     double methoda() {return d1;}
   }
D. static class InnerOne{
     protected double methoda() {return d1;}
   }
E. abstract class InnerOne{
     public abstract double methoda();
   }
说明如下:
一.静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 故 A、B 错
二.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;return d1 出错。

故 D 错
三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确
四.答案为C、E

7、垃圾回收机制,如何优化程序?
希望大家补上,谢谢

8、float型float f=3.4是否正确?
答:不正确。精度不准确,应该用强制类型转换,如下所示:float f=(float)3.4

9、介绍JAVA中的Collection FrameWork(包括如何写自己的数据结构)?
答:Collection FrameWork如下:
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)
Map提供key到value的映射

10、Java中异常处理机制,事件机制?

11、JAVA中的多形与继承?
希望大家补上,谢谢

12、抽象类与接口?
答:抽象类与接口都用于抽象,但是抽象类(JAVA中)可以有自己的部分实现,而接口则完全是一个标识(同时有多重继承的功能)。

13、Java 的通信编程,编程题(或问答),用JAVA SOCKET编程,读服务器几个字符,再写入本地显示?
答:Server端程序:
package test;
import java.net.*;
import java.io.*;

public class Server
{
 private ServerSocket ss;
 private Socket socket;
 private BufferedReader in;
 private PrintWriter out;
 public Server()
 {
  try
  {
   ss=new ServerSocket(10000);
   while(true)
   {
    socket = ss.accept();
    String RemoteIP = socket.getInetAddress().getHostAddress();
    String RemotePort = ":"+socket.getLocalPort();
    System.out.println("A client come in!IP:"+RemoteIP+RemotePort);
    in = new BufferedReader(new

InputStreamReader(socket.getInputStream()));
    String line = in.readLine();
    System.out.println("Cleint send is :" + line);
    out = new PrintWriter(socket.getOutputStream(),true);
    out.println("Your Message Received!");
    out.close();
    in.close();
    socket.close();
   }
  }catch (IOException e)
  {
   out.println("wrong");
  }
 }
 public static void main(String[] args)
 {
  new Server();
 }
};
Client端程序:
package test;
import java.io.*;
import java.net.*;

public class Client
{
 Socket socket;
 BufferedReader in;
 PrintWriter out;
 public Client()
 {
  try
  {
   System.out.println("Try to Connect to 127.0.0.1:10000");
   socket = new Socket("127.0.0.1",10000);
   System.out.println("The Server Connected!");
   System.out.println("Please enter some Character:");
   BufferedReader line = new BufferedReader(new

InputStreamReader(System.in));
   out = new PrintWriter(socket.getOutputStream(),true);
   out.println(line.readLine());
   in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   System.out.println(in.readLine());
   out.close();
   in.close();
   socket.close();
  }catch(IOException e)
  {
   out.println("Wrong");
  }
 }
 public static void main(String[] args)
 {
  new Client();
 }
};

14、用JAVA实现一种排序,JAVA类实现序列化的方法(二种)? 如在COLLECTION框架中,实现比较要实现什么样的接口?
答:用插入法进行排序代码如下
package test;
import java.util.*;
class  InsertSort
{
 ArrayList al;
 public InsertSort(int num,int mod)
 {
  al = new ArrayList(num);
  Random rand = new Random();
  System.out.println("The ArrayList Sort Before:");
  for (int i=0;i<num ;i++ )
  {
   al.add(new Integer(Math.abs(rand.nextInt()) % mod + 1));
   System.out.println("al["+i+"]="+al.get(i));
  }
 }
 public void SortIt()
 {
  Integer tempInt;
  int MaxSize=1;
  for(int i=1;i<al.size();i++)
  {
       tempInt = (Integer)al.remove(i);
    if(tempInt.intValue()>=((Integer)al.get(MaxSize-1)).intValue())
    {
     al.add(MaxSize,tempInt);
     MaxSize++;
     System.out.println(al.toString());
    } else {
     for (int j=0;j<MaxSize ;j++ )
     {
      if

(((Integer)al.get(j)).intValue()>=tempInt.intValue())
      {
       al.add(j,tempInt);
       MaxSize++;
       System.out.println(al.toString());
       break;
      }
     }
    }
  }
  System.out.println("The ArrayList Sort After:");
  for(int i=0;i<al.size();i++)
  {
   System.out.println("al["+i+"]="+al.get(i));
  }
 }
 public static void main(String[] args)
 {
  InsertSort is = new InsertSort(10,100);
  is.SortIt();
 }
}
JAVA类实现序例化的方法是实现java.io.Serializable接口
Collection框架中实现比较要实现Comparable 接口和 Comparator 接口

15、编程:编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。 但是要保证汉字不被截半个,如“我ABC”4,应该截为“我AB”,输入“我ABC汉DEF”,6,应该输出为“我ABC”而不是“我ABC+汉的半个”。
答:代码如下:
package test;

class  SplitString
{
 String SplitStr;
 int SplitByte;
 public SplitString(String str,int bytes)
 {
  SplitStr=str;
  SplitByte=bytes;
  System.out.println("The String is:’"+SplitStr+"’;SplitBytes="+SplitByte);
 }
 public void SplitIt()
 {
  int loopCount;
 

loopCount=(SplitStr.length()%SplitByte==0)?(SplitStr.length()/SplitByte):(SplitStr.length()/Split

Byte+1);
  System.out.println("Will Split into "+loopCount);
  for (int i=1;i<=loopCount ;i++ )
  {
   if (i==loopCount){
   

System.out.println(SplitStr.substring((i-1)*SplitByte,SplitStr.length()));
   } else {
   

System.out.println(SplitStr.substring((i-1)*SplitByte,(i*SplitByte)));
   }
  }
 }
 public static void main(String[] args)
 {
  SplitString ss = new SplitString("test中dd文dsaf中男大3443n中国43中国人

0ewldfls=103",4);
  ss.SplitIt();
 }
}

16、JAVA多线程编程。 用JAVA写一个多线程程序,如写四个线程,二个加1,二个对一个变量减一,输出。
希望大家补上,谢谢

17、STRING与STRINGBUFFER的区别。
答:STRING的长度是不可变的,STRINGBUFFER的长度是可变的。如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用StringBuffer的toString()方法

Jsp方面

1、jsp有哪些内置对象?作用分别是什么?
答:JSP共有以下9种基本内置组件(可与ASP的6种内部组件相对应):
 request 用户端请求,此请求会包含来自GET/POST请求的参数
   response 网页传回用户端的回应
   pageContext 网页的属性是在这里管理
   session 与请求有关的会话期
   application servlet 正在执行的内容
   out 用来传送回应的输出
   config servlet的构架部件
   page JSP网页本身
   exception 针对错误网页,未捕捉的例外

2、jsp有哪些动作?作用分别是什么?
答:JSP共有以下6种基本动作
   jsp:include:在页面被请求的时候引入一个文件。
   jsp:useBean:寻找或者实例化一个JavaBean。
   jsp:setProperty:设置JavaBean的属性。
   jsp:getProperty:输出某个JavaBean的属性。
   jsp:forward:把请求转到一个新的页面。
   jsp:plugin:根据浏览器类型为Java插件生成OBJECT或EMBED标记

3、JSP中动态INCLUDE与静态INCLUDE的区别?
答:动态INCLUDE用jsp:include动作实现
   <jsp:include page="included.jsp" flush="true" />它总是会检查所含文件中的变化,适合用于包含动态页面,并且可以带参数
   静态INCLUDE用include伪码实现,定不会检查所含文件的变化,适用于包含静态页面
   <%@ include file="included.htm" %>

4、两种跳转方式分别是什么?有什么区别?
答:有两种,分别为:
  <jsp:include page="included.jsp" flush="true">
  <jsp:forward page= "nextpage.jsp"/>
  前者页面不会转向include所指的页面,只是显示该页的结果,主页面还是原来的页面。执行完后还会回来,相当于函数调用。并且可以带参数.后者完全转向新页面,不会再回来。相当于go to 语句。

Servlet方面

1、说一说Servlet的生命周期?
答:servlet有良好的生存期的定义,包括加载和实例化、初始化、处理请求以及服务结束。这个生存期由javax.servlet.Servlet接口的init,service和destroy方法表达。

2、Servlet版本间(忘了问的是哪两个版本了)的不同?
希望大家补上,谢谢

3、JAVA SERVLET API中forward() 与redirect()的区别?
答:前者仅是容器中控制权的转向,在客户端浏览器地址栏中不会显示出转向后的地址;后者则是完全的跳转,浏览器将会得到跳转的地址,并重新发送请求链接。这样,从浏览器的地址栏中可以看到跳转后的链接地址。所以,前者更加高效,在前者可以满足需要时,尽量使用forward()方法,并且,这样也有助于隐藏实际的链接。在有些情况下,比如,需要跳转到一个其它服务器上的资源,则必须使用sendRedirect()方法。

4、Servlet的基本架构
public class ServletName extends HttpServlet {
  public void doPost(HttpServletRequest request, HttpServletResponse response) throws
      ServletException, IOException  {
      }
  public void doGet(HttpServletRequest request, HttpServletResponse response) throws
      ServletException, IOException  {
      }
}

Jdbc、Jdo方面

1、可能会让你写一段Jdbc连Oracle的程序,并实现数据查询.
答:程序如下:
package hello.ant;
import java.sql.*;
public class  jdbc
{
 String dbUrl="jdbc:oracle:thin:@127.0.0.1:1521:orcl";
 String theUser="admin";
 String thePw="manager";
 Connection c=null;
 Statement conn;
 ResultSet rs=null;
 public jdbc()
 {
  try{
    Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
          c = DriverManager.getConnection(dbUrl,theUser,thePw);
    conn=c.createStatement();
  }catch(Exception e){
   e.printStackTrace();
  }
 }
 public boolean executeUpdate(String sql)
 {
   try
   {
     conn.executeUpdate(sql);
     return true;
   }
   catch (SQLException e)
   {
     e.printStackTrace();
     return false;
   }
 }
 public ResultSet executeQuery(String sql)
 {
   rs=null;
   try
   {
     rs=conn.executeQuery(sql);
   }
   catch (SQLException e)
   {
     e.printStackTrace();
   }
   return rs;
 }
 public void close()
 {
   try
   {
     conn.close();
     c.close();
   }
   catch (Exception e)
   {
     e.printStackTrace();
   }
 }
 public static void main(String[] args)
 {
  ResultSet rs;
  jdbc conn = new jdbc();
  rs=conn.executeQuery("select * from test");
  try{
  while (rs.next())
  {
   System.out.println(rs.getString("id"));
   System.out.println(rs.getString("name"));
  }
  }catch(Exception e)
  {
   e.printStackTrace();
  }
 }
}

2、Class.forName的作用?为什么要用?
答:调用该访问返回一个以字符串指定类名的类的对象。

3、Jdo是什么?
答:JDO是Java对象持久化的新的规范,为java data object的简称,也是一个用于存取某种数据仓库中的对象的标准化API。JDO提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(如JDBC API的使用)。这些繁琐的例行工作已经转移到JDO产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO很灵活,因为它可以在任何数据底层上运行。JDBC只是面向关系数据库(RDBMS)JDO更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。

4、在ORACLE大数据量下的分页解决方法。一般用截取ID方法,还有是三层嵌套方法。
答:一种分页方法
<%
  int i=1;
  int numPages=14;
  String pages = request.getParameter("page") ;
  int currentPage = 1;
  currentPage=(pages==null)?(1):{Integer.parseInt(pages)}
  sql = "select count(*) from tables";
  ResultSet rs = DBLink.executeQuery(sql) ;
  while(rs.next()) i = rs.getInt(1) ;
  int intPageCount=1;
  intPageCount=(i%numPages==0)?(i/numPages):(i/numPages+1);
  int nextPage ;
  int upPage;
  nextPage = currentPage+1;
  if (nextPage>=intPageCount) nextPage=intPageCount;
  upPage = currentPage-1;
  if (upPage<=1) upPage=1;
  rs.close();
  sql="select * from tables";
  rs=DBLink.executeQuery(sql);
  i=0;
  while((i<numPages*(currentPage-1))&&rs.next()){i++;}
%>
//输出内容
//输出翻页连接
合计:<%=currentPage%>/<%=intPageCount%><a href="List.jsp?page=1">第一页</a><a

href="List.jsp?page=<%=upPage%>">上一页</a>
<%
  for(int j=1;j<=intPageCount;j++){
  if(currentPage!=j){
%>
  <a href="list.jsp?page=<%=j%>">[<%=j%>]</a>
<%
  }else{
  out.println(j);
  }
  }
%>
<a href="List.jsp?page=<%=nextPage%>">下一页</a><a href="List.jsp?page=<%=intPageCount%>">最后页

</a>


Xml方面

1、xml有哪些解析技术?区别是什么?
答:有DOM,SAX,STAX等
DOM:处理大型文件时其性能下降的非常厉害。这个问题是由DOM的树结构所造成的,这种结构占用的内存较多,而且DOM必须在解析文件之前把整个文档装入内存,适合对XML的随机访问SAX:不现于DOM,SAX是事件驱动型的XML解析方式。它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过在其回调事件中写入处理代码来处理XML文件,适合对XML的顺序访问
STAX:Streaming API for XML (StAX)

2、你在项目中用到了xml技术的哪些方面?如何实现的?
答:用到了数据存贮,信息配置两方面。在做数据交换平台时,将不能数据源的数据组装成XML文件,然后将XML文件压缩打包加密后通过网络传送给接收者,接收解密与解压缩后再同XML文件中还原相关信息进行处理。在做软件配置时,利用XML可以很方便的进行,软件的各种配置参数都存贮在XML文件中。

3、用jdom解析xml文件时如何解决中文问题?如何解析?
答:看如下代码,用编码方式加以解决
package test;
import java.io.*;
public class DOMTest
{
 private String inFile = "c:\\people.xml";
 private String outFile = "c:\\people.xml";
 public static void main(String args[])
 {
     new DOMTest();
    }
 public DOMTest()
 {
  try
     {
      javax.xml.parsers.DocumentBuilder builder =
      

javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder();
      org.w3c.dom.Document doc = builder.newDocument();
      org.w3c.dom.Element root = doc.createElement("老师");
      org.w3c.dom.Element wang = doc.createElement("王");
   org.w3c.dom.Element liu = doc.createElement("刘");
      wang.appendChild(doc.createTextNode("我是王老师"));
      root.appendChild(wang);
      doc.appendChild(root);
      javax.xml.transform.Transformer transformer =
       javax.xml.transform.TransformerFactory.newInstance().newTransformer();
      transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "gb2312");
      transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");  

  
      transformer.transform(new javax.xml.transform.dom.DOMSource(doc),
            new

javax.xml.transform.stream.StreamResult(outFile));
     }
     catch (Exception e)
     {
      System.out.println (e.getMessage());
     }
    }
}

4、编程用JAVA解析XML的方式.
答:用SAX方式解析XML,XML文件如下:
<?xml version="1.0" encoding="gb2312"?>
<person>
  <name>王小明</name>
  <college>信息学院</college>  
  <telephone>6258113</telephone>
  <notes>男,1955年生,博士,95年调入海南大学</notes>
 </person>
 事件回调类SAXHandler.java
 import java.io.*;
import java.util.Hashtable;
import org.xml.sax.*;
public class SAXHandler extends HandlerBase
  {
  private Hashtable table = new Hashtable();
  private String currentElement = null;
  private String currentValue = null;
  public void setTable(Hashtable table)
    {
    this.table = table;
    }
  public Hashtable getTable()
    {
    return table;
    }
  public void startElement(String tag, AttributeList attrs)
  throws SAXException
    {
    currentElement = tag;
    }
  public void characters(char[] ch, int start, int length)
  throws SAXException
    {
    currentValue = new String(ch, start, length);
    }
  public void endElement(String name) throws SAXException
    {
    if (currentElement.equals(name))
      table.put(currentElement, currentValue);
    }
  }
JSP内容显示源码,SaxXml.jsp:
<HTML>
<HEAD>
<TITLE>剖析XML文件people.xml</TITLE>
</HEAD>
<BODY>
<%@ page errorPage="ErrPage.jsp"
contentType="text/html;charset=GB2312" %>
<%@ page import="java.io.*" %>
<%@ page import="java.util.Hashtable" %>
<%@ page import="org.w3c.dom.*" %>
<%@ page import="org.xml.sax.*" %>
<%@ page import="javax.xml.parsers.SAXParserFactory" %>
<%@ page import="javax.xml.parsers.SAXParser" %>
<%@ page import="SAXHandler" %>
<%
File file = new File("c:\\people.xml");
FileReader reader = new FileReader(file);
Parser parser;
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
SAXHandler handler = new SAXHandler();
sp.parse(new InputSource(reader), handler);
Hashtable hashTable = handler.getTable();
out.println("<TABLE BORDER=2><CAPTION>教师信息表</CAPTION>");
out.println("<TR><TD>姓名</TD>" + "<TD>" +
  (String)hashTable.get(new String("name")) + "</TD></TR>");
out.println("<TR><TD>学院</TD>" + "<TD>" +
  (String)hashTable.get(new String("college"))+"</TD></TR>");
out.println("<TR><TD>电话</TD>" + "<TD>" +
  (String)hashTable.get(new String("telephone")) + "</TD></TR>");
out.println("<TR><TD>备注</TD>" + "<TD>" +
  (String)hashTable.get(new String("notes")) + "</TD></TR>");
out.println("</TABLE>");
%>
</BODY>
</HTML>

EJB方面

1、EJB2.0有哪些内容?分别用在什么场合? EJB2.0和EJB1.1的区别?
答:规范内容包括Bean提供者,应用程序装配者,EJB容器,EJB配置工具,EJB服务提供者,系统管理员。这里面,EJB容器是EJB之所以能够运行的核心。EJB容器管理着EJB的创建,撤消,激活,去活,与数据库的连接等等重要的核心工作。JSP,Servlet,EJB,JNDI,JDBC,JMS…..

2、EJB与JAVA BEAN的区别?
答:Java Bean 是可复用的组件,对Java Bean并没有严格的规范,理论上讲,任何一个Java类都可以是一个Bean。但通常情况下,由于Java Bean是被容器所创建(如Tomcat)的,所以Java Bean应具有一个无参的构造器,另外,通常Java Bean还要实现Serializable接口用于实现Bean的持久性。Java Bean实际上相当于微软COM模型中的本地进程内COM组件,它是不能被跨进程访问的。Enterprise Java Bean 相当于DCOM,即分布式组件。它是基于Java的远程方法调用(RMI)技术的,所以EJB可以被远程访问(跨进程、跨计算机)。但EJB必须被布署在诸如Webspere、WebLogic这样的容器中,EJB客户从不直接访问真正的EJB组件,而是通过其容器访问。EJB容器是EJB组件的代理,EJB组件由容器所创建和管理。客户通过容器来访问真正的EJB组件。

3、EJB的基本架构
答:一个EJB包括三个部分:
  Remote Interface 接口的代码
  package Beans;
  import javax.ejb.EJBObject;
  import java.rmi.RemoteException;
  public interface Add extends EJBObject
  {
   //some method declare
  }
  Home Interface 接口的代码
  package Beans;
  import java.rmi.RemoteException;
  import jaax.ejb.CreateException;
  import javax.ejb.EJBHome;
  public interface AddHome extends EJBHome
  {
    //some method declare
  }
  EJB类的代码
  package Beans;
  import java.rmi.RemoteException;
  import javax.ejb.SessionBean;
  import javx.ejb.SessionContext;
  public class AddBean Implements SessionBean
  {
    //some method declare
  } 

J2EE,MVC方面

1、MVC的各个部分都有那些技术来实现?如何实现?
答:MVC是Model-View-Controller的简写。"Model" 代表的是应用的业务逻辑(通过JavaBean,EJB组件实现), "View" 是应用的表示面(由JSP页面产生),"Controller" 是提供应用的处理过程控制(一般是一个Servlet),通过这种设计模型把应用逻辑,处理过程和显示逻辑分成不同的组件实现。这些组件可以进行交互和重用。

2、应用服务器与WEB SERVER的区别?
希望大家补上,谢谢


3、J2EE是什么?
答:Je22是Sun公司提出的多层(multi-diered),分布式(distributed),基于组件(component-base)的企业级应用模型(enterpriese application model).在这样的一个应用系统中,可按照功能划分为不同的组件,这些组件又可在不同计算机上,并且处于相应的层次(tier)中。所属层次包括客户层(clietn tier)组件,web层和组件,Business层和组件,企业信息系统(EIS)层。

4、WEB SERVICE名词解释。JSWDL开发包的介绍。JAXP、JAXM的解释。SOAP、UDDI,WSDL解释。
答:Web Service描述语言WSDL
SOAP即简单对象访问协议(Simple Object Access Protocol),它是用于交换XML编码信息的轻量级协议。
UDDI 的目的是为电子商务建立标准;UDDI是一套基于Web的、分布式的、为Web Service提供的、信息注册中心的实现标准规范,同时也包含一组使企业能将自身提供的Web Service注册,以使别的企业能够发现的访问协议的实现标准。


5、BS与CS的联系与区别。
希望大家补上,谢谢

6、STRUTS的应用(如STRUTS架构)
答:Struts是采用Java Servlet/JavaServer Pages技术,开发Web应用程序的开放源码的framework。 采用Struts能开发出基于MVC(Model-View-Controller)设计模式的应用构架。 Struts有如下的主要功能:
一.包含一个controller servlet,能将用户的请求发送到相应的Action对象。
二.JSP自由tag库,并且在controller servlet中提供关联支持,帮助开发员创建交互式表单应用。
三.提供了一系列实用对象:XML处理、通过Java reflection APIs自动处理JavaBeans属性、国际化的提示和消息。

设计模式方面

1、开发中都用到了那些设计模式?用在什么场合?
答:每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。


2、UML方面
答:标准建模语言UML。用例图,静态图(包括类图、对象图和包图),行为图,交互图(顺序图,合作图),实现图,

JavaScript方面

1、如何校验数字型?
var re=/^\d{1,8}$|\.\d{1,2}$/;
var str=document.form1.all(i).value;
var r=str.match(re);
if (r==null)
{
   sign=-4;
   break;
}
else{
   document.form1.all(i).value=parseFloat(str);
}


CORBA方面

1、CORBA是什么?用途是什么?
答:CORBA 标准是公共对象请求代理结构(Common Object Request Broker Architecture),由对象管理组织 (Object Management Group,缩写为 OMG)标准化。它的组成是接口定义语言(IDL), 语言绑定(binding:也译为联编)和允许应用程序间互操作的协议。 其目的为:
用不同的程序设计语言书写
在不同的进程中运行
为不同的操作系统开发


LINUX方面

1、LINUX下线程,GDI类的解释。
答:LINUX实现的就是基于核心轻量级进程的"一对一"线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。
GDI类为图像设备编程接口类库。
 

北大科学史与科学哲学 – 科学哲学 – 科学哲学分论 :  浑序组织——一种建立在复杂性基础上的新型组织
文章来源: 自然辩证法通讯200204
加入时间: 2004-02-20
阅读次数: 1091 次
浑序组织——一种建立在复杂性基础上的新型组织
金吾伦

【作者简介】金吾伦(1937- )男,中国社会科学院哲学研究所研究员,博士生导师,山西大学科学技术哲学研究中心兼职研究员。山西大学科学技术哲学研究中心,太原 030006
【内容提要】传统的组织已越来越不能适应环境变化的不确定性,复杂性组织正在应运而生。浑序组织是典型的复杂性组织。本文阐述了浑序组织及其观念诞生的背景。它的本质特征以及成熟浑序组织所要具备的条件。
【关键词】浑序/复杂性组织/浑序联盟
【正文】
〔中图分类号〕N94 〔文献标识码〕A 〔文章编号〕1000-0763(2002)04-0073-06
在信息和知识爆炸式成长,其成果被广泛而快速地应用的新时代,一切都在发生深刻而巨大变革的情况下,我们的组织和管理概念,是否还将继续保留着十七世纪留传下来的机械性、分离性,并用它们来管理我们这个社会?回顾以往,人们会问:
为什么任何地方的机构,不管是政治性、商业性或社会性的,都愈来愈难以管理好自身的事务?
为什么到处看到俱与所属的机构愈来愈对立或疏离?
为什么社会与生物圈愈来愈紊乱?
人们已日益认识到,这是机械性组织概念给人类带来的恶果,也是人们对复杂性组织概念缺乏认识并未能在实际中运用的结果。那么,对人类心灵和生物圈具有毁灭性的这种机械性组织概念,究竟源自何处?为什么我们总是看不清它的真面目?我们是否有可能脱离传统组织观念的羁绊,建设一种全新的,与人类心灵与宇宙相合的“漂亮的组织”?复杂性组织就是其中首选的组织。
  一、复杂性组织的兴起
20世纪90年代开始,学者们逐渐关心并着手探索对人类社会至关重要的新组织管理问题。
1990年,美国麻省理工学院(MIT)的系统动力学家彼得·圣吉(Peter Senge)出版了一本具有划时代意义的著作《第五项修炼》,书中提出了“学习型组织”(Learning organization)的新概念。圣吉和他的同事们“孜孜不倦地致力于将系统动力学与组织学习、创造原理、认知科学、群体深度对谈与模拟演练”等紧密结合在一起,发展出“学习型组织”这样一种人类梦寐以求的组织蓝图。圣吉在他的书中强调指出,“在西方世界,我们的社会组织已被分割成四分五裂。我们把生理的健康与心理的和精神的健康分割开来探讨,以致于人们虽然活得久些,但整体身心健康状况却每况愈下,所支付的社会成本也愈来愈高。学校的教育成为片断知识的传授,和枯燥的学术性演练,最后竟发展到愈来愈和个人成长与真正的学习脱节,成效也愈来愈差。政府各部门不仅已被分割得各自为政,且被各利益团体的不同需求分割,变成一部老旧瘫痪、无法有效运作的机器。事实上,与现代管理系统有关的每一件事情,都根源于这种分割的思想上,这也无可避免地造成竞争。”[1]“工业化的力量也是强大的分割力量;随着工业的进步,分割在西方以加速的步调演进,并不意外。农业革命时播下的分割种子,在烟囱、工厂和传统工业管理的气候中,步调更加快了。”([1],第5页)
《第五项修炼》强调系统思维方式,以求实现“破镜重圆”,让破碎的镜子重组成一面镜子,重视系统,观照整体:从混沌中求秩序,从片段中找整体,避免“系统的危机”。[2]
与组织管理变革创新有关的第二个重大事件是1998年10月在美国波士顿港湾附近成立的一个新研究机构:新英格兰复杂系统研究所(New England Complex Systems Institute)。这个研究所一成立就创办了一本新杂志,刊名为“突现:组织管理中复杂性问题”杂志(Emergence:A Journal of Complexity Issues in Organizations and Management)。这个研究所与杂志的宗旨就是研究组织管理中的复杂性问题,而1999年第一期(创刊号)中心讨论复杂性理论应用于组织管理,有关内容不在此处详述,有兴趣的读者可查阅《突现》。[3]随着研究的进展,出现了一批研究复杂性管理的学者(Complexifiers),他们把这类研究称为Complex-M,M即Management。他们相信,人类组织这类“复杂系统”类似于自然界中气候模式或动物种群一样的复杂行为。商业也是活系统,同样也需要复杂性的组织管理。
复杂性组织管理上最新的研究成果属于迪伊·哈克(Dee Hock),是他创造了“浑沌组织”这一概念。哈克是VISA国际的创始人。1999年哈克出版了一本重要著作:《浑序时代的诞生》(Birth of the Chaordic Age),此书已引起了学界和商界的广泛重视。本文题意“浑序组织”原得自于此,重点介绍与此相关的观念。台湾学者将此书译为“乱序”,我个人以为,“乱序”一词易引起误解,故此处改译为“浑序”,即“混(浑)沌有序”之意。哈克在书中以大量篇幅着意批判了机械论和还原论,他指出,“也许最早可以追溯到亚里士多德、柏拉图或更早。不过直到牛顿的科学和笛卡儿的哲学之后,才算是孕育了这些概念的现代版本,形成了机械性的思维。自此以后,这种思维就主导了我们的整个想法,我们的组织性质,乃至西方工业社会的结构,其影响之深少有人能完全体认,而且还迅速向全球扩散。这种思维甚至认为宇宙乃至其中万物,无分物理、生物或社会层面,都可以化简为类似钟表的机械运作,各个组成零件均以精准可测的线性因果律相互作用。如果我们能完全解析个别零件及其作用,即可据以重建世界及其中万物……”[4]
两个多世纪来,人们就是这样孜孜不倦地以机械的、分割的角度来观照并建构社会。人们相信自己终能营造出一个可操控自如的组织,以为只要在某处拉起某根操纵杆,就可以得到预期而分毫不差的结果。“只可惜预期中结果出现的机会却少之又少。反倒是种种恶果历历在目:恶化的财富与权力分配,千疮百孔的生态圈,面临的崩解的社会”,使世界陷入了如圣吉所说的“系统的危机”。
如果说牛顿的科学与笛卡儿的哲学所形成的机械性思维是孕育今日组织概念之父,那么,工业时代就是其母,两者共同支配了所有组织的演化。然而,时代的变迁要求有一套与之全然不同的组织概念,使其能适应人类的生活、工作和娱乐的方式走向多元化和复杂化,使权力和财富的分配更加公平合理,更能发挥人类的才智,并与人类的性灵以及生态环境更为和谐。
传统的组织管理概念正在走向尽头。复杂性组织管理正在迅速走向世界。[5]、[6]
  二、VISA国际是浑序组织的原型
《第5代管理》一书的作者查尔斯·M.萨维奇在其书中是在“自组织与智能企业”标题下,作为新型组织来介绍国际VISA组织的。这个组织“既不是中央集权式的等级体制也不是命令控制结构。”
VISA卡公司(VISA international)是“由狄伊·哈克和另外几个人于60年代后期创建的。它由所有的成员公司共同拥有,它的权力和功能按照最大限度的可能进行分配,其管理是分布式的,具有无限的可延伸和极端的持久性,而且能够轻易地适应差异和变化。它在混沌的金融交易世界中找到了自己的秩序。事实上,哈克将混沌(Chaos)和有序(Order)两个词合并起来造出了“混沌有序”(Chaord)这个词来形容VISA卡公司的特征。他称之为混沌有序的(Chaordic)公司。”[7]
根据哈克的回忆,1968年VISA组织还只是一组信念与一个模糊的想法。1970年,它正式诞生。VISA一词,本来是指进入外国时所需的通行证件。通常我们称为“签证”。用在这里的目的是,他们想建立一个全球服务的价值交换系统,“使世界团结在一起”。1999年,该系统已有二万二千家身为所有人会员的金融机构(跨国银行会员)提供这项产品,更有1500万个遍布全球200多个国家和地区的商店接受刷卡。VISA的持卡人有7亿5千万,年度交易次数达140亿,金额为12.5兆(万亿)美元,是全球经济中最庞大的一群消费购买力。VISA过去30年的年度增长率,最高达50%,最低也有20%。
VISA是一个半官方、半营利、半非营利、半咨询、半授权、半教育、半社会、半商业、半政治的联盟。它不属于以上任何一类,但却同时具有所有的特征。哈克后来称它为“浑序”组织(Chaordic organization)。
前面提到的《第五项修炼》的作者彼得·圣吉对此组织评论说:
“VISA international公司是21世纪组织的一个极其有趣的范例。依某些标准衡量,它可能是世界上最大的公司。如果把VISA卡的所有业务加在一起,其市场总资本将达到4000亿美元左右。(注:这是圣吉早几年的估计,目前事实上已达12万5千亿美元。——金吾伦注)而协调整个公司的雇员总共却只有区区几千名。这是一种彻底非传统的公司形式,……根本不存在VISA公司的正式的等级制机构,它完全是一个网络组织。但是,它是一个组织。它成立于30年前,具有独创的哲学和设计。统治它的是一套理念,一个非常明确的目的宣言和一套周密设计的运营原则。”([2],第159页)
VISA诞生之际,还没有复杂性理论,没有国际互联网,没有企业联盟,也还没有信息社会。VISA的诞生,全是凭藉观念、想法与信心的力量。由于当时还没有复杂性理论作指导,虽然VISA拥有浑序的核心与概念,但大多数会员对此未能充分理解和利用,仍然保持机械性与线性的观念,并继续以传统的架构与管理方式来处理这项业务。有一些新进入VISA组织的管理者,对相关的基本信念和概念并未深入的理解和奉行,常常有意无意地将旧有的思考包袱加在新的组织上。哈克意识到,要充分理解并发扬浑序概念需要经历庞大的文化变迁。
1984年,哈克决定离开由他亲手创立的VISA国际组织,“切断与企业界的所有关联,过着与世隔绝的耕读生活”。他为什么要离开?哈克交代的唯一理由是:“我觉得必须将生活向新的可能性敞开。”从他这句话和别的论述中,可以认为,他当时已不满足于VISA的创立,内心有更大的抱负,寻求创立更具普遍意义的组织和新的可能性——“浑序”组织。“这种组织似乎是从混乱中产生秩序,而与试图施加秩序(结果常常制造出许多混乱)的传统组织形成反差。”([4],第160页)
  三、浑序观念的诞生
离开VISA,哈克过着与企业界隔绝的耕读生活,“将生活向新的可能性敞开”,从事更深入的理论思考。
思考的是他“多年的钻研而不得其解”的问题:人是机器吗?机器是人吗?两者是否以我们无法理解的方式不可分割地连结起来?为什么我们开始想让机器像人,让人像机器?为什么我们开始认为地球与人类是分离的,地球只是一个提供免费原料的货仓,让我们可以制造机械化货币经济中各色的消费玩意:一个可供我们免费丢弃毒物垃圾的地方?我们为何在理性思维中开始将事物分割?这种分割是否可能只将事物在思维中分割而不在实体上分割?一个分割是否必然导至另一个分割?是谁决定了这种分割,让我们的思想与生活陷于日益狭隘的专业化与特殊化的框框中?如果分割的观念(身与心、因与果、人类与自然、竞争与合作、公共与私有、男与女、你与我)只不过是西方文明的一大幻象,再经过工业时代精炼,虽然有助于某些科学上的认知;但就理解与智慧却是根本的缺陷,又该如何呢?如果我们对分割性、特殊性与测量的看法,只不过是意识的大演化过程中智暂的脱轨,那又将如何呢?
哈克经过多年钻研得到这样的结论:
1.特殊性与分割性是人类心智上的弱点,而非宇宙的通则。
有人认为,身、心、灵是分立的东西,就像钟表里的齿轮、凸轮、弹簧一样。这在哈克看来,是极其荒谬的。因为机器、人、自然并非分立的。身、心、灵是不可分的一体,也与宇宙其他万物一体。“我们是万物无穷尽关联中的一个小宇宙:既是个别又是全体,既是自然又是非我,与宇宙合而为一”。
2.生命绝不会向量尺透露自己的秘密。
哈克认为培根、牛顿、笛卡儿等人以及他们创立机械论的科学所主张的,我们脑壳里那几磅灰色物质,不过是分离的粒子间颤动的电力和化学冲动,依循严格而普通的因果律。实际上,生命远比此要复杂得多。虽然现代科学及其对测量与特殊性的执着创造了许多奇迹,但科学大致不涉及理解与智慧。因此生命的秘密远未揭开。如果我们想在不确定的汪洋大海中捞取绝对,能捕捉到的只有疑惑。更何况,生命绝不是许多机械粒子的某种组合,这是说,生命是不可能通过拆分,进行测量而得到充分理解的,也没有足够的智慧引领人类朝着整体性、创造性、建设性的方向演变。
3.支配欲与控制欲会召来死亡:绝对的控制只存在棺材之内。
哈克认为,从组织管理的角度来说,绝对完美的预测和控制只存在于棺材里。因为生命就是爱恨情仇、酸甜苦辣、喜怒哀乐,还有成千上万件难以预测的事物交织而成。生命不在于控制、不在于获得、不在于拥有、不在于知道,甚至也不在于“活着”。生活是永恒无休止的“交易”。交易无法测知或控制,是一场必须亲身经历的神奇之旅。如果一个社会过度崇尚测量、预测和控制,就必然将导致许多不良的后果。
哈克之所以能获得这些独具智慧的洞见,是认真阅读《复杂》(Complexity)(注:这是由米切尔·沃尔德罗普(Mitchell Waldrup)所著的《复杂》(Complexity)一书,该书出版于1992年,国内译本为陈玲译,《复杂——诞生于秩序与混沌边缘的科学》,科学人文丛书,生活·读书·新知三联书店,1997年,书中详细介绍了圣塔菲研究所及其中几位主要成员的研究工作及新颖观点。——金吾伦注)一书及深入思考的结果。他形容看这本书是“与风雨的对话”,描述初读这本书的情景:“当时我丝毫没有料到,未来人生道路另一番的美好事物已在此乍露端倪”、“越读越着迷”。该书描述了几位来自不同领域的知名科学家和学者,为了追寻共同的理念而成立了一个小型机构。他们认为,经由研究复杂而有自组织与适应能力的系统(他们称之为“复杂”系统),或许可以催生出一种新的科学。两百年来,科学对宇宙与其中的万物,一直设法以符合精确、线性因果律的运行机制来解释。这些作者对此不以为然,他们担心一味追求专门、分离、特殊,可能会让人在理解最终真理上走进死胡同,因此他们提出了较为整体的方法。“这种复杂的连结性有一种性质,使事物能立即产生秩序,此时的现象无法由个别的知识来理解,而这种秩序也不见得依循线性的因果律。他们认为,所有复杂而有适应能力的系统都存在于混沌的边缘,它们也具有足够的自组织能力,以创造我们称之为‘秩序’的认知型态。”([4],第23页)
哈克强调,吸引他的倒并非是这些观念本身,因为这些观念与他多年思考的观念不谋而合。他数十年来,用来阐释“社会组织应立基于自然组织的方式上”的正是这些观念。真正吸引他的地方是,这些观念如今是由物理学和生物学界的人士提出来的。哈克看到了这些科学家提出了许多新名词,如自催化,非线性、自组织、复杂、调整、整体等,但他认为这些名词较为人难以理解。所以他根据这种体系,应该是在混沌与秩序间交换的窄缝中出现的认识,创造了一个新名词,分别借用“混沌”(Chaos)和“有序”(Order)的第一个音节,造出了“浑序”(Chaord)一字。
“浑序”(Chaord读音Kay’ord):
1.任何自我组织、自我管理、有适应能力、非线性、复杂的有机体、组织、社群或系统,无论物理,生物或社会行为,均能和谐地结合混沌与有序两种特性。
2.一个个体,其行为所展现的模式与几率,无法由其构成的部分推断或解释。
3.任何混沌排序的复合体。
4.以演化与自然为基本组织原则的个体。([4],第26页)
“浑序”概念的提出及其在组织管理上的应用,必将为给人类的组织型态带来深刻的变革,也可能成为社会变革的一个重要促动因素。
在浑序时代,成功主要源自活用理智而非死守规定,源自多数人的判断而非少数人的权威,源自动机而非强迫,源自内在的纪律而非外在的控制。这要求人们努力挣脱机械性、分割性和线性思考模式,而走向复杂性和整体性。浑序组织的有效运作,尤其需要人们有一个文化上的彻底改变。
  四、一个新型组织:浑序联盟
1993年,在哈克隐居生活的第九个年头,他一边认真读着我们在前面已提到的沃尔德鲁普的《复杂》一书,一边进入大自然,去“林间沉思”他那几十年一直致力思索的组织概念。
哈克在思考:西方所使用的分割性、机械性概念,的确不失为看待世界的有力方式,以及认识事实某些层面的有用方法,也是日常生活中实用的帮手。但如果将之视为理解事实的最佳方式,就会发生问题;如果视为唯一方式,更为导致毁灭。执着地相信线性、分割性和因果律并据此行事,就会否认事物间相互连结性与整体性。……正在哈克思考这些大自然与人类思维本质的奥秘时,他被应邀访问研究复杂性的机构——圣塔菲研究所(Santa Fe Institute)。
哈克访问圣塔菲研究所,目的不在了解他们所致力的研究科学,而是想了解研究所本身,这个组织的性质、管理、信念。他所关心的是,该研究所的性质、构造与行为,是否反映出它本身所研究的新兴科学?也即他感兴趣的是圣塔菲研究所这个组织的结构层面,而非他们所研究的科学内容。他想知道的是这种组织能否发展出一些社会与组织的概念,与科学、人类心灵以及生态圈更能和谐共存?
第一次接待他的是时任该所所长金姆·派尔基(Jim Pelkey)。研究所的学术气氛激起了他对浑序理念及其运用在其他组织可能性的探索。他在研究所的一次会上,结识了乔伊斯基金会(Joyce Foundation)的副执行长乔尔·杰琛丹纳(JoelGetzendanner),两人就浑序概念进行了多次深入的讨论,并于讨论后反复的思考。最后集中到以下几个核心问题上:1.浑序组织概念的本质;2.浑序组织的特征;3.要在整个社会中掀起普遍的浑序组织的改变、需要什么条件?
1.浑序组织概念的本质:山猫逮地鼠
“浑序”概念意味着什么?的确让人难以捉摸。笼统地说,“浑序”就是“浑沌”和“有序”之间的一种状态。但具体理解起来,还很模糊,甚至连哈克自己也还不甚清晰。一天,哈克在农场里看到一只山猫逮地鼠的情境,使他对“浑序”组织的概念本质有了形象生动的体会。哈克描述了这样一个情境。
“……一头山猫出现了,全神盯着厨房窗户下埋伏绿草的小丘上新掘的地鼠洞穴。……各只脚掌接续地以难以察觉的流畅动作举起往前伸,利爪先是插入草叶,接着调整好角度静静地放在地上。山猫以这种慢动作向前滑动,尾巴丝毫不动,眼睛眨也不眨,然后重心缓缓移动,身驱拱起,光滑的皮毛下肌肉起伏。……以人眼无法企及的速度。山猫窜了出去,一只地鼠落入山猫利爪箝制之下,仰天踢起洞口的尘土。”(第286页)
哈克对此赞叹不已,自称是他所见到的“对生的本质最完美的展现”。山猫逮地鼠的那一幕纯属天然,人力无从模拟。山猫的本质无法测量、设计、制造或了解。他并非混沌,但也未受控制(有序),而是混沌边缘有序的形态。这就是浑序。
山猫不是机器。我们应该有可能构思出有别于机器的组织。
2.浑序组织的特征:
一份网上资料[8]向我们展示了浑序组织具有以下特征:
·基于共有目标和原则;
·整体与部分都是自组织和自支配的;
·它的存在是它的组分的先在条件;
·从周遭获取权力,从核心获得统一;
·目的和原则的持续性;形式与功能的可变性;
·公平地分配权力、权利、义务与奖励;
·合作与竞争的和谐结合;
·在不断扩展的循环中学习、适应和创新;
·与人类精神和生态圈相容;
·解放并倍增机敏、创意和判断;
·容纳并培育多样性、复杂性和变易;
·建设性地利用和协调冲突与悖论;
·抑制和适当地嵌入命令和控制方法。
比较一下霍兰所描述的复杂适应系统的特征[9]可以看出,浑序组织与复杂适应系统是一致的。
3.成熟浑序组织的四个条件:
1)实例
除VISA和Internet之外,至少需要有十几个非常成功的浑序组织的新例子出现,而且必须遍布于教育、政府、社会服务、企业、环境等不同领域,并分属不同的国家与文化,这才能让大家接受浑序是一个可以普遍应用的观念。
这些新组织需超越所有现行的组织疆界,连续多元领域的人员与组织。通过浑序组织的许多实例,让人们认识到它的生命力所在,才能使他们接受新的组织观念或管理方法。
2)模型
建立多元空间的浑序组织模型,以便可供检验,并与现有组织进行比较。此一模型必须包含目前一般模型所欠缺的道德与心灵维度。
除实体模型之外,也要设计电脑模型,可以在短时间内展现出在共同目标与原则下,浑序组织如何自组织并随时间演化、创造、管理多元复杂的事物,形成一个可长可久的社会。
3)理论基础
实例需以理论作支撑。所以必须开发并宣导浑序组织的理论基础。该组织基础必须整合浑序组织在经济、政治、历史、科技、社会、哲学等方面的理论,建立普及浑序观念所必须的共同语言与范例。
4)组织
建立一个全球性浑序组织,以庞大的复杂结构把来自理论和经验的知识融合起来,把所有关心组织并愿以此尽力的个人与组织结合在一起以发展、传播并推行浑序的组织观念,形成一个分享知识的全球网络。该组织必须依循本身所信奉的理念自我组织,同时成为浑序组织的一个成功范例。这个组织就是哈克于1994年开始奔波筹建的“浑序联盟”(Chaordic Alliance)。
“浑序联盟”已经把许多组织联结在一起,包括由彼得·圣吉所领导的MIT组织学习中心也加入了这一联盟。浑序联盟正在发展和壮大中。
最后让我们引用哈克的话来结合我们对浑序组织的介绍:
“我们的现在和未来都是浑序的,现在的世界是浑序的,而我们的组织未来也必须变为浑序的。随着社会日益演化为多元与复杂,这也是一条通道,引领我们前往一个宜于生存的未来。……这种(浑序)组织蕴涵了本身持续的学习,秩序与改善内在能力;与人类心灵相互契合:与其他组织、所有人类,乃至其他所有生物以及地球共同和谐地演化,让万事万物均能发挥最大潜能。”([4],第297页)
我们期盼着这个建立在复杂性基础上的浑序组织,在实践基础上积累丰富的经验,创造深刻的理论,并获得成长壮大,使之成为新世纪里取代传统组织管理的一个新的有效形式。
附图
“浑序”定义图示
〔收稿日期〕2001年8月20日
【参考文献】
[1] 〔美〕彼得·圣吉著,郭进隆译,《第五项修炼——学习型组织的艺术与实务》,上海三联书店,1998年,第4页。
[2] 彼得·圣吉,“经受考验”,载罗文·吉布森编,《重思未来》,海南出版社,1999年,第154页。
[3] Emergence:A Journal of Complexity Issues in Organizations and Management,a Publication of The New England Conyolex Systems Institue,Vol[#]1,Issue[#]1,1999,其中共6篇重要文章。
[4] Dee Hock,Birth of the Chaordic Age,Locus Publishing Company,1999,台湾译本,李明译《乱序》,(台湾)大块文化出版股份有限公司,2000年,第51-52页,以下正文所注页码均引自本书。
[5] Robin Wood,Managing Complexity,Published by Profile Books Ltd,2000.
[6] 成思危主编,《复杂性科学探索》,民主与建设出版社,1999年
[7] 〔美〕查尔斯·M.萨维卡著,谢强华等译,《第五代管理》,珠海出版社,1998年,第188-189页。
[8] http://www.chaodic.org/chaordic/res-def.html,感谢友人孟庆俊先生惠赠多种外文资料,包括该资料。
[9] 约翰·H.霍兰著,周晓牧等译,《隐秩序——适应性造就复杂性》,上海科技教育出版社,2000年,第10-40页。

原著:Michael Dunn

翻译:Chengjie Sun

原文出处:CodeProject:The Complete Guide to C++ Strings, Part I

 引言

  毫无疑问,我们都看到过像 TCHAR, std::string, BSTR 等各种各样的字符串类型,还有那些以 _tcs 开头的奇怪的宏。你也许正在盯着显示器发愁。本指引将总结引进各种字符类型的目的,展示一些简单的用法,并告诉您在必要时,如何实现各种字符串类型之间的转换。
  在第一部分,我们将介绍3种字符编码类型。了解各种编码模式的工作方式是很重要的事情。即使你已经知道一个字符串是一个字符数组,你也应该阅读本部分。一旦你了解了这些,你将对各种字符串类型之间的关系有一个清楚地了解。
  在第二部分,我们将单独讲述string类,怎样使用它及实现他们相互之间的转换。

 字符基础 — ASCII, DBCS, Unicode

  所有的 string 类都是以C-style字符串为基础的。C-style 字符串是字符数组。所以我们先介绍字符类型。这里有3种编码模式对应3种字符类型。第一种编码类型是单子节字符集(single-byte character set or SBCS)。在这种编码模式下,所有的字符都只用一个字节表示。ASCII是SBCS。一个字节表示的0用来标志SBCS字符串的结束。
  第二种编码模式是多字节字符集(multi-byte character set or MBCS)。一个MBCS编码包含一些一个字节长的字符,而另一些字符大于一个字节的长度。用在Windows里的MBCS包含两种字符类型,单字节字符(single-byte characters)和双字节字符(double-byte characters)。由于Windows里使用的多字节字符绝大部分是两个字节长,所以MBCS常被用DBCS代替。
  在DBCS编码模式中,一些特定的值被保留用来表明他们是双字节字符的一部分。例如,在Shift-JIS编码中(一个常用的日文编码模式),0×81-0×9f之间和 0xe0-oxfc之间的值表示"这是一个双字节字符,下一个子节是这个字符的一部分。"这样的值被称作"leading bytes",他们都大于0×7f。跟随在一个leading byte子节后面的字节被称作"trail byte"。在DBCS中,trail byte可以是任意非0值。像SBCS一样,DBCS字符串的结束标志也是一个单字节表示的0。
  第三种编码模式是Unicode。Unicode是一种所有的字符都使用两个字节编码的编码模式。Unicode字符有时也被称作宽字符,因为它比单子节字符宽(使用了更多的存储空间)。注意,Unicode不能被看作MBCS。MBCS的独特之处在于它的字符使用不同长度的字节编码。Unicode字符串使用两个字节表示的0作为它的结束标志。
  单字节字符包含拉丁文字母表,accented characters及ASCII标准和DOS操作系统定义的图形字符。双字节字符被用来表示东亚及中东的语言。Unicode被用在COM及Windows NT操作系统内部。

你一定已经很熟悉单字节字符。当你使用char时,你处理的是单字节字符。双字节字符也用char类型来进行操作(这是我们将会看到的关于双子节字符的很多奇怪的地方之一)。Unicode字符用wchar_t来表示。Unicode字符和字符串常量用前缀L来表示。例如:

wchar_t wch = L”1”; // 2 bytes, 0×0031
wchar_t* wsz = L"Hello"; // 12 bytes, 6 wide characters
 字符在内存中是怎样存储的

  单字节字符串:每个字符占一个字节按顺序依次存储,最后以单字节表示的0结束。例如。"Bob"的存贮形式如下:

42 6F 62 00
B o b BOS

Unicode的存储形式,L"Bob"

42 00  6F 00 62 00 00 00
B o b BOS

使用两个字节表示的0来做结束标志。

  一眼看上去,DBCS 字符串很像 SBCS 字符串,但是我们一会儿将看到 DBCS 字符串的微妙之处,它使得使用字符串操作函数和永字符指针遍历一个字符串时会产生预料之外的结果。字符串" " ("nihongo")在内存中的存储形式如下(LB和TB分别用来表示 leading byte 和 trail byte)

93 FA 96 7B 8C EA 00
LB TB LB TB LB TB EOS
   EOS

值得注意的是,"ni"的值不能被解释成WORD型值0xfa93,而应该看作两个值93和fa以这种顺序被作为"ni"的编码。

 使用字符串处理函数

  我们都已经见过C语言中的字符串函数,strcpy(), sprintf(), atoll()等。这些字符串只应该用来处理单字节字符字符串。标准库也提供了仅适用于Unicode类型字符串的函数,比如wcscpy(), swprintf(), wtol()等。
  微软还在它的CRT(C runtime library)中增加了操作DBCS字符串的版本。Str***()函数都有对应名字的DBCS版本_mbs***()。如果你料到可能会遇到DBCS字符串(如果你的软件会被安装在使用DBCS编码的国家,如中国,日本等,你就可能会),你应该使用_mbs***()函数,因为他们也可以处理SBCS字符串。(一个DBCS字符串也可能含有单字节字符,这就是为什么_mbs***()函数也能处理SBCS字符串的原因)
  让我们来看一个典型的字符串来阐明为什么需要不同版本的字符串处理函数。我们还是使用前面的Unicode字符串 L"Bob":

42 00  6F 00 62 00 00 00
B o b BOS

  因为x86CPU是little-endian,值0×0042在内存中的存储形式是42 00。你能看出如果这个字符串被传给strlen()函数会出现什么问题吗?它将先看到第一个字节42,然后是00,而00是字符串结束的标志,于是strlen()将会返回1。如果把"Bob"传给wcslen(),将会得出更坏的结果。wcslen()将会先看到0×6f42,然后是0×0062,然后一直读到你的缓冲区的末尾,直到发现00 00结束标志或者引起了GPF。
  到目前为止,我们已经讨论了str***()和wcs***()的用法及它们之间的区别。Str***()和_mbs**()之间的有区别区别呢?明白他们之间的区别,对于采用正确的方法来遍历DBCS字符串是很重要的。下面,我们将先介绍字符串的遍历,然后回到str***()与_mbs***()之间的区别这个问题上来。

 正确的遍历和索引字符串

  因为我们中大多数人都是用着SBCS字符串成长的,所以我们在遍历字符串时,常常使用指针的++-和-操作。我们也使用数组下标的表示形式来操作字符串中的字符。这两种方式是用于SBCS和Unicode字符串,因为它们中的字符有着相同的宽度,编译器能正确的返回我们需要的字符。
  然而,当碰到DBCS字符串时,我们必须抛弃这些习惯。这里有使用指针遍历DBCS字符串时的两条规则。违背了这两条规则,你的程序就会存在DBCS有关的bugs。

1.在前向遍历时,不要使用++操作,除非你每次都检查lead byte;
2.永远不要使用-操作进行后向遍历。
  我们先来阐述规则2,因为找到一个违背它的真实的实例代码是很容易的。假设你有一个程序在你自己的目录里保存了一个设置文件,你把安装目录保存在注册表中。在运行时,你从注册表中读取安装目录,然后合成配置文件名,接着读取该文件。假设,你的安装目录是C:\Program Files\MyCoolApp,那么你合成的文件名应该是C:\Program Files\MyCoolApp\config.bin。当你进行测试时,你发现程序运行正常。
  现在,想象你合成文件名的代码可能是这样的:

bool GetConfigFileName ( char* pszName, size_t nBuffSize )
{
    char szConfigFilename[MAX_PATH];
 
    // Read install dir from registry… we”ll assume it succeeds.
 
    // Add on a backslash if it wasn”t present in the registry value.
    // First, get a pointer to the terminating zero.
    char* pLastChar = strchr ( szConfigFilename, ”\0” );
 
    // Now move it back one character.
    pLastChar–; 
 
    if ( *pLastChar != ”\\” )
        strcat ( szConfigFilename, "\\" );
 
    // Add on the name of the config file.
    strcat ( szConfigFilename, "config.bin" );
 
    // If the caller’’s buffer is big enough, return the filename.
    if ( strlen ( szConfigFilename ) >= nBuffSize )
        return false;
    else
        {
        strcpy ( pszName, szConfigFilename );
        return true;
        }
}     
  这是一段很健壮的代码,然而在遇到 DBCS 字符时它将会出错。让我们来看看为什么。假设一个日本用户使用了你的程序,把它安装在 C:\。下面是这个名字在内存中的存储形式:
 43 3A 5C 83 88 83 45 83 52 83 5C 00
      LB TB  LB TB  LB TB  LB TB   
C : \     EOS

  当使用 GetConfigFileName() 检查尾部的”\\”时,它寻找安装目录名中最后的非0字节,看它是等于”\\”的,所以没有重新增加一个”\\”。结果是代码返回了错误的文件名。
  哪里出错了呢?看看上面两个被用蓝色高量显示的字节。斜杠”\\”的值是0×5c。” ”的值是83 5c。上面的代码错误的读取了一个 trail byte,把它当作了一个字符。
  正确的后向遍历方法是使用能够识别DBCS字符的函数,使指针移动正确的字节数。下面是正确的代码。(指针移动的地方用红色标明)

bool FixedGetConfigFileName ( char* pszName, size_t nBuffSize )
{
    char szConfigFilename[MAX_PATH];
 
    // Read install dir from registry… we”ll assume it succeeds.
 
    // Add on a backslash if it wasn”t present in the registry value.
    // First, get a pointer to the terminating zero.
    char* pLastChar = _mbschr ( szConfigFilename, ”\0” );
 
    // Now move it back one double-byte character.
    pLastChar = CharPrev ( szConfigFilename, pLastChar );
 
    if ( *pLastChar != ”\\” )
        _mbscat ( szConfigFilename, "\\" );
 
    // Add on the name of the config file.
    _mbscat ( szConfigFilename, "config.bin" );

     // If the caller’’s buffer is big enough, return the filename.
    if ( _mbslen ( szInstallDir ) >= nBuffSize )
        return false;
    else
        {
        _mbscpy ( pszName, szConfigFilename );
        return true;
        }
}

  上面的函数使用CharPrev() API使pLastChar向后移动一个字符,这个字符可能是两个字节长。在这个版本里,if条件正常工作,因为lead byte永远不会等于0×5c。
  让我们来想象一个违背规则1的场合。例如,你可能要检测一个用户输入的文件名是否多次出现了”:”。如果,你使用++操作来遍历字符串,而不是使用CharNext(),你可能会发出不正确的错误警告如果恰巧有一个trail byte它的值的等于”:”的值。
与规则2相关的关于字符串索引的规则:2a. 永远不要使用减法去得到一个字符串的索引。
违背这条规则的代码和违背规则2的代码很相似。例如,

char* pLastChar = &szConfigFilename [strlen(szConfigFilename) - 1];
这和向后移动一个指针是同样的效果。

 回到关于str***()和_mbs***()的区别

  现在,我们应该很清楚为什么_mbs***()函数是必需的。Str***()函数根本不考虑DBCS字符,而_mbs***()考虑。如果,你调用strrchr("C:\\ ", ”\\”),返回结果可能是错误的,然而_mbsrchr()将会认出最后的双字节字符,返回一个指向真的”\\”的指针。
  关于字符串函数的最后一点:str***()和_mbs***()函数认为字符串的长度都是以char来计算的。所以,如果一个字符串包含3个双字节字符,_mbslen()将会返回6。Unicode函数返回的长度是按wchar_t来计算的。例如,wcslen(L"Bob")返回3。

 Win32 API中的MBCS和Unicode

两组 APIs:
  尽管你也许从来没有注意过,Win32中的每个与字符串相关的API和message都有两个版本。一个版本接受MBCS字符串,另一个接受Unicode字符串。例如,根本没有SetWindowText()这个API,相反,有SetWindowTextA()和SetWindowTextW()。后缀A表明这是MBCS函数,后缀W表示这是Unicode版本的函数。
  当你 build 一个 Windows 程序,你可以选择是用 MBCS 或者 Unicode APIs。如果,你曾经用过VC向导并且没有改过预处理的设置,那表明你用的是MBCS版本。那么,既然没有 SetWindowText() API,我们为什么可以使用它呢?winuser.h头文件包含了一些宏,例如:

BOOL WINAPI SetWindowTextA ( HWND hWnd, LPCSTR lpString );
BOOL WINAPI SetWindowTextW ( HWND hWnd, LPCWSTR lpString );
 
#ifdef UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif     
当使用MBCS APIs来build程序时,UNICODE没有被定义,所以预处理器看到:#define SetWindowText SetWindowTextA
  这个宏定义把所有对SetWindowText的调用都转换成真正的API函数SetWindowTextA。(当然,你可以直接调用SetWindowTextA() 或者 SetWindowTextW(),虽然你不必那么做。)
  所以,如果你想把默认使用的API函数变成Unicode版的,你可以在预处理器设置中,把_MBCS从预定义的宏列表中删除,然后添加UNICODE和_UNICODE。(你需要两个都定义,因为不同的头文件可能使用不同的宏。) 然而,如果你用char来定义你的字符串,你将会陷入一个尴尬的境地。考虑下面的代码:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowText ( hwnd, szNewText );
在预处理器把SetWindowText用SetWindowTextW来替换后,代码变成:

HWND hwnd = GetSomeWindowHandle();
char szNewText[] = "we love Bob!";
SetWindowTextW ( hwnd, szNewText );
  看到问题了吗?我们把单字节字符串传给了一个以Unicode字符串做参数的函数。解决这个问题的第一个方案是使用 #ifdef 来包含字符串变量的定义:

HWND hwnd = GetSomeWindowHandle();
#ifdef UNICODE
wchar_t szNewText[] = L"we love Bob!";
#else
char szNewText[] = "we love Bob!";
#endif
SetWindowText ( hwnd, szNewText );
你可能已经感受到了这样做将会使你多么的头疼。完美的解决方案是使用TCHAR.

 使用TCHAR

  TCHAR是一种字符串类型,它让你在以MBCS和UNNICODE来build程序时可以使用同样的代码,不需要使用繁琐的宏定义来包含你的代码。TCHAR的定义如下:

#ifdef UNICODE
typedef wchar_t TCHAR;
#else
typedef char TCHAR;
#endif
所以用MBCS来build时,TCHAR是char,使用UNICODE时,TCHAR是wchar_t。还有一个宏来处理定义Unicode字符串常量时所需的L前缀。

#ifdef UNICODE
#define _T(x) L##x
#else
#define _T(x) x
#endif
  ##是一个预处理操作符,它可以把两个参数连在一起。如果你的代码中需要字符串常量,在它前面加上_T宏。如果你使用Unicode来build,它会在字符串常量前加上L前缀。

TCHAR szNewText[] = _T("we love Bob!");
  像是用宏来隐藏SetWindowTextA/W的细节一样,还有很多可以供你使用的宏来实现str***()和_mbs***()等字符串函数。例如,你可以使用_tcsrchr宏来替换strrchr()、_mbsrchr()和wcsrchr()。_tcsrchr根据你预定义的宏是_MBCS还是UNICODE来扩展成正确的函数,就像SetWindowText所作的一样。
  不仅str***()函数有TCHAR宏。其他的函数如, _stprintf(代替sprinft()和swprintf()),_tfopen(代替fopen()和_wfopen())。 MSDN中"Generic-Text Routine Mappings."标题下有完整的宏列表。

 字符串和TCHAR typedefs

  由于Win32 API文档的函数列表使用函数的常用名字(例如,"SetWindowText"),所有的字符串都是用TCHAR来定义的。(除了XP中引入的只适用于Unicode的API)。下面列出一些常用的typedefs,你可以在msdn中看到他们。

type  Meaning in MBCS builds  Meaning in Unicode builds
WCHAR wchar_t wchar_t
LPSTR  zero-terminated string of char (char*) zero-terminated string of char (char*)
LPCSTR  constant zero-terminated string of char (const char*) constant zero-terminated string of char (const char*)
LPWSTR zero-terminated Unicode string (wchar_t*)  zero-terminated Unicode string (wchar_t*)
LPCWSTR constant zero-terminated Unicode string (const wchar_t*) constant zero-terminated Unicode string (const wchar_t*) 
TCHAR char wchar_t
LPTSTR zero-terminated string of TCHAR (TCHAR*)  zero-terminated string of TCHAR (TCHAR*)
LPCTSTR  constant zero-terminated string of TCHAR (const TCHAR*) constant zero-terminated string of TCHAR (const TCHAR*)

 何时使用 TCHAR 和 Unicode

  到现在,你可能会问,我们为什么要使用Unicode。我已经用了很多年的char。下列3种情况下,使用Unicode将会使你受益:

1.你的程序只运行在Windows NT系统中。
2. 你的程序需要处理超过MAX_PATH个字符长的文件名。
3. 你的程序需要使用XP中引入的只有Unicode版本的API.
  Windows 9x 中大多数的 API 没有实现 Unicode 版本。所以,如果你的程序要在windows 9x中运行,你必须使用MBCS APIs。然而,由于NT系统内部都使用Unicode,所以使用Unicode APIs将会加快你的程序的运行速度。每次,你传递一个字符串调用MBCS API,操作系统会把这个字符串转换成Unicode字符串,然后调用对应的Unicode API。如果一个字符串被返回,操作系统还要把它转变回去。尽管这个转换过程被高度优化了,但它对速度造成的损失是无法避免的。
  只要你使用Unicode API,NT系统允许使用非常长的文件名(突破了MAX_PATH的限制,MAX_PATH=260)。使用Unicode API的另一个优点是你的程序会自动处理用户输入的各种语言。所以一个用户可以输入英文,中文或者日文,而你不需要额外编写代码去处理它们。
  最后,随着windows 9x产品的淡出,微软似乎正在抛弃MBCS APIs。例如,包含两个字符串参数的SetWindowTheme() API只有Unicode版本的。使用Unicode来build你的程序将会简化字符串的处理,你不必在MBCS和Unicdoe之间相互转换。
  即使你现在不使用Unicode来build你的程序,你也应该使用TCHAR及其相关的宏。这样做不仅可以的代码可以很好地处理DBCS,而且如果将来你想用Unicode来build你的程序,你只需要改变一下预处理器中的设置就可以实现了。


 作者简介
  Michael Dunn:居住在阳光城市洛杉矶。他是如此的喜欢这里的天气以致于想一生都住在这里。他在4年级时开始编程,那时用的电脑是Apple //e。1995年,在 UCLA 获得数学学士学位,随后在Symantec 公司做 QA 工程师,在 Norton AntiVirus 组工作。他自学了 Windows 和 MFC 编程。1999-2000年,他设计并实现了 Norton AntiVirus 的新界面。 
  Michael 现在在 Napster(一个提供在线订阅音乐服务的公司)做开发工作,他还开发了UltraBar,一个IE工具栏插件,它可以使网络搜索更加容易,给了 googlebar 以沉重打击;他还开发了 CodeProject SearchBar;与人共同创建了 Zabersoft 公司,该公司在洛杉矶和丹麦的 Odense 都设有办事处。
  他喜欢玩游戏。爱玩的游戏有 pinball, bike riding,偶尔还玩 PS, Dreamcasth 和 MAME 游戏。他因忘了自己曾经学过的语言:法语、汉语、日语而感到悲哀。

在ORACLE大数据量下的分页解决方法。一般用截取ID方法,还有是三层嵌套方法。

一种分页方法
<%
  int i=1;
  int numPages=14;
  String pages = request.getParameter("page") ;
  int currentPage = 1;
  currentPage=(pages==null)?(1):{Integer.parseInt(pages)}
  sql = "select count(*) from tables";
  ResultSet rs = DBLink.executeQuery(sql) ;
  while(rs.next()) i = rs.getInt(1) ;
  int intPageCount=1;
  intPageCount=(i%numPages==0)?(i/numPages):(i/numPages+1);
  int nextPage ;
  int upPage;
  nextPage = currentPage+1;
  if (nextPage>=intPageCount) nextPage=intPageCount;
  upPage = currentPage-1;
  if (upPage<=1) upPage=1;
  rs.close();
  sql="select * from tables";
  rs=DBLink.executeQuery(sql);
  i=0;
  while((i<numPages*(currentPage-1))&&rs.next()){i++;}
%>
//输出内容
//输出翻页连接
合计:<%=currentPage%>/<%=intPageCount%><a href="List.jsp?page=1">第一页</a><a

href="List.jsp?page=<%=upPage%>">上一页</a>
<%
  for(int j=1;j<=intPageCount;j++){
  if(currentPage!=j){
%>
  <a href="list.jsp?page=<%=j%>">[<%=j%>]</a>
<%
  }else{
  out.println(j);
  }
  }
%>
<a href="List.jsp?page=<%=nextPage%>">下一页</a><a href="List.jsp?page=<%=intPageCount%>">最后页</a>