- Android游戲開發技術實戰詳解
- 褚尚軍 張加春編著
- 944字
- 2018-12-30 05:33:19
3.4 I/O文件操作
I/O是指輸入輸出數據流,也稱為數據流,說簡單點就是數據流的輸入輸出方式。輸入模式是程序創建的某個輸入對象的數據流,這些輸入對象的數據源可以是文件、網絡、壓縮包或者其他的數據,如圖3-17所示。

圖3-17 輸入模式
輸出模式與輸入模式恰好相反,輸出模式是由程序創建某個輸出對象的數據流,并打開數據對象,將數據寫入數據流。
3.4.1 流
流可以分為字節流和字符流兩種,字節流自然以字節為單位進行硬盤數據處理,而字符流以字符為單位對硬盤進行數據處理。
1. 字節流
通過字節訪問數據、讀寫數據,可以使用InputStream類和OutputStream類,這兩個類是輸入輸出的父類,FileInputStream類和FileOutputStream類繼承了它們,重寫了方法。
(1)FileInputStream類
在Java程序里,類FileInputStream負責輸入工作,其中含有如下兩個構造方法。
· FileInputStream(String filepath)
· FileInputStream(File fielObj)
其中,參數fileObj表示要打開的文件,filepath表示文件路徑。下面通過一個實例介紹FileInputStream類的用法。
實例3-4 練習使用FileInputStream類(daima\3\liuone)。
編寫文件Liuone.java,主要代碼如下所示。
import java.io.*; public class Liuone { public static void main(String args[]){ try { FileInputStream file=new FileInputStream("ai.txt"); while(file.available()>0) { System.out.print((char)file.read());//輸出文件內容 } file.close(); } catch(Exception e) { System.out.println("not found file");} } }
執行程序后得到如圖3-18所示的結果。

圖3-18 FileInputStream類應用
讀者在此需要注意,如果是使用Eclipse新建的項目,需要將文件ai.txt放置在項目的根目錄下,才能執行程序,若將記事本中的字符修改成中文,執行時會得到如圖3-19所示的結果。

圖3-19 不能顯示中文
(2)FileOutputStream類
類FileOutputStream用于輸出諸如圖像數據之類的原始字節的流。FileOutputStream中的方法與前面介紹的方法有所不同,含有如下3個構造方法。
· FileOutputStream(String filepath)
· FileOutputStream(File fielObj)
· FileOutputStream(String filepath,Boolean append)
其中,參數filepath表示需要被打開并且需要輸出數據的文件名,參數fileObj表示被打開并且要輸出的數據文件。
實例3-5 練習使用FileeOutputStream類(daima\3\Liuone2)。
編寫實例文件Liuone2.java,具體代碼如下所示。
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Liuone2 { public static void main(String args[]) { int i; try{ FileInputStream aa=new FileInputStream("di.txt"); FileOutputStream bb=new FileOutputStream("do.txt"); bb.write('0'); bb.write('7'); bb.write('.'); bb.write('8'); bb.write('.'); bb.write('1'); i=aa.read(); while(i!=-1) { bb.write(i); i=aa.read(); } aa.close(); bb.close(); } catch(IOException e ){ System.out.println("do not open this file"); } } }
執行程序后的效果如圖3-20所示。

圖3-20 FileOutputStream類應用
打開同目錄下的文件do.txt和di.txt,發現已經將文件di.txt里的內容復制到了文件do.txt中,效果如圖3-21所示。

圖3-21 字節流
2. 字符流
字節流是不能處理中文的,但是字符流可以處理中文,接下來將詳細講解使用FileReader類和FileWriter類操作字符流的知識。
(1)FileReader類
FileReader類實現文本輸入工作,有如下兩個重要的構造方法。
· FileReader(String filepath)
· FileReader(Fiel fileObj)
其中,參數filepath表示文件路徑,參數fileObj是指打開并被讀取的文件。
實例3-6 練習使用FileRader類(daima\3\Liutwo1)。
編寫實現文件Liutwo1.java,具體代碼如下所示。
import java.io.*; public class Liutwo1 { public static void main(String args[]) { try { FileReader fr=new FileReader("1.txt");//使用了reader的子類 char[] c=new char[1]; while(fr.read(c)!=-1) System.out.print(new String(c)); fr.close(); } catch(Exception e) { System.out.println(e); } } }
上述代碼執行后的效果如圖3-22所示。

圖3-22 字符流
(2)FileWriter類
類FileWriter實現文本輸出操作,有如下3個常用的構造方法。
· FileWriter(String filepath)
· FileWriter(String filepath, Boolean append)
· FileWriter(String fileObj)
其中,參數filepath表示文件路徑;fileObj指打開并被讀取的文件;參數Boolean append尤其重要,如果其值為true,將以追加的方式打開,如果為false,則以覆蓋的方式打開。
實例3-7 練習使用FileWriter類(daima\3\Liutwo3)。
編寫實現文件Liutwo3.java,主要代碼如下所示。
import java.io.*; public class Liutwo3 { public static void main(String args[]) throws IOException { int i; FileReader fr; FileWriter fw; try { fr=new FileReader("1.txt"); } catch(FileNotFoundException e) { System.out.println("not found this file"); return; } try { fw=new FileWriter("2.txt"); } catch(FileNotFoundException e) { System.out.println("error"); return; } try { i=fr.read(); while(i!=-1) { fw.write(i); i=fr.read(); } fr.close(); fw.close(); } catch(IOException e) { System.out.println("error"); } } }
上述代碼執行后的效果如圖3-23所示。

圖3-23 輸出執行結果
3.4.2 加快I/O操作效率
前面講解了字節流和字符流的基本知識。這些操作方式的速率比較慢,不適合遠程操作或大型的項目,如何加快它們的處理速率呢?接下來將講解加快I/O操作效率的知識。
1. 緩沖字節流
要加快字節流可以使用緩沖數據流進行處理。前面講解了對硬盤的讀寫使得文件流的效率低的問題。接下來將講解BufferedInputStream類和BufferedOutputStream類,輸入數據時,數據以塊為單位讀入緩沖區,它們有如下的構造方法:
· BufferedInputStream(Obj)
· BufferedOutputStream(Obj)
參數Obj為已經建立好的“輸入/輸出”數據流的對象。
實例3-8 練習使用BufferedInputStream類和BufferedOutputStream類(daima\3\Huanc)。
編寫實現文件Huanc.java,主要代碼如下所示。
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Huanc { public static void main(String args[]) throws IOException { FileInputStream fis; FileOutputStream fos; BufferedInputStream bis; BufferedOutputStream bos; int i; try { fis=new FileInputStream("wei1.txt"); bis=new BufferedInputStream(fis); fos=new FileOutputStream("wei2.txt"); bos=new BufferedOutputStream(fos); i=bis.read(); while(i!=-1) { bos.write(i); bos.flush(); i=bis.read(); } fis.close(); fos.close(); bis.close(); bos.close(); System.out.print("復制成功"); } catch(IOException e) { System.out.println("沒有找到文件"); } } }
執行程序的效果如圖3-24所示。

圖3-24 復制成功
此時打開源程序目錄下兩個文件,可以看到已成功地將文件wei1.txt中的內容復制到了文件wei2.txt中,其結果如圖3-25所示。

圖3-25 復制文件
2. 緩沖字符流
處理字符與處理字節一樣,通過緩沖方式可以加快處理效率。Java為我們提供了類BufferedReader和BufferedWrote,它們和緩沖字節流一樣能夠提高I/O的操作速度。在類BufferedReader和BufferedWroter中有如下兩個構造方法。
· BufferedReader(Obj)
· BufferedWroter(Obj)
實例3-9 練習使用類BufferedReader和BufferedWrote(daima\3\huand)。
編寫實現文件huand.java,主要代碼如下所示。
import java.io.*; public class huand { public static void main(String args[]) { try { FileReader fr=new FileReader("cq.txt"); BufferedReader br=new BufferedReader(fr); String a; while((a=br.readLine())!=null) { System.out.println(a); } fr.close(); br.close(); } catch(IOException e) { System.out.println(e); } } }
上述代碼執行后的效果如圖3-26所示。

圖3-26 緩沖字符流
注意:字節流是以單個字節為單位的,而字符流以單個字符為單位,通常情況下,一般的字符流是由多個字符組成的,Java語言內部可將一個字節轉換為字符流,這種轉換的工作由InputStreamReader和OutputSteamWriter完成,它們各自有兩個構造方法,通過這些方法,可以將字節流和字符相互轉換,最終轉換為符合自己要求的輸入/輸出流。
3.4.3 文件處理
處理文件的方式沒有前面講解的處理字節流和字符流的方法煩瑣,但是Java為我們提供了更加多樣的方法來實現文件處理。
1. 文件類File
當對一個文件進行操作時,需要知道這個文件的基本信息。在Java文件類中提供了獲取文件信息的方法。類File中包含如下3個構造方法。
· File(String path)
· File(String path,String file)
· File(File Obj,String file)
參數path表示文件的路徑,file表示文件名,obj表示目錄名稱的File對象。
在類File中還包含了很多的方法,其中最為常用的方法如下所示。
· Boolean exists():測試File對象對應的文件是否存在。
· Boolean isFile():測試File對象對應的是否是一個文件。
· Boolean isDirectory():測試File對象對應的是否是一個目錄。
· Boolean canRead():測試File對象對應的文件是否可讀。
· Boolean canWriter():測試File對象對應的文件是否可寫。
· Boolean isHidden():測試File對象對應的文件是否隱藏。
· Boolean createNewFile():創建一個新文件。
· Boolean setReadOnly():對File文件對象對應的文件設置刻度。
· String[] list():獲取File對象對應的文件目錄。
· Boolean Mkdir ():創建File對象對應的文件目錄。
· Boolean delete():刪除File對象對應的文件。
· Boolean equals(Object obj):比較兩個文件對象。
· Boolean renameTo(File dest):將對應的對象重命名。
· String toString():將File對象轉換為字符串。
· String getString():返回File對象對應的文件的名字。
· String getParent():返回File對象對應的文件的父目錄。
· String getPath():返回File對象對應的文件的相對路徑。
· String AbsolutePath():返回File對象對應的文件的絕對路徑。
· Long lastModified:返回文件最后修改的時間。
· Long length():返回文件的大小。
實例3-10 練習使用文件類File(daima\3\Fileone)。
編寫實現文件Fileone.java,主要代碼如下所示。
import java.io.*; class FileOne { public static void main(String args[]) { File a=new File("D:\\圖片"); File b=new File("D:\\圖片","新世紀.doc"); File c=new File(a,"新世紀.doc"); if(a.exists()) { System.out.println("a 檢查到D盤有圖片文件夾"); } if(b.isFile()) { System.out.println("b 檢查到D盤的圖片文件夾有新世紀.doc文件"); } if(c.isFile()) { System.out.println("c 檢查到D盤的圖片文件夾有新世紀.doc文件"); } } }
執行程序前,需要預先將文件夾“圖片”復制到D盤根目錄下,然后執行程序,會得到如圖3-27所示的效果。

圖3-27 文件類File
2. 使用文件類處理文件
使用文件類處理文件是十分常見的操作,為了加深讀者對它的理解,下面將通過一個實例來講解。
實例3-11 練習使用文件類處理文件(daima\3\Filetwo.java)。
編寫實現文件Filetwo.java,具體代碼如下所示。
import java.io.File; import java.io.IOException; public class Filetwo { public static void main(String args[]) { File f1=new File("D:\\mother\\wei"); File f2=new File(f1,"1.txt"); File f3=new File("E:\\father\\1.txt"); System.out.println(f2); System.out.println(); System.out.println("exists: "+f2.exists()); System.out.println("isfile: "+f2.isFile()); System.out.println("Directory: "+f2.isDirectory ()); System.out.println("Read: "+f2.canRead()); System.out.println("Write: "+f2.canWrite()); System.out.println("Hidden: "+f2.isHidden()); System.out.println("ReadOnly: "+f2.setReadOnly ()); System.out.println("Name: "+f2.getName()); System.out.println("Parent: "+f2.getParent()); System.out.println("AbsolutePath: "+f2.getAbsoluteFile()); System.out.println("lastModified: "+f2.lastModified ()); System.out.println("length: "+f2.length()); System.out.println("list "+f2.list()); System.out.println("mkdir "+f2.mkdir()); File f4=new File("Student.java"); System.out.println("newname"+f2); f2.renameTo(f4); System.out.println("Name: "+f4.getName()); System.out.println(f2+"exist "+f2.exists()); System.out.println(f4+"exist "+f2.exists()); System.out.println("delete"+f4); System.out.println("equals "+f2.equals(f4)); f4.delete(); System.out.println(f4+"exist "+f4.exists()); System.out.println("String "+f2.toString()); } }
在執行代碼之前,需要將文件夾“father”和“mother”復制到D盤根目錄下,執行后會得到如圖3-28所示的效果。

圖3-28 執行結果
3.4.4 訪問操作SD卡
在Android平臺中,可以在兩個地方對文件進行讀寫操作,一個是SD卡,另一個是手機的存儲文件夾。使用前面講解的I/O技術可以對SD卡中的文件或手機的存儲文件夾中的文件進行操作。基于SD卡的特殊性,需要事先實現程序對SD卡的訪問,才能操作SD卡中的文件。SD卡是當前智能手機的一部分,我們經常在SD卡中存儲大量的文件,如音樂、視頻和游戲。因為SD卡的重要性,所以這里不可避免地需要涉及操作SD卡文件的知識。
訪問SD卡中數據的方法與在Java中進行文件讀取操作的方法十分類似,只需注意正確地設置文件的位置及文件名即可。
在Android模擬器中,可以使用FAT32格式的磁盤鏡像作為SD卡的模擬,具體過程如下所示。
step 1 進入Android SDK目錄下的“tools”子目錄,然后運行如下命令。
mksdcard -l sdcard 512M /your_path_for_img/sdcard.img
通過上述命令創建了一個512MB的SD卡鏡像文件。
step 2 通過如下命令在運行模擬器的時候指定路徑,在此需要使用完整路徑。
emulator -sdcard /your_path_for_img/sdcard.img
這樣,模擬器中就可以使用“/sdcard”這個路徑來指向模擬的SD卡了。
接下來需要復制本機文件到SD卡中,甚至需要管理SD卡中的文件內容。實現方案有如下兩種。
· 在Linux下面可以mount成一個loop設備,先創建一個目錄,如android_sdcard,然后執行如下命令。
mount -o loop sdcard.img android_sdcard
這樣,管理這個目錄就是管理sdcard的內容了。
· 在Windows可視環境下可以用mtools來做管理,也可以用Android SDK自帶的如下命令(這個命令在linux下面也可以用)。
adb push local_file sdcard/remote_file
執行完上面的命令后,需要執行下面的命令,啟動Android模擬器。
emulator -avd avd1 -sdcard card/mycard.img
如果在開發環境(Eclipse)中,可以在Run Configuration對話框中設置啟動參數,當然,也可以在Preferences對話框中設置默認啟動參數。這樣在新建立的Android工程中就自動加入了裝載sdcard虛擬文件的命令行參數。
接下來,將通過一個具體的實例講解讀取SD卡中數據的方法。
實例3-12 操作內存和SD卡中的文件(daima\3\Filetwo)。
(1)解決思路
移動手機的存儲控件可分為內存控件和存儲卡控件。在本實例的屏幕中添加了兩個按鈕,分別用于添加和刪除內存或存儲卡內的文件,并且準備使用3個Activity,主程序是Entry Activity,另外兩個分別用于處理內存卡和存儲卡。當用戶選擇內存或存儲卡后,將以列表形式顯示其中所有的目錄和文件名,并在menu菜單中顯示“添加”或“刪除”按鈕。單擊“添加”按鈕后會顯示一個添加菜單,實現添加文件功能。當單擊“刪除”按鈕后,可以刪除指定的文件。
(2)具體實現
本實例的實現文件是SDC.java、SDC_1.java和SDC_2.java,接下來分別介紹這些文件的具體實現。
step 1 編寫文件SDC.java。
· 用getFilesDir()方法獲取SD Card的目錄,設置當SD Card無插入時myButton2處于不能按的狀態。對應代碼如下所示。
/* 取得目前File目錄 */ fileDir = this.getFilesDir(); /* 取得SD Card目錄 */ sdcardDir = Environment.getExternalStorageDirectory(); /* 當SD Card無插入時將myButton2設成不能按 */ if (Environment.getExternalStorageState().equals(Environment.MEDIA_REMOVED)) { myButton2.setEnabled(false); }
· 分別定義按鈕單擊處理事件setOnClickListener和setOnClickListener,具體代碼如下所示。
myButton1.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { String path = fileDir.getParent() + java.io.File.separator + fileDir.getName(); showListActivity(path); } }); myButton2.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View arg0) { String path = sdcardDir.getParent() + sdcardDir.getName(); showListActivity(path); } }); }
· 定義方法showListActivity(String path),并定義一個Intent對象intent,然后將路徑傳到SDC_1。具體代碼如下所示。
private void showListActivity(String path) { Intent intent = new Intent(); intent.setClass(SDC.this, SDC_1.class); Bundle bundle = new Bundle(); /* 將路徑傳到SDC_1 */ bundle.putString("path", path); intent.putExtras(bundle); startActivity(intent); } }
step 2 編寫文件SDC_1.java。
· 將主Activity傳來的path字符串作為傳入路徑,如果不存在這個路徑,則使用java.io.File來創建一個新的,具體代碼如下所示。
public class SDC_1 extends ListActivity { private List<String> items = null; private String path; protected final static int MENU_NEW = Menu.FIRST; protected final static int MENU_DELETE = Menu.FIRST + 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ex06_09_1); Bundle bunde = this.getIntent().getExtras(); path = bunde.getString("path"); java.io.File file = new java.io.File(path); /* 當該目錄不存在時將創建目錄 */ if (!file.exists()) { file.mkdir(); } fill(file.listFiles()); }
· 使用onOptionsItemSelected根據單擊的MENU選項實現添加或刪除操作,具體代碼如下所示。
public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch (item.getItemId()) { case MENU_NEW: /* 單擊添加MENU */ showListActivity(path, "", ""); break; case MENU_DELETE: /* 單擊刪除MENU */ deleteFile(); break; } return true; }
· 使用onCreateOptionsMenu(Menu menu)添加需要的MENU,具體代碼如下所示。
@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); /* 添加MENU */ menu.add(Menu.NONE, MENU_NEW, 0, R.string.strNewMenu); menu.add(Menu.NONE, MENU_DELETE, 0, R.string.strDeleteMenu); return true; }
· 當單擊文件名后獲取文件內容,具體代碼如下所示。
protected void onListItemClick (ListView l, View v, int position, long id) { File file = new File (path + java.io.File.separator + items.get(position)); /* 單擊文件取得文件內容 */ if (file.isFile()) { String data = ""; try { FileInputStream stream = new FileInputStream(file); StringBuffer sb = new StringBuffer(); int c; while ((c = stream.read()) != -1) { sb.append((char) c); } stream.close(); data = sb.toString(); } catch (Exception e) { e.printStackTrace(); } showListActivity(path, file.getName(), data); } }
· 使用方法fill(File[] files)將內容填充到文件,具體代碼如下所示。
private void fill(File[] files) { items = new ArrayList<String>(); if (files == null) { return; } for (File file : files) { items.add(file.getName()); } ArrayAdapter<String> fileList = new ArrayAdapter<String> (this,android.R.layout.simple_list_item_1, items); setListAdapter(fileList); }
· 使用showListActivity來顯示已經存在的文件列表,具體代碼如下所示。
private void showListActivity (String path, String ilename, String data) Intent intent = new Intent(); intent.setClass(SDC_1.this, SDC_2.class); Bundle bundle = new Bundle();
{
/* 文件路徑 */ bundle.putString("path", path); /* 文件名 */ bundle.putString("ilename", ilename); /* 文件內容 */ bundle.putString("data", data); intent.putExtras(bundle); startActivity(intent); }
· 使用方法deleteFile()刪除選定的文件,具體代碼如下所示。
private void deleteFile() { int position = this.getSelectedItemPosition(); if (position >= 0) { File file = new File(path + java.io.File.separator + items.get(position)); /* 刪除文件 */ file.delete(); items.remove(position); getListView().invalidateViews(); } } }
step 3 編寫文件SDC_2.java。
· 設置myEditText1來放置文件內容,然后定義Bundle對象bundle來獲取路徑path和數據data,具體代碼如下所示。
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.ex06_09_2); /* 放置文件內容的EditText */ myEditText1 = (EditText) findViewById(R.id.myEditText1); Bundle bunde = this.getIntent().getExtras(); path = bunde.getString("path"); data = bunde.getString("data"); fileName = bunde.getString("fileName"); myEditText1.setText(data); }
· 使用onOptionsItemSelected根據用戶選擇而進行操作,當選擇MENU_SAVE時會保存這個文件,具體代碼如下所示。
public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); switch (item.getItemId()) { case MENU_SAVE: saveFile(); break; } return true; }
· 使用onCreateOptionsMenu(Menu menu)添加一個MENU,具體代碼如下所示。
@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); /* 添加MENU */ menu.add(Menu.NONE, MENU_SAVE, 0, R.string.strSaveMenu); return true; }
· 使用方法saveFile()保存一個文件。定義LayoutInflater對象factory用于跳出存檔,然后通過myDialogEditText獲取Dialog里的EditText,最后實現存檔處理。具體代碼如下所示。
private void saveFile() { /* 跳出存檔的Dialog */ LayoutInflater factory = LayoutInflater.from(this); final View textEntryView = factory.inflate (R.layout.save_dialog, null); Builder mBuilder1 = new AlertDialog.Builder(SDC_2.this); mBuilder1.setView(textEntryView); /* 取得Dialog里的EditText */ myDialogEditText = (EditText) textEntryView.findViewById (R.id.myDialogEditText); myDialogEditText.setText(fileName); mBuilder1.setPositiveButton ( R.string.str_alert_ok,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialoginterface, int i) { /* 存檔 */ String Filename = path + java.io.File.separator + myDialogEditText.getText().toString(); java.io.BufferedWriter bw; try { bw = new java.io.BufferedWriter(new java.io.FileWriter( new java.io.File(Filename))); String str = myEditText1.getText().toString(); bw.write(str, 0, str.length()); bw.newLine(); bw.close(); } catch (IOException e) { e.printStackTrace(); } /* 回到SDC_1 */ Intent intent = new Intent(); intent.setClass(SDC_2.this, SDC_1.class); Bundle bundle = new Bundle(); /* 將路徑傳到SDC_1 */ bundle.putString("path", path); intent.putExtras(bundle); startActivity(intent); finish(); } }); mBuilder1.setNegativeButton(R.string.str_alert_cancel, null); mBuilder1.show(); } }
執行后的效果如圖3-29所示,當單擊一個按鈕后會顯示對應的存儲信息,如圖3-30所示。當單擊“MENU”后,會彈出兩個MENU選項,如圖3-31所示。此時,可以通過這兩個選項分別管理存儲卡中的數據。

圖3-29 初始效果

圖3-30 SD卡的文件信息

圖3-31 單擊MENU按鈕
注意:在Eclipse環境中可以在可視化環境下管理SD卡中的文件。
step 1 單擊Eclipse右上角的“DDMS”選項卡,如圖3-32所示。

圖3-32 “DDMS”選項卡
step 2 在右側列表中展開“mnt”選項,里面的“sdcard”文件夾就是系統的模擬SD卡目錄,如圖3-33所示。

圖3-33 SD卡目錄
圖3-34中操作按鈕的具體說明如下所示。

圖3-34 操作SD卡的按鈕
:下載SD卡中的文件到本地;
step 3 通過頂部的工具按鈕可以對SD卡進行操作,如圖3-34所示。
:上傳本地文件到SD卡;
:在SD卡中新建文件;
:刪除SD卡中的某個文件。