java IO流(下)

上一篇文章中总结了java IO流基本流对象操作方法及典型应用示例,本文学习并总结IO包中的其它常用重要对象,这些对象对完整及更加灵活地使用和操作IO流是必不可少的。这些流对象种类较多,不像上一篇中只有字符流、字节流及各自的缓冲区流对象等,学习时可以根据其名称记住这些流对象各自的特点和主要方法,实际项目使用时可以再查阅API手册。

File类
将文件或文件夹封装成对象,方便对文件或文件夹的属性进行操作。流只能操作数据,File对象可以作为参数传递给流的构造函数。

File类构造函数

可以将已有的和未出现的文件(夹)封装成File对象,File类构造函数有:
File(File parent, String child), 将路径名和文件名参数分开传入,方便单独操作文件名参数。
File(String parent, String child), 等同于上一种方式。
File(String pathname), pathname可以是相对路径也可以是绝对路径。
FIle(URI uri), 将uri转换成一个抽象路径名来创建File对象。
上述构造函数中,Windows下路径分隔符要使用"\\", linux中路径分隔符是"/", File类提供了跨平台的文件路径分隔符File.separator, 是File类的静态String成员。
File类重写了toString()方法,打印的是文件对象的路径名(相对路径或绝对路径,看创建的时候是什么)。

File常用方法

1. 创建
boolean createNewFile(), 在指定位置创建文件,如果该文件已经存在,则不创建,返回false;调用了系统底层方法,会抛出IOException.
static File createTempFile(String perfix, String suffix), 使用指定的前缀和后缀在系统默认临时文件目录创建临时文件。可使用echo %temp%(windows系统)或echo $temp(linux系统)查看默认临时文件目录。临时文件要程序运行时产生,程序结束后可能销毁,也可能留存(垃圾文件)。
static File createTempFile(String perfix, String suffix, File directory), 在指定目录下创建临时文件。
boolean mkdir(), 创建单级文件夹。
boolean mkdirs(), 创建多级文件夹。

2. 删除
boolean delete(), 删除失败时返回false,不抛异常.
void deleteOnExit(), 在程序退出时删除指定文件,不抛异常。
java删除文件不进回收站。

3. 判断
boolean canExecute(), 文件是否可执行,返回true时,可加入Runtime类执行该文件。
boolean compareTo(String pathname), 按字母顺序比较文件路径名。还可能自定义比较器按其它方式比较路径名。
boolean exists(), 文件是否存在,在使用下面2个方法判断File对象是文件还是文件夹时必须先判断文件是否存在,文件不存在时,下面2个方法都返回false.
boolean isFile(), 判断是不是文件。
boolean isDirectory(), 判断是不是目录。
boolean isHidden(), 是否隐藏,系统中有些文件,如windows下system开头的系统卷标目录,java无法访问,需要事先用此方法判断,不读取隐藏的文件。
boolean isAbsolute(), 是否是绝对路径,文件可以不存在,只要封装时带盘符,即返回true.

4. 获取
String getName(), 只返回文件名,没有路径名
String getPath(), 返回File对象封装时指定的路径名,相对或绝对路径。
String getParent(), 返回绝对路径中的父目录,如果封装时使用的是相对路径,且相对路径中有上一级目录,则返回该上级目录,否则返回null.
String getAbsolutePath(), 返回绝对路径。
File getAbsoluteFile(), 返回绝对路径封装成的对象。
long lastModified(), 最后修改时间,毫秒值。
long length(), 返回文件(夹)大小。文件夹大小是0;FileInputStream类中的int available()方法可以使用该方法实现, 不过available()函数只能返回int类型的值, 不能超出int取值范围。
boolean renameTo(File f), 重命名,可以实现文件移动(剪切).

5. 文件列表
static File[] listRoots(), 列出系统中有效盘符。
String[] list(), 列出一个目录下所有的文件名和文件夹名称,包含隐藏文件;如果调用此方法的File对象应该是一个已存在的目录,如果是不一个文件或不存在的目录,则返回null,操作返回值时会报空指针异常。
String[] list(FilenameFilter filter), 只返回满足过滤条件的文件和文件夹。

FilenameFilter是一个接口,接口中只有一个方法,具体应用时可以用内部类方式复写该方法:
boolean accept(File dir, String name), dir是要过滤的目录,name代表dir目录下的文件或文件夹名称。
list方法根据accept函数的返回值确定是否列出文件(夹)名,只有满足accept条件的文件(夹)名才被返回。

File[] listFile(), 返回指定目录下的文件对象和文件夹对象,可以使用前面介绍的方法操作返回的文件(夹)对象,更方便实用。

下面用上面学到的方法实现目录的复制、目录层次打印和目录的删除:

import java.util.*;
import java.io.*;
public class FileDemo {
        public static void main(String[] args) throws Exception 
        {
            //复制文件/文件夹
            File f=new File("FileZilla Server");//要复制和打印目录结构的文件
            File desf=new File("copy_FileZilla Server");//目标位置
            copy(f,desf);
            //打印目录结构
            List<String> list=new ArrayList<String>();
            String listFile="FileZilla Server_dir.txt";
            showDir(f,0,list);
            writeToFile(list,listFile);
            //删除一个目录
            removeDir(f);            
        }
        //将一个文件或文件夹拷贝到另一个位置
        public static void copy(File src,File des) throws Exception
        {
            //首先判断要拷贝的是文件还是文件夹,是文件直接调用copyFile,是文件夹时调用递归函数copyDirectory.            
            if(src.exists()&&!src.isHidden()){
                if(src.isDirectory()){
                    des.mkdir();
                    copyDirectory(src,des);
                }else{
                    copyFile(src,des);
                }
            }               
        }
        //使用字节缓冲流完成文件的复制。
        public static void copyFile(File src,File des) throws Exception
        {

            BufferedInputStream bufis=new BufferedInputStream(new FileInputStream(src.getAbsolutePath()));
            BufferedOutputStream bufos=new BufferedOutputStream(new FileOutputStream(des.getAbsolutePath()));
            int by=0;
            while((by=bufis.read())!=-1){
                bufos.write(by);
                bufos.flush();
            }                
        }
        //递归复制文件夹
        public static void copyDirectory(File src,File des) throws Exception{
            File[] files=src.listFiles();
            for(File f:files){
                if(f.isFile()){
                    File df=new File(des.getAbsolutePath()+File.separator+f.getName());
                    copyFile(f,df);
                }
                else{                                
                    File newDes=new File(des.getAbsolutePath()+File.separator+f.getName());
                    File newSrc=new File(src.getAbsolutePath()+File.separator+f.getName());
                    newDes.mkdir();
                    copyDirectory(newSrc,newDes);
                }
            }               
        }
        //递归删除一个目录下的所有文件
        public static void removeDir(File dir){
            if(dir.exists()){
                File[] files=dir.listFiles();
                for(int x=0;x<files.length;x++){
                    if(!files[x].isHidden()&&files[x].isDirectory())
                        removeDir(files[x]);
                    else
                        System.out.println(files[x].toString()+":-file-: "+files[x].delete());
                    System.out.println(dir+":-dir-:"+dir.delete());                       
                }
            }
        }
        //列出一个目录结构中所有文件和文件夹,带层次,并保存到一个List集合中
        public static void showDir(File dir, int level,List<String> list) throws IOException
        {
            list.add(getLevel(level)+dir.getName());
            level++;
            File[] files=dir.listFiles();
            for(int x=0;x<files.length;x++){
                if(files[x].isDirectory())
                    showDir(files[x],level,list);
                else
                    list.add(getLevel(level)+files[x].getName());
            }
        }
        //下面这个函数是为了打印出层次结构。        
        public static String getLevel(int level){
            StringBuilder sb=new StringBuilder();
            sb.append("|--");
            for(int x=0;x<level;x++)
                sb.insert(0,"|  ");
            return sb.toString();
        } 
        //将List集合中的数据保存到硬盘中。
        public static void writeToFile(List<String> list, String listFile) throws IOException
        {
            BufferedWriter bufw=null;
            bufw=new BufferedWriter(new FileWriter(listFile));
            for(String s:list){
                bufw.write(s);
                bufw.newLine();
                bufw.flush();
            }
        }
}

Properties集合
Properties是HashTable的子类,具体Map集合的特点,里面存储的键值对都是字符串。
Properties是集合中与IO技术相结合的容器,该集合的特点是可用于键值对形式的配置文件。软件配置文件常见的有2种形式,xml文件和键值对文件(ini文件、properties文件都是键值对)。

Properties构造函数
Properties(), 无参数构造函数,创建一个空属性集合。
Properties(Properties default), 创建一个带指定默认值的属性集合。

Properties常用方法

1. 设置和获取元素
Object setProperty(String key, String value), 调用的是HashTable的put方法。
String getProperty(String key), 根据键获取值。
set<String> stringPropertyNames(), 返回键集合,可遍历返回的键集,调用上一个方法获取所有键的值。

2. Properties存取配置文件
void load(InputStream in), 从输入流中读取属性集合。从1.2版本开始就有了。
void load(Reader r), 从1.6版本开始有的。该方法可以用之前学过的IO流和String字符串知识来自己实现,将是与该方法一致。
void list(PrintStream out), 将属性集合输出到指定输出流上。从1.2版本开始就有了。
void list(PrintWriter out), 从1.6版本开始有的。
void store(PrintStream out, String comments), void store(PrintWriter out, String comments), 将内存中修改的属性键值保存到输出流中.

Properties基本用法示例:

import java.io.*;
import java.util.Properties;
public class PropertiesDemo {
	public static void main(String[] args) throws IOException
	{
		loadDemo(); 
		myLoad();
	}
	public static void loadDemo() throws IOException
	{
		Properties prop=new Properties();
		FileInputStream fis=new FileInputStream("info.txt");
		prop.load(fis);
		prop.setProperty("lisi", "90");//setProperty()改变的是内存中的值
		FileOutputStream fos=new FileOutputStream("info.txt");
		prop.store(fos, "haha");//将内存中的修改值保存到输出流中,并自动加上注释和当前时间。
		prop.list(System.out);
		fis.close();
		fos.close();
	}
	//实现与load()方法相似的功能
	public static void myLoad() throws IOException
	{
		//用一个流与info.txt文件关联
		BufferedReader bufr=new BufferedReader(new FileReader("info.txt"));
		String line=null;
		Properties prop=new Properties();
		//遍历读取一行数据,将该行数据用"="进行分割
		while((line=bufr.readLine())!=null){
			String[] arr=line.split("=");
			//等号左边作为键,右边作为值,存入到Properties集合即可。
			prop.setProperty(arr[0], arr[1]);
		}
		bufr.close();
		prop.list(System.out);
	}
}

下面是Properties操作配置文件的典型例子:

/*
 * 统计程序运行次数。
 */
import java.io.*;
import java.util.Properties;
public class RunCount{
	public static void main(String[] args) throws IOException
	{
		Properties prop=new Properties();
		File file=new File("count.ini");
		if(!file.exists())//判断文件是否存在,只创建一次
			file.createNewFile();
		FileInputStream fis=new FileInputStream(file);
		prop.load(fis);
		int count=0;
		String value=prop.getProperty("time");
		if(value!=null){
			count=Integer.parseInt(value);
			if(count>=5){
				System.out.println("使用次数已到!");
				return;
			}
			count++;
			prop.setProperty("time", count+"");
			FileOutputStream fos=new FileOutputStream(file);
			prop.store(fos, "");
			fis.close();
			fos.close();			
		}
	}
}
序列流-SequenceInputStream
SequenceInputStream, 是InputStream的子类,没有对应的序列输出流。
SequenceInputStream表示其他输入流的逻辑串联,可以将多个输入流顺序串连成一个流,只有读到最后一个流的结束标识时,序列流才结束。
SequenceInputStream可用来实现多个源文件到一个目的文件的复制。

SequenceInputStream构造函数
SequenceInputStream(InputStream s1, InputStream s2), 将2个流拼成一个序列流,先读取s1, 再读取s2.
SequenceInputStream(Enumeration<? extends InputStream> e), 传到多个输入流的枚举,可将多个流拼成一个序列流。

下面是一个序列流使用示例:

import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
public class SequenceDemo {
	public static void main(String[] args) throws IOException 
	{
		Vector<FileInputStream> v=new Vector<FileInputStream>();
		v.add(new FileInputStream("1.txt"));
		v.add(new FileInputStream("2.txt"));
		v.add(new FileInputStream("3.txt"));
		Enumeration<FileInputStream> en=v.elements();
		SequenceInputStream sis=new SequenceInputStream(en);
		FileOutputStream fos=new FileOutputStream("4.txt");
		byte[] buf=new byte[1024];
		int len=0;
		while((len=sis.read(buf))!=-1){
			fos.write(buf, 0, len);
		}
		fos.close();
		sis.close();//关闭所有源输入流。		
	}

}
切割文件
将一个文件切割成体积较小的多个碎片文件。多个碎片文件可使用合并流合成一个文件。
import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
public class SplitFile {
	static int count;
	public static void main(String[] args) throws IOException 
	{
		splitFile();
		merge();
	}
	public static void merge() throws IOException
	{
		ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
		for(int x=0;x<count;x++)
			al.add(new FileInputStream("C:\\splitfiles\\"+x+".part"));
		//迭代器的匿名内部类中使用,需要用filna修饰。
		final Iterator<FileInputStream> it=al.iterator();
		//使用匿名内部类创建枚举对象
		Enumeration<FileInputStream> en=new Enumeration<FileInputStream>(){
			public boolean hasMoreElements(){
				return it.hasNext();
			}
			public FileInputStream nextElement(){
				return it.next();
			}
		};
		//创建合并流,将多个碎片文件合成一个文件
		SequenceInputStream sis=new SequenceInputStream(en);
		FileOutputStream fos=new FileOutputStream("C:\\splitfiles\\2.bmp");
		byte[] buf=new byte[1024];
		int len=0;
		while((len=sis.read(buf))!=-1){
			fos.write(buf,0,len);
		}
		fos.close();
		sis.close();		
	}
	public static void splitFile() throws IOException
	{
		FileInputStream fis=new FileInputStream("C:\\1.bmp");
		FileOutputStream fos=null;
		byte[] buf=new byte[1024*1024];
		count=0;
		int len=0;
		//切割文件,每个文件大小不超过1M; 如果要切割电影文件等较大文件,每上碎片文件100M,那需要每存入1M时获取时输出流文件的大小 ,在fos累计达到64M时就新创建另一个输出流
		while((len=fis.read(buf))!=-1){
			fos=new FileOutputStream("C:\\splitfiles\\"+(count++)+".part");
			fos.write(buf, 0, len);
			fos.close();
		}
		fis.close();		
	}
}
对象的序列化-ObjectInputStream和ObjectOutputStream
ObjectInputStream和ObjectOutputStream, 分别是InputStream和OutputStream的子类,可直接操作对象,实现对象的反序列化和序列化(持久化)。
被操作的对象必须实现Serializable接口,否则会报NotSerializableException异常;Serializable接口在IO包中,类实现该接口后才允许序列化和反序列化;Serializable接口中里面没有方法,这类接口称为标识接口。
实现Serializable接口的类,编译时会生成UID,此UID标识通常是给编译器使用的;类被序列化后可以被修改,再编译时生成的UID会不一样;类中的每个成员(变量和函数)都有一个数字标识(数字标签),UID就是根据成员的数字标签算出来的。

ObjectInputStream和ObjectOutputStream构造方法
ObjectInputStream(), 为完全重新实现 ObjectInputStream 的子类提供一种方式。
ObjectInputStream(InputStream in), 创建从指定字节流读取的 ObjectInputStream。
ObjectOutputStream的构造函数形式与ObjectInputStream类似。

读取和写入方法
void write(int val), 写入int数据的低8位。
void writeInt(int val), 写入32位int数据。类似还有写入其它7种基本数据类型的write方法。
void writeObject(Object obj), 写入对象。
ObjectInputStream类中read方法形式与write方法基本相对应。

对象序列化和反序列化的一个简单示例如下,代码中也标注了一些注意事项。

class Person implements Serializable
{
    //手动将UID写死,不用编译器算出来的值,这样即使类改变了,之前已经序列化的对象也还能读取出来。
    public static final long serializableVersionUID=42l;
    int age;
    String name;
    static String country="cn";//静态成员不能序列化,静态成员是类成员,是不属于对象的,
    transient double weight;//被transient修饰的成员也不能序列化。
    Person(String name, int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return this.name+"...."+this.age;
    }
}
public class ObjectStreamdDemo{
    public static void main(String[] args){
        writeObj();
        readObj();//反序列化对象时,对象所属类的UID必须与该对象序列化时类的UID一致才行,否则会报ClassNotFoundException异常。
    }
    public static void readObj() throws IOException
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream("obj.txt"));
        Person p=(Person)ois.readObject();
        System.out.println(p);
        ois.close();
    }
    public static void writeObj() throws IOException
    {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("obj.txt"));//对象一般不会存在txt文件中,而.object文件中
        oos.writeObject(new Person("lisi",30));
        oos.close();
    }
}

管道流-PipedInputStream和PipedOutputStream
PipedInputStream和PipedOutputStream,是字节流基类的子类,这2个流可以直接连接进行写入和读取,不需要再通过字节数组这个中转站。

PipedInputStream和PipedOutputStream连接的2种方式
1. 创建对象时连接,使用带参数的构造函数
PipedInputStream(PipedOutputStream src), 创建连接到指定管道输出流的管道输入流。
PipedOutputStream(PipedInputStream snk), 创建连接到指定管道输入流的管道输出流。
2. 使用空参数构造函数创建管道流,并调用connect方法连接
管道输出流中有void connect(PipedInputStream snk)方法, 管道输入流中有void connect(PipedOutputStream src)方法。

管道流使用时通常使用多线程, 一个线程写数据,另一个读数据,不建议使用单线程;PipedInputStream中的read方法是个阻塞式方法,没有数据可读时会自动阻塞,有数据时自动恢复读取数据。

随机访问文件流-RandomAccessFile
直接继承自Object类,该类的实例对象支持对随机访问文件的读取和写入,因此是java IO包中的成员。

实际该类对象可以完成读写的原理就是内部封装了字节输入流和字节输出流。
对象内部实际封装了一个大型byte数组,存在指向该隐含数组的光标或索引,称为文件指针,该文件指针可以通过getFilePointer方法读取,并通过seek 方法设置。
输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。

该类构造函数形式:
RandomAccessFile(File file, String mode)
RandomAccessFile(String name, String mode)
其中mode可取值"r"、“rw"、"rws" 和"rwd", 具体含义如下:
"r" : 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出IOException.
"rw" : 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws":打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" : 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备.
如果模式为"r",不会创建文件,只会去读取一个已存在的文件,如果该文件不存在,则会出现IOException。
如果模式为"rw",操作的文件不存在时,会自动创建,如果存在则不会覆盖。

RandomAccessFile中的读写方法
int read(),读取一个字节数据,类型提升为int返回。
int read(byte[] buf), 将数据读到一个字节数组中。
int read(byte[] buf , int off, int len), 将数据读到字节数组的指定位置。
int readInt(), 读取一个32位int整数,类似地还有读取其它7种基本数据类型的read方法。
写入数据的write方法形式与上面read方法形式基本是对应的。

调整文件指针的方法
void seek(long pos), 设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
int skipBytes(int n), 尝试跳过输入的 n 个字节以丢弃跳过的字节,只能往下跳,不能往上跳。
这2个方法都会抛IOException.
存入RanddomAcces的数据最好是有规律的,这样数据比较容易定位和读取。

RandomAccessFiles对象可以实现数据的分段写,进而可用多个线程负责各个段的读写(多线程下载的原理)。

操作基本数据类型的流对象
DataInputStream和DataOutputStream, 分别是FileInputStream和FileOutputStream的子类,可以直接读写8种基本数据类型。

构造函数可以直接接收任何字节流对象:
DataInputStream(InputStream in), DataOutputStream(OutputStream).

读写方法:int readInt(), void writeInt(int v)等可以直接读写8种基本数据类型,读数据时必须按写入数据的顺序读,否则读出的数据不正确。
void writeUTF(String str),以与机器无关方式使用UTF-8修改版编码将一个字符串写入基础输出流,以该方式写入的数据只能用readUTF()读出,通过使用UTF-8字符集的转换流也无法读取。
static String readUTF(), 以UTF-8修改版编码格式读取字符串。UTF-8中一个汉字占3个字节,UTF-8修改版中一个汉字4个字节。

操作数组的流对象
ByteArrayInputStream、ByteArrayOutputStream,用流的思想来操作数组,分别是InputStream和OutputStream的子类。

构造函数形式
ByteArrayInputStream(byte[] buf)
ByteArrayInputStream(byte[] buf, int offset, int length)
字节数组读取流构造时,需要接收数据源,数据源是一个字节数组。
ByteArrayOutputStream()
ByteArrayOutputStream(int size)
字节数组写入流构造时,不用定义数据目的,因为该对象内部已经封装了可变长度(可自动增长)的字节数组,这就是目的地。

因为这2个对象都操作数组(读写方法可接收的参数与读取流一致),不调用任何底层方法,不使用系统资源,所以,不用进行close()关闭,即可关闭后流仍可使用

ByteArrayOutputStream类中有一个写入方法可直接将数据写到输出流
void writeTo(OutputStream out), 该方法会抛出IOException,这2个对象中只有该方法会抛异常。

类似地,还有操作字符数组和字符串的流对象分别是:
CharArrayReader和CharArrayWriter, StringReader和StringWriter,原理和使用方法类似字节数组流对象,都是内存中的流。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值