随笔 - 63  文章 - 0  trackbacks - 0
<2024年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(2)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

  1. package cn.edu.tongji.cims.wade.system;   
  2.   
  3. import java.io.*;   
  4.   
  5. public class FileOperate {   
  6.   public FileOperate() {   
  7.   }   
  8.   
  9.   /**  
  10.    * 新建目录  
  11.    * @param folderPath String 如 c:/fqf  
  12.    * @return boolean  
  13.    */  
  14.   public void newFolder(String folderPath) {   
  15.     try {   
  16.       String filePath = folderPath;   
  17.       filePath = filePath.toString();   
  18.       java.io.File myFilePath = new java.io.File(filePath);   
  19.       if (!myFilePath.exists()) {   
  20.         myFilePath.mkdir();   
  21.       }   
  22.     }   
  23.     catch (Exception e) {   
  24.       System.out.println("新建目录操作出错");   
  25.       e.printStackTrace();   
  26.     }   
  27.   }   
  28.   
  29.   /**  
  30.    * 新建文件  
  31.    * @param filePathAndName String 文件路径及名称 如c:/fqf.txt  
  32.    * @param fileContent String 文件内容  
  33.    * @return boolean  
  34.    */  
  35.   public void newFile(String filePathAndName, String fileContent) {   
  36.   
  37.     try {   
  38.       String filePath = filePathAndName;   
  39.       filePath = filePath.toString();   
  40.       File myFilePath = new File(filePath);   
  41.       if (!myFilePath.exists()) {   
  42.         myFilePath.createNewFile();   
  43.       }   
  44.       FileWriter resultFile = new FileWriter(myFilePath);   
  45.       PrintWriter myFile = new PrintWriter(resultFile);   
  46.       String strContent = fileContent;   
  47.       myFile.println(strContent);   
  48.       resultFile.close();   
  49.   
  50.     }   
  51.     catch (Exception e) {   
  52.       System.out.println("新建目录操作出错");   
  53.       e.printStackTrace();   
  54.   
  55.     }   
  56.   
  57.   }   
  58.   
  59.   /**  
  60.    * 删除文件  
  61.    * @param filePathAndName String 文件路径及名称 如c:/fqf.txt  
  62.    * @param fileContent String  
  63.    * @return boolean  
  64.    */  
  65.   public void delFile(String filePathAndName) {   
  66.     try {   
  67.       String filePath = filePathAndName;   
  68.       filePath = filePath.toString();   
  69.       java.io.File myDelFile = new java.io.File(filePath);   
  70.       myDelFile.delete();   
  71.   
  72.     }   
  73.     catch (Exception e) {   
  74.       System.out.println("删除文件操作出错");   
  75.       e.printStackTrace();   
  76.   
  77.     }   
  78.   
  79.   }   
  80.   
  81.   /**  
  82.    * 删除文件夹  
  83.    * @param filePathAndName String 文件夹路径及名称 如c:/fqf  
  84.    * @param fileContent String  
  85.    * @return boolean  
  86.    */  
  87.   public void delFolder(String folderPath) {   
  88.     try {   
  89.       delAllFile(folderPath); //删除完里面所有内容   
  90.       String filePath = folderPath;   
  91.       filePath = filePath.toString();   
  92.       java.io.File myFilePath = new java.io.File(filePath);   
  93.       myFilePath.delete(); //删除空文件夹   
  94.   
  95.     }   
  96.     catch (Exception e) {   
  97.       System.out.println("删除文件夹操作出错");   
  98.       e.printStackTrace();   
  99.   
  100.     }   
  101.   
  102.   }   
  103.   
  104.   /**  
  105.    * 删除文件夹里面的所有文件  
  106.    * @param path String 文件夹路径 如 c:/fqf  
  107.    */  
  108.   public void delAllFile(String path) {   
  109.     File file = new File(path);   
  110.     if (!file.exists()) {   
  111.       return;   
  112.     }   
  113.     if (!file.isDirectory()) {   
  114.       return;   
  115.     }   
  116.     String[] tempList = file.list();   
  117.     File temp = null;   
  118.     for (int i = 0; i < tempList.length; i++) {   
  119.       if (path.endsWith(File.separator)) {   
  120.         temp = new File(path + tempList[i]);   
  121.       }   
  122.       else {   
  123.         temp = new File(path + File.separator + tempList[i]);   
  124.       }   
  125.       if (temp.isFile()) {   
  126.         temp.delete();   
  127.       }   
  128.       if (temp.isDirectory()) {   
  129.         delAllFile(path+"/"+ tempList[i]);//先删除文件夹里面的文件   
  130.         delFolder(path+"/"+ tempList[i]);//再删除空文件夹   
  131.       }   
  132.     }   
  133.   }   
  134.   
  135.   /**  
  136.    * 复制单个文件  
  137.    * @param oldPath String 原文件路径 如:c:/fqf.txt  
  138.    * @param newPath String 复制后路径 如:f:/fqf.txt  
  139.    * @return boolean  
  140.    */  
  141.   public void copyFile(String oldPath, String newPath) {   
  142.     try {   
  143.       int bytesum = 0;   
  144.       int byteread = 0;   
  145.       File oldfile = new File(oldPath);   
  146.       if (oldfile.exists()) { //文件存在时   
  147.         InputStream inStream = new FileInputStream(oldPath); //读入原文件   
  148.         FileOutputStream fs = new FileOutputStream(newPath);   
  149.         byte[] buffer = new byte[1444];   
  150.         int length;   
  151.         while ( (byteread = inStream.read(buffer)) != -1) {   
  152.           bytesum += byteread; //字节数 文件大小   
  153.           System.out.println(bytesum);   
  154.           fs.write(buffer, 0, byteread);   
  155.         }   
  156.         inStream.close();   
  157.       }   
  158.     }   
  159.     catch (Exception e) {   
  160.       System.out.println("复制单个文件操作出错");   
  161.       e.printStackTrace();   
  162.   
  163.     }   
  164.   
  165.   }   
  166.   
  167.   /**  
  168.    * 复制整个文件夹内容  
  169.    * @param oldPath String 原文件路径 如:c:/fqf  
  170.    * @param newPath String 复制后路径 如:f:/fqf/ff  
  171.    * @return boolean  
  172.    */  
  173.   public void copyFolder(String oldPath, String newPath) {   
  174.   
  175.     try {   
  176.       (new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹   
  177.       File a=new File(oldPath);   
  178.       String[] file=a.list();   
  179.       File temp=null;   
  180.       for (int i = 0; i < file.length; i++) {   
  181.         if(oldPath.endsWith(File.separator)){   
  182.           temp=new File(oldPath+file[i]);   
  183.         }   
  184.         else{   
  185.           temp=new File(oldPath+File.separator+file[i]);   
  186.         }   
  187.   
  188.         if(temp.isFile()){   
  189.           FileInputStream input = new FileInputStream(temp);   
  190.           FileOutputStream output = new FileOutputStream(newPath + "/" +   
  191.               (temp.getName()).toString());   
  192.           byte[] b = new byte[1024 * 5];   
  193.           int len;   
  194.           while ( (len = input.read(b)) != -1) {   
  195.             output.write(b, 0, len);   
  196.           }   
  197.           output.flush();   
  198.           output.close();   
  199.           input.close();   
  200.         }   
  201.         if(temp.isDirectory()){//如果是子文件夹   
  202.           copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);   
  203.         }   
  204.       }   
  205.     }   
  206.     catch (Exception e) {   
  207.       System.out.println("复制整个文件夹内容操作出错");   
  208.       e.printStackTrace();   
  209.   
  210.     }   
  211.   
  212.   }   
  213.   
  214.   /**  
  215.    * 移动文件到指定目录  
  216.    * @param oldPath String 如:c:/fqf.txt  
  217.    * @param newPath String 如:d:/fqf.txt  
  218.    */  
  219.   public void moveFile(String oldPath, String newPath) {   
  220.     copyFile(oldPath, newPath);   
  221.     delFile(oldPath);   
  222.   
  223.   }   
  224.   
  225.   /**  
  226.    * 移动文件到指定目录  
  227.    * @param oldPath String 如:c:/fqf.txt  
  228.    * @param newPath String 如:d:/fqf.txt  
  229.    */  
  230.   public void moveFolder(String oldPath, String newPath) {   
  231.     copyFolder(oldPath, newPath);   
  232.     delFolder(oldPath);   
  233.   
  234.   }   
  235. }  
posted @ 2009-05-07 11:16 lanxin1020 阅读(395) | 评论 (0)编辑 收藏

        如果在Java程序中你使用Java Native Interface(JNI) 来调用某个特定平台下的本地库文件,你就会发现这个过程很单调、乏味。Jeff Friesen一直在介绍一个知名度很低的Java开源项目:Java Native Access---它能够避免因使用JNI导致的错误和乏味,同时它还能让你通过编程的方式调用C语言库。

        在Java语言没有提供必要的APIs的情况下,Java程序使用Java Native Interface (JNI)来调用特定平台下的本地库是必要的。例如:在Windows XP平台中,我使用过JNI来调用通用串行总线和基于TWAIN的扫描仪器的库;在更古老的Windows NT平台中,调用过智能卡的库。

        我按照一个基本的、乏味的流程来解决这些问题:首先,我创建一个Java类用来载入JNI-friendly库(这个库能过访问其他的库)并且声明这个类的本地方法。然后,在使用JDK中的javah工具为JNI-friendly库中的函数---函数和这个类中的本地方法一一对应---创建一个代理。最后,我使用C语言写了一个库并用C编译器编译了这些代码。

        尽管完成这些流程并不是很困难,但是写C代码是一个很缓慢的过程---例如: C语言中的字符串处理是通过指针来实现的,这会很复杂的。而且,使用JNI很容易出现错误,导致内存泄漏、很难找到程序崩溃的原因。

        在Java开源系列的第二篇文章中,我要介绍一个更简单、更安全的解决方法:Todd Fast and Timothy Wall的Java Native Access (JNA) 项目。JNA能够让你在Java程序中调用本地方法时避免使用C和Java Native Interface。在这篇文章中,让我以简要的介绍        JNA和运行示例必需的软件来开始下面的内容。然后,向你展示如何使用JNA将3个Windows本地库中的有用代码移植到Java程序中。

Get started with JNA(JNA入门)

Java Native Access 项目 在Java.net上,你可以到这个网站上现在这个项目的代码和在线帮助文档。虽然在下载有5个相关的jar文件,在本文中你仅仅需要下载其中的jna.jar和example.jar。

Jna.jar提供基本的、运行这些示例文件必需的jna运行环境。这个jna.jar文件除了有Unix、Linux、Windows和Mac OS X平台相关的JNT-friendly本地库外,还包含其他几个类包。每一个本地库都是用来访问相对应平台下的本地方法的。

        example.jar包含了不同的示例来表明JNA的用途。其中的一个例子是使用JNA来实现一个在不同平台下的透明视窗技术的API。在文章最后的示例中将要展示如何使用这个API修复上个月的文章关于VerifyAge2应用中辨认透明效果的问题。

获取本地时间(Get local time)

如果你在Java Native Access 首页 看过“JNA如何入门”,你就会知道一个很简单的关于调用Windows 平台下的API函数:GetSystemTime() 的JNA示例。这个不完整的例子只是展示了JNA的基本特点。(在例子的基础上,我做了一个更完整的基于Windows的例子来介绍JNA)我在Windows平台下完善了这个例子来介绍JNA。

第一例子基于Windows GetLocalTime() API函数返回本地当前的时间和日期。和GetSystemTime()不同的是,返回的时间/日期是协调通用时间(UTC)格式的,GetLocalTime()返回的时间/日期信息的格式是根据当前时区来表示。

在一个Java程序中使用JNA调用GetLocalTime,你需要知道这个函数所在的Windows平台下的动态链接库(DLL)的名称(和可能所在的地理区域)。我们发现GetLocalTime()和GetSystemTime在同一个DLL文件中:kernel32.dll。你还需要知道GetLocalTime()在C语言环境中的申明。申明如下Listing 1:

Listing 1. GetLocalTime在C语言中的申明

typedef struct
{
   WORD wYear;
   WORD wMonth;
   WORD wDayOfWeek;
   WORD wDay;
   WORD wHour;
   WORD wMinute;
   WORD wSecond;
   WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;

VOID GetLocalTime(LPSYSTEMTIME lpst);


这个基于C语言的申明表明传到这个函数的参数数目和类型。在这个例子中,只有一个参数---一个指向Windows SYSTEMTIME结构体的指针。而且,每个结构体成员的类型是16bit长度的无符号整型。根据这些信息,你能够创建一个完全描述GetLocalTime()函数的接口,如Listing 2中所示:

Listing 2. Kernel32.java

// Kernel32.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface Kernel32 extends StdCallLibrary
{
   public static class SYSTEMTIME extends Structure
   {
      public short wYear;
      public short wMonth;
      public short wDayOfWeek;
      public short wDay;
      public short wHour;
      public short wMinute;
      public short wSecond;
      public short wMilliseconds;
   }

   void GetLocalTime (SYSTEMTIME result);
}


Kernel32 接口(The Kernel32 interface)

因为JNA使用通过一个接口来访问某个库中的函数,Listing 2表示了一个描述GetLocalTime()的接口。根据约定,我把接口命名为Kernel32是因为GetLocalTime()在Windows的kernel32.dll库。

这个接口必须继承com.sun..jna.Library接口。因为Windows API函数遵循stdcall调用协议(stdcall calling convention),为Windows API申明的接口也必须继承com.sun.jna.win32. StdCallLibrary接口。因此这个接口共继承了Library 和 com.sun.jna.win32.StdCall两个接口。

在前面,你已经知道了GetLocalTime() 需要一个指向SYSTEMTIME结构体的指针作为它唯一的参数。因为Java不支持指针,JNA是通过申明一个com.sun.jna.Structure的子类来代替的。根据java文档中抽象类的概念,在参数环境中,Structure相当于C语言的struct*。

在SYSTEMTIME类中的字段和C结构体中的相对应的属性字段的顺序是一一对应的。保证字段顺序的一致性是非常重要的。例如,我发现交换wYear和wMonth会导致wYear和wMonth值互换。

每个字段在java中是short integer类型的。按照JNA首页上 “默认类型映射”章节给出的提示,这个short integer分配类型是正确。然而,我们应该知道一个重要的区别:Windows平台下的WORD类型等同于C语言环境中的16-bit的无符号的short integer,而java中short integer是16-bit有符号的short integer。

一个类型映射的问题

通过比较一个API 函数返回的整型值,你会发现Windows/C 语言的无符号整型和Java语言的有符号整型的JNA类型映射是有问题的。在比较的过程中,如果你不细心,那么错误的执行过程可能导致决定性情况。导致这种后果是因为忘记任何数值的符号位的确定是根据:在无符号整型的情况下会被解释为正号,而在有符号整型的进制中被理解为负号的。

通过Kernel32获取本地时间(Access the local time with Kernel32)

JNA首页上的GetSystemTime()示例已经表明必须使用预先申明的接口为本地库分配一个实例对象。你可以通过com.sun.jna.Native类中静态公用方法loadLibrary(String name, Class interfaceClass)来完成上述的目标。Listing 3 所示:

Listing 3. LocalTime.java

// LocalTime.java

import com.sun.jna.*;

public class LocalTime
{
   public static void main (String [] args)
   {
      Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32",
                                                    Kernel32.class);
      Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();
      lib.GetLocalTime (time);
      System.out.println ("Year is "+time.wYear);
      System.out.println ("Month is "+time.wMonth);
      System.out.println ("Day of Week is "+time.wDayOfWeek);
      System.out.println ("Day is "+time.wDay);
      System.out.println ("Hour is "+time.wHour);
      System.out.println ("Minute is "+time.wMinute);
      System.out.println ("Second is "+time.wSecond);
      System.out.println ("Milliseconds are "+time.wMilliseconds);
   }
}


Listing 3 执行Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class);来分配一个Kernel32实例对象并且装载kernel32.dll。因为kernel32.dll是Windows平台下标准的dll文件,所以不要指定访问这个库的路径。然而,如果找不到这个dll文件,loadLibrary()会抛出一个UnsatisfiedLinkError异常。

Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();创建了一个SYSTEMTIME结构体的示例。初始化后下面是lib.GetLocalTime (time);,这句话使用本地的时间/日期来给这个实例赋值。几个System.out.println()语句是输出这些值。

编译和运行这个应用(Compile and run the application)

这部分很容易。假设jna.jar、Kernel32.java和LocalTime.java是放在当前文件夹中,调用java –cp jna.jar;. LocalTime.java来编译这个应用的源代码。如果在Windows平台下,调用invoke java –cp jna.jar;. LocalTime 来运行这个应用。你可以得到类似与Listing 4的输出结果:

Listing 4. 从LocalTime.java生成的输出

Year is 2007
Month is 12
Day of Week is 3
Day is 19
Hour is 12
Minute is 35
Second is 13
Milliseconds are 156


获取操纵杆信息(Accessing joystick device info)

上面的例子已经介绍了JNA,但是这个获取本地时间和日期的例子并没有很好的利用这个技术,甚至也没有体现JNI的价值。Java语言中的System.currentTimeMillis()函数已经以毫秒的格式返回了这些信息。因为Java语言没有为游戏控制器提供API,所以获取操纵杆的信息更适合JNA的使用。

例如,你要构建一个平台无关的Java库,而且这些库使用JNA调用Linux, Mac OS X, Windwos和Unix平台中本地的操纵杆API。为了简洁和方便起见,这个例子仅仅是调用Windows平台下的操纵杆API。而且我将重点介绍这个API很小的一部分。

类似GetLocalTime(),第一步是辨别出操作杆API的DLL,这个DLL是winmm.dll,和kernel32.dll在同一个文件夹中,它包含了操作杆的API和其他的多媒体APIs。还需知道要被使用的操作杆函数基于C语言的声明。这些函数声明已经在Listing 5中列出来了。

Listing 5. C-based declarations for some Joystick API functions

#define MAXPNAMELEN 32

typedef struct
{
   WORD  wMid;                  // manufacturer identifier
   WORD  wPid;                  // product identifier
   TCHAR szPname  MAXPNAMELEN ; // product name
   UINT  wXmin;                 // minimum x position
   UINT  wXmax;                 // maximum x position
   UINT  wYmin;                 // minimum y position
   UINT  wYmax;                 // maximum y position
   UINT  wZmin;                 // minimum z position
   UINT  wZmax;                 // maximum z position
   UINT  wNumButtons;           // number of buttons
   UINT  wPeriodMin;            // smallest supported polling interval when captured
   UINT  wPeriodMax;            // largest supported polling interval when captured
}
JOYCAPS, *LPJOYCAPS;

MMRESULT joyGetDevCaps(UINT IDDevice, LPJOYCAPS lpjc, UINT cbjc);

UINT joyGetNumDevs(VOID);


操作杆API的函数(Functions of the Joystick API)

在Windows平台下是通过以joy作为函数名开始的函数以及被各种函数调用的结构体来实现操作杆API的。例如,joyGetNumDevs()返回的是这个平台下支持的操作杆设备最多的数目;joyGetDevCaps()返回的是每个连接上的操纵杆的质量。

joyGetDevCaps()函数需要3个参数:
* 处在0到joyGetNumDevs()-1之间的操作杆ID
* 保存返回的质量信息的JOYCAPS结构体的地址
* JOYCAPS结构体的字节大小
虽然它的结果不同,这个函数返回的是一个32位的无符号整型结果,而且0表示一个已经连接的操纵杆。

JOYCAPS结构体有3种类型。Windows平台下的WORD(16位无符号短整型)类型对应的是Java语言中16位有符号短整型。除此之外,Windows下的UINT(32位无符号整型)类型是和Java语言中32位有符号整型相对应的。而Windows平台上的text character就是TCHAR类型。

微软通过TCHAR类型使开发人员能够从ASCII类型的函数参数平滑的转移到Unicode字符类型的函数参数上。而且,拥有text类型参数的函数的实现是通过宏转变为对应的ASCII或者wide-character的函数。例如,joyGetDevCaps()是一个对应joyGetDevCapsA() 和 joyGetDevCapsW()的宏。

使用TCHAR(Working with TCHAR)

使用TCHAR和将TCHAR转变的宏会导致基于C语言的申明向基于JNA接口的转换
变得有点复杂—你在使用ASCII或者wide-character版本的操纵杆函数吗?两种版本都在如下的接口中展示了:

Listing 6. WinMM.java

// WinMM.java

import com.sun.jna.*;
import com.sun.jna.win32.*;

public interface WinMM extends StdCallLibrary
{
   final static int JOYCAPSA_SIZE = 72;

   public static class JOYCAPSA extends Structure
   {
      public short wMid;
      public short wPid;
      public byte szPname [] = new byte [32];
      public int wXmin;
      public int wXmax;
      public int wYmin;
      public int wYmax;
      public int wZmin;
      public int wZmax;
      public int wNumButtons;
      public int wPeriodMin;
      public int wPeriodMax;
   }

   int joyGetDevCapsA (int id, JOYCAPSA caps, int size);

   final static int JOYCAPSW_SIZE = 104;

   public static class JOYCAPSW extends Structure
   {
      public short wMid;
      public short wPid;
      public char szPname [] = new char [32];
      public int wXmin;
      public int wXmax;
      public int wYmin;
      public int wYmax;
      public int wZmin;
      public int wZmax;
      public int wNumButtons;
      public int wPeriodMin;
      public int wPeriodMax;
   }

   int joyGetDevCapsW (int id, JOYCAPSW caps, int size);

   int joyGetNumDevs ();
}


Listing 6没有介绍JNA的新特性。实际上,JNA强调了对本地库的接口命名规则。同时,还展示了如何将TCHAR映射到Java语言中的byte和char数组。最后,它揭示了以常量方式声明的结构体的大小。Listing 7展示了当调用joyGetDevCapsA() 和 joyGetDevCapsW()时如何使用这些常量。

Listing 7. JoystickInfo.java

// JoystickInfo.java

import com.sun.jna.*;

public class JoystickInfo
{
   public static void main (String [] args)
   {
      WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);
      int numDev = lib.joyGetNumDevs ();

      System.out.println ("joyGetDevCapsA() Demo");
      System.out.println ("---------------------\n");

      WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA ();
      for (int i = 0; i < numDev; i++)
           if (lib.joyGetDevCapsA (i, caps1, WinMM.JOYCAPSA_SIZE) == 0)
           {
               String pname = new String (caps1.szPname);
               pname = pname.substring (0, pname.indexOf ('\0'));
               System.out.println ("Device #"+i);
               System.out.println ("  wMid = "+caps1.wMid);
               System.out.println ("  wPid = "+caps1.wPid);
               System.out.println ("  szPname = "+pname);
               System.out.println ("  wXmin = "+caps1.wXmin);
               System.out.println ("  wXmax = "+caps1.wXmax);
               System.out.println ("  wYmin = "+caps1.wYmin);
               System.out.println ("  wYmax = "+caps1.wYmax);
               System.out.println ("  wZmin = "+caps1.wZmin);
               System.out.println ("  wZmax = "+caps1.wZmax);
               System.out.println ("  wNumButtons = "+caps1.wNumButtons);
               System.out.println ("  wPeriodMin = "+caps1.wPeriodMin);
               System.out.println ("  wPeriodMax = "+caps1.wPeriodMax);
               System.out.println ();
           }

      System.out.println ("joyGetDevCapsW() Demo");
      System.out.println ("---------------------\n");

      WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();
      for (int i = 0; i < numDev; i++)
           if (lib.joyGetDevCapsW (i, caps2, WinMM.JOYCAPSW_SIZE) == 0)
           {
               String pname = new String (caps2.szPname);
               pname = pname.substring (0, pname.indexOf ('\0'));
               System.out.println ("Device #"+i);
               System.out.println ("  wMid = "+caps2.wMid);
               System.out.println ("  wPid = "+caps2.wPid);
               System.out.println ("  szPname = "+pname);
               System.out.println ("  wXmin = "+caps2.wXmin);
               System.out.println ("  wXmax = "+caps2.wXmax);
               System.out.println ("  wYmin = "+caps2.wYmin);
               System.out.println ("  wYmax = "+caps2.wYmax);
               System.out.println ("  wZmin = "+caps2.wZmin);
               System.out.println ("  wZmax = "+caps2.wZmax);
               System.out.println ("  wNumButtons = "+caps2.wNumButtons);
               System.out.println ("  wPeriodMin = "+caps2.wPeriodMin);
               System.out.println ("  wPeriodMax = "+caps2.wPeriodMax);
               System.out.println ();
           }
   }
}

尽管和LocalTime这个示例类似,JoystickInfo执行WinMM lib = (WinMM) Native.loadLibrary ("winmm", WinMM.class);这句话来获取一个WinMM的实例,并且载入winmm.dll。它还执行WinMM.JOYCAPSA caps1 = new WinMM.JOYCAPSA (); 和 WinMM.JOYCAPSW caps2 = new WinMM.JOYCAPSW ();初始化必需的结构体实例。

编译和运行这个程序(Compile and run the application)

假如jna.jar,WinMM.java和JoystickInfo.java在同一个文件夹中,调用 javac -cp jna.jar;. JoystickInfo.java 来编译这个应用的源代码。
在windows平台下,调用java -cp jna.jar;. JoystickInfo就可以运行这个应用程序了。如果没有操纵杆设备,你应该得到Listing 8中的输出。

将C语言中的string类型转换为Java语言的String类型

pname = pname.substring (0, pname.indexOf ('\0')); 这段代码将一个C string 转换成了Java string. 如果不使用这个转换,C语言的string结束符’\0’和string后面的无用字符都会成为Java语言中String实例对象的内容。

Listing 8. 输出操纵杆信息(Output of JoystickInfo)

joyGetDevCapsA() Demo
---------------------

joyGetDevCapsW() Demo
---------------------


上面的输出是因为每次调用joyGetDevCap()返回的是一个非空值,这表示没有操纵杆/游戏控制器设备或者是出现错误。为了获取更多有意思的输出,将一个设备连接到你的平台上并且再次运行JoystickInfo。如下,将一个微软SideWinder即插即用游戏触摸板设备联上之后我获取了如下的输出:

Listing 9. 操纵杆连接上之后的运行结果(Output after running JoystickInfo with a joystick attached)

joyGetDevCapsA() Demo
---------------------

Device #0
  wMid = 1118
  wPid = 39
  szPname = Microsoft PC-joystick driver
  wXmin = 0
  wXmax = 65535
  wYmin = 0
  wYmax = 65535
  wZmin = 0
  wZmax = 65535
  wNumButtons = 6
  wPeriodMin = 10
  wPeriodMax = 1000

joyGetDevCapsW() Demo
---------------------

Device #0
  wMid = 1118
  wPid = 39
  szPname = Microsoft PC-joystick driver
  wXmin = 0
  wXmax = 65535
  wYmin = 0
  wYmax = 65535
  wZmin = 0
  wZmax = 65535
  wNumButtons = 6
  wPeriodMin = 10
  wPeriodMax = 1000


窗口透明度(Transparent windows)

在这系列文章中上篇文章是关于Bernhard Pauler's 气泡提示(balloontip)工程的。我构建了一个叫做VerifyAge的、包含有一个气泡提示的GUI应用。Figure 1中显示了这个GUI应用的一个小问题:这个气泡提示的没有经过修饰的对话框部分遮住了应用窗口的一部分边框,导致了无法点击这个边框的最小化和最大化按钮,并且使整个GUI很难看.
image
尽管未修饰部分的对话框不能显示气泡提示的透明度,java语言不支持窗口透明度。幸运的是,我们可以通过使用com.sun.jna.examples.WindowUtils类调用JNA的examples.jar文件来解决这个问题。
WindowUtils提供在Unix,Linux,Mac OS X和Windows平台上使用JNA’s来实现窗口透明的工具方法。例如, public static void setWindowMask(final Window w, Icon mask) 让你根据像素而不是通过预定的掩罩(mask)参数来选取某部分的窗口。这个功能将在Listing 10中展示:

Listing 10. Using JNA to render a window transparent


// Create a mask for this dialog. This mask has the same shape as the
// dialog's rounded balloon tip and ensures that only the balloon tip
// part of the dialog will be visible. All other dialog pixels will
// disappear because they correspond to transparent mask pixels.

// Note: The drawing code is based on the drawing code in
// RoundedBalloonBorder.

Rectangle bounds = getBounds ();
BufferedImage bi = new BufferedImage (bounds.width, bounds.height,
                                      BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.createGraphics ();
g.fillRoundRect (0, 0, bounds.width, bounds.height-VERT_OFFSET,
                 ARC_WIDTH*2, ARC_HEIGHT*2);
g.drawRoundRect (0, 0, bounds.width-1, bounds.height-VERT_OFFSET-1,
                 ARC_WIDTH*2, ARC_HEIGHT*2);
int [] xPoints = { HORZ_OFFSET, HORZ_OFFSET+VERT_OFFSET, HORZ_OFFSET };
int [] yPoints = { bounds.height-VERT_OFFSET-1, bounds.height-VERT_OFFSET
                   -1, bounds.height-1 };
g.fillPolygon (xPoints, yPoints, 3);
g.drawLine (xPoints [0], yPoints [0], xPoints [2], yPoints [2]);
g.drawLine (xPoints [1], yPoints [1], xPoints [2], yPoints [2]);
g.dispose ();
WindowUtils.setWindowMask (this, new ImageIcon (bi));


在Listing 10中的代码段是从本文代码文档(code archive)里的加强版的VerifyAge2 应用中的TipFrame的构造函数结尾部分摘录的。这个构造函数定义了围绕提示气泡的掩罩(mask)的形状,在这个形状范围里描绘不透明的像素。
假如你当前文件夹中有examples.jar, jna.jar, 和 VerifyAge2.java,调用 javac -cp examples.jar;balloontip.jar VerifyAge2.java 来编译源文件.然后调用java -Dsun.java2d.noddraw=true -cp examples.jar;balloontip.jar;. VerifyAge2运行这个应用. Figure 2 展示了透明示例.
image

总结(In conclusion)

JNA项目有很长的历史了(追溯到1999年),但是它第一次发布是在2006年11月。从此以后它慢慢的被需要将本地C代码整合到Java工程中的开发者注意到了。因为JNA能够用来解决JuRuby中常见一个问题:缺乏对POSIX调用的支持(lack of support for POSIX calls),它也在JRuby程序员中掀起些波浪。JNA也同样被作为实现用低级C代码继承Ruby的一种解决方案(extending Ruby with low-level C code)。
我喜欢使用JNA来工作,相信你也会发现它比使用JNI来访问本地代码更简单、更安全。无需多言,JNA还有更多的特性在本文中没有体现出来。查阅它的资源部分:获取这个开源java项目更多的信息(learn more about this open source Java project)。用它做demo,而且在论坛(discussion forum )上共享你的经验。 下一个月我会带着另一个开源项目回来的,这个开源项目会给你每天的java开发带来益处。

附录:WindowUtils.setWindowMask()的替代品

在刚刚写完这篇文章后,我发现java语言支持在6u10版本中支持窗口的透明和形状定制。读完Kirill Grouchnikov的博客后,我用WindowUtils.setWindowMask()的替代品修改了VerifyAge2,如下:
// Create and install a balloon tip shape to ensure that only this part
// of the dialog will be visible.

Rectangle bounds = getBounds ();
GeneralPath gp;
gp = new GeneralPath (new RoundRectangle2D.Double (bounds.x, bounds.y,
                                                   bounds.width,
                                                   bounds.height-
                                                   VERT_OFFSET,
                                                   ARC_WIDTH*2-1,
                                                   ARC_HEIGHT*2-1));
gp.moveTo (HORZ_OFFSET, bounds.height-VERT_OFFSET);
gp.lineTo (HORZ_OFFSET, bounds.height);
gp.lineTo (HORZ_OFFSET+VERT_OFFSET+1, bounds.height-VERT_OFFSET);
AWTUtilities.setWindowShape (this, gp);


这段代码使用新类AWTUtilities(在com.sun.awt包中),而且public void setWindowShape(Window w, Shape s)函数将TipFrame和JDialog窗口设置气泡形状。
posted @ 2009-05-07 11:08 lanxin1020 阅读(812) | 评论 (0)编辑 收藏
Java代码
  1. 简单介绍及应用如下:    
  2.   一、JAVA中所需要做的工作    
  3.   在JAVA程序中,首先需要在类中声明所调用的库名称,如下:    
  4.   
  5. static {    
  6. System.loadLibrary(“goodluck”);    
  7. }   
  8.   
  9.   
  10.   在这里,库的扩展名字可以不用写出来,究竟是DLL还是SO,由系统自己判断。    
  11.   
  12.   还需对将要调用的方法做本地声明,关键字为native。且只需要声明,而不需要具体实现。如下:    
  13.   
  14. public native static void set(int i);    
  15. public native static int get();   
  16.   
  17.   
  18.   然后编译该JAVA程序文件,生成CLASS,再用JAVAH命令,JNI就会生成C/C++的头文件。    
  19.   
  20.   例如程序testdll.java,内容为:    
  21.   
  22. public class testdll    
  23. {    
  24. static    
  25. {    
  26. System.loadLibrary("goodluck");    
  27. }    
  28. public native static int get();    
  29. public native static void set(int i);    
  30. public static void main(String[] args)    
  31. {    
  32. testdll test = new testdll();    
  33. test.set(10);    
  34. System.out.println(test.get());    
  35. }    
  36. }   
  37.   
  38.   
  39.   用javac testdll.java编译它,会生成testdll.class。    
  40.   再用javah testdll,则会在当前目录下生成testdll.h文件,这个文件需要被C/C++程序调用来生成所需的库文件。    
  41.   
  42.   二、C/C++中所需要做的工作    
  43.   对于已生成的.h头文件,C/C++所需要做的,就是把它的各个方法具体的实现。然后编译连接成库文件即可。再把库文件拷贝到JAVA程序的路径下面,就可以用JAVA调用C/C++所实现的功能了。    
  44.   接上例子。我们先看一下testdll.h文件的内容:    
  45.   
  46. /* DO NOT EDIT THIS FILE - it is machine generated */    
  47. #include    
  48. /* Header for class testdll */    
  49. #ifndef _Included_testdll    
  50. #define _Included_testdll    
  51. #ifdef __cplusplus    
  52. extern "C" {    
  53. #endif    
  54. /*   
  55. * Class: testdll   
  56. * Method: get   
  57. * Signature: ()I   
  58. */    
  59. JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);    
  60. /*   
  61. * Class: testdll   
  62. * Method: set   
  63. * Signature: (I)V   
  64. */    
  65. JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);    
  66. #ifdef __cplusplus    
  67. }    
  68. #endif    
  69. #endif   
  70.   
  71.   
  72.   在具体实现的时候,我们只关心两个函数原型    
  73.   JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和    
  74.   JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);   
  75.   
  76.   这里JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是要被JNI调用的。而jint是以JNI为中介使JAVA的int类型与本 地的int沟通的一种类型,我们可以视而不见,就当做int使用。函数的名称是JAVA_再加上java程序的package路径再加函数名组成的。参数 中,我们也只需要关心在JAVA程序中存在的参数,至于JNIEnv*和jclass我们一般没有必要去碰它。    
  77.   
  78.   好,下面我们用testdll.cpp文件具体实现这两个函数:    
  79.   
  80. #include "testdll.h"    
  81. int i = 0;    
  82. JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass)    
  83. {    
  84. return i;    
  85. }    
  86. JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint j)    
  87. {    
  88. i = j;    
  89. }   
  90.   
  91.   
  92.   编译连接成库文件,本例是在WINDOWS下做的,生成的是DLL文件。并且名称要与JAVA中需要调用的一致,这里就是goodluck.dll 。把goodluck.dll拷贝到testdll.class的目录下,java testdll运行它,就可以观察到结果了。    
  93.   我的项目比较复杂,需要调用动态链接库,这样在JNI传送参数到C程序时,需要对参数进行处理转换。才可以被C程序识别。   
  94.   大体程序如下:   
  95.   
  96. public class SendSMS {    
  97. static    
  98. {    
  99.   
  100.   
  101.     
  102. System.out.println(System.getProperty("java.library.path"));    
  103. System.loadLibrary("sms");    
  104. }    
  105. public native static int SmsInit();    
  106. public native static int SmsSend(byte[] mobileNo, byte[] smContent);    
  107. }    
  108.   
  109.   在这里要注意的是,path里一定要包含类库的路径,否则在程序运行时会抛出异常:   
  110.   java.lang.UnsatisfiedLinkError: no sms in java.library.path   
  111.   at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1491)   
  112.   at java.lang.Runtime.loadLibrary0(Runtime.java:788)   
  113.   at java.lang.System.loadLibrary(System.java:834)   
  114.   at com.mobilesoft.sms.mobilesoftinfo.SendSMS.(SendSMS.java:14)   
  115.   at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)   
  116.   Exception in thread "main"  
  117.   
  118.   指引的路径应该到.dll文件的上一级,如果指到.dll,则会报:   
  119.   java.lang.UnsatisfiedLinkError: C:\sms.dll: Can't find dependent libraries   
  120.   at java.lang.ClassLoader$NativeLibrary.load(Native Method)   
  121.   at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1560)   
  122.   at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1485)   
  123.   at java.lang.Runtime.loadLibrary0(Runtime.java:788)   
  124.   at java.lang.System.loadLibrary(System.java:834)   
  125.   at com.mobilesoft.sms.mobilesoftinfo.test.main(test.java:18)   
  126.   Exception in thread "main"  
  127.   
  128.   通过编译,生成com_mobilesoft_sms_mobilesoftinfo_SendSMS.h头文件。(建议使用Jbuilder进行编 译,操作比较简单!)这个头文件就是Java和C之间的纽带。要特别注意的是方法中传递的参数jbyteArray,这在接下来的过程中会重点介绍。   
  129.   
  130. /* DO NOT EDIT THIS FILE - it is machine generated */    
  131. #include    
  132. /* Header for class com_mobilesoft_sms_mobilesoftinfo_SendSMS */    
  133. #ifndef _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS    
  134. #define _Included_com_mobilesoft_sms_mobilesoftinfo_SendSMS    
  135. #ifdef __cplusplus    
  136. extern "C" {    
  137. #endif    
  138. /*   
  139. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS   
  140. * Method: SmsInit   
  141. * Signature: ()I   
  142. */    
  143. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit    
  144. (JNIEnv *, jclass);    
  145. /*   
  146. * Class: com_mobilesoft_sms_mobilesoftinfo_SendSMS   
  147. * Method: SmsSend   
  148. * Signature: ([B[B)I   
  149. */    
  150. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend    
  151. (JNIEnv *, jclass, jbyteArray, jbyteArray);    
  152. #ifdef __cplusplus    
  153. }    
  154. #endif    
  155. #endif   
  156.   
  157.   
  158.   
  159.   对于我要调用的C程序的动态链接库,C程序也要提供一个头文件,sms.h。这个文件将要调用的方法罗列了出来。   
  160.   
  161. /*   
  162. * SMS API   
  163. * Author: yippit   
  164. * Date: 2004.6.8   
  165. */    
  166. #ifndef MCS_SMS_H    
  167. #define MCS_SMS_H    
  168. #define DLLEXPORT __declspec(dllexport)    
  169. /*sms storage*/    
  170. #define SMS_SIM 0    
  171. #define SMS_MT 1    
  172. /*sms states*/    
  173. #define SMS_UNREAD 0    
  174. #define SMS_READ 1    
  175. /*sms type*/    
  176. #define SMS_NOPARSE -1    
  177. #define SMS_NORMAL 0    
  178. #define SMS_FLASH 1    
  179. #define SMS_MMSNOTI 2    
  180. typedef struct tagSmsEntry {    
  181. int index; /*index, start from 1*/    
  182. int status; /*read, unread*/    
  183. int type; /*-1-can't parser 0-normal, 1-flash, 2-mms*/    
  184. int storage; /*SMS_SIM, SMS_MT*/    
  185. char date[24];    
  186. char number[32];    
  187. char text[144];    
  188. } SmsEntry;    
  189. DLLEXPORT int SmsInit(void);    
  190. DLLEXPORT int SmsSend(char *phonenum, char *content);    
  191. DLLEXPORT int SmsSetSCA(char *sca);    
  192. DLLEXPORT int SmsGetSCA(char *sca);    
  193. DLLEXPORT int SmsSetInd(int ind);    
  194. DLLEXPORT int SmsGetInd(void);    
  195. DLLEXPORT int SmsGetInfo(int storage, int *max, int *used);    
  196. DLLEXPORT int SmsSaveFlash(int flag);    
  197. DLLEXPORT int SmsRead(SmsEntry *entry, int storage, int index);    
  198. DLLEXPORT int SmsDelete(int storage, int index);    
  199. DLLEXPORT int SmsModifyStatus(int storage, int index); /*unread -> read*/    
  200. #endif   
  201.   
  202.   
  203.   在有了这两个头文件之后,就可以进行C程序的编写了。也就是实现对JNI调用的两个方法。在网上的资料中,由于调用的方法实现的都比较简单,(大多是打印字符串等)所以避开了JNI中最麻烦的部分,也是最关键的部分,参数的传递。     
  204. 由于Java和C的编码是不同的,所以传递的参数是要进行再处理,否则C程序是会对参数在编译过程中提出警告,例如;warning C4024: 'SmsSend' : different types for formal and actual parameter 2等。   
  205.   Sms.c的程序如下:   
  206.   
  207. #include "sms.h"    
  208. #include "com_mobilesoft_sms_mobilesoftinfo_SendSMS.h"    
  209. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsInit(JNIEnv * env, jclass jobject)    
  210. {    
  211. return SmsInit();    
  212. }    
  213.   
  214. JNIEXPORT jint JNICALL Java_com_mobilesoft_sms_mobilesoftinfo_SendSMS_SmsSend(JNIEnv * env, jclass jobject, jbyteArray mobileno, jbyteArray smscontent)    
  215. {    
  216. char * pSmscontent ;    
  217. //jsize theArrayLengthJ = (*env)->GetArrayLength(env,mobileno);    
  218. jbyte * arrayBody = (*env)->GetByteArrayElements(env,mobileno,0);    
  219. char * pMobileNo = (char *)arrayBody;    
  220. printf("[%s]\n ", pMobileNo);    
  221. //jsize size = (*env)->GetArrayLength(env,smscontent);    
  222. arrayBody = (*env)->GetByteArrayElements(env,smscontent,0);    
  223. pSmscontent = (char *)arrayBody;    
  224. printf("<%s>\n", pSmscontent);    
  225. return SmsSend(pMobileNo,pSmscontent);    
  226. }   
  227.   
  228.   
  229.   
  230.   对于C或C++,在程序上是会有稍微的不同,这可以由读者对其进行适当的修改。这里要注意的是GetArrayLength,GetByteArrayElements等这些JNI中已经包含的方法,这些方法是专门对转换参数类型而提供的。具体的方法有很多,在下一篇中会做专门的介绍。   
  231.   在完成了上述的文件后,可以对sms.c进行编译,生成.dll文件(建议在release中编译,这样动态链接库的容积会比较小!)   
  232.   完成.dll文件的编译后,就可以在Java中调用C程序中的方法了。例如文件test.java   
  233.   
  234. public class test {    
  235. public test() {    
  236. }    
  237. public static void main(String[] args) {    
  238. byte[] mobileno = {    
  239. 0x310x330x360x360x310x360x330x300x360x360x370x00};    
  240. String smscontentemp = "早上好";    
  241. byte[] temp = {0};    
  242. try {    
  243. byte[] smscontentdb = smscontentemp.getBytes("gbk");    
  244. byte[] smscontent = new byte[smscontentdb.length + temp.length];    
  245. System.arraycopy(smscontentdb, 0, smscontent, 0, smscontentdb.length);    
  246. System.arraycopy(temp, 0, smscontent, smscontentdb.length, temp.length);    
  247. SendSMS sendSMS = new SendSMS();    
  248. sendSMS.SmsInit();    
  249. if (sendSMS.SmsSend(mobileno, smscontent) >= 0) {    
  250. System.out.println("chenggong !");    
  251. }    
  252. else {    
  253. System.out.println("shibai !");    
  254. }    
  255. }catch (Exception ex) {}    
  256. }    
  257. }   
  258.   
  259.   
  260.   
  261.   在这个文件中要注意的有一点,就是在传递字节数组到C程序中时,最后的结尾一定要以0结束。这是一个偷懒的做法,不过是个有效的做法。因为大多数情况 下,接口是由第三方提供的。所以我们一般是不知道在C的方法里,具体是怎么处理参数的。而C又是要求数组是有长度。所以,在Java中,如果你不想写程序 传数组的长度,那么在数组中以0结尾就是最方便的方法了。当然,如果有更好的方法也希望大家提出。   
  262.   
  263.   到这里,一个完整的Java通过JNI调用动态链接库的程序就完成了。实际上也不是很复杂。只要多注意一下细节,是很容易得出来的。  
posted @ 2009-05-07 11:04 lanxin1020 阅读(167) | 评论 (0)编辑 收藏
Properties 基本知识
如果不熟悉 java.util.Properties 类,那么现在告诉您它是用来在一个文件中存储键-值对的,其中键和值是用等号分隔的,如清单 1 所示。

清单 1. 一组属性示例
foo=bar
fu=baz

将清单 1 装载到 Properties 对象中后,您就可以找到两个键( foo 和 fu )和两个值( foo 的 bar 和 fu 的 baz )了。这个类支持带 \u 的嵌入 Unicode 字符串,但是这里重要的是每一项内容都当作 String 。

清单 2 显示了如何装载属性文件并列出它当前的一组键和值。只需传递这个文件的 InputStream 给 load() 方法,就会将每一个键-值对添加到 Properties 实例中。然后用 list() 列出所有属性或者用 getProperty() 获取单独的属性。
清单 2. 装载属性
Java代码
  1. import java.util.*;   
  2. import java.io.*;   
  3.   
  4. public class LoadSample {   
  5.     public static void main(String args[]) throws Exception {   
  6.       Properties prop = new Properties();   
  7.       FileInputStream fis =    
  8.         new FileInputStream("sample.properties");   
  9.       prop.load(fis);   
  10.       prop.list(System.out);   
  11.       System.out.println("\nThe foo property: " +   
  12.           prop.getProperty("foo"));   
  13.     }   
  14. }  

运行 LoadSample 程序生成如清单 3 所示的输出。注意 list() 方法的输出中键-值对的顺序与它们在输入文件中的顺序不一样。 Properties 类在一个散列表(hashtable,事实上是一个 Hashtable 子类)中储存一组键-值对,所以不能保证顺序。

清单 3. LoadSample 的输出

-- listing properties --
fu=baz
foo=bar

The foo property: bar

XML 属性文件
这里没有什么新内容。 Properties 类总是这样工作的。不过,新的地方是从一个 XML 文件中装载一组属性。它的 DTD 如清单 4 所示。

清单 4. 属性 DTD

dtd 写道
Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>    
  2. <!-- DTD for properties -->    
  3. <!ELEMENT properties ( comment?, entry* ) >    
  4. <!ATTLIST properties version CDATA #FIXED "1.0">    
  5. <!ELEMENT comment (#PCDATA) >    
  6. <!ELEMENT entry (#PCDATA) >    
  7. <!ATTLIST entry key CDATA #REQUIRED>  



如果不想细读 XML DTD,那么可以告诉您它其实就是说在外围 <properties> 标签中包装的是一个 <comment> 标签,后面是任意数量的 <entry> 标签。对每一个 <entry> 标签,有一个键属性,输入的内容就是它的值。清单 5 显示了 清单 1中的属性文件的 XML 版本是什么样子的。

清单 5. XML 版本的属性文件
Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>   
  2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">   
  3. <properties>   
  4. <comment>Hi</comment>   
  5. <entry key="foo">bar</entry>   
  6. <entry key="fu">baz</entry>   
  7. </properties>  

如果清单 6 所示,读取 XML 版本的 Properties 文件与读取老格式的文件没什么不同。
清单 6. 读取 XML Properties 文件
Java代码
  1. import java.util.*;   
  2. import java.io.*;   
  3.   
  4. public class LoadSampleXML {   
  5.     public static void main(String args[]) throws Exception {   
  6.       Properties prop = new Properties();   
  7.       FileInputStream fis =   
  8.         new FileInputStream("sampleprops.xml");   
  9.       prop.loadFromXML(fis);   
  10.       prop.list(System.out);   
  11.       System.out.println("\nThe foo property: " +   
  12.           prop.getProperty("foo"));   
  13.     }   
  14. }  

关于资源绑定的说明
虽然 java.util.Properties 类现在除了支持键-值对,还支持属性文件作为 XML 文件,不幸的是,没有内置的选项可以将 ResourceBundle 作为一个 XML 文件处理。是的, PropertyResourceBundle 不使用 Properties 对象来装载绑定,不过装载方法的使用是硬编码到类中的,而不使用较新的 loadFromXML() 方法。

运行清单 6 中的程序产生与原来的程序相同的输出,如 清单 2所示。

保存 XML 属性
新的 Properties 还有一个功能是将属性存储到 XML 格式的文件中。虽然 store() 方法仍然会创建一个类似 清单 1 所示的文件,但是现在可以用新的 storeToXML() 方法创建如 清单 5 所示的文件。只要传递一个 OutputStream 和一个用于注释的 String 就可以了。清单 7 展示了新的 storeToXML() 方法。

清单 7. 将 Properties 存储为 XML 文件
Java代码
  1. import java.util.*;   
  2. import java.io.*;   
  3.   
  4. public class StoreXML {   
  5.     public static void main(String args[]) throws Exception {   
  6.       Properties prop = new Properties();   
  7.       prop.setProperty("one-two""buckle my shoe");   
  8.       prop.setProperty("three-four""shut the door");   
  9.       prop.setProperty("five-six""pick up sticks");   
  10.       prop.setProperty("seven-eight""lay them straight");   
  11.       prop.setProperty("nine-ten""a big, fat hen");   
  12.       FileOutputStream fos =   
  13.         new FileOutputStream("rhyme.xml");   
  14.       prop.storeToXML(fos, "Rhyme");   
  15.       fos.close();   
  16.     }   
  17. }  

运行清单 7 中的程序产生的输出如清单 8 所示。

清单 8. 存储的 XML 文件
Java代码
  1. <?xml version="1.0" encoding="UTF-8"?>   
  2. <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">   
  3. <properties>   
  4. <comment>Rhyme</comment>   
  5. <entry key="seven-eight">lay them straight</entry>   
  6. <entry key="five-six">pick up sticks</entry>   
  7. <entry key="nine-ten">a big, fat hen</entry>   
  8. <entry key="three-four">shut the door</entry>   
  9. <entry key="one-two">buckle my shoe</entry>   
  10. </properties>  

结束语
使用 XML 文件还是使用老式的 a=b 类型的文件完全取决于您自己。老式文件从内存的角度看肯定是轻量级的。不过,由于 XML 的普遍使用,人们会期望 XML 格式流行起来,因为它已经被广泛使用了,只不过没有用到 Properties 对象。选择完全在您。分析软件包 private XMLUtils 类的源代码以获得关于所使用的 XML 解析的更多信息。
Java代码
  1. import java.io.FileInputStream;   
  2. import java.io.IOException;   
  3. import java.io.InputStream;   
  4. import java.util.Properties;   
  5. /**  
  6. * 实现properties文件的读取  
  7. * @author bbflyerwww  
  8. * @date 2006-08-02  
  9. */  
  10. public class PTest {   
  11.       public static void main(String[] args) {   
  12.           try {   
  13.               long start = System.currentTimeMillis();   
  14.               InputStream is = new FileInputStream("conf.properties");   
  15.               Properties p = new Properties();   
  16.               p.load(is);   
  17.               is.close();   
  18.               System.out.println("SIZE : " + p.size());   
  19.               System.out.println("homepage : " + p.getProperty("homepage"));   
  20.               System.out.println("author : " + p.getProperty("author"));   
  21.               System.out.println("school : " + p.getProperty("school"));   
  22.               System.out.println("date : " + p.getProperty("date"));   
  23.               long end = System.currentTimeMillis();    
  24.               System.out.println("Cost : " + (end - start));   
  25.           } catch (IOException ioe) {   
  26.               ioe.printStackTrace();   
  27.           }   
  28.       }   
  29. }  

conf.properties
Java代码
  1. # Configuration fileauthor = bbflyerwww   
  2. school = WuHan University   
  3. date = 2006-08-02  


Result

SIZE:4
author : bbflyerwww
school : WuHan University
date : 2006-08-02
Cost : 0
posted @ 2009-05-06 22:52 lanxin1020 阅读(463) | 评论 (0)编辑 收藏

一、LOG4J组成

    LOG4J主要由三大组件组成:
    . Logger: 决定什么日志信息应该被输出、什么日志信息应该被忽略;
    . Appender: 指定日志信息应该输出到什么地方, 这些地方可以是控制台、文件、网络设备;
    . Layout: 指定日志信息的输出格式;

    一个Logger可以有多个Appender,也就是说日志信息可以同时输出到多个设备上,每个Appender对应
    一种Layout(示例见下图)。

              ↗  Appender1  →  Layout
     /    
    Logger
     ﹨  
              ↘  Appender2  →  Layout


二、Logger组件

    1. Logger组件提供的方法:

       Logger组件是LOG4J的核心组件,它代表了Log4J的日志记录器,它能够对日志信息进行分类筛选。它由org.apache.log4j.Logger类实现,提供了如下方法:

 

java 代码
  1. package org.apache.log4j;   
  2.   
  3. public class Logger {   
  4.   
  5.            // Creation & retrieval methods:   
  6.            public static Logger getRootLogger();   
  7.            public static Logger getLogger(String name);   
  8.   
  9.            // printing methods:   
  10.            public void debug(Object message);   
  11.            public void info(Object message);   
  12.            public void warn(Object message);   
  13.            public void error(Object message);   
  14.            public void fatal(Object message);   
  15.       
  16.            // generic printing method:   
  17.            public void log(Priority p, Object message);   
  18. }   

    2. 在配置文件中配置Logger组件

       可在Log4J配置文件中配置自己的Logger组件,示例:

       log4j.logger.myLogger=WARN

       以上代码定义了一个Logger组件,名称为myLogger,日志级别为WARN。
 
    3. 日志级别种类:

       一共有五种,级别由高到低依次是:fatal、error、warn、info、debug。获得Logger实例后,我们可调用以下方法之一输出日志信息:

       public void debug(Object message);          //输出debug级别的日志信息;
       public void info(Object message);           //输出info级别的日志信息;
       public void warn(Object message);           //输出warn级别的日志信息;
       public void error(Object message);          //输出error级别的日志信息;
       public void fatal(Object message);          //输出fatal级别的日志信息;
       public void log(Priority p, Object message);//输出参数Priority指定级别的日志信息;

       以上方法只有当它的级别大于或等于Logger组件配置的日志级别时才调用。以前面我们配置的myLogger为例,它的日志级别为WARN, 那么在程序中,它的warn()、error()、fatal()方法会被执行。对于log()方法,只有当它的参数Priority指定的日志级别大于或等于WARN时,它才会被执行。

    4. 为什么需要对日志进行分级?
   
       在写程序的时候,为了调试程序,我们会在很多出错的地方输出大量的日志信息。当程序调试完,不需要这些信息时,将程序中这些输出日志信息代码删除吗?这样费时费力,对于大型程序几乎不可行。通过对日志分级,假如不想输出WARN级别的日志信息,则Logger组件的级别调高即可,省时省心。

    5. Logger组件的继承性

       Log4J提供了一个root Logger,它是所有Logger组件的“祖先”,它永远存在,且不能通过名字检索或引用,通过Logger.getRootLogger()方法取得它。配置root Logger代码:

       log4j.rootLogger=INFO,console

       可在配置文件中方便地配置存在继承关系的Logger组件,凡是在符号“.”后面的组件都会成为在符号“.”前面的Logger组件的子类。例如:

       log4j.apache.myLogger=WARN
       log4j.apache.myLogger.mySonLogger=,file

       以上代码中, mySonLogger是myLogger的子类Logger组件。Logger组件的继承关系:
       . 如果子类Logger组件没有定义日志级别,则将继承父类的日志级别;
       . 如果子类Logger组件定义了日志级别,就不会继承父类的日志级别;
       . 黙认情况下,子类Logger组件会继承父类所有的Appender,把它们加入到自己的Appener;
       . 如果把子类Logger组件的additivity标志设为false,那么它就不会继承父类Appender。additivity标志 默认值为false;

       以上配置的三个Logger继承关系示例如图:
    
       root Logger: 日志级别=INFO  appender清单=console
                            ↑
       myLogger: 日志级别=WARN appender清单=null
                            ↑
       mySonLogger: 日志级别=null appender清单=file

       这三个Logger组件实际日志级别和Appender如下表:

       Logger组件          日志级别          Appender清单
       root Logger         INFO              console
       myLogger            WARN              console(继承)
       mySonLogger         WARN(继承)        file,console(继承)
      
三、Appender组件

    Appender组件决定将日志信息输出到什么地方。支持以下目的地:
    . 控制台(Console);
    . 文件(File);
    . GUI组件(GUI component);
    . 套接口服务器(Remote socket server);
    . NT的事件记录器(NT Event Logger);
    . UNIX Syslog守护进程(Remote UNIX Syslog daemon);

    一个Logger可同时对应多个Appender,示例:myLogger配置二个Appender: 一个file, 一个是console:

    log4j.logger.myAppender=WARN,file,console

    log4j.appender.file=org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=log.txt

    log4j.apender.console=org.apache.log4j.ConsoleAppender

四、Layout组件

    Layout组件决定日志输出格式,有以下几种类型:
    . org.apache.log4j.HTMLLayout(以HTML表格形式布局);
    . org.apache.log4j.PatternLayout(可以灵活地指定布局模式);
    . org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串);
    . org.apache.log4j.TTCCLayout(包含日志产生的时间、线程和类别等信息);
   
    为名称为console的Appender配置SimpleLayout,代码如下:

    log4j.appender.console.layout=org.apache.log4j.SimpleLayout

    输出日志格式如下:

    WARN - This is a log message from the myLogger
   
    为名称为file的Appender配置PatternLayout,代码如下:

    log4j.appender.file.layout=org.apache.log4j.PatternLayout
    log4j.appender.file.layout.ConversionPattern=%t %p - %m%n

    输出日志格式如下:

    THREAD-1 WARN - This is a log message from the myLogger

    PatternLayout让开发者依照ConversionPattern定义输出格式。ConversionPattern中一些指定日志内容和格式的预定义符号说明如下:

    符号         描述
    %r           自程序开始后消耗的毫秒数
    %t           表示日志记录请求生成的线程
    %p           表示日专语句的优先级
    %r           与日志请求相关的类别名称
    %c           日志信息所在的类名
    %m%n         表示日志信息的内容

五、Log4J的基本用法

    1. 定义配置文件
       Log4J支持二种配置文件格式:XML和Java属性文件(采用“键=值”形式)。以下为Java属性文件
       格式配置文件:
     
       . 配置Logger组件
        
         配置root Logger语法为:log4j.rootLogger=[priority],appenderName,appenderName,...
         配置自定义Logger组件语法为:log4j.logger.loggerName=[priority],appenderName,appenderName,...

         其中:priority为日志级别,可选值包括FATAL、ERROR、WARN、INFO、DEBUG、ALL;
               appenderName指定Appender组件,可指定多个;        

       . 配置Appender组件

         配置日志信息输出目的地Appender, 语法为:
         log4j.appender.appenderName=fully.ualified.name.of.appender.class
         log4j.appender.appenderName.option1=value1
         ...
         log4j.appender.appenderName.optionN=valueN

         Log4J提供的Appender有以下几种:

         a. org.apache.log4j.ConsoleAppender(控制台);
         b. org.apache.log4j.FileAppender(文件);
         c. org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件);
         d. org.apache.log4j.RollingFileAppender(文件大小到指定尺寸产生一个新的文件);
         e. org.apache.log4j.WriteAppender(将日志信息以流格式发送到任意指定地方);

       . 配置Layout组件

         配置Layout组件语法为:
         log4j.appender.appenderName.layout=fully.ualified.name.of.appender.class
         log4j.appender.appenderName.layout.option1=value1
         ...
         log4j.appender.appenderName.layout.optionN=valueN

         下面为一配置文件示例,文件名为log4j.properties:

         ## LOGGERS ##

         #configure root logger
         log4j.rootLogger=INFO,console
         #define a logger named myLogger
         log4j.logger.myLogger=WARN
         #define a second logger that is a child to myLogger
         log4j.logger.myLogger.mySonLogger=,file

         ## APPENDERS ##

         #define an appender named console, which is set to be a ConsoleAppender
         log4j.appender.console=org.apache.log4j.ConsoleAppender

         # define an appender named file, which is set to be a RollingFileAppender
         log4j.appender.file=org.apache.log4j.FileAppender
         log4j.appender.file.File=log.txt

         ## LAYOUTS ##
         # assian a SimpleLayout to console appender
         log4j.appender.console.layout=org.apache.log4j.SimpleLayout

         # assian a PatternLayout to file appender
         log4j.appender.file.layout=org.apache.log4j.PatternLayout
         log4j.appender.file.layout.ConversionPattern=%t%p-%m%n
        
    2. 程序中使用Log4j

       . 获得日志记录器:

         获得rootLogger:Logger rootLogger=Logger.getRootLogger();
         获得自定义Logger:Logger myLogger = Logger.getLogger("log4j.logger.myLogger");

       . 读取日志记录器,配置Log4J环境;

         a. BasicConfigurator.configure(): 自动快速地使用默认Log4J环境;
         b. Property.configurator.configure(String configFilename): 读取使用Java属性格式的配置文件并配置Log4J环境;
         c. DOMConfigurator.configure(String filename): 读取XML形式的配置文件并配置LOG4J环境;

       . 输出日志信息;

         在程序代码中需要生成日志的地方,调用Logger的各种输出日志方法输出不同级别的日志,例如:
        
         myLogger.debug("Thie is a log message from the " + myLogger.getName());

         下面为一使用Log4J的程序,程序名为Test.java:

java 代码
  1.  import org.apache.log4j.Logger;   
  2.  import org.apache.log4j.PropertyConfigurator;   
  3.     
  4.  public class Test {   
  5.   
  6.    public static void main(String[] args) {   
  7.      //Get an instance of the myLogger   
  8.      Logger myLogger = Logger.getLogger("myLogger");   
  9.       
  10.      //Get an instance of the childLogger   
  11.      Logger mySonLogger = Logger.getLogger("myLogger.mySonLogger");   
  12.      //Load the proerties using the PropertyConfigurator   
  13.      PropertyConfigurator.configure("log4j.properties");   
  14.   
  15.      //Log Messages using the Parent Logger   
  16.      myLogger.debug("Thie is a log message from the " + myLogger.getName());   
  17.      myLogger.info("Thie is a log message from the " + myLogger.getName());   
  18.      myLogger.warn("Thie is a log message from the " +  myLogger.getName());   
  19.      myLogger.error("Thie is a log message from the " + myLogger.getName());   
  20.      myLogger.fatal("Thie is a log message from the " + myLogger.getName());   
  21.   
  22.      mySonLogger.debug("Thie is a log message from the " + mySonLogger.getName());   
  23.      mySonLogger.info("Thie is a log message from the " + mySonLogger.getName());   
  24.      mySonLogger.warn("Thie is a log message from the " +  mySonLogger.getName());   
  25.      mySonLogger.error("Thie is a log message from the " + mySonLogger.getName());   
  26.      mySonLogger.fatal("Thie is a log message from the " + mySonLogger.getName());   
  27.    }   
  28. }   

        程序运行结果为:

        WARN - Thie is a log message from the myLogger
        ERROR - Thie is a log message from the myLogger
        FATAL - Thie is a log message from the myLogger
        WARN - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        FATAL - Thie is a log message from the myLogger.mySonLogger

        另在Test.class所在的目录下看到一个log.txt文件,内容如下:

        WARN - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        FATAL - Thie is a log message from the myLogger.mySonLogger

        如将配置文件log4j.properties中语句

 log4j.logger.myLogger.mySonLogger=,file

 改为

 log4j.logger.myLogger.mySonLogger=,file,console

 再次运行程序,结果如下:

        WARN - Thie is a log message from the myLogger
        ERROR - Thie is a log message from the myLogger
        FATAL - Thie is a log message from the myLogger
        WARN - Thie is a log message from the myLogger.mySonLogger
        WARN - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        ERROR - Thie is a log message from the myLogger.mySonLogger
        FATAL - Thie is a log message from the myLogger.mySonLogger         
        FATAL - Thie is a log message from the myLogger.mySonLogger

        mySonLogger的日志在控制台上输出了二次,这是因为mySonLogger继承了父类console Appender,
        本身又定义了一个console Appender, 因而有二个console Appender。

六、在web应用中使用Log4J

    创建一个Servlet,在它初始化方法中读取Log4J配置文件并配置Log4J环境,这个Servlet在Web应用启
    动时候被加载和初始化,然后就可在其它Web组件中获取Logger对象并输出日志。

    1. 创建用于配置Log4J环境的Servlet

java 代码
  1. import javax.servlet.*;   
  2. import javax.servlet.http.*;   
  3. import java.io.*;   
  4. import java.util.*;   
  5.   
  6. import org.apache.log4j.PropertyConfigurator;   
  7.   
  8. public class Log4JServlet extends HttpServlet {   
  9.       public void init() throws ServletException {   
  10.            String path = getServletContext().getRealPath("/");   
  11.            //getInitParameter("propfile")方法从web.xml文件中读取Log4J配置文件的名字"profile"。   
  12.            String propfile = path + getInitParameter("propfile");   
  13.            PropertyConfigurator.configure(propfile);   
  14.       }   
  15. }   
  16.   

      该Servlet在web.xml中的配置如下:

xml 代码
  1. <servlet>  
  2.   <servlet-name>log4jServlet</servlet-name>  
  3.   <servlet-class>Log4JServlet</servlet-class>  
  4.   <init-param>  
  5.     <param-name>propfile</param-name>  
  6.     <param-value>/WEB-INF/log4j.properties</param-value>  
  7.   </init-param>  
  8.   <load-on-startup>1</load-on-startup>  
  9. </servlet>  

2. 在login.jsp中输出日志
       <%@page import="org.apache.log4j.Logger"%>
       <html>
         <head>
           <title>login</title>
         </head>
         <body>
           <%
             Logger myLogger = Logger.getLogger("myLogger");
             Logger mySonLogger = Logger.getLogger("myLogger.mySonLogger");
             myLogger.debug("Thie is a log message from the " + myLogger.getName());
             myLogger.info("Thie is a log message from the " + myLogger.getName());
             myLogger.warn("Thie is a log message from the " +  myLogger.getName());
             myLogger.error("Thie is a log message from the " + myLogger.getName());
             myLogger.fatal("Thie is a log message from the " + myLogger.getName());

             mySonLogger.debug("Thie is a log message from the " + mySonLogger.getName());
             mySonLogger.info("Thie is a log message from the " + mySonLogger.getName());
             mySonLogger.warn("Thie is a log message from the " +  mySonLogger.getName());
             mySonLogger.error("Thie is a log message from the " + mySonLogger.getName());
             mySonLogger.fatal("Thie is a log message from the " + mySonLogger.getName());
           %>
           <br>
             <form name="loginForm" method="post" action="dispatcher">
             username: <input type="text" name="username">
             <br>
             password: <input type="text" name="password">
             <br>
             <input type="submit" name="submit" value="submit">
           </form>
         </body>
       </html>
              
    3. 发布运行使用Log4J的web应用
       1) 将Log4J的JAR文件拷贝至目录:<WEB应用所在目录>/WEB-INF/lib
       2) 创建Log4J的配置文件log4j.properties, 存放目录为:<WEB应用所在目录>/WEB-INF。内容同前面配置文件示例。
       3) 编译Log4JServlet, 存放至目录: <WEB应用所在目录>/WEB-INF/classes
       4) 修改web.xml文件,加入以下内容:

xml 代码
  1. <servlet>  
  2.   <servlet-name>log4jServlet</servlet-name>  
  3.   <servlet-class>Log4JServlet</servlet-class>  
  4.   <init-param>  
  5.     <param-name>profile</param-name>  
  6.     <param-value>/WEB-INF/log4j.properties</param-value>  
  7.   </init-param>  
  8.   <load-on-startup>1</load-on-startup>  
  9. </servlet>  


       5) 启动服务器,访问login.jsp页面,在服务器控制台上看到如下日志:
          WARN - Thie is a log message from the myLogger
          ERROR - Thie is a log message from the myLogger
          FATAL - Thie is a log message from the myLogger
          WARN - Thie is a log message from the myLogger.mySonLogger
          ERROR - Thie is a log message from the myLogger.mySonLogger
          FATAL - Thie is a log message from the myLogger.mySonLogger

          另在<WEB应用所在目录>/WEB-INF目录下看到一个log.txt文件,内容如下:

          WARN - Thie is a log message from the myLogger.mySonLogger
          ERROR - Thie is a log message from the myLogger.mySonLogger
          FATAL - Thie is a log message from the myLogger.mySonLogger

posted @ 2009-05-04 17:19 lanxin1020 阅读(161) | 评论 (0)编辑 收藏

在实际编程时,要使Log4j真正在系统中运行事先还要对配置文件进行定义。定义步骤就是对Logger、Appender及Layout的分别使用。
  Log4j支持两种配置文件格式,一种是XML格式的文件,一种是java properties(key=value)【Java特性文件(键=值)】。下面我们介绍使用Java特性文件做为配置文件的方法
  具体如下:
  
  1、配置根Logger 其语法为:
  log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
  level : 是日志记录的优先级,分为OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定义的级别。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。通过在这里定义的级别,您可以控制到应用程序中相应级别的日志信息的开关。比如在这里定 义了INFO级别,则应用程序中所有DEBUG级别的日志信息将不被打印出来。
  appenderName:就是指定日志信息输出到哪个地方。您可以同时指定多个输出目的地。
  例如:log4j.rootLogger=info,A1,B2,C3
  
  2、配置日志信息输出目的地 ,其语法为:
  log4j.appender.appenderName = fully.qualified.name.of.appender.class   //
     "fully.qualified.name.of.appender.class" 可以指定下面五个目的地中的一个:
      1.org.apache.log4j.ConsoleAppender(控制台)
      2.org.apache.log4j.FileAppender(文件)
      3.org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
      4.org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
      5.org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
        1.ConsoleAppender选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              Target=System.err:默认情况下是:System.out,指定输出控制台
        2.FileAppender 选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              File=mylog.txt:指定消息输出到mylog.txt文件。
              Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
        3.DailyRollingFileAppender 选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              File=mylog.txt:指定消息输出到mylog.txt文件。
              Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
              DatePattern='.'yyyy-ww:每周滚动一次文件,即每周产生一个新的文件。当然也可以指定按月、周、天、时和分。即对应的格式如下:
              1)'.'yyyy-MM: 每月
              2)'.'yyyy-ww: 每周
              3)'.'yyyy-MM-dd: 每天
              4)'.'yyyy-MM-dd-a: 每天两次
              5)'.'yyyy-MM-dd-HH: 每小时
              6)'.'yyyy-MM-dd-HH-mm: 每分钟
        4.RollingFileAppender 选项
              Threshold=WARN:指定日志消息的输出最低层次。
              ImmediateFlush=true:默认值是true,意谓着所有的消息都会被立即输出。
              File=mylog.txt:指定消息输出到mylog.txt文件。
              Append=false:默认值是true,即将消息增加到指定文件中,false指将消息覆盖指定的文件内容。
              MaxFileSize=100KB: 后缀可以是KB, MB 或者是 GB. 在日志文件到达该大小时,将会自动滚动,即将原来的内容移到mylog.log.1文件。
              MaxBackupIndex=2:指定可以产生的滚动文件的最大数。 实际应用:
  log4j.appender.A1=org.apache.log4j.ConsoleAppender //这里指定了日志输出的第一个位置A1是控制台ConsoleAppender
  
  3、配置日志信息的格式 其语法为:
  A. log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
          "fully.qualified.name.of.layout.class" 可以指定下面4个格式中的一个:
        1.org.apache.log4j.HTMLLayout(以HTML表格形式布局),
       2.org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
       3.org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
       4.org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)
            1.HTMLLayout 选项
              LocationInfo=true:默认值是false,输出java文件名称和行号
              Title=my app file: 默认值是 Log4J Log Messages.
            2.PatternLayout 选项
              ConversionPattern=%m%n :指定怎样格式化指定的消息。
            3.XMLLayout   选项
              LocationInfo=true:默认值是false,输出java文件和行号
  实际应用:
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    B . log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
        这里需要说明的就是日志信息格式中几个符号所代表的含义:
         -X号: X信息输出时左对齐;
            %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
            %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
            %r: 输出自应用启动到输出该log信息耗费的毫秒数
            %c: 输出日志信息所属的类目,通常就是所在类的全名
            %t: 输出产生该日志事件的线程名
            %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java:10)
            %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
            %%: 输出一个"%"字符
            %F: 输出日志消息产生时所在的文件名称
            %L: 输出代码中的行号
            %m: 输出代码中指定的消息,产生的日志具体信息
            %n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行
        可以在%与模式字符之间加上修饰符来控制其最小宽度、最大宽度、和文本的对齐方式。如:
              1)%20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,默认的情况下右对齐。
              2)%-20c:指定输出category的名称,最小的宽度是20,如果category的名称小于20的话,"-"号指定左对齐。
              3)%.30c:指定输出category的名称,最大的宽度是30,如果category的名称大于30的话,就会将左边多出的字符截掉,但小于30的话也不会有空格。
              4)%20.30c:如果category的名称小于20就补空格,并且右对齐,如果其名称长于30字符,就从左边交远销出的字符截掉。
  这里上面三个步骤是对前面Log4j组件说明的一个简化;下面给出一个具体配置例子,在程序中可以参照执行:
  log4j.rootLogger=INFO,A1,B2
  log4j.appender.A1=org.apache.log4j.ConsoleAppender
  log4j.appender.A1.layout=org.apache.log4j.PatternLayout
  log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
  根据上面的日志格式,某一个程序的输出结果如下:
  0  INFO 2003-06-13 13:23:46968 ClientWithLog4j Client socket: Socket[addr=localhost/127.0.0.1,port=8002,localport=2014]
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server says: 'Java server with log4j, Fri Jun 13 13:23:46 CST 2003'
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j GOOD
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Command 'HELLO' not understood.'
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j HELP
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j Server responds: 'Vocabulary: HELP QUIT'
  16  DEBUG 2003-06-13 13:23:46984 ClientWithLog4j QUIT

  4. # 当输出信息于回滚文件时
      log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender   //指定以文件的方式输出日志
        log4j.appender.ROLLING_FILE.Threshold=ERROR
        log4j.appender.ROLLING_FILE.File=rolling.log   //文件位置,也可以用变量${java.home}、rolling.log
        log4j.appender.ROLLING_FILE.Append=true
        log4j.appender.ROLLING_FILE.MaxFileSize=10KB   //文件最大尺寸
        log4j.appender.ROLLING_FILE.MaxBackupIndex=1   //备份数
        log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
        log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n     
××××××××××××××××××××××××××××××××××××××××××××××××

附:Log4j比较全面的配置
LOG4J的配置之简单使它遍及于越来越多的应用中了:Log4J配置文件实现了输出到控制台、文件、回滚文件、发送日志邮件、输出到数据库日志表、自定义标签等全套功能。择其一二使用就够用了,
log4j.rootLogger=DEBUG,CONSOLE,A1,im
log4j.addivity.org.apache=true
# 应用于控制台
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
#log4j.appender.CONSOLE.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD] n%c[CATEGORY]%n%m[MESSAGE]%n%n
#应用于文件
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=file.log
log4j.appender.FILE.Append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# Use this layout for LogFactor 5 analysis
# 应用于文件回滚
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log   //文件位置,也可以用变量${java.home}、rolling.log
log4j.appender.ROLLING_FILE.Append=true     //true:添加   false:覆盖
log4j.appender.ROLLING_FILE.MaxFileSize=10KB   //文件最大尺寸
log4j.appender.ROLLING_FILE.MaxBackupIndex=1   //备份数
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

#应用于socket
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern=[start]%d{DATE}[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]%n%c[CATEGORY]%n%m[MESSAGE]%n%n

# Log Factor 5 Appender
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
# 发送日志给邮件
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold=FATAL
log4j.appender.MAIL.BufferSize=10
log4j.appender.MAIL.From=web@www.wuset.com
log4j.appender.MAIL.SMTPHost=www.wusetu.com
log4j.appender.MAIL.Subject=Log4J Message
log4j.appender.MAIL.To=web@www.wusetu.com
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n
# 用于数据库
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES ('[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n')
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=SampleMessages.log4j
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
#自定义Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = corlin@cybercorlin.net
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern =[framework] %d - %c -%-4r [%t] %-5p %c %x - %m%n

posted @ 2009-05-04 14:40 lanxin1020 阅读(121) | 评论 (0)编辑 收藏
Log4j下载
在apache网站:jakarta.apache.org/log4j 可以免费下载到Log4j最新版本的软件包。

Log4j使用
Log4j的包下载完成后,解压,将其中打包好的的log4j-1.x.x.jar导入你的工程LIB中。
Log4j之所以受欢迎的原因之一是它的灵活性。Log4j提供了灵活的配置方法,默认是调用BasicConfigurator.configure()来进行配置,但如果只是简单的调用BasicConfigurator.configure()来进行配置工作,那么所有的配置都是固定的,不方便以后修改配置。另一种是动态配置,Log4j提供了PropertyConfigurator.configure(……)来动态配置,参数可以是一个properties文件所在路径的String对象,可以是一个properties文件所在路径的URL对象,也可以是一个properties对象。如果要用XML文件来配置信息,则可用类型的DOMConfigurator()函数来从一个XML文件中加载配置信息。这种方式更方便修改配置。

动态配置

Java代码
  1. package http;       
  2.       
  3. import org.apache.log4j.BasicConfigurator;       
  4. import org.apache.log4j.Logger;       
  5. import org.apache.log4j.PropertyConfigurator;       
  6. import org.apache.log4j.xml.DOMConfigurator;       
  7.       
  8. public class Log4jDemo {       
  9.     static Logger log = Logger.getLogger(Log4jDemo.class.getClass());       
  10.     /**     
  11.      * main     
  12.      * @param args     
  13.      */      
  14.     public static void main(String[] args) {       
  15.         BasicConfigurator.configure();//默认配置       
  16.         PropertyConfigurator.configure("c:/log4j.properties");       
  17.         //动态配置,参数可以是一个properties文件所在路径的String对象       
  18.         //可以是一个properties文件所在路径的URL对象,也可以是一个properties对象       
  19.                
  20.         DOMConfigurator.configure("c:/log4j.xml");//XML配置文件       
  21.       
  22.         //PropertyConfigurator.configure()的参数还可以是XML、Properties对象       
  23.                
  24.         //下面就可使用log4j       
  25.         log.info("info");       
  26.         log.debug("debug");       
  27.         log.error("error");       
  28.         log.warn("warn");       
  29.     }       
  30.       
  31. }      


J2EE应用log4j
上面代码描述了Log4j的简单应用,其实使用Log4j也就是这样简单方便。当然除了上面的配置方法,还有其它,比如做一个J2EE应用,在J2EE应用使用Log4j,必须先在启动服务时加载Log4j的配置文件进行初始化,可以在web.xml中进行。


java 代码
Java代码
  1. import java.io.IOException;       
  2. import java.io.PrintWriter;       
  3.       
  4. import javax.servlet.ServletException;       
  5. import javax.servlet.http.HttpServlet;       
  6. import javax.servlet.http.HttpServletRequest;       
  7. import javax.servlet.http.HttpServletResponse;       
  8.       
  9.       
  10.       
  11. public class J2eeLog4jDemo extends HttpServlet {       
  12.       
  13.     public void destroy() {       
  14.         super.destroy();        
  15.     }          
  16.     public void doGet(HttpServletRequest request, HttpServletResponse response)       
  17.             throws ServletException, IOException {       
  18.     }       
  19.     public void doPost(HttpServletRequest request, HttpServletResponse response)       
  20.             throws ServletException, IOException {             
  21.     }       
  22.     public void init() throws ServletException {       
  23.         //通过web.xml来动态取得配置文件       
  24.         String prefix = getServletContext().getRealPath("/");       
  25.         String file = getInitParameter("log4j");       
  26.       
  27.         //如果没有给出相应的配置文件,则不进行初始化       
  28.         if(file != null)        
  29.         {       
  30.             PropertyConfigurator.configure(prefix+file);       
  31.         }       
  32.     }       
  33.       
  34. }      

Web.xml 代码
Java代码
  1. <servlet>      
  2. <servlet-name>j2eelog4jdemoservlet-name>      
  3. <servlet-class>J2eeLog4jDemoservlet-class>      
  4. <init-param>      
  5. <param-name>log4jparam-name>      
  6. <param-value>log4j.propertiesparam-value>      
  7. init-param>      
  8. <load-on-startup>1load-on-startup>  //设为1时,Web容器启动即进行加载    
  9. servlet  


Spring配置Log4j
Spring中配置Log4j只要配置applicationContext.xml文件,Log4j的配置文件放在Web工程的根目录下,默认是objectname/root下,可以在web.xml中设置工程根目录.

设置根目录


web.xml 代码
Java代码
  1. <!--不定义webAppRootKey参数,webAppRootKey就是缺省的"webapp.root"-->      
  2.  <context-param>      
  3.   <param-name>webAppRootKeyparam-name>      
  4.   <param-value>webapp.rootparam-value>      
  5.  context-param>  


配置applicationContext.xml


applicationContext.xml 代码
Java代码
  1. <!--由Sprng载入的Log4j配置文件位置-->      
  2. <context-param>      
  3.  <param-name>log4jConfigLocationparam-name>      
  4.  <param-value>/WEB-INF/log4j.propertiesparam-value>      
  5.  <!--在这里定位配置文件,需要的是从root开始的绝对路径-->      
  6. context-param>      
  7.       
  8.       
  9.       
  10. <!--Spring默认刷新Log4j配置文件的间隔,单位为millisecond-->      
  11. <context-param>      
  12.  <param-name>log4jRefreshIntervalparam-name>      
  13.  <param-value>60000param-value>      
  14. context-param>      
  15.       
  16. <!--Spring log4j 监听器-->      
  17. <listener>      
  18.  <listener-class>org.springframework.web.util.Log4jConfigListenerlistener-class>      
  19. listener>     


同时使用commons-logging和Log4j


1)首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类
2)如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类
3)否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类
4)否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类)
5)否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog


将commons-logging和Log4j的jar包都放置到classpath下,同时也将Log4j的配置文件放到classpath中,两者就可以很好的合作,实现如下:
Java代码
  1. package com.doctorcom.model;       
  2.       
  3. import org.apache.commons.logging.Log;       
  4.       
  5. public class LogFactorySupport {           
  6.           
  7.     public Log getLog(){       
  8.         Log log = org.apache.commons.logging.LogFactory.getLog(LogFactorySupport.class);       
  9.         log.info("");       
  10.         log.debug("");       
  11.     }       
  12.            
  13. }   


java 代码

Log4j配置内容
看一个简单的java属性配置文件log4j.properties:

properties 代码
Java代码
  1. #指定根Logger,及日志输出级别,大于等于该级别的日志将被输出( DEBUG < INFO < WARN < ERROR < FATAL ) 设为OFF可以关闭日志       
  2. log4j.rootLogger=DEBUG, A1,A2       
  3. #指定log输出目的,这里设为输出日志到指定目录的文件my.log中       
  4. log4j.appender.A1=org.apache.log4j.FileAppender       
  5. log4j.appender.A1.File=\\logs\\my.log   #当前根目录下    
  6. #指定日志信息的格式       
  7. log4j.appender.A1.layout=org.apache.log4j.PatternLayout        
  8. log4j.appender.A1.layout.ConversionPattern=%r %d{yyyy-MM-dd HH:mm:ss} %c %p -%m%n       
  9.       
  10. #把A2输出到控制台       
  11. log4j.appender.A2=org.apache.log4j.ConsoleAppender       
  12. log4j.appender.A2.layout=org.apache.log4j.SimpleLayout        
  13.       
  14. #还可以单独指定输出某个包的日志级别       
  15. #log4j.logger.com.study.HelloLog4j=INFO   


1、配置根Logger,其语法为:
log4j.rootLogger = [ level ] , appenderName, appenderName2
level:日志的级别,指定这条日志信息的重要性。分为ALL < DEBUG < INFO < WARN <error fatal=""></error>一般常用的为 DEBUG , INFO ,WARN ,ERROR四种,分别对应Logger类的四种方法
debug(Object message ) ;
info(Object message ) ;
warn(Object message ) ;
error(Object message ) ;
如果设置级别为INFO,则优先级大于等于INFO级别(如:INFO、WARN、ERROR)的日志信息将可以被输出,小于该级别的如:DEBUG将不会被输出
appenderName :就是指定日志信息输出目的地,比如(打印到控制台,输出到文件等)。同一条日志信息可以配置多个输出目的地。

2、配置log输出目的地
Log4j提供以下几种:
org.apache.log4j.ConsoleAppender(控制台)
org.apache.log4j.FileAppender(文件)
org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
3、log信息的格式
org.apache.log4j.HTMLLayout(HTML表格形式)
org.apache.log4j.SimpleLayout(简单格式的日志,只包括日志信息的级别和指定的信息字符串 ,如:DEBUG - Hello)
org.apache.log4j.TTCCLayout(日志的格式包括日志产生的时间、线程、类别等等信息)
org.apache.log4j.PatternLayout(灵活地自定义日志格式)

当使用org.apache.log4j.PatternLayout来自定义信息格式时,可以使用
log4j.appender.A1.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p -%m%n 来格式化信息
%c    输出所属类的全名,可写为 %c{Num} ,Num类名输出的范围  如:"com.sun.aaa.classB", %C{2}将使日志输出输出范围为:aaa.classB
%d    输出日志时间其格式为 可指定格式 如 %d{HH:mm:ss}等
%l    输出日志事件发生位置,包括类目名、发生线程,在代码中的行数
%n    换行符
%m    输出代码指定信息,如info(“message”),输出message
%p    输出日志的优先级,即 FATAL ,ERROR 等
%r    输出从启动到显示该条日志信息所耗费的时间(毫秒数)
%t    输出产生该日志事件的线程名
posted @ 2009-05-04 14:37 lanxin1020 阅读(155) | 评论 (0)编辑 收藏

HashMap 与 TreeMap的区别

HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。

集合框架”提供两种常规的Map实现:HashMapTreeMap (TreeMap实现SortedMap接口)。在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()equals()的实现。  这个TreeMap没有调优选项,因为该树总处于平衡状态。

2、两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?hash code是什么意思

 hashcode是给一系列hash算法用的,比如hashtable。不同的对象应该有不同的hashcode,同一个对象应该有同样的hashcode

更正,不是同一个对象,而是相等的对象,应该有相同的hashcode

hash算法是什么啊,作用? hash算法基本就是为了将一个对象和一个整数对应起来,不同的对象对应不同的整数。
(x.equals(y) == true)那这个的话就是去比较它们所对应的整数?
不是。有一个equals()函数,和一个hashcode()函数

3、String a="abc";String b=new String("abc");String c="abc";

System.out.println(a==b);f
System.out.println(a==c);t
System.out.println(b==c);f
System.out.println(a.equals(b));
输出结果是什么?
为什么?

4、a=0;b=0;
if((a=3)>0|(b=3)>0){}
if((a=3)>0||(b=3)>0){}分别说出a,b的值

posted @ 2009-04-18 09:42 lanxin1020 阅读(206) | 评论 (0)编辑 收藏
     摘要: TREEMAP的排序机制   1package com.sf;  2  3import java.text.CollationKey;  4import java.text.Collator;  5import java.util.Comparator;  6import ...  阅读全文
posted @ 2009-04-18 09:36 lanxin1020 阅读(1083) | 评论 (0)编辑 收藏

内部类:定义在其他类里面的类。
使用内部类的理由:
1.内部类方法能够访问外部类的任何数据成员包括私有成员。
2.对同一个包的其他类,内部类是不可见的。
3.匿名内部类能够方便的定义回调而不用写太多方法。

非静态内部类没有默认的构造函数,非静态内部类的构造函数都有一个外围类对象的引用。
内部类的特殊语法规则:
1.相对内部类,引用其外部类隐式对象的形式:OuterClass.this
2.调用内部类的构造函数:outerObject.new InnerClass(construction parameters);
3.外部类外面引用内部类:OuterClass.InnerClass

内部类是一种编译器现象与虚拟机无关。编译器将内部类翻译为用$分隔外部类名和内部类名的常规类文件,虚拟机对此并无所知。
使用javap -private OuterClass$InnerClass。javap这个工具确实挺不错的,对分析字节码和源码都有很大的帮助。
可以看出详细的内部类源码清单,其中包括了编译器自动添加的部分:
public class Outer
{
 public class Inner
 {
 }
}
当内部类是非静态内部类时相应的内部类的详细源码如下:
Compiled from "Outer.java"
public class Outer$Inner extends java.lang.Object{
    final Outer this$0;  //编译器自动在内部类里面添加了指向外部类对象的引用
    public Outer$Inner(Outer);  //内部类的构造函数默认有一个外部类对象作为参数。
}

当内部类是静态内部类时:
Compiled from "Outer.java"
public class Outer$Inner extends java.lang.Object{
    public Outer$Inner(); //没有了对外部类对象的引用
}


如下代码模拟了上面内部类的情形唯一不同的是这里的Inner没有访问Outer私有数据的权限:
class Outer{
   Inner in = new Inner(this);
}

class Inner{
   public Inner(Outer outer){
      this.outer = outer;
   }
  
   private Outer outer;
}

 

//那么权限是如何控制的呢?当内部类中的方法访问到外部类的私有数据时(注意如果内部类没有方法去访问外部类的私有数据不会生成该静态方法static int access$000(Outer);)
public class Outer
{
 private int i;
 public void methodOne()
 {
 }

 class Inner
 {
  public void print(){
   System.out.println(Outer.this.i);
  }
 }
}

相应的外部类详细源码如下:
public class Outer
{
   public Outer();

   public void methodOne();
   static int access$000(Outer); //由编译器合成的用于内部类对外部类进行特殊访问权限的控制:这也是
                                //为什么内部类能够访问外部类中的私有数据的原因。
   private int i;
}

内部类访问外部类的private数据的这种方式很可能导致危险,虽然access$000不是一个合法的Java方法名,但是熟悉class文件结构的黑客可以使用十六进制编辑器轻松创建一个用虚拟机指令调用那个方法的类文件。由于隐秘的访问方法需要拥有包可见性,所以攻击代码需要与被攻击类放在同一个包中。总之,如果内部类访问了外部类的私有数据域,就有可能通过附加在外部类所在包中的其他类访问私有数据。

局部内部类:定义在方法之中,因此局部类没有public和private修饰符。对外界是完全隐藏的。
局部类不仅能够访问外部类,还能访问方法中的局部变量(或方法的参数)。不过那些局部变量要声明为final的。


匿名内部类:用外部类+$+数字的形式表示。一个外部类中有多个匿名内部类时,其生成的class文件对应有Outer$(1、2、3)的形式表示。注意到该匿名内部类是final的。
final class Outer$1
{
   Outer$1(Outer);

   public void method();

   final Outer this$0;
}

嵌套类:当内部类不需要访问外部类的数据成员时应该使用嵌套类。注意只有内部类可以声明为static的其他不行。
在接口中声明的内部类自动转换为public static的,在接口中定义的成员自动都是public static的。

内部类构造函数的可见性与其类的可见性相同。

 

posted @ 2009-04-16 14:06 lanxin1020 阅读(141) | 评论 (0)编辑 收藏
仅列出标题
共7页: 上一页 1 2 3 4 5 6 7 下一页