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卡中的某个文件。