2000年3月28日星期二

ログ機能編

◆ログ機能とは
アプリケーションの動作状況やネットワークでの通信状況などの記録をとる機能のことです。J2SE
v1.4のログ機能を用いることによって、Javaアプリケーションでのセキュリティー、バグ、パフォーマンスなどの情報を出力することができます。
◆ここでの環境
OS Windows 2000
J2SE SDK 1.4.0
◆Java実行環境の構築
アプリケーション編を参考にしてJava実行環境を構築してください。
◆java.util.loggingパッケージ
ログ機能はjava.util.loggingパッケージに入っています。
Loggerクラス
アプリケーションがログを出力する時に使用するクラス
Handlerクラス
Loggerからログのメッセージを取得して出力するクラス
Handlerクラスにはログの出力先に応じて使い分ける以下のようなサブクラスが存在します
Handlerクラス

のサブクラス
説明
StreamHandler 出力ストリームにログを書き込む場合に使用
ConsoleHandler 標準エラー出力にログを書き込む場合に使用
FileHandler ファイルにログを書き込む場合に使用
SocketHandler ソケットにログを書き込む場合に使用
MemoryHandler メモリ上にログを書き込む場合に使用
◆ログの出力レベル
ログにはログの重要度を表すレベルがあります。SEVEREが最も高くFINESTが最も低くなります。ログの出力時にログのレベルを設定しておくとそのレベル以上のログのみ出力されるようになります。レベルはLevelクラスの定数で提供されています。下の定数以外に「OFF」と「ALL」があり、ログのレベルをOFFに設定するとログが出力されなくなり、ALLを設定するとすべてのログが出力されるようになります。
優先順位 出力レベル 説明
高 SEVERE 致命的なエラーが発生した場合
  WARNING 起こりうる問題が発生した場合
  INFO 重要な情報を出力する場合
  CONFIG 設定情報を出力する場合
  FINE デバック情報を出力する場合
  FINER より詳細なデバック情報を出力する場合
低 FINEST もっとも詳細なデバック情報を出力する場合
◆ログのフォーマット
ログのフォーマットはFormatterクラスで行います。HandlerクラスのsetFomatterメソッドでFormatterクラスを設定します。
用意されているFormatterクラス
Formatterクラス 説明
SimpleFormatter テキスト形式でフォーマットします
XMLFormatter XML形式でフォーマットします
なお、独自の形式で出力したい場合Formatterクラスを継承して独自のFormatterクラスを作成して使用するこもできます。
◆Javaソースコード
ここではhelloworldlog.txtというファイルにログを出力するプログラムを作りたいと思います。
HelloWorldLoggin.java(ここからダウンロード)
import java.util.logging.*;

public class HelloWorldLogging {
  public static void main(String argv[]) {
    try {
      // javahello.loggingという名前のLoggerを生成
      Logger logger = Logger.getLogger("javahello.logging");
      // FileHandlerを生成
      FileHandler fh = new FileHandler("helloworldlog.txt");
      // Formatterを設定します
      fh.setFormatter(new SimpleFormatter());
      // ログの出力先を追加
      logger.addHandler(fh);
      // ログの出力レベルを設定(ここではすべて出力するように設定)
      logger.setLevel(Level.ALL);
      // ログ出力
      logger.log(Level.INFO , "ログ機能でHello World");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
 
◆コンパイルと実行
コマンドプロンプト
C:\作業フォルダ\JavaHello>javac
HelloWorldLogging.java

C:\作業フォルダ\JavaHello>java
HelloWorldLogging

2002/02/01 11:21:22 HelloWorldLogging main

情報: ログ機能でHello World

C:\作業フォルダ\JavaHello>

おおおおお!メッセージが表示された!
ログファイルに出力されているか確認してみましょう。実行したフォルダに「javahellolog.txt」ファイルができているはずです。
javahellolog.txt
2002/02/01 11:21:22 HelloWorldLogging main
情報: ログ機能でHello World
おおおおお!ログが出力されていますね!
 
◆独自のフォーマット形式で出力
Formatterクラスを作ってフォーマット形式を指定してみます。
■Formatterクラスの作成
HelloWorldFormatter.java(ここからダウンロード)
import java.util.logging.*;
import java.util.*;
import java.text.*;

class HelloWorldFormatter extends Formatter {

  /**
   * 指定されたログレコードをフォーマットし、フォーマットされた文字列を返します
   */
  public String format(LogRecord rec) {
    StringBuffer buf = new StringBuffer();
    buf.append("レベル:");
    buf.append(rec.getLevel());
    buf.append("\n");
    buf.append("ログ:");
    buf.append(formatMessage(rec));
    buf.append("\n");
    return buf.toString();
  }
  /**
   * フォーマットされたレコードのセットのヘッダ文字列を返します
   */
  public String getHead(Handler h) {
    DateFormat df = DateFormat.getDateInstance(DateFormat.FULL, Locale.JAPAN);
    return "[" + df.format(new Date()) + "]\n";
  }
  /**
   * フォーマットされたレコードセットの末尾の文字列を返します
   */
  public String getTail(Handler h) {
    return "";
  }
}
 
■ログを出力するソース
上で作成したFormatterクラスを設定するようにログを出力するソースコードを編集
HelloWorldFormatLogging.java(ここからダウンロード)
import java.util.logging.*;

public class HelloWorldFormatLogging {
  public static void main(String argv[]) {
    try {
      // javahello.loggingという名前のLoggerを生成
      Logger logger = Logger.getLogger("javahello.logging");
      // FileHandlerを生成
      FileHandler fh = new FileHandler("helloworldlog.txt");
      // Formatterを設定します
      fh.setFormatter(new HelloWorldFormatter());
      // ログの出力先を追加
      logger.addHandler(fh);
      // ログの出力レベルを設定(ここではすべて出力するように設定)
      logger.setLevel(Level.ALL);
      // ログ出力
      logger.log(Level.INFO , "ログ機能でHello World");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
 
■コンパイルと実行
コマンドプロンプト
C:\作業フォルダ\JavaHello>javac
HelloWorldFormatLogging.java HelloWorldFormatter.java



C:\作業フォルダ\JavaHello>java HelloWorldFormatLogging

2002/11/22 20:39:39 HelloWorldFormatLogging main

情報: ログ機能でHello World

C:\作業フォルダ\JavaHello>

■ログの確認
ログが出力されているか確認してみましょう。
javahellolog.txt
[2002年11月22日 (金曜日)]
レベル:INFO
ログ:ログ機能でHello World
おおおお!フォーマットされて出力されていますね!
 
◆ログ出力の設定を外部ファイルで設定する
■logging.propertesファイル
ログ出力のデフォルトの設定は%JAVA_HOME%\jre\lib\logging.propertiesファイルで設定されている値が使用されます。このファイルを編集することでログ出力に関する設定を変更することができます。
logging.properties
############################################################

# Default Logging Configuration File

#

# You can use a different file by specifying a filename

# with the java.util.logging.config.file system property.


# For example java -Djava.util.logging.config.file=myfile

############################################################

############################################################

# Global properties

############################################################

# "handlers" specifies a comma separated
list of log Handler

# classes. These handlers will be installed during VM
startup.

# Note that these classes must be on the system classpath.

# By default we only configure a ConsoleHandler, which
will only

# show messages at the INFO and above levels.

handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line
instead.

#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.

# This specifies which kinds of events are logged across

# all loggers. For any given facility this global level

# can be overriden by a facility specific level

# Note that the ConsoleHandler also has a separate level

# setting to limit messages printed to the console.

.level= INFO

############################################################

# Handler specific properties.

# Describes specific configuration info for Handlers.

############################################################

# default file output is in user's home directory.

java.util.logging.FileHandler.pattern = %h/java%u.log

java.util.logging.FileHandler.limit = 50000

java.util.logging.FileHandler.count = 1

java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# Limit the message that are printed on the console
to INFO and above.

java.util.logging.ConsoleHandler.level = INFO

java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter



############################################################

# Facility specific properties.

# Provides extra control for each logger.

############################################################

# For example, set the com.xyz.foo logger to only log
SEVERE

# messages:

com.xyz.foo.level = SEVERE

 
■設定例
(例1) デフォルトだとConsoleHandlerのみ設定されているので、ファイルに出力するようにFileHandleも設定します。
#handlers= java.util.logging.ConsoleHandler

handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
 
(例2) ファイルハンドラのプロパティを設定できます。デフォルトでは以下のようになっています。
java.util.logging.FileHandler.pattern = %h/java%u.log

java.util.logging.FileHandler.limit = 50000

java.util.logging.FileHandler.count = 1

java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
<プロパティの説明>
キー 説明
java.util.logging.FileHandler.pattern 出力ファイルの名前付けのパターン


(実行時に置き換えられる文字列)
"/" ローカルパス名の区切り文字

"%t" システムの一時ディレクトリ
"%h" user.home
システムプロパティの値
"%g" ログのローテーションを識別する生成番号

"%u" 重複を解決する一意の番号
"%%" 単一のパーセント符号
% に変換
java.util.logging.FileHandler.limit 任意の 1 つのファイルに書き込む最大バイト数
java.util.logging.FileHandler.count 使用するファイル数
java.util.logging.FileHandler.formatter 使用するFormatter
 
(例3) javahello.loggingという名前のロガーのデフォルトの出力レベルをSEVEREに設定する場合以下の行をlogging.propertiesファイルに追加します。
javahello.logging.level = SEVERE
 

2000年3月27日星期一

JavaのIE7无法下载文件问题

※注意
Internet Explorer 7 を使用してファイルダウンロードを行った場合、正常にファイルのダウンロードが行われない不具合が報告されています。
以下の方法を行うことで、この問題を回避することができます。

回避策


Setp1 コントロールパネルを開く
[ スタートメニュー ] より、[ コントロールパネル ] をクリックします。


Step2 地域と言語のオプションを開く
[ 日付、時刻、地域と言語のオプション ] をクリックします。

次に、 [ 地域と言語のオプション ] をクリックします。


Step3 地域と言語のオプション [ 言語 ] タブ
  1. [ 言語 ] タブをクリックします。
  2. [ 詳細 ] ボタンをクリックします。

Step4 テキストサービスと入力言語 [ 詳細設定 ] タブ
  1. [ 詳細設定 ] タブをクリックします。
  2. [ 詳細なテキスト サービスをオフにする ] をクリックし、チェックマークを付けます。
  3. [ OK ] ボタンをクリックし、画面を閉じます。

Step5 開いているウィンドウを閉じる
  1. [ 地域と言語のオプション ] 画面は [ OK ] ボタンをクリックし、閉じます
  2. [ コントロールパネル ] 画面は [ × ] ボタンをクリックし、閉じます。

JSPでCSV帳票下载


Javaで帐票下载(Web)

説明:必要なライブラリ
①poi-2.5.1-final-20040804.jar
②poi-contrib-2.5.1-final-20040804.jar
③poi-scratchpad-2.5.1-final-20040804.jar





一:服务器端生成Excel文件后在客户端执行帐票下载操作

package excelDownLoad;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServlet;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.hssf.util.Region;

import veriserve.common.F30Excel;

public class ExcelDownLoadServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
private short bgColorNull = HSSFColor.WHITE.index;
private short bgColor = HSSFColor.GREY_25_PERCENT.index;

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
//生成帐票
outPutXls();

//下载帐票
downloadFile(request, response);
}

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
doPost(request,response);
}

//生成帐票
private void outPutXls(){
try {
F30Excel xlsInstance = F30Excel.getInstance();
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("人事");
sheet.setDefaultColumnWidth((short) (10.82));// デフォルトの列幅の設定

//セル幅
// sheet.setColumnWidth((short)0,(short)5120);//140ピクセル

HSSFRow row = sheet.createRow((short) 0);

//タイトル
sheet.addMergedRegion(new Region(0, (short) 0, 0, (short) 9));
xlsInstance.createCell(wb, row, (short) 0, "人事情報管理システム", bgColorNull, (short)19,false,"CENTER",false);

//日付
sheet.addMergedRegion(new Region(0, (short) 10, 0, (short) 11));
xlsInstance.createCell(wb, row, (short) 10, "帳票出力:2010/09/24 14:57", bgColorNull, (short)10,false,"RIGHT",false);

HSSFRow row2 = sheet.createRow((short) 2);

xlsInstance.createCell(wb, row2, (short) 0, "検索入社理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 1, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 2, "検索退職理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 3, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 4, "検索転籍理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 5, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 6, "検索出向理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 7, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 8, "検索異動理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 9, "", bgColorNull, (short)10,true,"",false);

HSSFRow row3 = sheet.createRow((short) 4);
sheet.addMergedRegion(new Region(4, (short) 0, 4, (short) 1));
xlsInstance.createCell(wb, row3, (short) 0, "人事情報照会一覧", bgColorNull, (short)10.82,false,"",true);

HSSFRow row4 = sheet.createRow((short) 5);
xlsInstance.createCell(wb, row4, (short) 0, "社員番号", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 1, "社員氏名", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 2, "入社日", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 3, "入社理由", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 4, "退社日", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 5, "退社理由", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 6, "転籍日", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 7, "転籍理由", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 8, "出向日", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 9, "出向理由", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 10, "異動日", bgColor, (short)10, true, "", false);
xlsInstance.createCell(wb, row4, (short) 11, "異動理由", bgColor, (short)10, true, "", false);

for (int i = 0; i < 10; i++) { 
HSSFRow rowT = sheet.createRow((short) 6 + i); 

xlsInstance.createCell(wb, rowT, (short) 0, "201001", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 1, "山田 直子", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 2, "2000/01/02", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 3, "中途入社", bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 4, "2000/01/02", bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 5, "転籍退社", bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 6, "2000/01/02", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 7, "転入", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 8, "2000/01/02", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 9, "出向受入解除",bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 10, "2000/01/02", bgColorNull, (short)10, true, "", false); 
xlsInstance.createCell(wb, rowT, (short) 11, "部門間異動", bgColorNull, (short)10, true, "", false); 
} 

String path = this.getServletContext().getRealPath("/")+"data/tyouhyou.xls"; 

FileOutputStream fileOut = new FileOutputStream(path); 

wb.write(fileOut); fileOut.close(); 
} catch (Exception ex) { 
ex.printStackTrace(); 
} 
} 

//下载帐票 
private void downloadFile(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { 
OutputStream os = res.getOutputStream(); 

try { 
String path = this.getServletContext().getRealPath("/") + "data/tyouhyou.xls"; 

FileInputStream hFile = new FileInputStream(path); 
BufferedInputStream bis = new BufferedInputStream(hFile); // レスポンス設定 
res.setContentType("application/octet-stream;charset=Windows-31J"); 
res.setHeader("Content-Disposition","attachment; filename=tyouhyou.xls"); 

int len = 0; 
byte[] buffer = new byte[1024]; 

while ((len = bis.read(buffer)) >= 0) {
os.write(buffer, 0, len);
}

bis.close();
} catch (IOException e) {
printOutNotFound(res);
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {

} finally {
os = null;
}
}
}
}

//帐票文件未找到时的处理
private void printOutNotFound(HttpServletResponse res) {
try {
OutputStream toClient = res.getOutputStream();
res.setContentType("text/html;charset=utf-8");
toClient.write("File not found".getBytes());
toClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}


二:服务器端不生成Excel文件而是将文件流直接在客户端下载

package excelDownLoad;

import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServlet;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.hssf.util.Region;

import veriserve.common.F30Excel;

public class ExcelDownLoadServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
private short bgColorNull = HSSFColor.WHITE.index;
private short bgColor = HSSFColor.GREY_25_PERCENT.index;

public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
ArrayList titleList= new ArrayList();

titleList.add("社員番号");
titleList.add("社員氏名");
titleList.add("入社日");
titleList.add("入社理由");
titleList.add("退社日");
titleList.add("退社理由");
titleList.add("転籍日");
titleList.add("転籍理由");
titleList.add("出向日");
titleList.add("出向理由");
titleList.add("異動日");
titleList.add("異動理由");

ArrayList dataList= new ArrayList();

for (int i = 0; i < 6; i++) {
ArrayList rowList= new ArrayList();

rowList.add("201001");
rowList.add("筒井 功");
rowList.add("2005/10/25");
rowList.add("転籍入社");
rowList.add("2009/09/31");
rowList.add("転籍退社");
rowList.add("2005/10/25");
rowList.add("転籍理由");
rowList.add("2005/10/25");
rowList.add("出向理由");
rowList.add("2005/10/25");
rowList.add("異動理由");

dataList.add(rowList);
}

//帐票下载
downloadFile(titleList,dataList,request,response);
}

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
doPost(request,response);
}

//帐票下载
private void downloadFile(ArrayList titleList, ArrayList dataList,HttpServletRequest request, HttpServletResponse response) {
try {
F30Excel xlsInstance = F30Excel.getInstance();
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("人事");
sheet.setDefaultColumnWidth((short) (10.82));// デフォルトの列幅の設定

//セル幅
// sheet.setColumnWidth((short)0,(short)5120);//140ピクセル
// sheet.setColumnWidth((short)1,(short)7680);//210ピクセル
// sheet.setColumnWidth((short)2,(short)7680);//210ピクセル

HSSFRow row = sheet.createRow((short) 0);

//タイトル
sheet.addMergedRegion(new Region(0, (short) 0, 0, (short) 9));
xlsInstance.createCell(wb, row, (short) 0, "人事情報管理システム", bgColorNull, (short)19,false,"CENTER",false);

//日付
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd HH:mm");
String dateStr = sdf1.format(new Date());

sheet.addMergedRegion(new Region(0, (short) 10, 0, (short) 11));
xlsInstance.createCell(wb, row, (short) 10, "帳票出力:" + dateStr, bgColorNull, (short)10,false,"RIGHT",false);

HSSFRow row2 = sheet.createRow((short) 2);

xlsInstance.createCell(wb, row2, (short) 0, "検索入社理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 1, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 2, "検索退職理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 3, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 4, "検索転籍理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 5, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 6, "検索出向理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 7, "", bgColorNull, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 8, "検索異動理由", bgColor, (short)10,true,"",false);
xlsInstance.createCell(wb, row2, (short) 9, "", bgColorNull, (short)10,true,"",false);

HSSFRow row3 = sheet.createRow((short) 4);
sheet.addMergedRegion(new Region(4, (short) 0, 4, (short) 1));
xlsInstance.createCell(wb, row3, (short) 0, "人事情報照会一覧", bgColorNull, (short)10.82,false,"",true);

//※情報一覧リストタイトルの行番号********************************
short mainRowIndex = 5;

HSSFRow row4 = sheet.createRow(mainRowIndex);

for(int i=0;i < titlelist.size(); i++){
xlsInstance.createCell(wb, row4, (short) i, (String)titleList.get(i), bgColor, (short)10, true, "", false);
}

for (int i = 0; i < dataList.size(); i++) {
HSSFRow rowT = sheet.createRow(mainRowIndex + 1 + i);

ArrayList rowList = (ArrayList)dataList.get(i);

xlsInstance.createCell(wb, rowT, (short) 0, (String)rowList.get(0), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 1, (String)rowList.get(1), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 2, (String)rowList.get(2), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 3, (String)rowList.get(3), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 4, (String)rowList.get(4), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 5, (String)rowList.get(5), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 6, (String)rowList.get(6), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 7, (String)rowList.get(7), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 8, (String)rowList.get(8), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 9, (String)rowList.get(9), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 10, (String)rowList.get(10), bgColorNull, (short)10, true, "", false);
xlsInstance.createCell(wb, rowT, (short) 11, (String)rowList.get(11), bgColorNull, (short)10, true, "", false);
}

// EXCELのコンテントタイプを設定
response.setContentType("application/msexcel");

// ファイル名に日本語を使うなら、下記のようにエンコードする
byte[] sjis = "帳票.xls".getBytes("Shift_JIS");
String fname = new String(sjis, "ISO8859_1");

// ファイル名を設定
response.setHeader("Content-Disposition", "attachment; filename=" + fname);

OutputStream out = response.getOutputStream(); 
wb.write(out);
out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}

Java生成文件流直接下载汇总

いよいよWEBアプリでExcelファイルをダウンロードします。ファイルをダウンロードするには、ExcelファイルをWEBサーバの適当なディレクトリに置いて、そのファイルにリンクを付ける方法があります。その方法では2つの問題があります。1つはExcelファイルがWEBサーバに残ることです。Excelファイルを定期的に削除する必要が生じます。2つ目はExcelファイルへのリンクURLを直接入力すると、アクセスしてはいけないユーザもアクセスできる可能性があります。ファイルへのアクセスをどうやって制限するか考えなければなりません。

OutputStreamに対して書き込みする方法があります。これだと、物理的にExcelファイルをWEBサーバにおく必要がありません。その方法をサンプルソースを示しながら説明したいと思います。

Servletのファイルダウンロードテクニック

最近はフレームワークを使うことが増えたので、Servletを書く機会が減っていると思います。しかし、Serveltでファイルダウンロードする手順は、ダウンロードの基本になります。まずはServletでのファイルダウンロード手順を見てみましょう。

ファイルダウンロードの手順は至ってシンプルです。

  1. Excelオブジェクトを作成する。
  2. OutputStreamにExcelオブジェクトを書き込む。
  3. OutputStreamをクローズする。
以上です。

以下のサンプルソースでは日本語を扱うちょっとした2つのコツも盛り込んでいます。1つ目は、Excelのセルに日本語をセットするときのコツです。2つ目は、ダウンロードファイルのファイル名に日本語を使うときのコツです。

/*
 * NewServlet.java
 *
 * Created on 2007/04/18, 22:06
 */

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

import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

/**
 *
 * @author kiyomiya
 * @version
 */
public class NewServlet extends HttpServlet {
    
    /** Processes requests for both HTTP GET 
     * and POST methods.
     * @param request servlet request
     * @param response servlet response
     */
    protected void processRequest(
            HttpServletRequest request, 
            HttpServletResponse response)
    throws ServletException, IOException {
        
        // 新規EXCELファイルを作成
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        HSSFRow row = sheet.createRow(0);
        HSSFCell cell = row.createCell((short)0);
        
        // 日本語を正しく表示するために文字コードを設定する
        cell.setEncoding(HSSFCell.ENCODING_UTF_16);
        cell.setCellValue("EXCELダウンロード");
        
        // EXCELのコンテントタイプを設定
        response.setContentType("application/msexcel");
        
        // ファイル名に日本語を使うなら、下記のようにエンコードする
        byte[] sjis = "サンプル.xls".getBytes("Shift_JIS");
        String fname = new String(sjis, "ISO8859_1");
        // 英数字なら単純に String fname = "sample.xml" で良い
        
        // ファイル名を設定
        response.setHeader("Content-Disposition", 
            "attachment; filename=" + fname);
        
        OutputStream out = response.getOutputStream();      
        workbook.write(out);
        out.close();
    }
    
    // 
    /** Handles the HTTP GET method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doGet(HttpServletRequest request, 
        HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
    
    /** Handles the HTTP POST method.
     * @param request servlet request
     * @param response servlet response
     */
    protected void doPost(HttpServletRequest request, 
        HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }
    
    /** Returns a short description of the servlet.
     */
    public String getServletInfo() {
        return "Short description";
    }
    // 
}

Strutsのファイルダウンロードテクニック

代表的なフレームワークであるStrutsでのサンプルも示します。この中でも日本語を扱うちょっとしたコツを含めました。シート名に日本語を使うときのコツです。それにしてもStrutsを使う開発案件が増えましたね...

import java.io.OutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
/*
 * NewAction.java
 *
 * Created on 2007/04/19, 22:05
 */

/**
 *
 * @author kiyomiya
 */
public class NewAction extends Action {
    
    public ActionForward execute(
            ActionMapping mapping,
            ActionForm form,
            HttpServletRequest request,
            HttpServletResponse response)
            throws Exception {
        
        // 新規EXCELファイルを作成
        HSSFWorkbook workbook = new HSSFWorkbook();
        HSSFSheet sheet = workbook.createSheet();
        
        // シート名に日本語をつけるには、一度シートを
        // create してから名前をセットする
        workbook.setSheetName(0, "日本語のシート名", 
                HSSFWorkbook.ENCODING_UTF_16);
        
        HSSFRow row = sheet.createRow(0);
        HSSFCell cell = row.createCell((short)0);
        cell.setCellValue(true);
        
        // EXCELのコンテントタイプを設定
        response.setContentType("application/msexcel");
        
        // ファイル名を設定
        response.setHeader("Content-Disposition", 
            "attachement; filename=sample.xls");
        
        OutputStream out = response.getOutputStream();
        workbook.write(out);
        out.close();

        return null;
    }
}

JSPでのファイルダウンロードテクニック

最後にJSPのサンプルを示します。以前にServletを一切使わない、JSPだけを使ったWEBアプリの開発案件に携わったことがあります。私の見る限りでは、Servletを使わない方式は決して珍しくないように思います。

<%@page pageEncoding="UTF-8"%>
<%@ page import="org.apache.poi.hssf.usermodel.*" %>
<%@ page import="java.io.OutputStream" %>
<%--
The taglib directive below imports the JSTL library. 
If you uncomment it,you must also add the JSTL library 
to the project. The Add Library... action on Libraries 
node in Projects view can be used to add the JSTL 1.1 library.
--%>
<%--
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> 
--%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

<% 

// 新規EXCELファイルを作成
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet();
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell((short)0);

cell.setCellValue("EXCEL sample");

// EXCELのコンテントタイプを設定
response.setContentType("application/msexcel");

// ファイル名を設定
response.setHeader("Content-Disposition", 
    "attachment; filename=jsp.xls");

OutputStream outs = response.getOutputStream();
workbook.write(outs);
outs.close();

%>

このサンプルは次の環境で動作確認しています。

  • Windows XP Professional SP2
  • Internet Explore 6.0 SP2
  • Excel 2002 SP3
  • JDK 1.6
  • NetBeans IDE 5.5 バンドル版 Tomcat 5.5.17

このサンプルはNetBeans IDE 5.5で作成しました。

2000年3月26日星期日

Java文字列置换

// [s]の中にある[s1]が全部[s2]に置き換え
public String replaceStr(String s, String s1, String s2) {
return Pattern.compile(s1).matcher(s).replaceAll(s2);
}

Java学习网站大全

http://www-900.ibm.com/developerWor...ava/index.shtml IBM的JAVA专题——永远的蓝色巨人
http://www.huihoo.com/ 灰狐动力——Enterprise Open Source
http://www.jdon.com/ J道——JAVA和J2EE解决之道
http://www.chinaunix.net/ ChinaUnix——我们在努力打造一个Unix时代!
http://www.theserverside.com/ TheServerSide.COM——Your Enterprise Java Community
http://www.onjava.com/ O'REILLY ON java.com
http://www.matrix.org.cn/ Matrix与Java共舞
http://www.java-source.net/ JavaSource
http://www.javaresearch.org/ Java研究组织——汇聚技术精英,增进技术交流
http://www.cn-java.com/ 中文Java技术网——为Java爱好者服务
http://www.javalobby.org/ JavaLobby
http://www.javajia.com/ Java家
http://www.cnjavaclub.com/ 中国JAVA俱乐部
http://www.jspcn.net/ JSP中文网
http://www.cnjsp.org/ 中国JSP技术网站
http://www.xuejava.com/ 学Java——学习Java的精髓
http://tech.ccidnet.com/ 赛迪网技术天地
http://www.uml.org.cn/ UML软件工程组织
http://dev2dev.bea.com.cn/index.jsp BEAdev2dev在线
http://www.kissjava.com/ 爪哇流氓
http://java.about.com/ 来自About.com的Java新闻和技术文章
http://www.jcp.org/en/home/index Java Community Process
http://www.java-cn.com/ Java中文站——每一天我们都在进步
http://www.chedong.com/ 车东
http://www.javayou.com/ Java自有人
http://www.javaref.cn/default.htm Java Developer

Java技术论坛
http://bbs.chinajavaworld.com/ ChinaJavaWorld技术论坛
http://www.javaworld.com.tw/jute/index.html JavaWorld技术论坛
http://forum.javaeye.com/ Java视线论坛
http://www.jdon.com/jive/index.jsp J道论坛
http://gceclub.sun.com.cn/NASApp/sme/jive/index.jsp Sun技术社区论坛
http://www.cjsdn.com/ 中国Java开发网
http://spring.jactiongroup.net/ SpringFramework中文论坛
http://www.cartexpert.com/forum/forum.jsp?column=24 Jive研究论坛
http://www.javafan.net/jive/index.jsp JavaFan论坛

Java优秀Blog
http://blog.csdn.net/casularm Casularm Blog
http://hedong.3322.org/ 竹笋炒肉
http://blog.csdn.net/baijsp Java and OpenSource Software
http://leosky.java.mblogger.cn/ leo的Blog
http://java2guru.java.mblogger.cn/ J2EE Blog
http://blog.csdn.net/chenyun2000 Open Java Project
http://gigix.blogdriver.com/gigix/index.html 透明思考
http://blog.tomxp.com/Home.html 东波的Blog
http://blog.csdn.net/liaoxingya276 liaoxingya的Blog
http://blog.csdn.net/arielxp !Java
http://www.nncn.com/blog/weblog.php HuiFei's Blog

Java热门QQ群
6927954   Java爱好者              
5747018   Java开源框架研究
2184090   Java最后通道          
5464040   Java技术交流
6090680   Java讨论组              
6868040   Java讨论区
2848217   Java技术交流          
5287881   Java社团
3057466   Java世界                  
3657130   Java夜未眠
4874250   Java开发者              
6522650   Java联盟
4972763   Java爱好者              
6880123   Java技术交流
2473372   Java学习班              
6347116   Java学院
3049837   Java人生路              
1407900   Java爱好者
2371661   Java最爱                  
7096701   Java思想与技术
4421567   Java菜鸟交流室
3493424   Java学习班
1822386   Java学习小组
3905714   Java乐园
4085683   Java疯狂编程
5718771   Java社区
4578000   Jsp技术群
4384534   Jsp学习组
4617990   Jsp学习班
1845656   Jsp爱好者
2493314   Jsp交流中心
3241217   Jsp专题学习
6788666   Jsp爱好者
3417234   Jsp交流
7378475   Jsp开发联盟
2347397   Java和Jsp技术讨论
1020937   Jsp 1693379 Jsp
200016     J2ME 4453572 J2ME
6074752   J2ME 4835602 J2ME
1772284   J2ME 155348 开发联盟 WAP J2ME

Hashtable和HashMap的区别

Hashtable和HashMap的区别:
1.Hashtable是Dictionary的子类,HashMap是Map接口的一个实现类;

2.Hashtable中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。即是说,在多线程应用程序中,不用专门的操作就安全地可以使用Hashtable了;而对于HashMap,则需要额外的同步机制。但HashMap的同步问题可通过Collections的一个静态方法得到解决:
Map Collections.synchronizedMap(Map m)
这个方法返回一个同步的Map,这个Map封装了底层的HashMap的所有方法,使得底层的HashMap即使是在多线程的环境中也是安全的。

3.在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,即可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。

Vector、ArrayList、List剖析

线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。

Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap

Collection接口
  Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
  所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
  如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
    Iterator it = collection.iterator(); // 获得一个迭代子
    while(it.hasNext()) {
      Object obj = it.next(); // 得到下一个元素
    }
  由Collection接口派生的两个接口是List和Set。

List接口
  List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
  除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
  实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。

LinkedList类
  LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
  注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
    List list = Collections.synchronizedList(new LinkedList(...));

ArrayList类
  ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
  每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
  和LinkedList一样,ArrayList也是非同步的(unsynchronized)。

Vector类
  Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。

Stack 类
  Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。

Set接口
  Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
  很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
  请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。

Map接口
  请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。

Hashtable类
  Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
  添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
    Hashtable numbers = new Hashtable();
    numbers.put(“one”, new Integer(1));
    numbers.put(“two”, new Integer(2));
    numbers.put(“three”, new Integer(3));
  要取出一个数,比如2,用相应的key:
    Integer n = (Integer)numbers.get(“two”);
    System.out.println(“two = ” + n);
  由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
  如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
  Hashtable是同步的。

HashMap类
  HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。

WeakHashMap类
  WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。

总结
  如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
  如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
  要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
  尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。

同步性
Vector是同步的。这个类中的一些方法保证了Vector中的对象是线程安全的。而ArrayList则是异步的,因此ArrayList中的对象并不是线程安全的。因为同步的要求会影响执行的效率,所以如果你不需要线程安全的集合那么使用ArrayList是一个很好的选择,这样可以避免由于同步带来的不必要的性能开销。
数据增长
从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
使用模式
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。这一切意味着什么呢?
这意味着,你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的?O(1),但它在索引一个元素的使用缺比较慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因为你可以简单的使用索引来代替创建iterator对象的操作。LinkList也会为每个插入的元素创建对象,所有你要明白它也会带来额外的开销。
最后,在《Practical Java》一书中Peter Haggar建议使用一个简单的数组(Array)来代替Vector或ArrayList。尤其是对于执行效率要求高的程序更应如此。因为使用数组(Array)避免了同步、额外的方法调用和不必要的重新分配空间的操作

浅析Object类

说到toString()方法,就不得不先看看Object类.
Object 类是一个超级类,是一切类的父类;看看sun的JDK就知道,其实他的方法并不多,但都非常实用.
我始终对Object有一丝神秘感,昨天朋友问起Clone()仍有很多困惑;
所以今天决定,来揭开Object的面纱.下面就是我通过翻阅资料和阅读JDK后,对Object的一些理解,如有错误请指出:
package java.lang;
public class Object { }
在JDK1.4中它有11个方法,主要的方法有 clone(), equals(), getClass(), notfiy(), toString(), wait() .......
我在这里只重点说说clone(), equals(), toString()
1.clone()
所有具有clone功能的类都有一个特性,那就是它直接或间接地实现了Cloneable接口。否则,我们在尝试调用clone()方法时,将会触发CloneNotSupportedException异常。
protected native Object clone() throws CloneNotSupportedException;(在JDK中)
可以看出它是一个protected方法,所以我们不能简单地调用它,
native : 关键字native,表明这个方法使用java以外的语言实现。所修辞的方法不包括实现. (参考:http://blog.csdn.net/mingjava/archive/2004/11/14/180946.aspx)
在这里我就不具体讲clone() 是如何实现Cloneable接口,如何覆盖; 这说说clone的作用和用法;
对于 object x,
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
x.clone().getClass() == x.getClass()
以上返回的值都为true
2 equals()
其原型为:
public boolean equals(Object obj) {
return (this == obj);
}
Object 中定义的 equals(Object) 是比较的引用,与 "==" 作用相同。
3. toString()其原型为:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString() 方法是最为常见的,在很多类中都对他进行覆盖:
先看两个例子
例一:
class Example{
private String s = "abc";
public Example(String str) {
this.s = str + this.s ;
}
public static void main(String[] args){
Example ex = new Example("123");
System.out.println(ex);
System.out.println(ex.s);
}
public String toString() {
this.s = "cba" +this.s;
return s;
}
输出结果:
cba123abc
cba123abc
例二.
class Example{
private String s = "abc";
public Example(String str) {
this.s = str + this.s ;
}
public static void main(String[] args){
Example ex = new Example("123");
System.out.println(ex);
System.out.println(ex.s);
}
public String toString2() {
this.s = "cba" + this.s;
return s;
}
输出结果:
Example@1858610
123abc
例一:和例二;只修改了tostring的方法名,但执行却有很大的不同 ;
例一中: tostring()被覆盖,在执行new Example("123");的时候,先调用构造方法(暂不执行)再加载执行父类中的方法tostring()(这里被覆盖,则运行本类中的方法代码),最后执行构造方法;这里涉及到执行的顺序,不着讨论; 然后在执行到 System.out.println(ex); 时,实际是打印方法tostring()返回的string值;
在例二中:tostring()并没有被覆盖,toString2() 是类中普通的方法, 所以new Example("123");时,不会加载执行toString2() ,所以s值为"123abc";而System.out.println(ex);打印出来的结果,是原型中看到的 类名 + @ + 十六进制数字表示的该对象的散列码
方法tostring()使用还很多,我们在编写类的时候,都最好能有自己的tostring,把原型覆盖掉;

用Java实现语音引擎

  为应用程序加上语音能力有什么好处呢?粗略地讲,是为了趣味,它适合所有注重趣味的
应用,比如游戏。当然,从更严肃的角度来讲,它还涉及到应用的可用性问题。注意,这
里我考虑的不仅是可视化界面固有的不足,而且还有这样一些情形:一些时候,让双眼离
开当前的工作很不方便,甚至是不合法的。比如,假设有一个带语音功能的浏览器,你就
可以在外出散步或开车上班的同时,用听的方式浏览自己喜爱的网站。

  从目前来看,邮件阅读器或许是语音技术更实际的应用,在JavaMail API的帮助下,
这一切已经可能。邮件阅读器可以定期地检查收件箱,然后用语音“You have new mail,
would you like me to read it to you?”引起你的注意。按照类似的思路,我们还可以
考虑一个带语音功能的提醒器,把它连接到一个日历应用:它会及时地提醒你“Don't
forget your meeting with the boss in 10 minutes!”。

  也许你已经被这些主意吸引,或者有了自己更好的主意,现在让我们继续。首先我将
介绍如何启用本文提供的语音引擎,这样,如果你认为语音引擎的实现细节过于复杂,就
可以直接使用它而忽略其实现细节。
  一、试用语音引擎

要使用这个语音引擎,你必须在CLASSPATH中加入本文提供的javatalk.jar文件,然后从
命令行运行(或者从Java程序调用)com.lotontech.speech.Talker类。如果从命令行运
行,则命令为:

java com.lotontech.speech.Talker "h|e|l|oo"

如果从Java程序调用,则代码为:

com.lotontech.speech.Talker talker=new com.lotontech.speech.Talker();

talker.sayPhoneWord("h|e|l|oo");

现在,对于在命令行上(或者调用sayPhoneWord()方法时)提供的“h|e|l|oo”字符串,你
或许有所不解。下面我就来解释一下。

语音引擎的工作原理是把细小的声音样本连接起来,每一个样本都是人的语言发音(英
语)的一个最小单位。这些声音样本称为音素(allophone)。每一个因素对应一个、二
个或者三个字母。从前面“hello”的语音表示可以看出,一些字母组合的发音显而易见,
还有一些却不是很明显:

h -- 读音显而易见
e -- 读音显而易见
l -- 读音显而易见,但注意两个“l”被简缩成了一个“l”。
OO -- 应该读作“hello”中的读音,不应读作“bot”、“too”中的读音。

下面是一个有效音素的清单:
a : 如cat
b : 如cab
c : 如cat
d : 如dot
e : 如bet
f : 如frog
g : 如frog
h : 如hog
i : 如pig
j : 如jig
k : 如keg
l : 如leg
m : 如met
n : 如begin
o : 如not
p : 如pot
r : 如rot
s : 如sat
t : 如sat
u : 如put
v : 如have
w : 如wet
y : 如yet
z : 如zoo
aa : 如fake
ay : 如hay
ee : 如bee
ii : 如high
oo : 如go
bb : b的变化形式,重音不同
dd : d的变化形式,重音不同
ggg : g的变化形式,重音不同
hh : h的变化形式,重音不同
ll : l的变化形式,重音不同
nn : n的变化形式,重音不同
rr : r的变化形式,重音不同
tt : t的变化形式,重音不同
yy : y的变化形式,重音不同
ar : 如car
aer : 如care
ch : 如which
ck : 如check
ear : 如beer
er : 如later
err : 如later (长音)
ng : 如feeding
or : 如law
ou : 如zoo
ouu : 如zoo (长音)
ow : 如cow
oy : 如boy
sh : 如shut
th : 如thing
dth : 如this
uh : u 的变化形式
wh : 如where
zh : 如Asian

人说话的时候,语音在整个句子之内起落变化。语调变化使得语音更自然、更富有感染
力,使得问句和陈述句能够相互区别。请考虑下面两个句子:

It is fake -- f|aa|k

Is it fake? -- f|AA|k

也许你已经猜想到,提高语调的方法是使用大写字母。

以上就是使用该软件时你需要了解的东西。如果你对其后台实现细节感兴趣,请继续阅读。
  二、实现语音引擎

语音引擎的实现只包括一个类,四个方法。它利用了J2SE 1.3包含的Java Sound API。在
这里,我不准备全面地介绍这个API,但你可以通过实例学习它的用法。Java Sound API
并不是一个特别复杂的API,代码中的注释将告诉你必须了解的知识。

下面是Talker类的基本定义:

package com.lotontech.speech;

import javax.sound.sampled.*;

import java.io.*;

import java.util.*;

import java.net.*;

public class Talker
{
private SourceDataLine line=null;
}

如果从命令行执行Talker,下面的main()方法将作为入口点运行。main()方法获取第一个
命令行参数,然后把它传递给sayPhoneWord()方法:

/*
* 读出在命令行中指定的表示读音的字符串
*/
public static void main(String args[])
{
Talker player=new Talker();

if (args.length>0) player.sayPhoneWord(args[0]);
System.exit(0);
}

sayPhoneWord()方法既可以通过上面的main()方法调用,也可以在Java程序中直接调用。
从表面上看, sayPhoneWord()方法比较复杂,其实并非如此。实际上,它简单地遍历所
有单词的语音元素(在输入字符串中语音元素以“|”分隔),通过一个声音输出通道一个
元素一个元素地播放出来。为了让声音更自然一些,我把每一个声音样本的结尾和下一个
声音样本的开头合并了起来:

/*
* 读出指定的语音字符串
*/
public void sayPhoneWord(String word)
{
// 为上一个声音构造的模拟byte数组
byte[] previousSound=null;

// 把输入字符串分割成单独的音素
StringTokenizer st=new StringTokenizer(word,"|",false);

while (st.hasMoreTokens())
{
// 为音素构造相应的文件名字
String thisPhoneFile=st.nextToken();
thisPhoneFile="/allophones/"+thisPhoneFile+".au";

// 从声音文件读取数据
byte[] thisSound=getSound(thisPhoneFile);

if (previousSound!=null)
{
// 如果可能的话,把前一个音素和当前音素合并
int mergeCount=0;

if (previousSound.length>=500 && thisSound.length>=500)
mergeCount=500;

for (int i=0; i
{
previousSound[previousSound.length-mergeCount+i]
=(byte)((previousSound[previousSound.length
-mergeCount+i]+thisSound[i])/2);
}

// 播放前一个音素
playSound(previousSound);

// 把经过截短的当前音素作为前一个音素
byte[] newSound=new byte[thisSound.length-mergeCount];

for (int ii=0; ii
newSound[ii]=thisSound[ii+mergeCount];
previousSound=newSound;
}
else
previousSound=thisSound;
}

// 播放最后一个音素,清理声音通道
playSound(previousSound);
drain();
}

在sayPhoneWord()的后面,你可以看到它调用playSound()输出单个声音样本(即一个音
素),然后调用drain()清理声音通道。下面是playSound()的代码:

/*
* 该方法播放一个声音样本
*/
private void playSound(byte[] data)
{
if (data.length>0) line.write(data, 0, data.length);
}

下面是drain()的代码:
/*
* 该方法清理声音通道
*/
private void drain()
{
if (line!=null) line.drain();
try {Thread.sleep(100);} catch (Exception e) {}
}

现在回过头来看sayPhoneWord(),这里还有一个方法我们没有分析,即getSound()方法。

getSound()方法从一个au文件以字节数据的形式读入预先录制的声音样本。要了解读取数
据、转换音频格式、初始化声音输出行(SouceDataLine)以及构造字节数据的详细过
程,请参考下面代码中的注释:

/*
* 该方法从文件读取一个音素,
* 并把它转换成byte数组
*/
private byte[] getSound(String fileName)
{
try
{
URL url=Talker.class.getResource(fileName);
AudioInputStream stream = AudioSystem.getAudioInputStream(url);
AudioFormat format = stream.getFormat();

// 把一个ALAW/ULAW声音转换成PCM以便回放
if ((format.getEncoding() == AudioFormat.Encoding.ULAW) ||
(format.getEncoding() == AudioFormat.Encoding.ALAW))
{
AudioFormat tmpFormat = new AudioFormat(
AudioFormat.Encoding.PCM_SIGNED,
format.getSampleRate(), format.getSampleSizeInBits() * 2,
format.getChannels(), format.getFrameSize() * 2,
format.getFrameRate(), true);
stream = AudioSystem.getAudioInputStream(tmpFormat, stream);
format = tmpFormat;
}

DataLine.Info info = new DataLine.Info(
Clip.class, format,
((int) stream.getFrameLength() * format.getFrameSize()));
if (line==null)
{
// 输出线还没有实例化
// 是否能够找到合适的输出线类型?
DataLine.Info outInfo = new DataLine.Info(SourceDataLine.class,format);

if (!AudioSystem.isLineSupported(outInfo))
{
System.out.println("不支持匹配" + outInfo + "的输出线");
throw new Exception("不支持匹配" + outInfo + "的输出线");
}

// 打开输出线
line = (SourceDataLine) AudioSystem.getLine(outInfo);
line.open(format, 50000);
line.start();
}

int frameSizeInBytes = format.getFrameSize();
int bufferLengthInFrames = line.getBufferSize() / 8;
int bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
byte[] data=new byte[bufferLengthInBytes];

// 读取字节数据,并计数

int numBytesRead = 0;
if ((numBytesRead = stream.read(data)) != -1)
{
int numBytesRmaining = numBytesRead;
}

// 把字节数据切割成合适的大小
byte[] newData=new byte[numBytesRead];

for (int i=0; i
newData[i]=data[i];

return newData;
}
catch (Exception e)
{
return new byte[0];
}
}

这就是全部的代码,包括注释在内,一个大约150行代码的语音合成器。
  三、文本-语音转换

以语音元素的格式指定待朗读的单词似乎过于复杂,如果要构造一个能够朗读文本(比如
Web页面或Email)的应用,我们希望能够直接指定原始的文本。

深入分析这个问题之后,我在本文后面的ZIP文件中提供了一个试验性的文本-语音转换
类。运行这个类,它将显示出分析结果。文本-语音转换类可以从命令行执行,如下所示:

java com.lotontech.speech.Converter "hello there"

输出结果类如:
hello -> h|e|l|oo
there -> dth|aer

如果运行下面这个命令:
java com.lotontech.speech.Converter "I like to read JavaWorld"

则输出结果为:
i -> ii
like -> l|ii|k
to -> t|ouu
read -> r|ee|a|d
java -> j|a|v|a
world -> w|err|l|d

这个转换类是如何工作的呢?实际上,我的方法相当简单,转换过程就是以一定的次序应
用一组文本替换规则。例如对于单词“ant”、“want”、“wanted”、“unwanted”和
“unique”,则我们想要应用的替换规则可能依次为:

用“|y|ou|n|ee|k|”替换“*unique*”
用“|w|o|n|t|”替换“*want*”
用“|a|”替换“*a*”
用“|e|”替换“*e*”
用“|d|”替换“*d*”
用“|n|”替换“*n*”
用“|u|”替换“*u*”
用“|t|”替换“*t*”

对于“unwanted”,输出序列为:

unwanted

un[|w|o|n|t|]ed (规则2)

[|u|][|n|][|w|o|n|t|][|e|][|d|] (规则4、5、6、7)

u|n|w|o|n|t|e|d (删除多余的符之后)

你将看到包含字母“wont”的单词和包含字母“ant”的单词以不同的方式发音,还将看到在
特例规则的作用下,“unique”作为一个完整单词优先于其他规则,从而“unique”这个单词
读作“y|ou...”而不是“u|n...”。

  结束语:本文提供了一个可以随时运行的、方便的语音引擎,你可以在自己的Java
1.3应用中使用它。如果仔细分析一下代码,它还为你提供了一个用JavaSound API播放音
频片断的实用教程。要让它变得真正有用,你应该考虑一下文本-语音转换技术,因为对
于我前面提到的文本阅读应用来说,这是真正的支撑基础。要改善本文方案的效果,你必
须构造出一个庞大的替换规则库,精心调整应用规则的优先次序。希望你比我更有毅力!

Java邮件发送程序

Java的网络功能非常强大,开发和使用也非常简单,难怪microsoft极力要争回程序语言的霸主地位。笔者根据smtp协议使用java Socket写了一个发送邮件的程序,将此与各位分享。
实现的原理非常简单,首先建立和邮件服务器的Socket连接,然后进行和服务器握手,然后发送smtp指令,并封装邮件体,然后发送即可。大家不妨一试。由什么问题可以和我联系:

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

public class SMTPSender{

Socket socket=null;
PrintWriter outData=null;
BufferedReader inData=null;

String smtpServer="";

String user="";
String pass="";
String from="";

String LINEFEED="\r\n";
boolean isNeedAuthLogin=false;
Vector to=new Vector();

public static void main(String[] args){
SMTPSender smtp=new SMTPSender();
smtp.setMailServer("mail.ehawa.com");
smtp.setMailFrom("root@ehawa.com","???","???");
smtp.addMailTo("root@ehawa.com");
if(smtp.send("hello","这是一个测试!")){
System.out.println("邮件发送成功!");
}else System.out.println("邮件发送失败!");
}
public void setMailServer(String s){
smtpServer=s;
}
public void setMailFrom(String s,String uid,String pwd){
this.from=s;
this.user=uid;
this.pass=pwd;
this.isNeedAuthLogin=(this.user!=null&&this.pass!=null&&!this.user.equals("")&&!this.pass.equals(""));
}
public boolean addMailTo(String mailAddr){
to.addElement(mailAddr);
return true;
}
public boolean send(String subject,String content){
try{
if(smtpServer==null||smtpServer.equals(""))return false;
if(from==null||from.equals(""))return false;
if(to.size()<1)return false; socket=new Socket(smtpServer,25); outData=new PrintWriter(socket.getOutputStream()); inData=new BufferedReader(new InputStreamReader(socket.getInputStream())); //与邮件服务器连接成功 readResponse("220"); //HELO host sendRequest("HELO "+smtpServer+LINEFEED); readResponse("250"); if(isNeedAuthLogin){ //AUTH LOGIN sendRequest("AUTH LOGIN"+LINEFEED); readResponse("334"); //USERNAME: sendRequest(new String(Base64.encodeString(user))+LINEFEED); readResponse("334"); //PASSWORD: sendRequest(new String(Base64.encodeString(pass))+LINEFEED); readResponse("235"); } //MAIL FROM:<..>
sendRequest("MAIL FROM:<"+from+">"+LINEFEED);
readResponse("250");
//RCPT T<..>
for(Enumeration enu=to.elements();enu.hasMoreElements();){
String to1=(String)enu.nextElement();
sendRequest("RCPT T<"+to1+">"+LINEFEED);
readResponse("250");
}
//DATA
sendRequest("DATA"+LINEFEED);
readResponse("354");
//邮件内容
StringBuffer s1=new StringBuffer("From: <"+from+">"+LINEFEED);
s1.append("T <"+to+">"+LINEFEED);
s1.append("Subject: "+subject+LINEFEED);
s1.append("Date: "+new java.util.Date().toLocaleString()+LINEFEED);
s1.append("Content-Type: text/plain;charset=\"GB2312\""+LINEFEED);
s1.append(LINEFEED);
s1.append(content);
s1.append(LINEFEED+"."+LINEFEED);//发送
sendRequest(s1.toString());
readResponse("250");
//QUIT退出
sendRequest("QUIT"+LINEFEED);
readResponse("221");
try{
inData.close();
inData=null;
}catch(Exception ex){}
try{
outData.close();
outData=null;
}catch(Exception ex){}
try{
socket.close();
socket=null;
}catch(Exception ex){}
}catch(Exception e){
return false;
//e.printStackTrace();
}
return true;
}
private void readResponse(String cmd)throws Exception{
String tmp=inData.readLine();
if(tmp.startsWith(cmd));//System.out.println(" [S:]"+tmp);
else throw new Exception("##########邮件发送失败!##########"+tmp);
while(tmp.startsWith(cmd+"-"))tmp=inData.readLine();
}
private void sendRequest(String msg){
//System.out.print("***[C:]"+msg);
outData.write(msg);
outData.flush();
}
public void close(){
try{
inData.close();
inData=null;
}catch(Exception ex){}
try{
outData.close();
outData=null;
}catch(Exception ex){}
try{
socket.close();
socket=null;
}catch(Exception ex){}
}
}

JavaMail API详解

一、JavaMail API简介
JavaMail API是读取、撰写、发送电子信息的可选包。我们可用它来建立如Eudora、Foxmail、MS Outlook Express一般的邮件用户代理程序(Mail User Agent,简称MUA)。而不是像sendmail或者其它的邮件传输代理(Mail Transfer Agent,简称MTA)程序那样可以传送、递送、转发邮件。从另外一个角度来看,我们这些电子邮件用户日常用MUA程序来读写邮件,而MUA依赖着MTA处理邮件的递送。
在清楚了到MUA与MTA之间的关系后,让我们看看JavaMail API是如何提供信息访问功能的吧!JavaMail API被设计用于以不依赖协议的方式去发送和接收电子信息,这个API被分为两大部分:

基本功能:如何以不依赖于协议的方式发送接收电子信息,这也是本文所要描述的,不过在下文中,大家将看到这只是一厢情愿而已。
第二个部分则是依赖特定协议的,比如SMTP、POP、IMAP、NNTP协议。在这部分的JavaMail API是为了和服务器通讯,并不在本文的内容中。

二、相关协议一览
在我们步入JavaMail API之前,先看一下API所涉及的协议。以下便是大家日常所知、所乐于使用的4大信息传输协议:
SMTP
POP
IMAP
MIME
当然,上面的4个协议,并不是全部,还有NNTP和其它一些协议可用于传输信息,但是由于不常用到,所以本文便不提及了。理解这4个基本的协议有助于我们更好的使用JavaMail API。然而JavaMail API是被设计为与协议无关的,目前我们并不能克服这些协议的束缚。确切的说,如果我们使用的功能并不被我们选择的协议支持,那么JavaMail API并不可能如魔术师一样神奇的赋予我们这种能力。

1.SMTP
简单邮件传输协议定义了递送邮件的机制。在下文中,我们将使用基于Java-Mail的程序与公司或者ISP的SMTP服务器进行通讯。这个SMTP服务器将邮件转发到接收者的SMTP服务器,直至最后被接收者通过POP或者IMAP协议获取。这并不需要SMTP服务器使用支持授权的邮件转发,但是却的确要注意SMTP服务器的正确设置(SMTP服务器的设置与JavaMail API无关)。

2.POP
POP是一种邮局协议,目前为第3个版本,即众所周知的POP3。POP定义了一种用户如何获得邮件的机制。它规定了每个用户使用一个单独的邮箱。大多数人在使用POP时所熟悉的功能并非都被支持,例如查看邮箱中的新邮件数量。而这个功能是微软的Outlook内建的,那么就说明微软Outlook之类的邮件客户端软件是通过查询最近收到的邮件来计算新邮件的数量来实现前面所说的功能。因此在我们使用JavaMail API时需要注意,当需要获得如前面所讲的新邮件数量之类的信息时,我们不得不自己进行计算。

3.IMAP
IMAP使用在接收信息的高级协议,目前版本为第4版,所以也被称为IMAP4。需要注意的是在使用IMAP时,邮件服务器必须支持该协议。从这个方面讲,我们并不能完全使用IMAP来替代POP,不能期待IMAP在任何地方都被支持。假如邮件服务器支持IMAP,那么我们的邮件程序将能够具有以下被IMAP所支持的特性:每个用户在服务器上可具有多个目录,这些目录能在多个用户之间共享。
其与POP相比高级之处显而易见,但是在尝试采取IMAP时,我们认识到它并不是十分完美的:由于IMAP需要从其它服务器上接收新信息,将这些信息递送给用户,维护每个用户的多个目录,这都为邮件服务器带来了高负载。并且IMAP与POP的一个不同之处是POP用户在接收邮件时将从邮件服务器上下载邮件,而IMAP允许用户直接访问邮件目录,所以在邮件服务器进行备份作业时,由于每个长期使用此邮件系统的用户所用的邮件目录会占有很大的空间,这将直接导致邮件服务器上磁盘空间暴涨。

4.MIME
MIME并不是用于传送邮件的协议,它作为多用途邮件的扩展定义了邮件内容的格式:信息格式、附件格式等等。一些RFC标准都涉及了MIME:RFC 822, RFC 2045, RFC 2046, RFC 2047,有兴趣的Matrixer可以阅读一下。而作为JavaMail API的开发者,我们并不需关心这些格式定义,但是这些格式被用在了程序中。

5.NNTP和其它的第三方协议
正因为JavaMail API在设计时考虑到与第三方协议实现提供商之间的分离,故我们可以很容易的添加一些第三方协议。SUN维护着一个第三方协议实现提供商的列表:http://java.sun.com/products/javamail/Third_Party.html,通过此列表我们可以找到所需要的而又不被SUN提供支持的第三方协议:比如NNTP这个新闻组协议和S/MIME这个安全的MIME协议。

三、安装
1.安装JavaMail
为了使用JavaMail API,需要从http://java.sun.com/products/javamail/downloads/index.html下载文件名格式为javamail-[version].zip的文件(这个文件中包括了JavaMail实现),并将其中的mail.jar文件添加到CLASSPATH中。这个实现提供了对SMTP、IMAP4、POP3的支持。
注意:在安装JavaMail实现之后,我们将在demo目录中发现许多有趣的简单实例程序。
在安装了JavaMail之后,我们还需要安装JavaBeans Activation Framework,因为这个框架是JavaMail API所需要的。如果我们使用J2EE的话,那么我们并无需单独下载JavaMail,因为它存在于J2EE.jar中,只需将J2EE.jar加入到CLASSPATH即可。

2.安装JavaBeans Activation Framework
从http://java.sun.com/products/javabeans/glasgow/jaf.html下载JavaBeans Activation Framework,并将其添加到CLASSPATH中。此框架增加了对任何数据块的分类、以及对它们的处理的特性。这些特性是JavaMail API需要的。虽然听起来这些特性非常模糊,但是它对于我们的JavaMail API来说只是提供了基本的MIME类型支持。
到此为止,我们应当把mail.jar和activation.jar都添加到了CLASSPATH中。
当然如果从方便的角度讲,直接把这两个Jar文件复制到JRE目录的lib/ext目录中也可以。

四、初次认识JavaMail API
1.了解我们的JavaMail环境
A.纵览JavaMail核心类结构
打开JavaMail.jar文件,我们将发现在javax.mail的包下面存在着一些核心类:Session、Message、Address、Authenticator、Transport、Store、Folder。而且在javax.mail.internet包中还有一些常用的子类。
B.Session
Session类定义了基本的邮件会话。就像Http会话那样,我们进行收发邮件的工作都是基于这个会话的。Session对象利用了java.util.Properties对象获得了邮件服务器、用户名、密码信息和整个应用程序都要使用到的共享信息。
Session类的构造方法是私有的,所以我们可以使用Session类提供的getDefaultInstance()这个静态工厂方法获得一个默认的Session对象:
Properties props = new Properties();// fill props with any informationSession session = Session.getDefaultInstance(props, null);
或者使用getInstance()这个静态工厂方法获得自定义的Session:
Properties props = new Properties();// fill props with any informationSession session = Session.getInstance(props, null);
从上面的两个例子中不难发现,getDefaultInstance()和getInstance()方法的第二个参数都是null,这是因为在上面的例子中并没有使用到邮件授权,下文中将对授权进行详细介绍。
从很多的实例看,在对mail server进行访问的过程中使用共享的Session是足够的,即使是工作在多个用户邮箱的模式下也不例外。

C.Message
当我们建立了Session对象后,便可以被发送的构造信息体了。在这里SUN提供了Message类型来帮助开发者完成这项工作。由于Message是一个抽象类,大多数情况下,我们使用javax.mail.internet.MimeMessage这个子类,该类是使用MIME类型、MIME信息头的邮箱信息。信息头只能使用US-ASCII字符,而非ASCII字符将通过编码转换为ASCII的方式使用。
为了建立一个MimeMessage对象,我们必须将Session对象作为MimeMessage构造方法的参数传入:
MimeMessage message = new MimeMessage(session);
注意:对于MimeMessage类来讲存在着多种构造方法,比如使用输入流作为参数的构造方法。

在建立了MimeMessage对象后,我们需要设置它的各个part,对于MimeMessage类来说,这些part就是MimePart接口。最基本的设置信息内容的方法就是通过表示信息内容和米么类型的参数调用setContent()方法:
message.setContent("Hello", "text/plain");
然而,如果我们所使用的MimeMessage中信息内容是文本的话,我们便可以直接使用setText()方法来方便的设置文本内容。
message.setText("Hello");
前面所讲的两种方法,对于文本信息,后者更为合适。而对于其它的一些信息类型,比如HTML信息,则要使用前者。
别忘记了,使用setSubject()方法对邮件设置邮件主题:
message.setSubject("First");

D.Address
到这里,我们已经建立了Session和Message,下面将介绍如何使用邮件地址类:Address。像Message一样,Address类也是一个抽象类,所以我们将使用javax.mail.internet.InternetAddress这个子类。
通过传入代表邮件地址的字符串,我们可以建立一个邮件地址类:
Address address = new InternetAddress("president@whitehouse.gov");
如果要在邮件地址后面增加名字的话,可以通过传递两个参数:代表邮件地址和名字的字符串来建立一个具有邮件地址和名字的邮件地址类:
Address address = new InternetAddress("president@whitehouse.gov", "George Bush");
本文在这里所讲的邮件地址类是为了设置邮件信息的发信人和收信人而准备的,在建立了邮件地址类后,我们通过message的setFrom()和setReplyTo()两种方法设置邮件的发信人:
message.setFrom(address);message.setReplyTo(address);
若在邮件中存在多个发信人地址,我们可用addForm()方法增加发信人:
Address address[] = ...;message.addFrom(address);
为了设置收信人,我们使用addRecipient()方法增加收信人,此方法需要使用Message.RecipientType的常量来区分收信人的类型:
message.addRecipient(type, address)
下面是Message.RecipientType的三个常量:
Message.RecipientType.TO
Message.RecipientType.CC
Message.RecipientType.BCC
因此,如果我们要发送邮件给总统,并发用一个副本给第一夫人的话,下面的方法将被用到:
Address toAddress = new InternetAddress("vice.president@whitehouse.gov");Address ccAddress = new InternetAddress("first.lady@whitehouse.gov");message.addRecipient(Message.RecipientType.TO, toAddress);message.addRecipient(Message.RecipientType.CC, ccAddress);
JavaMail API并没有提供检查邮件地址有效性的机制。当然我们可以自己完成这个功能:验证邮件地址的字符是否按照RFC822规定的格式书写或者通过DNS服务器上的MX记录验证等。

E.Authenticator
像java.net类那样,JavaMail API通过使用授权者类(Authenticator)以用户名、密码的方式访问那些受到保护的资源,在这里“资源”就是指邮件服务器。在javax.mail包中可以找到这个JavaMail的授权者类(Authenticator)。
在使用Authenticator这个抽象类时,我们必须采用继承该抽象类的方式,并且该继承类必须具有返回PasswordAuthentication对象(用于存储认证时要用到的用户名、密码)getPasswordAuthentication()方法。并且要在Session中进行注册,使Session能够了解在认证时该使用哪个类。
下面代码片断中的MyAuthenticator就是一个Authenticator的子类。
Properties props = new Properties();// fill props with any informationAuthenticator auth = new MyAuthenticator();Session session = Session.getDefaultInstance(props, auth);

F.Transport
在发送信息时,Transport类将被用到。这个类实现了发送信息的协议(通称为SMTP),此类是一个抽象类,我们可以使用这个类的静态方法send()来发送消息:
Transport.send(message);
当然,方法是多样的。我们也可由Session获得相应协议对应的Transport实例。并通过传递用户名、密码、邮件服务器主机名等参数建立与邮件服务器的连接,并使用sendMessage()方法将信息发送,最后关闭连接:
message.saveChanges(); // implicit with send()Transport transport = session.getTransport("smtp");transport.connect(host, username, password);transport.sendMessage(message, message.getAllRecipients());transport.close();
评论:上面的方法是一个很好的方法,尤其是在我们在同一个邮件服务器上发送多个邮件时。因为这时我们将在连接邮件服务器后连续发送邮件,然后再关闭掉连接。send()这个基本的方法是在每次调用时进行与邮件服务器的连接的,对于在同一个邮件服务器上发送多个邮件来讲可谓低效的方式。
注意:如果需要在发送邮件过程中监控mail命令的话,可以在发送前设置debug标志:
session.setDebug(true)。

G.Store和Folder
接收邮件和发送邮件很类似都要用到Session。但是在获得Session后,我们需要从Session中获取特定类型的Store,然后连接到Store,这里的Store代表了存储邮件的邮件服务器。在连接Store的过程中,极有可能需要用到用户名、密码或者Authenticator。
// Store store = session.getStore("imap");Store store = session.getStore("pop3");store.connect(host, username, password);
在连接到Store后,一个Folder对象即目录对象将通过Store的getFolder()方法被返回,我们可从这个Folder中读取邮件信息:
Folder folder = store.getFolder("INBOX");folder.open(Folder.READ_ONLY);Message message[] = folder.getMessages();
上面的例子首先从Store中获得INBOX这个Folder(对于POP3协议只有一个名为INBOX的Folder有效),然后以只读(Folder.READ_ONLY)的方式打开Folder,最后调用Folder的getMessages()方法得到目录中所有Message的数组。

注意:对于POP3协议只有一个名为INBOX的Folder有效,而对于IMAP协议,我们可以访问多个Folder(想想前面讲的IMAP协议)。而且SUN在设计Folder的getMessages()方法时采取了很智能的方式:首先接收新邮件列表,然后再需要的时候(比如读取邮件内容)才从邮件服务器读取邮件内容。
在读取邮件时,我们可以用Message类的getContent()方法接收邮件或是writeTo()方法将邮件保存,getContent()方法只接收邮件内容(不包含邮件头),而writeTo()方法将包括邮件头。
System.out.println(((MimeMessage)message).getContent());
在读取邮件内容后,别忘记了关闭Folder和Store。
folder.close(aBoolean);store.close();
传递给Folder.close()方法的boolean 类型参数表示是否在删除操作邮件后更新Folder。

H.继续向前进!
在讲解了以上的七个Java Mail核心类定义和理解了简单的代码片断后,下文将详细讲解怎样使用这些类实现JavaMail API所要完成的高级功能。

五、使用JavaMail API
在明确了JavaMail API的核心部分如何工作后,本人将带领大家学习一些使用Java Mail API任务案例。
1.发送邮件
在获得了Session后,建立并填入邮件信息,然后发送它到邮件服务器。这便是使用Java Mail API发送邮件的过程,在发送邮件之前,我们需要设置SMTP服务器:通过设置Properties的mail.smtp.host属性。
String host = ...;String from = ...;String to = ...;// Get system propertiesProperties props = System.getProperties();// Setup mail serverprops.put("mail.smtp.host", host);// Get sessionSession session = Session.getDefaultInstance(props, null);// Define messageMimeMessage message = new MimeMessage(session);message.setFrom(new InternetAddress(from));message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));message.setSubject("Hello JavaMail");message.setText("Welcome to JavaMail");// Send messageTransport.send(message);
由于建立邮件信息和发送邮件的过程中可能会抛出异常,所以我们需要将上面的代码放入到try-catch结构块中。

2.接收邮件
为了在读取邮件,我们获得了session,并且连接到了邮箱的相应store,打开相应的Folder,然后得到我们想要的邮件,当然别忘记了在结束时关闭连接。
String host = ...;String username = ...;String password = ...;// Create empty propertiesProperties props = new Properties();// Get sessionSession session = Session.getDefaultInstance(props, null);// Get the storeStore store = session.getStore("pop3");store.connect(host, username, password);// Get folderFolder folder = store.getFolder("INBOX");folder.open(Folder.READ_ONLY);// Get directoryMessage message[] = folder.getMessages();for (int i=0, n=message.length; i
上面的代码所作的是从邮箱中读取每个邮件,并且显示邮件的发信人地址和主题。从技术角度讲,这里存在着一个异常的可能:当发信人地址为空时,getFrom()[0]将抛出异常。

下面的代码片断有效的说明了如何读取邮件内容,在显示每个邮件发信人和主题后,将出现用户提示从而得到用户是否读取该邮件的确认,如果输入YES的话,我们可用Message.writeTo(java.io.OutputStream os)方法将邮件内容输出到控制台上,关于Message.writeTo()的具体用法请看JavaMail API。
BufferedReader reader = new BufferedReader ( new InputStreamReader(System.in));// Get directoryMessage message[] = folder.getMessages();for (int i=0, n=message.length; i
3.删除邮件和标志
设置与message相关的Flags是删除邮件的常用方法。这些Flags表示了一些系统定义和用户定义的不同状态。在Flags类的内部类Flag中预定义了一些标志:
Flags.Flag.ANSWERED
Flags.Flag.DELETED
Flags.Flag.DRAFT
Flags.Flag.FLAGGED
Flags.Flag.RECENT
Flags.Flag.SEEN
Flags.Flag.USER
但需要在使用时注意的:标志存在并非意味着这个标志被所有的邮件服务器所支持。例如,对于删除邮件的操作,POP协议不支持上面的任何一个。所以要确定哪些标志是被支持的??通过访问一个已经打开的Folder对象的getPermanetFlags()方法,它将返回当前被支持的Flags类对象。
删除邮件时,我们可以设置邮件的DELETED标志:
message.setFlag(Flags.Flag.DELETED, true);
但是首先要采用READ_WRITE的方式打开Folder:
folder.open(Folder.READ_WRITE);
在对邮件进行删除操作后关闭Folder时,需要传递一个true作为对删除邮件的擦除确认。
folder.close(true);
Folder类中另一种用于删除邮件的方法expunge()也同样可删除邮件,但是它并不为sun提供的POP3实现支持,而其它第三方提供的POP3实现支持或者并不支持这种方法。
另外,介绍一种检查某个标志是否被设置的方法:Message.isSet(Flags.Flag flag)方法,其中参数为被检查的标志。

4.邮件认证
我们在前面已经学会了如何使用Authenticator类来代替直接使用用户名和密码这两字符串作为Session.getDefaultInstance()或者Session.getInstance()方法的参数。在前面的小试牛刀后,现在我们将了解到全面认识一下邮件认证。
我们在此取代了直接使用邮件服务器主机名、用户名、密码这三个字符串作为连接到POP3 Store的方式,使用存储了邮件服务器主机名信息的属性文件,并在获得Session时传入自定义的Authenticator实例:
// Setup propertiesProperties props = System.getProperties();props.put("mail.pop3.host", host);// Setup authentication, get sessionAuthenticator auth = new PopupAuthenticator();Session session = Session.getDefaultInstance(props, auth);// Get the storeStore store = session.getStore("pop3");store.connect();

PopupAuthenticator类继承了抽象类Authenticator,并且通过重载Authenticator类的getPasswordAuthentication()方法返回PasswordAuthentication类对象。而getPasswordAuthentication()方法的参数param是以逗号分割的用户名、密码组成的字符串。
import javax.mail.*;import java.util.*;public class PopupAuthenticator extends Authenticator { public PasswordAuthentication getPasswordAuthentication(String param) { String username, password; StringTokenizer st = new StringTokenizer(param, ","); username = st.nextToken(); password = st.nextToken(); return new PasswordAuthentication(username, password); }}

5.回复邮件
回复邮件的方法很简单:使用Message类的reply()方法,通过配置回复邮件的收件人地址和主题(如果没有提供主题的话,系统将默认将“Re:”作为邮件的主体),这里不需要设置任何的邮件内容,只要复制发信人或者reply-to到新的收件人。而reply()方法中的boolean参数表示是否将邮件回复给发送者(参数值为false),或是恢复给所有人(参数值为true)。
补充一下,reply-to地址需要在发信时使用setReplyTo()方法设置。
MimeMessage reply = (MimeMessage)message.reply(false);reply.setFrom(new InternetAddress("president@whitehouse.gov"));reply.setText("Thanks");Transport.send(reply);

6.转发邮件
转发邮件的过程不如前面的回复邮件那样简单,它将建立一个转发邮件,这并非一个方法就能做到。
每个邮件是由多个部分组成,每个部分称为一个邮件体部分,是一个BodyPart类对象,对于MIME类型邮件来讲就是MimeBodyPart类对象。这些邮件体包含在成为Multipart的容器中对于MIME类型邮件来讲就是MimeMultiPart类对象。在转发邮件时,我们建立一个文字邮件体部分和一个被转发的文字邮件体部分,然后将这两个邮件体放到一个Multipart中。说明一下,复制一个邮件内容到另一个邮件的方法是仅复制它的DataHandler(数据处理者)即可。这是由JavaBeans Activation Framework定义的一个类,它提供了对邮件内容的操作命令的访问、管理了邮件内容操作,是不同的数据源和数据格式之间的一致性接口。
// Create the message to forwardMessage forward = new MimeMessage(session);// Fill in headerforward.setSubject("Fwd: " + message.getSubject());forward.setFrom(new InternetAddress(from));forward.addRecipient(Message.RecipientType.TO, new InternetAddress(to));// Create your new message partBodyPart messageBodyPart = new MimeBodyPart();messageBodyPart.setText( "Here you go with the original message:\n\n");// Create a multi-part to combine the partsMultipart multipart = new MimeMultipart();multipart.addBodyPart(messageBodyPart);// Create and fill part for the forwarded contentmessageBodyPart = new MimeBodyPart();messageBodyPart.setDataHandler(message.getDataHandler());// Add part to multi partmultipart.addBodyPart(messageBodyPart);// Associate multi-part with messageforward.setContent(multipart);// Send messageTransport.send(forward);

7.使用附件
附件作为与邮件相关的资源经常以文本、表格、图片等格式出现,如流行的邮件客户端一样,我们可以用JavaMail API从邮件中获取附件或是发送带有附件的邮件。

A.发送带有附件的邮件
发送带有附件的邮件的过程有些类似转发邮件,我们需要建立一个完整邮件的各个邮件体部分,在第一个部分(即我们的邮件内容文字)后,增加一个具有DataHandler的附件而不是在转发邮件时那样复制第一个部分的DataHandler。

如果我们将文件作为附件发送,那么要建立FileDataSource类型的对象作为附件数据源;如果从URL读取数据作为附件发送,那么将要建立URLDataSource类型的对象作为附件数据源。

然后将这个数据源(FileDataSource或是URLDataSource)对象作为DataHandler类构造方法的参数传入,从而建立一个DataHandler对象作为数据源的DataHandler。

接着将这个DataHandler设置为邮件体部分的DataHandler。这样就完成了邮件体与附件之间的关联工作,下面的工作就是BodyPart的setFileName()方法设置附件名为原文件名。

最后将两个邮件体放入到Multipart中,设置邮件内容为这个容器Multipart,发送邮件。
// Define messageMessage message = new MimeMessage(session);message.setFrom(new InternetAddress(from));message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));message.setSubject("Hello JavaMail Attachment");// Create the message part BodyPart messageBodyPart = new MimeBodyPart();// Fill the messagemessageBodyPart.setText("Pardon Ideas");Multipart multipart = new MimeMultipart();multipart.addBodyPart(messageBodyPart);// Part two is attachmentmessageBodyPart = new MimeBodyPart();DataSource source = new FileDataSource(filename);messageBodyPart.setDataHandler(new DataHandler(source));messageBodyPart.setFileName(filename);multipart.addBodyPart(messageBodyPart);// Put parts in messagemessage.setContent(multipart);// Send the messageTransport.send(message);
如果我们使用servlet实现发送带有附件的邮件,则必须上传附件给servlet,这时需要注意提交页面form中对编码类型的设置应为multipart/form-data。


B.读取邮件中的附件
读取邮件中的附件的过程要比发送它的过程复杂一点。因为带有附件的邮件是多部分组成的,我们必须处理每一个部分获得邮件的内容和附件。
但是如何辨别邮件信息内容和附件呢?Sun在Part类(BodyPart类实现的接口类)中提供了getDisposition()方法让开发者获得邮件体部分的部署类型,当该部分是附件时,其返回之将是Part.ATTACHMENT。但附件也可以没有部署类型的方式存在或者部署类型为Part.INLINE,无论部署类型为Part.ATTACHMENT还是Part.INLINE,我们都能把该邮件体部分导出保存。
Multipart mp = (Multipart)message.getContent();for (int i=0, n=multipart.getCount(); i下列代码中使用了saveFile方法是自定义的方法,它根据附件的文件名建立一个文件,如果本地磁盘上存在名为附件的文件,那么将在文件名后增加数字表示区别。然后从邮件体中读取数据写入到本地文件中(代码省略)。
// from saveFile()File file = new File(filename);for (int i=0; file.exists(); i++) { file = new File(filename+i);}
以上是邮件体部分被正确设置的简单例子,如果邮件体部分的部署类型为null,那么我们通过获得邮件体部分的MIME类型来判断其类型作相应的处理,代码结构框架如下:
if (disposition == null) { // Check if plain MimeBodyPart mbp = (MimeBodyPart)part; if (mbp.isMimeType("text/plain")) { // Handle plain } else { // Special non-attachment cases here of // image/gif, text/html, ... }...}

8.处理HTML邮件
前面的例子中发送的邮件都是以文本为内容的(除了附件),下面将介绍如何接收和发送基于HTML的邮件。
A.发送HTML邮件
假如我们需要发送一个HTML文件作为邮件内容,并使邮件客户端在读取邮件时获取相关的图片或者文字的话,只要设置邮件内容为html代码,并设置内容类型为text/html即可:
String htmlText = "
Hello
" + "";message.setContent(htmlText, "text/html"));
请注意:这里的图片并不是在邮件中内嵌的,而是在URL中定义的。邮件接收者只有在线时才能看到。
在接收邮件时,如果我们使用JavaMail API接收邮件的话是无法实现以HTML方式显示邮件内容的。因为JavaMail API邮件内容视为二进制流。所以要显示HTML内容的邮件,我们必须使用JEditorPane或者第三方HTML展现组件。

以下代码显示了如何使用JEditorPane显示邮件内容:
if (message.getContentType().equals("text/html")) { String content = (String)message.getContent(); JFrame frame = new JFrame(); JEditorPane text = new JEditorPane("text/html", content); text.setEditable(false); JScrollPane pane = new JScrollPane(text); frame.getContentPane().add(pane); frame.setSize(300, 300); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.show();}

B.在邮件中包含图片
如果我们在邮件中使用HTML作为内容,那么最好将HTML中使用的图片作为邮件的一部分,这样无论是否在线都会正确的显示HTML中的图片。处理方法就是将HTML中用到的图片作为邮件附件并使用特殊的cid URL作为图片的引用,这个cid就是对图片附件的Content-ID头的引用。
处理内嵌图片就像向邮件中添加附件一样,不同之处在于我们必须通过设置图片附件所在的邮件体部分的header中Content-ID为一个随机字符串,并在HTML中img的src标记中设置为该字符串。这样就完成了图片附件与HTML的关联。
String file = ...;// Create the messageMessage message = new MimeMessage(session);// Fill its headersmessage.setSubject("Embedded Image");message.setFrom(new InternetAddress(from));message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));// Create your new message partBodyPart messageBodyPart = new MimeBodyPart();String htmlText = "
Hello
" + "";messageBodyPart.setContent(htmlText, "text/html");// Create a related multi-part to combine the partsMimeMultipart multipart = new MimeMultipart("related");multipart.addBodyPart(messageBodyPart);// Create part for the imagemessageBodyPart = new MimeBodyPart();// Fetch the image and associate to partDataSource fds = new FileDataSource(file);messageBodyPart.setDataHandler(new DataHandler(fds));messageBodyPart.setHeader("Content-ID","");// Add part to multi-partmultipart.addBodyPart(messageBodyPart);// Associate multi-part with messagemessage.setContent(multipart);

9.在邮件中搜索短语
JavaMail API提供了过滤器机制,它被用来建立搜索短语。这个短语由javax.mail.search包中的SearchTerm抽象类来定义,在定义后我们便可以使用Folder的Search()方法在Folder中查找邮件:
SearchTerm st = ...;Message[] msgs = folder.search(st);
下面有22个不同的类(继承了SearchTerm类)供我们使用:
AND terms (class AndTerm)
OR terms (class OrTerm)
NOT terms (class NotTerm)
SENT DATE terms (class SentDateTerm)
CONTENT terms (class BodyTerm)
HEADER terms (FromTerm / FromStringTerm, RecipientTerm / RecipientStringTerm, SubjectTerm, etc.)
使用这些类定义的断语集合,我们可以构造一个逻辑表达式,并在Folder中进行搜索。下面是一个实例:在Folder中搜索邮件主题含有“ADV”字符串或者发信人地址为friend@public.com的邮件。
SearchTerm st = new OrTerm( new SubjectTerm("ADV:"), new FromStringTerm("friend@public.com"));Message[] msgs = folder.search(st);

六、参考资源
JavaMail API Home
Sun’s JavaMail API基础
JavaBeans Activation Framework Home
javamail-interest mailing list
Sun's JavaMail FAQ
jGuru's JavaMail FAQ
Third Party Products List
七、代码下载
http://java.sun.com/developer/onlineTraining/JavaMail/exercises.html