Java中IO流操作
本文主要涉及到对Java中IO常用方法的使用介绍,如File、字节流、字符流、内存操作流以及RandomAccessFile。相比较而言虽然本文很是基础,但也确实是很受用的。缺少了对缓冲输入输出流部分的介绍,当时也是想单独记录一篇笔记的。其实是还没有毕业时记录的学习笔记,一直在移动硬盘中存着的,所以说是很基础的知识点。
1.File类
File类是整个IO包中唯一一个与文件本身有关的操作类,所谓的与文件本身有关就是指的创建、删除等的操作。File类是一个跨平台的类,所以要注意在各个操作系统中的区别。此时可以采用File类的静态属性来使操作具有跨平台性。
System.out.println(File.separator);//与系统有关的名称分隔符 / System.out.println(File.pathSeparator);//与系统有关的路径分隔符 ; System.out.println(Arrays.toString(File.listRoots()));//列出可用的文件系统根。[C:\, D:\, E:\, F:\, G:\, H:\]
注意:File类可以表示一个文件,也可以表示一个目录,在java中文件和目录都属于这个类,而且区分不是非常的明显。为了判断,File类中给出了以下方法:
- 判断是否是文件 public boolean isFile()
- 判断是否是目录(或文件夹)public boolean isDirectory()
File f1=new File("D:"+File.separator+"tomcat");//D盘有一个tomcat的文件夹 System.out.println(f1.isFile());//false System.out.println(f1.isDirectory());//true File f2=new File("D:"+File.separator+"a.txt");//D盘有一个a.txt的文件 System.out.println(f2.isFile());//true System.out.println(f2.isDirectory());//false File f3=new File("D:"+File.separator+"tom");//D盘没有一个tom的文件夹 File f4=new File("D:"+File.separator+"b.txt");//D盘没有一个b.txt的文件 System.out.println(f4.isFile());//false System.out.println(f3.isDirectory());//false
1.1.创建删除文件
File类下的方法是对磁盘上的文件的磁盘操作,但是无法读取文件的内容。
注意:创建一个文件对象和创建一个文件在java中时两个不同的概念。前者是在虚拟机中创建了一个文件,但却没有将它真正的创建到OS的文件系统中,随着虚拟机的关闭,这个文件就消失了。而创建一个文件才是真正的在操作系统的文件系统中创建一个文件。但是一旦涉及到文件内容读写,则不用createNewFile()也会新建一个文件。
public boolean createNewFile() throws IOException /* * 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件 * 若是此路径已经存在同名文件,则不会重新创建一个文件 */ File f=new File("D:"+File.separator+"a.txt"); try { f.createNewFile(); } catch (IOException e) { e.printStackTrace(); } /* * 若是指定路径的文件不存在,也不会抛出异常 */ File f=new File("D:"+File.separator+"test.txt"); f.delete();
1.2.创建删除目录
/* * 若是父目录test及子目录login都不存在,则f.mkdir()必不能创建目录 * 此时必须用f.mkdirs()创建。 * 若已经存在则不会创建新的目录 */ File f=new File("D:\\test\\login"); f.mkdir(); f.delete();//只能删除最里层的一层子目录,也就是login目录
1.3.列出特定目录下的文件名
public String[] list()显示出来的只是一个文件或文件夹的名,但并不是完整的路径名 File f=new File("D:\\Program Files"); if(f.isDirectory()){ String list[]=f.list(); for(int i=0;i<list.length;i++){ System.out.println(list[i]); } } //输出结果: Adobe Apache Software Foundation apache-tomcat-6.0.18 Baidu Baofeng
1.4.列出完整路径的文件对象
public File[] listFiles()该方法输出的是一个完整的路径,并且返回一个文件对象 File f=new File("D:\\Program Files"); if(f.isDirectory()){ File list[]=f.listFiles(); for(int i=0;i<list.length;i++){ System.out.println(list[i]); } }
输出结果
D:\Program Files\Adobe D:\Program Files\Apache Software Foundation D:\Program Files\apache-tomcat-6.0.18 D:\Program Files\Baidu D:\Program Files\Baofeng
1.5 列出给的目录中全部的文件路径
本程序肯定只能依靠递归的操作完成,因为在一个给定的路径下有可能还是文件夹,那么如果是文件夹的话则肯定要继续列出,重复判断。包括windows的资源管理器就是采用了这种形式的代码完成的。
public static void main(String[] args) { File f=new File("D:\\tomcat"); list(f); } public static void list(File file){ if(file.isDirectory()){ File f[]=file.listFiles(); if(f!=null){ for(int i=0;i<f.length;i++){ list(f[i]); } } } System.out.println(file); }
2.字节流和字符流
File类本身与文件操作有关,但是要操作文件内容则必须采用字节流或字符流实现。
- 使用File类找到文件
- 通过字节流或字符流的子类进行对象的实例化
- 进行读或写的操作
- 关闭字节或字符流
2.1.字节输出流OutputStream
OutputStream类
public abstract class OutputStream extends Object implements Closeable, Flushable
字节输出流是一个抽象类,常用如下几个方法:
public void close() throws IOException public void write(byte[] b) throws IOException public void write(byte[] b, int off, int len) throws IOException public abstract void write(int b) throws IOException
FileOutputStream类
public class FileOutputStream extends OutputStream
最常用的两个构造方法如下:
public FileOutputStream(File file) throws FileNotFoundException public FileOutputStream(String name,boolean append) throws FileNotFoundException public FileOutputStream(File file, boolean append) throws FileNotFoundException public static void main(String[] args) { //1.通过File找到文件 File file=new File("D:"+File.separator+"test.txt"); OutputStream out=null; try { //实例化OutputStream对象 out=new FileOutputStream(file); String info="hello world"; byte data[]=info.getBytes();//将字符串变成数组 out.write(data); out.close(); } catch (IOException e) { e.printStackTrace(); } }
这种方式是通过字节数组一次性输出到文件,当然也可以通过一个循环一个字节一个字节输出到文件:
byte data[]=info.getBytes();//将字符串变成数组 for(int i=0;i<data.length;i++){ out.write(data[i]); } out.close();
上述两种方式的输出都不是很理想,可以每次输入文件一定的长度,也是采取循环的方式输出到文件。
public static void main(String[] args) { //1.通过File找到文件 File file=new File("D:"+File.separator+"test.txt"); OutputStream out=null; try { //实例化OutputStream对象 out=new FileOutputStream(file); String info="1234567890abcdefghijklmnopqrstuvwrst"; byte data[]=info.getBytes();//将字符串变成数组 /** * 就是讲一个非常长的数组平均分成若干份,每次都输出文件一个定长的子数组 */ out.write(data, 0, data.length/2); out.close(); } catch (IOException e) { e.printStackTrace(); }
上述几种方式的输出到文件都会覆盖原文件中的内容,用另外一个构造函数out=new FileOutputStream(file,true); 这样就可以每次都在输出的文件的末尾追追加内容了。
若想使每次写入文件内容都换行,则可以每次写入末尾夹“\r\n”2.2.字节输入流InputStream
程序中可以使用Output Stream进行输出的操作,也可以使用InputStream完成输入的操作,此类定义如下:
public abstract class InputStream extends Object implements Closeable
InputStream类中定义的方法如下:
- 关闭:public void close() throws IOException
- 读取一个字节:public abstract int read() throws IOException
- 读取一组内容:public int read(byte[] b) throws IOException
如果要读,则肯定需要一个数组,数组肯定要首先开辟好大小,用于接收内容。
但是,与OutputStream类似,要读取就要观察FileInputStream类的构造方法:
public class FileInputStream extends InputStream 构造方法:public FileInputStream(File file) throws FileNotFoundException 构造方法:public FileInputStream(String name) throws FileNotFoundException public static void main(String[] args) { //1.通过File找到文件 File file=new File("D:"+File.separator+"test.txt"); InputStream is=null; try { is=new FileInputStream(file); byte data[]=new byte[1024];//静态开辟一个空间 int len=is.read(data); System.out.println(new String(data,0,len)); System.out.println(data.length);//1024 } catch (IOException e) { e.printStackTrace(); } }
上述方式是一次性读取一个制定数组大小长度的字节,也可以动态指定开辟空间的大小。
public static void main(String[] args) { //1.通过File找到文件 File file=new File("D:"+File.separator+"test.txt"); InputStream is=null; try { is=new FileInputStream(file); byte data[]=new byte[(int)file.length()];//动态开辟一个空间 int len=is.read(data); System.out.println(new String(data,0,len)); System.out.println(data.length);//57 } catch (IOException e) { e.printStackTrace(); }
上述两种方式是每次都读取一个数组长度的字节,下面则是一个一个的读取。
is=new FileInputStream(file); byte data[]=new byte[(int)file.length()];//动态开辟一个空间 for(int i=0;i<data.length;i++){ data[i]=(byte) is.read(); } System.out.println(new String(data,0,data.length)); System.out.println(data.length);//57
文件输入流可以不用File类也可以,直接传递一个字符串,这样也行,代码如下。
public static void main(String[] args) { InputStream is=null; try { is=new FileInputStream("D:"+File.separator+"test.txt");//直接用另一个构造函数的方式 byte data[]=new byte[1024];//开辟一个空间 int len=is.read(data); System.out.println(new String(data,0,len)); System.out.println(data.length);//1024 } catch (IOException e) { e.printStackTrace(); } }
小结:文件输入(InputStream)输出(OutputStream)都可以不传递一个文件对象,直接传一个文件的路径字符串。
2.3.字符输出流writer
Writer为字符输出流,也是一个抽象类,当然需要一个子类FileWriter来操作文件内容。
public static void main(String[] args) { //1.通过File找到文件 File file=new File("D:"+File.separator+"test.txt"); Writer out=null; try { out=new FileWriter(file); String info="hello world"; out.write(info); out.close(); } catch (IOException e) { e.printStackTrace(); } }
当然同字节流一样,也可以追加,也可以一次写入一个字符数组或单个字符。
2.4.字符输入流Reader
public static void main(String[] args) { // 1.通过File找到文件 File file = new File("D:" + File.separator + "test.txt"); Reader is = null; try { is = new FileReader(file); char data[] = new char[1024];// 开辟一个空间 int len = is.read(data); System.out.println(new String(data, 0, len)); } catch (IOException e) { e.printStackTrace(); } }
2.5字节流和字符流的区别
字节流和字符流在使用上的代码结构都是非常类似的,但是其内部本身也是有区别的,因为在进行字符流操作的时候会使用到缓冲区,而字节流操作的时候是不会使用到缓冲区的。
在输出的时候,OutputStream类即使最后没有关闭内容也可以输出。但是如果是Writer的话,则如果不关闭,最后一条内容是无法输出的,因为所有的内容都是保存在了缓冲区之中,每当调用了close()方法就意味着清空缓冲区了。那么可以证明字符流确实使用了缓冲区:
- 字节流:程序->文件
- 字符流:程序->缓冲区->文件
如果现在字符流即使不关闭也可以完成输出的话,则必须强制性清空缓冲区:
public void flush() throws IOException
两者相比,肯定使用字节流更加的方便,而且在程序中像图片、MP3 等都是采用字节的方式的保存,那么肯定字节流会比字符流使用的更广泛。
3.内存操作流
在之前讲解的FileInputStream和FileOutputStream所有操作的目标都是文件,那么现在如果要求有一些临时的信息通过IO流的话,如果将这些信息保存在文件中的话肯定不合理,因为操作的背后还得把文件删除掉,所以此时IO中就提供了一些内存操作流,通过内存操作流输入输出的目标是内存。
ByteArrayInputStream类
public class ByteArrayInputStream extends InputStream public ByteArrayInputStream(byte[] buf) 使用buf作为其的缓存区数组。
ByteArrayOutputStream类
public ByteArrayOutputStream() public ByteArrayOutputStream(int size) public class ByteArrayOutputStream extends OutputStream //将字符串数组大写 public static void main(String[] args) { String info="hello world"; ByteArrayOutputStream bos=null; ByteArrayInputStream bis=null; //将内存保存在内存中 bis=new ByteArrayInputStream(info.getBytes()); bos=new ByteArrayOutputStream(); int temp=0; while((temp=bis.read())!=-1){ bos.write(Character.toUpperCase(temp)); } String str=bos.toString();//取出内存中输出流 System.out.println(str); }
现在虽然完成了内存的操作,但是可以发现现在的IO都是从内存中,也就是说可以将内存当作一个临时的文件进行操作,所以内存操作流一般在产生临时文件内容的时候使用。
4.RandomAccessFile
随即访问文件流,要想随机访问,则在存储数据时要使数据长度一致,否则无法实现该功能的。
public class RandomAccessFile extends Object implements DataOutput, DataInput, Closeable public RandomAccessFile(File file,String mode) throws FileNotFoundException
值 含意 "r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 "rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 "rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 "rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
4.1随机写入文件
使用从DataOutput接口中的方法writeXxx()方法写入数据。
public static void main(String[] args) { File file=new File("D:"+File.separator+"test.txt"); try { RandomAccessFile raf=new RandomAccessFile(file,"rwd"); //写入第一条数据 String name="zhangsan";//8位字符长度 int age=20; raf.writeBytes(name);//以字节的方式写入 raf.writeInt(age);//以整数形式写入 //写入第二条数据 name="lisi ";//不够8位,补齐8位 age=22; raf.writeBytes(name);//以字节的方式写入 raf.writeInt(age);//以整数形式写入 //写入第三条数据 name="wangwu ";//不够8位,补齐8位 age=23; raf.writeBytes(name);//以字节的方式写入 raf.writeInt(age);//以整数形式写入 raf.close(); } catch (IOException e) { e.printStackTrace(); } }
4.2. 随机读取文件
读取文件时都是从DataInput接口中实现的方法,有一系列readXxx()方法,可以读取各种类型的数据。因为RandomAccessFile可以随机读取文件,所以又一系列控制方法。
回到读取点public void seek(long pos) throws IOException 跳过多少个字节public int skipBytes(int n) throws IOExceptionpublic static void main(String[] args) { File file=new File("D:"+File.separator+"test.txt"); try { RandomAccessFile raf=new RandomAccessFile(file,"r"); byte b[]=null; int age; raf.skipBytes(12);//跳过第一个人的信息 b=new byte[8]; for(int i=0;i<b.length;i++){ b[i]=raf.readByte(); } age=raf.readInt();//读取数字 System.out.println("第二个人的信息:"); System.out.println("\t|--姓名:"+new String(b)); System.out.println("\t|--年龄:"+age); raf.seek(0);//第一个人的信息 for(int i=0;i<b.length;i++){ b[i]=raf.readByte(); } age=raf.readInt();//读取数字 System.out.println("第一个人的信息:"); System.out.println("\t|--姓名:"+new String(b)); System.out.println("\t|--年龄:"+age); raf.skipBytes(12);//跳过第二个人的信息 for(int i=0;i<b.length;i++){ b[i]=raf.readByte(); } age=raf.readInt();//读取数字 System.out.println("第三个人的信息:"); System.out.println("\t|--姓名:"+new String(b)); System.out.println("\t|--年龄:"+age); raf.close(); } catch (IOException e) { e.printStackTrace(); } }