Client
package com.company;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.company.bo.LogData;
import com.company.bo.LogRec;
import com.company.util.IOUtil;
/**
* 客戶端應用程序:
* 運行在unix系統上,作用是定期讀取系統日誌文件wtmpx文件,
* 收集每個用戶的登入登出日誌,將匹配成對的日誌信息發送至服務器
*/
public class Client {
//UNIX系統日誌文件wtmpx文件
private File logFile;
//保存解析後的日孩子文件
private File textLogFile;
//保存每次解析日誌文件後的位置(書籤)的文件
private File lastPositionFile;
//每次從wtmpx文件中解析日誌的條數
private int batch;
//保存每次配對完畢後的所有配對日誌的文件
private File logRecFile;
//保存每次配對後,沒有配對成功的登入日誌的文件
private File loginFile;
/**
* 構造方法初始化
*/
public Client(){
try {
this.batch = 10;
logFile = new File("wtmpx");
lastPositionFile = new File("last-position.txt");
textLogFile = new File("log.txt");
logRecFile = new File("logrec.txt");
loginFile = new File("login.txt");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 該方法爲第一大步第二小步的邏輯
* 用於檢查wtmpx文件是否還有數據可讀
* @return -1:沒有數據可讀了
* 其他數字:繼續讀取的位置
*/
public long hasLogs(){
try {
//默認從文件開始讀取
long lastposition = 0;
/*
* 這裏有兩種情況
* 1:沒有找到last-position.txt
* 文件,這說明從來沒讀過wtmpx
* 2:有last-position.txt文件,
* 那麼,那麼就從文件記錄的位置開始讀取
*/
if(lastPositionFile.exists()){
lastposition = IOUtil.readLong(lastPositionFile);
}
/*
* 必要判斷,wtmpx文件的總大小
* 減去這次準備開始讀取的位置,應當
* 大於一條日誌所佔用的字節量(372)
*/
if(logFile.length()-lastposition < LogData.LOG_LENGTH){
lastposition = -1;
}
return lastposition;
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
/**
* 當前RandomAccessFile讀取的位置
* 在logfile中是否還有內容可讀
*/
public boolean hasLogsByStep(RandomAccessFile raf) throws IOException{
if(logFile.length()-raf.getFilePointer()>LogData.LOG_LENGTH){
return true;
}else{
return false;
}
}
/**
* 第一大步:
* 從wtmpx文件中一次讀取batch條日誌,並解析爲batch條字符串,
* 每行字符串表示一條日誌,然後寫入log.txt文件中
*
* return true:解析成功
* return false:解析失敗
*/
public boolean readNextLogs(){
/*
* 解析步驟:
* 1:先判斷wtmpx文件是否存在
* 2:判斷是否有新數據可讀
* 3:從上次讀取的位置繼續開始讀取
* 4:循環batch次,讀取batch個372字節,並轉換爲batch個日誌
* 5:將解析後的batch個日誌寫入log.txt文件中
*/
//1:先判斷wtmpx文件是否存在
if(!logFile.exists()){
return false;
}
//2:判斷是否有新數據可讀
long lastposition = hasLogs();
if(lastposition<0){
return false;
}
/*
* 爲了避免重複執行第一步,而導致原來第一步中已經解析的日誌文件被廢棄,
* 我們可以先判斷:若第一步執行完畢後生成的log.txt文件存在,就不再執行第一步了。
* 該文件會在第二步執行完畢後刪除。
*/
if(textLogFile.exists()){
return true;
}
try{
RandomAccessFile raf = new RandomAccessFile(logFile,"r");
//移動到指定位置,開始繼續讀取
raf.seek(lastposition);
//定義集合,用於保存解析後的日誌
List<LogData> logs = new ArrayList<LogData>();
//循環batch次,解析batch條日誌
for(int i=0;i<batch;i++){
/*
* 是否還有日誌可讀
*/
if(!hasLogsByStep(raf)){
break;
}
//讀取用戶名
String user = IOUtil.readString(raf,LogData.USER_LENGTH);
//讀取PID
raf.seek(lastposition+LogData.PID_OFFSET);
int pid = IOUtil.readInt(raf);
//讀取type
raf.seek(lastposition+LogData.TYPE_OFFSET);
short type = IOUtil.readShort(raf);
//讀取time
raf.seek(lastposition+LogData.TIME_OFFSET);
int time = IOUtil.readInt(raf);
//讀取host
raf.seek(lastposition+LogData.HOST_OFFSET);
String host = IOUtil.readString(raf, LogData.HOST_LENGTH);
//將RandomAccessFile的遊標位置定位到該條數據的末尾
raf.seek(lastposition+LogData.LOG_LENGTH);
//將lastposition設置爲raf的遊標位置,以便下次循環使用
lastposition = raf.getFilePointer();
//System.out.println("遊標位置:"+lastposition);
/*
* 將解析出來的數據存入一個LogData對象中,
* 再將該對象存入集合中
*/
LogData log = new LogData(user,pid,type,time,host);
logs.add(log);
}
// System.out.println("解析日誌數:"+logs.size());
// for(LogData log : logs){
// System.out.println(log);
// }
/*
* 將解析後的日誌,寫入log.txt文件中
*/
IOUtil.saveList(logs, textLogFile);
/*
* 將這次解析後RandomAccessFile的遊標位置記錄,
* 以便於下次解析的時候繼續讀取。
*/
IOUtil.saveLong(lastposition, lastPositionFile);
}catch(Exception e){
}
return false;
}
/**
* 第二大步的:
* 匹配日誌
* 大體步驟:
* 1:讀取log.txt文件,將第一步解析出的日期讀取出來
* 並轉爲若干個LogData對象存入list集合中等待配對
* 2:讀取login.txt文件,將上次沒有配對成功的登入日誌讀取出來
* 並轉換爲若干個LogData對象,也存入List集合中,等待這次配對
* 3:循環list,將登入登出日誌分別存入到2個map中,value就是對應的日誌對象,
* key都是【user,pid,ip】這樣格式的字符串
* 4:循環登出的map,並通過key尋找登入map中的登入日誌,
* 以達到配對的目的,將配對的日誌轉換爲一個LogRec對象存入一個list集合中
* 5:將所有配對成功的日誌寫入文件logrec.txt
* 6:將所有沒配對成功的日誌寫入文件login.txt
* @return
*/
public boolean matchLogs(){
/*
* 必要的判斷
*/
if(!textLogFile.exists()){
return false;
}
/*
* 當第二步執行完畢後,會生成兩個文件:logrec.txt, login.txt
* 若第三步在執行時出現錯誤,我們若重新執行第二步,
* 會將上次第二步已經配對的日誌覆蓋,從而導致數據丟失。
* 爲此我們要做一個必要的判斷,就是
* logrec.txt文件若存在,則說明第二步
* 已經完成,但是第三部沒有順利執行。
* 因爲第三步執行完畢後,會將該文件刪除。
* 所以,若存在,則第二步不再執行。
*/
if(logRecFile.exists()){
return true;
}
/*
* 業務邏輯
*/
try{
/*
* 1讀取log.txt文件,將第一步解析出的日期讀取出來
* 並轉爲若干個LogData對象存入list集合中等待配對
*/
List<LogData> list = IOUtil.loadLogData(textLogFile);
/*2讀取login.txt文件,將上次沒有配對成功的登入日誌讀取出來,
並轉換爲若干個LogData對象,也存入List集合中,等待這次配對*/
if(loginFile.exists()){
list.addAll(IOUtil.loadLogData(logRecFile));
}
/*3循環list,將登入登出日誌分別存入到2個map中,value就是對應的日誌對象,
key都是【user,pid,ip】這樣格式的字符串*/
Map<String,LogData> loginMap = new HashMap<String,LogData>();
Map<String,LogData> logoutMap = new HashMap<String,LogData>();
for(LogData log : list){
if(log.getType()==LogData.TYPE_LOGIN){
putLogToMap(log, loginMap);
}else if(log.getType()==LogData.TYPE_LOGOUT){
putLogToMap(log, logoutMap);
}
}
/*4:循環登出的map,並通過key尋找登入map中的登入日誌,
以達到配對的目的,將配對的日誌轉換爲一個LogRec對象存入一個list集合中*/
Set<Entry<String,LogData>> set =logoutMap.entrySet();
//用於存放所有配對成功的日誌的集合
List<LogRec> logRecList = new ArrayList<LogRec>();
for(Entry<String,LogData> entry : set){
/*
* 從登出map中,取出key
*/
String key = entry.getKey();
/*
* 根據登出的key,從登入map中
* 以相同的key刪除元素,刪除的
* 就是對應的登入日誌
*/
LogData login = loginMap.remove(key);
if(login!=null){
//匹配後,轉爲一個LogRec對象
LogRec logrec = new LogRec(login,entry.getValue());
//將配對日誌存入集合
logRecList.add(logrec);
}
}
//出了for循環,相當於配對工作就完畢了
//5:將所有配對成功的日誌寫入文件logrec.txt
IOUtil.saveList(logRecList, logRecFile);
//6:將所有沒配對成功的日誌寫入文件login.txt
Collection<LogData> c = loginMap.values();
IOUtil.saveList(new ArrayList<LogData>(c), loginFile);
/*
* 當第二步執行完畢後,
* log.txt文件就可以刪除了
*/
textLogFile.delete();
return true;
}catch(Exception e){
e.printStackTrace();
/*
* 若第二步出現異常,那麼第二步生成的
* 配對文件logrec.txt文件就是無效的。
* 應當刪除,以便於重新執行第二步
*/
if(logRecFile.exists()){
logRecFile.delete();
}
return false;
}
}
/**
* 將給定的日誌存入給定的map中
* @param log
* @param map
*/
private void putLogToMap(LogData log, Map<String,LogData> map){
map.put(log.getUser()+","+log.getPid()+","+log.getHost(), log);
}
/**
* 第三步:
* 將配對的日誌發送至服務端
* 步驟:
* 1:創建socket用於連接服務端
* 2:通過socket獲取輸出流,並逐步包裝爲
* 緩衝字符輸出流,字符集是utf-8
* 3:創建緩衝字符輸入流,用於讀取
* logrec.txt(讀取配對日誌)
* 4:從logrec.txt文件中讀取每一行日誌信息
* 併發送至服務端
* 5:通過socket獲取輸入流,並逐步包裝爲
* 緩衝字符輸入流,用於讀取服務端的響應
* 6:讀取服務器的響應,若是ok,則說明
* 服務端成功接收了我們發送的配對日誌
* 那麼就將logrec.txt文件刪除。
* 第三步執行完畢。
* 若返回的響應不是ok,則表示發送沒有
* 成功,那麼該方法返回false,應當
* 重新嘗試執行第三步。
* @return
*/
public boolean sendLogToServer(){
/*
* 必要判斷
*/
if(!logRecFile.exists()){
return false;
}
/*
* 業務邏輯
*/
Socket socket = null;
BufferedReader br = null;
try{
socket = new Socket("localhost",8088);
OutputStream out = socket.getOutputStream();
OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
PrintWriter pw = new PrintWriter(osw);
//讀取logrec.txt
FileInputStream fis = new FileInputStream(logRecFile);
InputStreamReader isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
String line = null;
/*
* 循環從logrec.txt文件中讀取每一行
* 配對日誌,併發送至服務端
*/
while((line=br.readLine())!=null){
pw.println(line);
}
//最後發送一個over,表示發送完畢了
pw.println("over");
pw.flush();
//已經將logrec.txt文件中的內容發送了
//發送完,將讀取文件的流關掉
br.close();
/*
* 通過socket創建輸入流,用於讀取服務端的響應
*/
InputStream in = socket.getInputStream();
BufferedReader brServer = new BufferedReader(new InputStreamReader(in,"UTF-8"));
//讀取服務端發送回來的響應
String response = brServer.readLine();
if("OK".equals(response)){
/*
* 服務端正確接收發送的日之後
* 就可以將第二步生成的logrec.txt
* 文件刪除了。
*/
logRecFile.delete();
return true;
}
return false;
}catch(Exception e){
e.printStackTrace();
return false;
}finally{
//將socket關閉
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
}
}
//讀取文件的輸入流也可能沒關閉
if(br!=null){
try {
br.close();
} catch (IOException e) {
}
}
}
}
/**
* 客戶端開始工作的方法
*/
public void start(){
/*
* 開始方法中,我們要循環以下3個步驟
* 1:從wtmpx文件中一次解析batch跳日誌
* 2:將解析後的日誌,和上次沒有匹配的日誌一起配成對
* 3:將匹配成對的日誌發送至服務端
*/
while(true){
//1:從wtmpx文件中一次解析batch跳日誌
readNextLogs();
//2將解析後的日誌,和上次沒有匹配的日誌一起配成對
matchLogs();
//3
sendLogToServer();
}
}
public static void main(String[] args) {
Client client = new Client();
client.start();
}
}
Server
package com.company;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 服務端應用程序
*/
public class Server {
//運行在服務端的socket
private ServerSocket server;
//線程池,用於管理客戶端連接的交互線程
private ExecutorService threadPool;
//保存所有客戶端發送過來的配對日誌的文件
private File serverLogFile;
//創建一個雙緩衝隊列,用於存儲配對日誌
private BlockingQueue<String> messageQueue;
/**
* 構造方法,用於初始化服務器
*/
public Server() throws IOException{
try{
/*
* 創建ServerSocket時需要指定服務器端口
*/
System.out.println("初始化服務端");
server = new ServerSocket(8088);
//初始化線程池
threadPool = Executors.newFixedThreadPool(50);
//初始化保存的日誌
serverLogFile = new File("server-log.txt");
//初始化緩衝隊列
messageQueue = new LinkedBlockingQueue<String>();
System.out.println("服務器初始化完畢");
}catch(IOException e){
e.printStackTrace();
throw e;
}
}
/**
* 服務端開始工作的方法
*/
public void start(){
try{
/*
* 將寫日誌的線程啓動起來
*/
WriteLogThread thread = new WriteLogThread();
thread.start();
/*
* ServerSocket的accept方法
* 用於監聽8088端口,等待客戶端的連接
* 該方法是一個阻塞方法,直到一個
* 客戶端連接,否則該方法一直阻塞。
* 若一個客戶端連接了,會返回該客戶端的
* Socket
*/
while(true){
System.out.println("等待客戶端連接");
Socket socket = server.accept();
/*
* 當一個客戶端連接後,啓動一個線程
* ClientHandler,將該客戶端的
* socket傳入,使得該線程處理與該
* 客戶端交互。
* 這樣,我們能再次進入循環,接收
* 下一個客戶端的連接了。
*/
Runnable handler = new ClientHandler(socket);
//Thread t = new Thread(handler);
//t.start();
/*
* 使用線程池分配空閒線程來處理
* 當前連接的客戶端
*/
threadPool.execute(handler);
}
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
Server server;
try{
server = new Server();
server.start();
}catch(IOException e){
e.printStackTrace();
System.out.println("服務器初始化失敗");
}
}
/**
* 服務端中的一個線程,用於與某個客戶端交互。
* 使用線程的目的是使得服務端可以處理多客戶端了。
*/
class ClientHandler implements Runnable{
//當前線程處理的客戶端socket
private Socket socket;
/**
* 根據給定的客戶端的socket,創建
* 線程體
* @param socket
*/
public ClientHandler(Socket socket){
this.socket = socket;
}
/**
* 該線程會將當前socket中的輸入流獲取
* 用來循環讀取客戶端發送過來的消息
*/
@Override
public void run() {
/*
* 定義在try語句外的目的是,爲了在
* finally中也可以引用到
*/
PrintWriter pw = null;
try{
/*
* 爲了讓服務端與客戶端發送信息,
* 我們需要通過socket獲取輸出流。
*/
OutputStream out = socket.getOutputStream();
pw = new PrintWriter(
new OutputStreamWriter(out,"UTF-8"), true);
//獲取輸入流
InputStream in = socket.getInputStream();
BufferedReader br = new BufferedReader(
new InputStreamReader(in,"UTF-8"));
String message = null;
/*
* 循環讀取客戶端發送過來的每一組
* 配對日誌
* 讀取到一組,就將該日誌存入
* 消息隊列,等待被寫入文件。
*/
while((message=br.readLine())!=null){
/*
* 若讀取到客戶端發送的內容是over
* 表示客戶端發送完畢所有的日誌了
* 應當停止再接收客戶端發送的內容了
*/
if("over".equals(message)){
break;
}
messageQueue.offer(message);
}
/*
* 當退出循環,說明所有客戶端發送的日誌
* 均接收成功,並存入了消息隊列中。
* 那麼我們回覆客戶端OK
*/
pw.println("OK");
}catch(Exception e){
//在windows中的客戶端,
//報錯通常是因爲客戶端斷開了連接
pw.println("ERROR");
}finally{
/*
* 無論是linux用戶還是windows
* 用戶,當與服務端斷開連接後
* 我們都應該在服務端也與客戶端
* 斷開連接
*/
try {
socket.close();
} catch (IOException e) {
}
}
}
}
/**
* 該線程在server中僅有一個實例
* 作用是:
* 循環從消息隊列中取出一個配對日誌,
* 並寫入sever-log.txt文件中
* 當隊列沒有日誌後,就休眠一段時間
* 等待客戶端發送新的日誌過來
*/
class WriteLogThread extends Thread{
@Override
public void run() {
try{
PrintWriter pw = new PrintWriter(serverLogFile);
while(true){
if(messageQueue.size()>0){
String log = messageQueue.poll();
pw.println(log);
}else{
pw.flush();
Thread.sleep(500);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
}
LogData
package com.company.bo;
/**
* LogData的每一個實例用於表示wtmpx文件中的每一條日誌信息
*/
public class LogData {
/**
* 日誌wtmpx文件中的長度
* 每條日誌的長度都是372個字節
*/
public static final int LOG_LENGTH=372;
/**
* user在單挑日誌中的起始字節
*/
public static final int USER_OFFSET=0;
/**
* user在日誌中佔用的字節量
*/
public static final int USER_LENGTH=32;
/**
* pid的起始位置
*/
public static final int PID_OFFSET=68;
/**
* type在日誌中的起始位置
*/
public static final int TYPE_OFFSET=72;
/**
* time在日誌中的起始位置
*/
public static final int TIME_OFFSET=80;
/**
* host在日誌中的起始位置
*/
public static final int HOST_OFFSET=114;
/**
* host在日誌中的長度
*/
public static final int HOST_LENGTH=258;
/**
* 日誌類型:登入爲7
*/
public static final short TYPE_LOGIN=7;
/**
* 日誌類型:登出爲8
*/
public static final short TYPE_LOGOUT=8;
//登錄用戶名
private String user;
//進程id
private int pid;
//日誌類型(登入/登出)
private short type;
//生成日誌的時間(登入登出的時間),以秒爲單位
private int time;
//登錄用戶的ip地址
private String host;
public LogData(){}
public LogData(String user, int pid, short type, int time, String host) {
super();
this.user = user;
this.pid = pid;
this.type = type;
this.time = time;
this.host = host;
}
/**
* 給定一個字符串
* (格式應該是當前類toString方法生成)
* 將該字符串轉換爲一個LogData對象
*/
public LogData(String line){
//1:按照“,”拆分字符串
String[] array = line.split(",");
//2:將數組中的每一項設置到屬性上即可
this.user = array[0];
this.pid = Integer.parseInt(array[1]);
this.type = Short.parseShort(array[2]);
this.time = Integer.parseInt(array[3]);
this.host = array[4];
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public short getType() {
return type;
}
public void setType(short type) {
this.type = type;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
@Override
public String toString() {
return user + "," + pid + "," + type + "," + time + "," + host;
}
}
LogRec
package com.company.bo;
/**
* 該類用於描述一組匹配成對的日誌
*/
public class LogRec {
private LogData login;
private LogData logout;
public LogRec(LogData login, LogData logout) {
super();
this.login = login;
this.logout = logout;
}
public LogData getLogin() {
return login;
}
public void setLogin(LogData login) {
this.login = login;
}
public LogData getLogout() {
return logout;
}
public void setLogout(LogData logout) {
this.logout = logout;
}
/**
* toString()
* 格式:
* login.toString()|logout.toString()
*/
@Override
public String toString() {
return login + "|" +logout.toString();
}
}
IOUtil
package com.company.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;
import com.company.bo.LogData;
/**
* 該類是一個工具類,負責讀寫數據,
* 把讀寫邏輯單獨定義在該類的目的是爲了重用這些邏輯。
*/
public class IOUtil {
/**
* 從給定的文件中讀取第一行字符串,
* 並將其轉爲一個long值返回
*/
public static long readLong(File file){
BufferedReader br = null;
try{
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
String line = br.readLine();
long l = Long.parseLong(line);
return l;
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}finally{
try {
if(br != null){
br.close();
}
} catch (IOException e) {
}
}
}
/**
* 從給定的RandomAccessFile的當前位置處
* 連續讀取len個字節,並轉爲字符串
*/
public static String readString(RandomAccessFile raf, int len) throws IOException{
byte[] buf = new byte[LogData.USER_LENGTH];
raf.read(buf);
String str = new String(buf,"ISO8859-1");
return str.trim();
}
/**
* 從給定的RandomAccessFile當前位置處,
* 讀取一個int值並返回
*/
public static int readInt(RandomAccessFile raf) throws IOException{
return raf.readInt();
}
/**
* 從給定的RandomAccessFile當前位置處,
* 讀取一個short值並返回
*/
public static short readShort(RandomAccessFile raf) throws IOException{
return raf.readShort();
}
/**
* 將給定的集合中的每個元素的toString方法返回的字符串
* 作爲一行內容寫入給定的文件中
*/
public static void saveList(List list,File file) throws IOException{
PrintWriter pw = null;
try {
pw = new PrintWriter(file);
for(Object o : list){
pw.println(o);
}
}finally{//異常拋出,故不catch,但流要關閉
if(pw != null){
pw.close();
}
}
}
/**
* 將給定的long值作爲一行字符串寫入給定的文件中
*/
public static void saveLong(long l,File file) throws IOException{
PrintWriter pw =null;
try{
pw = new PrintWriter(file);
pw.println(l);
}finally{//異常拋出,故不catch,但流要關閉
if(pw!=null){
pw.close();
}
}
}
/**
* 從指定的文件中按行讀取每一條日誌,並
* 轉換爲一個LogData對象,最終將所有日誌
* 對象存入一個List集合中並返回
* @param file
* @return
*/
public static List<LogData> loadLogData(File file) throws IOException{
BufferedReader br = null;
try{
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
br = new BufferedReader(isr);
List<LogData> list = new ArrayList<LogData>();
String line = null;
while((line=br.readLine()) != null){
/*
* 解析過程應當交給LogData
* 原因在於該字符串的格式是由LogData自身的toString決定的
* 所以解析自然也應該交給它
*/
LogData log = new LogData(line);
list.add(log);
}
return list;
}finally{
if(br != null){
br.close();
}
}
}
}