2006年6月24日

 我想把一个基于数据库的WEB应用程序加上缓存,以提高性能,开源的java缓存系统不少,先拿JCS( Java Caching System)试试。

     关于JCS的介绍,小红帽的文章已写得非常清楚了,附后。
     先到http://jakarta.apache.org/jcs/Downloads.html
下载jcs-1.2.6.jar,找了半天也没有找到它的源码和API文档,不知为什么?
在这个站点有: Using JCS: Some basics for the web ,不错,就用它练习。

一、创建值对象
假设有一BOOK,它在数据库中的表为:
Table BOOK
  BOOK_ID_PK
  TITLE
  AUTHOR
  ISBN
  PRICE
  PUBLISH_DATE


创建值对象如下:

package com.genericbookstore.data;

import java.io.Serializable;
import java.util.Date;

public class BookVObj implements Serializable
{
    public int bookId = 0;
    public String title;
    public String author;
    public String ISBN;
    public String price;
    public Date publishDate;

    public BookVObj()
    {
    }
}
        
二、创建缓存管理器
应用中对book数据的访问都通过缓存管理器。
package com.genericbookstore.data;

import org.apache.jcs.JCS;
// in case we want to set some special behavior
import org.apache.jcs.engine.behavior.IElementAttributes;

public class BookVObjManager
{
    private static BookVObjManager instance;
    private static int checkedOut = 0;
    public static JCS bookCache;

    private BookVObjManager()//构造函数
    {
        try
        {
            bookCache = JCS.getInstance("bookCache");
        }
        catch (Exception e)
        {
            // Handle cache region initialization failure
        }

        // Do other initialization that may be necessary, such as getting
        // references to any data access classes we may need to populate
        // value objects later
    }

    /**
     * Singleton access point to the manager.
     */
    public static BookVObjManager getInstance()
    {
        synchronized (BookVObjManager.class)
        {
            if (instance == null)
            {
                instance = new BookVObjManager();
            }
        }

        synchronized (instance)
        {
            instance.checkedOut++;
        }

        return instance;
    }


 /**
     * Retrieves a BookVObj.  Default to look in the cache.
     */
    public BookVObj getBookVObj(int id)
    {
        return getBookVObj(id, true);
    }

    /**
     * Retrieves a BookVObj. Second argument decides whether to look
     * in the cache. Returns a new value object if one can't be
     * loaded from the database. Database cache synchronization is
     * handled by removing cache elements upon modification.
     */
    public BookVObj getBookVObj(int id, boolean fromCache)
    {
        BookVObj vObj = null;

        // First, if requested, attempt to load from cache

        if (fromCache)
        {
            vObj = (BookVObj) bookCache.get("BookVObj" + id);
        }

        // Either fromCache was false or the object was not found, so
        // call loadBookVObj to create it

        if (vObj == null)
        {
            vObj = loadBookVObj(id);
        }

        return  vObj;
    }

    /**
     * Creates a BookVObj based on the id of the BOOK table.  Data
     * access could be direct JDBC, some or mapping tool, or an EJB.
     */
    public BookVObj loadBookVObj(int id)
    {
        BookVObj vObj = new BookVObj();

        vObj.bookId = id;

        try
        {
            boolean found = false;

            // load the data and set the rest of the fields
            // set found to true if it was found

            found = true;

            // cache the value object if found

            if (found)
            {
                // could use the defaults like this
                // bookCache.put( "BookVObj" + id, vObj );
                // or specify special characteristics

                // put to cache

                bookCache.put("BookVObj" + id, vObj);
            }

        }
        catch (Exception e)
        {
            // Handle failure putting object to cache
        }

        return vObj;
    }

    /**
     * Stores BookVObj's in database.  Clears old items and caches
     * new.
     */
    public void storeBookVObj(BookVObj vObj)
    {
        try
        {
            // since any cached data is no longer valid, we should
            // remove the item from the cache if it an update.

            if (vObj.bookId != 0)
            {
                bookCache.remove("BookVObj" + vObj.bookId);
            }

            // put the new object in the cache

            bookCache.put("BookVObj" + vObj.bookId, vObj);
        }
        catch (Exception e)
        {
            // Handle failure removing object or putting object to cache.
        }
    }
}
        
三、配置文件cache.ccf,它定义你配置何种类型的缓存、缓存的大小、过期时间等。

#WEB-INF/classes/cache.ccf(以下内容不要换行)
jcs.default=
jcs.default.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=1000
jcs.default.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache

jcs.default.cacheattributes.UseMemoryShrinker=true
jcs.default.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.default.cacheattributes.ShrinkerIntervalSeconds=60
jcs.default.cacheattributes.MaxSpoolPerRun=500
jcs.default.elementattributes=org.apache.jcs.engine.ElementAttributes
jcs.default.elementattributes.IsEternal=false

四、测试的JSP文件
<%@page import="com.genericbookstore.data.*,java.util.*" %>

<%

BookVObjManager cache = BookVObjManager.getInstance();


BookVObj bv1=cache.getBookVObj(8,true);

out.println(bv1.bookId);
%>

五、测试:http://127.0.0.1:8080/jcs/testjcs.jsp

所有源文件请下载。

附: JCS(Java Caching System)简介以及相关文档

作者:小红帽

概述
JCS是Jakarta的项目Turbine的子项目。它是一个复合式的缓冲工具。可以将对象缓冲到内存、硬盘。具有缓冲对象时间过期设定。还可以通过JCS构建具有缓冲的分布式构架,以实现高性能的应用。
对于一些需要频繁访问而每访问一次都非常消耗资源的对象,可以临时存放在缓冲区中,这样可以提高服务的性能。而JCS正是一个很好的缓冲工具。缓冲工具对于读操作远远多于写操作的应用性能提高非常显著。
JCS的详细说明在 http://jakarta.apache.org/turbine/jcs/

JCS的特性
JCS除了简单的将对象缓冲在内存中以外,还具有几个特性,以适应企业级缓冲系统的需要。这些特性包括时间过期、索引式硬盘缓冲、并行式的分布缓冲等。
内存缓冲
JCS现在支持两种内存缓冲算法LRU和MRU。通常都是使用LRU算法。
org.apache.stratum.jcs.engine.memory.lru.LRUMemoryCache
使用内存缓冲区需要定义缓冲区大小,当超过缓冲区限制时,会将缓冲内容抛弃掉。如果有配硬盘缓冲,则将挤出来的缓冲内容写入硬盘缓冲区。

时间过期
JCS对于缓冲的对象,可以设定缓冲过期时间,一个对象在缓冲区中停留的时间超过这个时间,就会被认为是“不新鲜”而被放弃。

索引式硬盘缓冲
一方面,为了避免缓冲区过大,撑爆虚拟机的内存,另一方面又希望能够缓冲更多的对象,JCS可以将超出缓冲区大小的对象缓存到硬盘上。配置上也比较方便,只需要指定缓冲临时文件的存放目录位置。硬盘缓冲将缓冲对象的内容写到文件上,但是将访问索引保存在内存中,因此也能够达到尽可能高的访问效率。

并行式的分布缓冲(Lateral)
通常,将对象缓冲在内存中,一方面提高了应用的性能,而另一方面却使得应用不可以分布式发布。因为假设一个应用配置在两台服务器上并行运行,而两台服务器单独缓冲,则很容易导致两个缓冲区内容出现版本上的不一致而出错。一个机器上修改了数据,这个动作会影响到本地内存缓冲区和数据库服务器,但是却不会通知到另一台服务器,导致另一台上缓冲的数据实际上已经无效了。
并行式的分布缓冲就是解决这个问题。可以通过配置,将几台服务器配成一个缓冲组,组内每台服务器上有数据更新,会横向将更新的内容通过TCP/IP协议传输到其他服务器的缓冲层,这样就可以保证不会出现上述情况。这个的缺点是如果组内的并行的服务器数量增大后,组内的数据传输量将会迅速上升。这种方案适合并行服务器的数量比较少的情况。

Client/Server式的缓冲(Remote)
客户/服务端式的缓冲集群。这种方式支持一个主服务器和最高达到256个客户端。客户端的缓冲层会尝试连接主服务器,如果连接成功,就会在主服务器上注册。每个客户端有数据更新,就会通知到主服务器,主服务器会将更新通知到除消息来源的客户端以外的所有的客户端。
每个客户端可以配置超过一个服务器,第一个服务器是主服务器,如果与第一个服务器连接失败,客户端会尝试与备用的服务器连接,如果连接成功,就会通过备用服务器与其他客户端对话,同时会定期继续尝试与主服务器取得连接。如果备用服务器也连接失败,就会按照配置顺序尝试与下一个备用服务器连接。
这种方式下,更新通知是一种轻量级的,一个机器上的数据更新,不会把整个数据传输出去,而只是通知一个ID,当远程的其他机器收到更新通知后,就会把对应ID的缓冲对象从本地的内存缓冲区中移除,以保证不会在缓冲区内出现错误数据。
这种构造需要分别配置客户端和服务器,配置比较麻烦。

配置方法
JCS的好处之一,就是应用在开发的时候,可以不用去构思底层的缓冲配置构架。同一个应用,只需要修改配置,就可以改变缓冲构架,不需要修改应用的源代码。配置方法也比较简单,就是修改配置文件cache.ccf。这个文件放置在WEB-INF/classes目录下。配置格式类似log4j的配置文件格式。下面介绍一下使用各种缓冲结构的配置方法。

内存缓冲
#WEB-INF/classes/cache.ccf(以下内容不要换行)
jcs.default=
jcs.default.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=1000
jcs.default.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
上面配置了默认缓冲属性。一个应用中,由于对象类型的不同,可能会使用多个缓冲区,每个缓冲区都会有一个名字,如果在配置文件中没有指明特定的缓冲区的属性,所有的缓冲区都会根据默认属性来构建。上面的内容,指明缓冲区的大小为存放1000个对象,内存缓冲器使用LRUMemoryCache对象。可选的还有MRUMemoryCache,应该可以自定义新的内存缓冲区。1000个缓冲对象这个容量,是指每个缓冲区都缓冲1000个,而不是指所有缓冲区总容量。以上配置,就可以让应用运行起来。

时间过期
如果需要引入时间过期机制,则需要加上
jcs.default.cacheattributes.cacheattributes.UseMemoryShrinker=true
jcs.default.cacheattributes.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.default.cacheattributes.cacheattributes.ShrinkerIntervalSeconds=60
这里指明对象超过3600秒则过期,每隔60秒检查一次。


索引式硬盘缓冲
索引式硬盘缓冲是辅助缓冲的一种,使用时需要做以下事情
#定义一个硬盘缓冲区产生器(Factory),取名为DC
jcs.auxiliary.DC=org.apache.stratum.jcs.auxiliary.disk.indexed.IndexedDiskCacheFactory
jcs.auxiliary.DC.attributes=org.apache.stratum.jcs.auxiliary.disk.indexed.IndexedDiskCacheAttributes
jcs.auxiliary.DC.attributes.DiskPath=g:/dev/jakarta-turbine-stratum/raf
#这里其实就是指明了缓冲文件存放到那里去。
然后,做以下修改
jcs.default=DC
这样,所有未特别指定属性的缓冲区都会自己使用一个硬盘缓冲区,缓冲文件会以缓冲区的名字来命名。存放在指定的目录下。


横向式的并行缓冲
并行式的配置如下
jcs.auxiliary.LTCP=org.apache.jcs.auxiliary.lateral.LateralCacheFactory
jcs.auxiliary.LTCP.attributes=org.apache.jcs.auxiliary.lateral.LateralCacheAttributes
jcs.auxiliary.LTCP.attributes.TransmissionTypeName=TCP
jcs.auxiliary.LTCP.attributes.TcpServers=192.168.10.129:1121,192.168.10.222:1121
jcs.auxiliary.LTCP.attributes.TcpListenerPort=1121
jcs.auxiliary.LTCP.attributes.PutOnlyMode=false
这里的配置是在41,129,221三台机器上实现并行缓冲的。
大家都在1121端口上监听,同时与另外两台机器连接。如果连接失败,就会等待一个时间后再连接一次,直到连接成功为止。三台机器中任意一台的缓冲区发生更新,比如put和remove动作,就会把更新传递给另外两台。


单独指明某个缓冲区的属性
如果,针对某个缓冲区,比如叫做TestCache1,需要单独配置属性,可以如下配置。
jcs.region.testCache1=DC,LTCP
jcs.region.testCache1.cacheattributes=org.apache.stratum.jcs.engine.CompositeCacheAttributes
jcs.region.testCache1.cacheattributes.MaxObjects=1000
jcs.region.testCache1.cacheattributes.MemoryCacheName=org.apache.stratum.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.testCache1.cacheattributes.UseMemoryShrinker=true
jcs.region.testCache1.cacheattributes.MaxMemoryIdleTimeSeconds=3600
jcs.region.testCache1.cacheattributes.ShrinkerIntervalSeconds=60

system.GroupIdCache
这个概念我也不是很清楚。不过JCS文档中指出配置以下内容会比较好。
jcs.system.groupIdCache=DC
jcs.system.groupIdCache.cacheattributes=org.apache.stratum.jcs.engine.CompositeCacheAttributes
jcs.system.groupIdCache.cacheattributes.MaxObjects=10000

jcs.system.groupIdCache.cacheattributes.MemoryCacheName=org.apache.stratum.jcs.engine.memory.lru.LRUMemoryCache
这可能是JCS自己的组管理体系上的缓冲区。


Client/Server式的缓冲(Remote)
这种构架需要单独配置客户端和服务端,如果要研究,可以查看 http://jakarta.apache.org/turbine/jcs/RemoteAuxCache.html


来源 : java学习室

posted @ 2006-06-24 23:13 ujsydong 阅读(329) | 评论 (0)编辑 收藏

2006年5月24日

服务器的IIS经常自动停机,每次都要我手动上去重启服务,查了许多相关贴子都说是Access的JET引擎导致IIS停机,我这里论坛用的是DVBBS免费的Access版,才懒的去改SQL版呢。本来前两天下了个"WEB服务器运行状况监控V1.3.3",本指望它能帮点忙,也省的自己动手,不过它重启的方法有问题,加上有个选项“重启IIS(失败则重启电脑)”,使得服务器一天重启N多次。一怒之下又自己上了。

此方法同样可适用Tomcat等服务,只要指定页面和服务名即可。也可以用于Web端重启服务器,不过因该不好用,除非你让Tomcat的启动账号有Administrator的权限。

												import java.io.InputStream;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;
import java.util.Date;/**
 * <p>Title:IIS服务监护 </p>
 * <p>Description: </p>
 * <p>Copyright: flashman.com.cn Copyright (c) 2005</p>
 * <p>Company: flashman.com.cn</p>
 * @Jeff zhu
 * @version 1.0
 */public class IISMonitor extends Thread{//服务操作成功标识//  private static final String SUCCESS_SIGN = "successfully";//英文系统privatestatic final String SUCCESS_SIGN ="成功";//中文系统//页面检测间隔时间privatestatic final int REFRESH_TIMER = 5 * 60 * 1000;//页面活动时间privatestatic final int MONITOR_TIMER = 10 * 1000;

  Runtime runtime;public IISMonitor(){
    runtime = Runtime.getRuntime();}/**
   * 启动服务
   * @param name 服务名
   * @return
   */public boolean startService(String name){return execute("net start "+ name).indexOf(SUCCESS_SIGN)>= 0;}/**
   * 停止服务
   * @param name 服务名
   * @return
   */public boolean stopService(String name){return execute("net stop "+ name +" /y").indexOf(SUCCESS_SIGN)>= 0;}/**
   * 页面是否活动
   * @param link
   * @return
   */public boolean pageIsAlive(String link){

    boolean result =true;

    PageCheck page =new PageCheck(link);
    page.start();

    long now =System.currentTimeMillis();
    try {while(!page.alive){this.sleep(1000);if(System.currentTimeMillis()- now > MONITOR_TIMER){
          result =false;break;}//        System.out.println(System.currentTimeMillis() - now);}}
    catch (InterruptedException ex){
      ex.printStackTrace();
      result =false;}

    page.stop();return result;}/**
   * 执行命令行
   * @param cmd
   * @return 控制台信息
   */privateString execute(String cmd){String line, message ="";

    try {

      Process process = Runtime.getRuntime().exec(cmd);

      try {
        BufferedReader readIn =new BufferedReader(new InputStreamReader(process.getInputStream()));while((line = readIn.readLine())!=null){
          message += line +"\n";}
        readIn.close();} catch (IOException ex){
        ex.printStackTrace();}}catch (IOException ex){
      message ="";}//System.out.println(message);return message;}/**
   * 启动方法
   * @param args
   */publicstaticvoid main(String[] args){new Thread(){publicvoid run(){

        IISMonitor monitor =new IISMonitor();while(true){
          try {String link, message ="";System.out.print(newDate().toLocaleString()+" checked...");
            link ="http://localhost/iisMonitor.asp";if(monitor.pageIsAlive(link)){System.out.print("the page is Alive!\n");}else{System.out.print("the page is Dead!!!\n");//StopIISSystem.out.print("Stoped IIS...");if(monitor.stopService("iisadmin")){System.out.print("successfully.\n");}else{System.out.print("unsuccessful!!!\n");}//StartIISSystem.out.print("Started IIS...");if(monitor.startService("W3SVC")){System.out.print("successfully.\n");}else{System.out.print("unsuccessful!!!\n");}}System.out.println(message);this.sleep(IISMonitor.REFRESH_TIMER);}
          catch (Exception ex){
            ex.printStackTrace();}}}}.start();}}/**
 * 页面检测类
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 * @author not attributable
 * @version 1.0
 */
class PageCheck extends Thread{public boolean alive;privateString link;public PageCheck(String link){this.alive =false;this.link = link;}publicvoid run(){

    try {
      URLConnection urlConnection;String line, result ="";

      urlConnection =new URL(link).openConnection();
      BufferedReader readIn =new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));while((line = readIn.readLine())!=null){
        result += line +"\n";}
      readIn.close();
      alive =true;}
    catch (Exception ex){
      ex.printStackTrace();
      alive =false;}}}

 

下面这段存成iisMonitor.asp放在服务器上

<%
Response.Buffer=false
Response.Write now

'假死测试'For I=1 To 1000000' Response.Write I & "<br>"'Next%>
										
posted @ 2006-05-24 15:31 ujsydong 阅读(209) | 评论 (0)编辑 收藏

2006年5月16日

Proxool -0.8.3
Proxool.xml mysql 配置
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- the proxool configuration can be embedded within your own application's.
Anything outside the "proxool" tag is ignored. -->
<something-else-entirely>
<proxool>
    <alias>ujs</alias>
    <driver-url>jdbc:mysql://localhost:3306/ujs?useUnicode=true&amp;characterEncoding=UTF-8</driver-url>
    <driver-class>com.mysql.jdbc.Driver</driver-class>
    <driver-properties>
      <property name="user" value="root"/>
      <property name="password" value="myoa888"/>
    </driver-properties>
    <maximum-connection-count>200</maximum-connection-count>
    <house-keeping-test-sql>select CURRENT_DATE</house-keeping-test-sql>
</proxool>
</something-else-entirely>

myweb.xml
<!--连接池
-->
 <display-name>proxool</display-name>
 <servlet>
   <servlet-name>ServletConfigurator</servlet-name>
   <servlet-class>org.logicalcobwebs.proxool.configuration.ServletConfigurator</servlet-class>
   <init-param>
  <param-name>xmlFile</param-name>
  <param-value>WEB-INF/proxool.xml</param-value>
   </init-param>
        <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet>
   <servlet-name>proxool_Admin</servlet-name>
   <servlet-class>org.logicalcobwebs.proxool.admin.servlet.AdminServlet</servlet-class>
 </servlet>

 <servlet-mapping>
   <servlet-name>proxool_Admin</servlet-name>
   <url-pattern>/proxoolAdmin</url-pattern>
 </servlet-mapping>

posted @ 2006-05-16 11:17 ujsydong 阅读(710) | 评论 (0)编辑 收藏

2005年12月9日

□ 笨三 发表于 2005-8-4 10:07:39

学C语言的时候,我们相爱,成为恋人!
学VB的时候,我们分手,成为朋友!
学J2EE的时候,我们决定忘记,成为陌路人!


璐璐:
我原来真的那么差劲吗?
可能我从来都站在自己的角度想问题!
我和我女朋友,也就一个星期见一次!
我还是很忙,正在做J2EE的毕业设计,天天都忙到凌晨三四点,
以至于她告诉我,不如娶个电脑当老婆!
我想实现我的梦,所以我选择深圳,以后再去上海!
但这样又会发生什么样的变化,我不敢去想!
我曾经以为一切的事情都不会发生改变!
后来,我明白了计划赶不上变化!
很常一段时间我在痛苦和自责中活着,我毕竟是人!
说这些,我不得不承认,我现在还爱你,只不过是放在最深处!
忘记,我做不到,时间可以使人忘记一切,但曾经走过的路,去过的地方,哪怕是电视塔下的那片草坪,
我无法忘记,那是因为我还看的见!
我曾试图忘记,但却没有撕毁那张封信的勇气!
时间不会因为我而倒退,地球也不会停止转动,
于是我每天还是要带着微笑活着!
做人累,忘记人更累!
如果桂林是缘分的开始,那么在长沙收到的第一封信就是我判为有期徒刑!
如果温州是缘分的开始,那么从你手中接过的第二封信就已经是判我为无期徒刑!
我曾经想上诉,于是你消失了!
之后我就进了监狱,过了好久猪狗不如的生活后,我越狱了!
但是我还保留着那份相对于人,而不相对于时间的爱情判决书!
几年后,你出现了,但我却因为越狱而终身无法上诉!
如果时间可以GOTO,我愿意在我们的爱情外加装FOR循环
如果时间可以GOTO,我愿意在我们的爱情内加装IF判断
如果时间可以GOTO,我愿意为自己加装垃圾回收机制
如果时间可以GOTO,我甚至可以背叛J2EE,而投靠.NET,不用JAVA,而改用C#
如果时间可以GOTO......
错,我犯过!
而无法挽回的错,我仍然犯过!
犯错时我一点意识都没有,当意识到犯了无法挽回的错时,也就意味着连改的机会都没有了!
曾经说爱你,现在已经没有权利,只能放在内心深处,不为人知!
曾经说对不起,现在已经没有意义,但真心祝你快乐,幸福!
相信你以后的他,一定比我好千百倍,
不会连陪你的时间都可以数的清,
不会三五天消失都不打一个电话,
不会因为要调试代码而晚上让你独自回去,
不会让你伤心,难过,
不会......

璐璐,最后,在执行DELETE 前,让我再看一次吧!

SELECT * FROM MyLove 

ID      Datetime        Address           Forget
1       2000-02-14    电视塔草坪      没有忘记[默认值]
.         ....-.-..                .....                    没有忘记[默认值] 
.         ....-.-..                .....                    没有忘记[默认值] 
.         ....-.-..                .....                    没有忘记[默认值] 
521   2002-02-14  宿舍楼下           没有忘记[默认值]


曾经很少使用INSERT , 却使用UPDATE修改Forget列,但不管怎么修改
到最后都变成默认值,因为曾经我无法忘记!

璐璐,再见了,就像你说的那样,让我们忘记对方吧,珍惜现在所拥有的!

正在删除对方的QQ号码......
删除成功!

正在删除对方的E-mail......
删除成功!

正在删除对方的电话......
删除成功!

删除MyLove表中所有内容,将执行"DELETE MyLove",确认吗?(Y/N):_

Y

重要信息删除,请输入密码:_

if(my!=521&&you!=521)System.out.println("再见了,我的爱人!");

密码正确!

正在删除MyLove表中所有内容......
................................
................................
................................
................................

删除成功!!   


//感言
//程序写累了,就写写自己的程序人生,有些感伤!
//朋友,在为祖国软件业做贡献的同时,别忘了你的她!

posted @ 2005-12-09 19:29 ujsydong 阅读(199) | 评论 (0)编辑 收藏

2005年12月7日

C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是"是"的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
这个测试适于不同水平的应试者,大多数初级水平的应试者的成绩会很差,经验丰富的程序员应该有很好的成绩。为了让你能自己决定某些问题的偏好,每个问题没有分配分数,如果选择这些考题为你所用,请自行按你的意思分配分数。
预处理器(Preprocessor)

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)


#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
&#8226;; #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)


&#8226;; 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
&#8226;; 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
&#8226;; 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。


#define MIN(A,B) ((A) <= (B) ? (A) : (B))

这个测试是为下面的目的而设的:
&#8226;; 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
&#8226;; 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
&#8226;; 懂得在宏中小心地把参数用括号括起来
&#8226;; 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

least = MIN(*p++, b);


3. 预处理器标识#error的目的是什么?
如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。
死循环(Infinite loops)


4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
这个问题用几个解决方案。我首选的方案是:

while(1)
{
?}



一些程序员更喜欢如下方案:

for(;;)
{
?}



这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。
第三个方案是用 goto

Loop:
...
goto Loop;


应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

数据声明(Data declarations)

5. 用变量a给出下面的定义
a) 一个整型数(An integer)
b)一个指向整型数的指针( A pointer to an integer)
c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r
d)一个有10个整型数的数组( An array of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
Static
6. 关键字static的作用是什么?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
&#8226;; 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
&#8226;; 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
&#8226;; 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。


Const

7.关键字const有什么含意?
我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:
下面的声明都是什么意思?

const int a;
int const a;
const int *a;
int * const a;
int const * a const;

/******/
前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
&#8226;; 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
&#8226;; 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
&#8226;; 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
&#8226;; 并行设备的硬件寄存器(如:状态寄存器)
&#8226;; 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
&#8226;; 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
&#8226;; 一个参数既可以是const还可以是volatile吗?解释为什么。
&#8226;; 一个指针可以是volatile 吗?解释为什么。
&#8226;; 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}

下面是答案:
&#8226;; 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
&#8226;; 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
&#8226;; 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:


int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}


由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

位操作(Bit manipulation)

9. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
对这个问题有三种基本的反应
&#8226;; 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。
&#8226;; 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。
&#8226;; 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:


#define BIT3 (0x1 << 3)
static int a;

void set_bit3(void) {
a |= BIT3;
}
void clear_bit3(void) {
a &= ~BIT3;
}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~操作。
访问固定的内存位置(Accessing fixed memory ***s)

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

A more obscure approach is:
一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55;

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts)

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("\nArea = %f", area);
return area;
}

这个函数有太多的错误了,以至让人不知从何说起了:
&#8226;; ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
&#8226;; ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
&#8226;; 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
&#8226;; 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

*****
代码例子(Code examples)

12 . 下面的代码输出是什么,为什么?

void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts("> 6") : puts("<= 6");
}
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。 因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。
13. 评价下面的代码片断:

unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0;

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧...

动态内存分配(Dynamic memory al***)
14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?
这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:
下面的代码片段的输出是什么,为什么?

char *ptr;
if ((ptr = (char *)malloc(0)) ==
NULL)
else
puts("Got a null pointer");
puts("Got a valid pointer");

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是"Got a valid pointer"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。
Typedef
:
15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *
typedef struct s * tPS;

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;
tPS p3,p4;

第一个扩展为

struct s * p1, p2;

.
上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c;
c = a+++b;

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b;

因此, 这段代码持行后a = 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。
好了,伙计们,你现在已经做完所有的测试了。这就是我出的C语言测试题,我怀着愉快的心情写完它,希望你以同样的心情读完它。如果是认为这是一个好的测试,那么尽量都用到你的找工作的过程中去吧。天知道也许过个一两年,我就不做现在的工作,也需要找一个。
Nigel Jones 是一个顾问,现在住在Maryland,当他不在水下时,你能在多个范围的嵌入项目中找到他。 他很高兴能收到读者的来信,他的email地址是: NAJones@compuserve.com 。
References
&#8226;; Jones, Nigel, "In Praise of the #error directive," Embedded Systems Programming, September 1999, p. 114.
&#8226;; Jones, Nigel, " Efficient C Code for Eight-bit MCUs ," Embedded Systems Programming, November 1998, p. 66.

posted @ 2005-12-07 22:29 ujsydong 阅读(179) | 评论 (0)编辑 收藏
仅列出标题