• Java学习笔记(7)——输入输出


    1、File

    不同的操作系统对于档案系统路径的设定各有差别,例如在Windows中,一个路径的表示法可能是:

    "c:\\Windows\\Fonts\\"

    而在Linux下的路径设定可能是:

    "/home/justin/"

    Windows的路径指定是使用UNC(Universal Naming Convention)路径名,以\\开始表示磁盘根目录,如果没有以\\开始表示相对路径,c是可选的磁盘指定,后面跟随着 : 字符。而UNIX-Like系统的路径指定以 / 开始表示绝对路径,不以 / 开始表示相对路径。

    因而在程序中设定路径时会有系统相依性的问题,File类别提供一个抽象的、与系统独立的路径表示,您给它一个路径字符串,它会将它转换为与系统无关的抽象路径表示,这个路径可以指向一个档案、目录或是URI,您可以用以下四种方式来建构File的实例:

    File(File parent, String child)
    File(String pathname)
    File(String parent, String child)
    File(URI uri)

    一个File的实例被建立时,它就不能再被改变内容;File类别除了用来表示一个档案或目录的抽象表示之外,它还提供了不少相关操作方法,您可以用它来对档案系统作一些查询与设定的动作。

    来看个简单的程序:

    FileDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    import java.util.*;
    public class FileDemo {
    	public static void main(String[] args) {
    		try {
    			File file = new File(args[0]);
    			if(file.isFile()) { // 是否为档案
    				System.out.println(args[0] + " 档案");
    				System.out.print(
    				file.canRead() ? "可读 " : "不可读 ");
    				System.out.print(
    				file.canWrite() ? "可写 " : "不可写 ");
    				System.out.println(
    				file.length() + "字节");
    			}
    			else {
    				// 列出所有的档案及目录
    				File[] files = file.listFiles();
    				ArrayList fileList =
    				new ArrayList();
    				for(int i = 0; i < files.length; i++) {
    					// 先列出目录
    					if(files[i].isDirectory()) { //是否为目录
    						// 取得路径名
    						System.out.println("[" +
    						files[i].getPath() + "]");
    					}
    					else {
    						// 档案先存入fileList,待会再列出
    						fileList.add(files[i]);
    					}
    				}
    				// 列出档案
    				for(File f: fileList) {
    					System.out.println(f.toString());
    				}
    				System.out.println();
    			}
    		}
    		catch(ArrayIndexOutOfBoundsException e) {
    			System.out.println(
    			"using: java FileDemo pathname");
    		}
    	}
    }
    

    执行结果:

    java onlyfun.caterpillar.FileDemo C:\
    [C:\WINDOWS]
    [C:\Documents and Settings]
    [C:\Program Files]
    [C:\System Volume Information]
    [C:\Recycled]
    C:\A3N_A3L.10
    C:\bootfont.bin
    C:\ntldr
    C:\NTDETECT.COM
    C:\boot.ini
    C:\CONFIG.SYS
    C:\AUTOEXEC.BAT
    C:\IO.SYS
    C:\MSDOS.SYS
    C:\Finish.log
    C:\pagefile.sys
    C:\VIRTPART.DAT

    9、RandomAccessFile

    档案存取通常是「循序的」,每在档案中存取一次,读取档案的位置就会相对于目前的位置前进,然而有时候您必须对档案的某个区段进行读取或写入的动作,也就是进行「随机存取」(Random access),也就是说存取档案的位置要能在档案中随意的移动,这时您可以使用RandomAccessFile,使用seek()方法来指定档案存取的位置,指定的单位是字节,藉由它您就可以对档案进行随机存取的动作。

    为了方便,通常在随机存取档案时会固定每组资料的长度,例如一组学生个人数据,Java中并没有像C/C++中可以直接写入一个固定长度结构(Structure)的方法,所以在固定每组长度的方面您必须自行设计。

    下面这个程序示范了如何使用RandomAccessFile来写入档案,并随机读出一笔您所想读出的资料:

    Student.java
    package onlyfun.caterpillar;
    public class Student {
    	private String name; // 固定 15 字符
    	private int score;
    	public Student() {
    		setName("noname");
    	}
    	public Student(String name, int score) {
    		setName(name);
    		this.score = score;
    	}
    	public void setName(String name) {
    		StringBuilder builder = null;
    		if(name != null)
    		builder = new StringBuilder(name);
    		else
    		builder = new StringBuilder(15);
    		builder.setLength(15);
    		this.name = builder.toString();
    	}
    	public void setScore(int score) {
    		this.score = score;
    	}
    	public String getName() {
    		return name;
    	}
    	public int getScore() {
    		return score;
    	}
    	// 每笔数据固定写入34字节
    	public static int size() {
    		return 34;
    	}
    }
    
    RandomAccessFileDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    import java.util.*;
    public class RandomAccessFileDemo {
    	public static void main(String[] args) {
    		Student[] students = {
    			new Student("Justin", 90),
    			new Student("momor", 95),
    			new Student("Bush", 88),
    		new Student("caterpillar", 84)};
    		try {
    			File file = new File(args[0]);
    			// 建立RandomAccessFile实例并以读写模式开启档案
    			RandomAccessFile randomAccessFile =
    			new RandomAccessFile(file, "rw");
    			for(int i = 0; i < students.length; i++) {
    				randomAccessFile.writeChars(students[i].getName());
    				randomAccessFile.writeInt(students[i].getScore());
    			}
    			Scanner scanner = new Scanner(System.in);
    			System.out.print("读取第几笔数据?");
    			int num = scanner.nextInt();
    			randomAccessFile.seek((num-1) * Student.size());
    			Student student = new Student();
    			student.setName(readName(randomAccessFile));
    			student.setScore(randomAccessFile.readInt());
    			System.out.println("姓名:" + student.getName());
    			System.out.println("分数:" + student.getScore());
    			randomAccessFile.close();
    		}
    		catch(ArrayIndexOutOfBoundsException e) {
    			System.out.println("请指定文件名称");
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    	private static String readName(
    	RandomAccessFile randomAccessfile)
    	throws IOException {
    		char[] name = new char[15];
    		for(int i = 0; i < name.length; i++)
    		name[i] = randomAccessfile.readChar();
    		return new String(name).replace('\0', ' ');
    	}
    }
    
    

    在实例化一个RandomAccessFile对象时,要设定档案开启的方式,设定"r"表示只供读取,设定"rw"表示可读可写;为了让每组数据长度固 定,在写入name时,我们使用 StringBuilder 并设定其长度固定为15个字符,而读回name时则直接读回15个字符,然后再去掉空格符传回。

    10、InputStream与OutputStream

    计算机中的数据都是以0与1的方式来储存,如果您要在两个装置之间进行数据的存取,当然也是以0与1位的方式来进行,实际上数据的流动是透过电路,而上面 的数据则是电流,而在程序上来说,将数据目的地与来源之间抽象化为一个串流(Stream),而当中流动的则是位数据。

    01010101 Stream -->
    来源地 ===================== 目的地

    在Java中有两个类别用来作串流的抽象表示:InputStream与OutputStream。

    InputStream是所有表示位输入串流的类别之父类别,它是一个抽象类别,子类会重新定义它当中所定义的方法, InputStream用于从装置来源地读取数据的抽象表示,例如System中的标准输入串流 in 对象就是一个 InputStream,在程序开始之后,这个串流对象就会开启,以从标准输入装置中读取数据,这个装置通常是键盘或是其它使用者定义的装置。

    OutputStream是所有表示位输出串流的类别之父类别,它是一个抽象类别,子类会重新定义它当中所定义的方法, OutputStream是用于将数据写入目的地的抽象表示,例如System中的标准输出串流对象 out ,out 的类型是PrintStream, 这个类别是OutputStream的子类别(FilterOutputStream继承OutputStream, PrintStream再继承FilterOutputStream),在程序开始之后,这个串流对象就会开启,您可以将数据透过它来写入目的地装置,这 个装置通常是屏幕或其它使用者定义的装置。

    下面程序可以读取键盘输入串流,并将资料以10进位方式显示在屏幕上:

    StreamDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class StreamDemo {
    	public static void main(String[] args) {
    		try {
    			System.out.print("输入字符: ");
    			System.out.println("输入字符十进制表示: " +
    			System.in.read());
    			System.out.println("换行字符十进制表示: " +
    			System.in.read());
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    执行结果: 

    输入字符: A?
    输入字符十进制表示: 65 
    换行字符十进制表示: 10

    字符A输入后被标准输入串流读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了),在这边要注意的是read()只读取一个字节的数据,而当输入A并按Enter键时,实际上在串流中会有A的位数据与换行字符的位数据,换行字符的位数据以十进制来表示的话就是10。

    操作系统之间的换行字符各不相同,Windows 为"\r\n",Linux 为'\n',而 Mac 为'\r'。

    11、BufferedInputStream、 BufferedOutputStream

    在介绍 FileInputStream、 FileOutputStream的 例子中,您使用了一个数组来作为数据读入的缓冲区,以档案存取为例的话,您知道磁盘存取的速度是远低于内存中的数据存取速度,为了减少对磁盘的存 ,您一次读入一定长度的数据,如上一个主题范例中的1024字节,而写入时也是一次写入一定长度的数据,这可以增加数据存取的效率。

    BufferedInputStream与BufferedOutputStream可以为InputStream类的对象增加缓冲区功能,使用它们,您无需自行设计缓冲区。

    BufferedInputStream的数据成员buf是个位数组,预设为2048字节大小,当读取数据来源时,例如档案, BufferedInputStream会尽量将buf填满,当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取,当buf中的数据不足时,BufferedInputStream才会再从数据来源中提取数据。

    BufferedOutputStream的数据成员buf是个位数组,预设为512个字节,当写入数据时,会先将资料存至buf中,当buf已满时才会一次将数据写至目的地,而不是每次写入都对目的地作写入。

    将上一个主题的范例作个改写,这次不用自行设定缓冲区并进行判断了,使用BufferedInputStream、 BufferedOutputStream让程序看来简单一些,也比较有效率:

    BufferedStreamDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class BufferedStreamDemo {
    	public static void main(String[] args) {
    		try {
    			byte[] data = new byte[1];
    			File srcFile = new File(args[0]);
    			File desFile = new File(args[1]);
    			BufferedInputStream bufferedInputStream =
    			new BufferedInputStream(
    			new FileInputStream(srcFile));
    			BufferedOutputStream bufferedOutputStream =
    			new BufferedOutputStream(
    			new FileOutputStream(desFile));
    			System.out.println("复制档案:" +
    			srcFile.length() + "字节");
    			while(bufferedInputStream.read(data) != -1) {
    				bufferedOutputStream.write(data);
    			}
    			// 将缓冲区中的数据全部写出
    			bufferedOutputStream.flush();
    			// 关闭串流
    			bufferedInputStream.close();
    			bufferedOutputStream.close();
    		}
    		catch(ArrayIndexOutOfBoundsException e) {
    			System.out.println(
    			"using: java UseFileStream src des");
    			e.printStackTrace();
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    为了确保缓冲区中的数据一定被写出,建议最后执行flush()将缓冲区中的数据全部写出目的串流中。

    BufferedInputStream、BufferedOutputStream并没有改变来源InputStream或目的 OutputStream的行为,读入或写出时的动作还是InputStream、OutputStream负责, BufferedInputStream、BufferedOutputStream只是在这之前动态的为它们加上一些功能(像是缓冲区功能),在这边是 以档案存取串流为例,实际上您可以在其它串流对象上加上BufferedInputStream、BufferedOutputStream功能。

    12、FileInputStream、 FileOutputStream

    FileInputStream是InputStream的子类,由名称上就可以知道, FileInputStream主要就是从指定的档案中读取数据至目的地。

    FileOutputStream是OutputStream的子类,顾名思义,FileInputStream主要就是从来源地写入数据至指定的档案中。

    标准输入输出串流对象在程序一开始就会开启,但只有当您建立一个FileInputStream或FileOutputStream的实例时,实际的串流才会开启,而不使用串流时,也必须自行关闭串流,以释放与串流相依的系统资源。

    下面这个程序可以复制档案,程序先从来源档案读取数据至一个位缓冲区中,然后再将位数组的数据写入目的档案:

    FileStreamDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class FileStreamDemo {
    	public static void main(String[] args) {
    		try {
    			byte[] buffer = new byte[1024];
    			FileInputStream fileInputStream =
    			new FileInputStream(new File(args[0]));
    			FileOutputStream fileOutputStream =
    			new FileOutputStream(new File(args[1]));
    			System.out.println("复制档案:" +
    			fileInputStream.available() + "字节");
    			while(true) { // 从来源档案读取数据至缓冲区
    				if(fileInputStream.available() < 1024) {
    					int remain;
    					while((remain = fileInputStream.read())
    					!= -1) {
    						fileOutputStream.write(remain);
    					}
    					break;
    				}
    				else {
    					fileInputStream.read(buffer);
    					// 将数组数据写入目的档案
    					fileOutputStream.write(buffer);
    				}
    			}
    			// 关闭串流
    			fileInputStream.close();
    			fileOutputStream.close();
    			System.out.println("复制完成");
    		}
    		catch(ArrayIndexOutOfBoundsException e) {
    			System.out.println(
    			"using: java FileStreamDemo src des");
    			e.printStackTrace();
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    这个程序示范了两个 read() 方法,一个可以读入指定长度的数据至数组,一个一次可以读入一个字节,每次读取之后,读取的指标都会往前进,您使用available()方法获得还有多少字节可以读取;除了使用File来建立FileInputStream、FileOutputStream的实例之外,您也可以直接使用字符串指定路径来建立。

    不使用串流时,记得使用close()方法自行关闭串流,以释放与串流相依的系统资源。

    13、ObjectInputStream、ObjectOutputStream

    在Java这样支持对象导向的程序中撰写程序,很多数据都是以对象的方式存在,在程序运行过后,您会希望将这些数据加以储存,以供下次执行程序时使用,这时您可以使用ObjectInputStream、ObjectOutputStream来进行这项工作。

    要被储存的对象必须实作Serializable接口,说是实作,其实Serializable中并没有规范任何必须实作的方法,所以这边所谓实作的意义,其实像是对对象贴上一个标志,代表该对象是可以序列化的(Serializable)。

    一个实作的例子如下所示:

    Student.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class Student implements Serializable {
    	private static final long serialVersionUID = 1L;
    	private String name;
    	private int score;
    	public Student() {
    		name = "N/A";
    	}
    	public Student(String name, int score) {
    		this.name = name;
    		this.score = score;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public void setScore(int score) {
    		this.score = score;
    	}
    	public String getName() {
    		return name;
    	}
    	public int getScore() {
    		return score;
    	}
    	public void showData() {
    		System.out.println("name: " + name);
    		System.out.println("score: " + score);
    	}
    }
    

    您要注意到serialVersionUID,这代表了可序列化对象的版本, 如果您没有提供这个版本讯息,则会自动依类名称、实现的接口、成员等讯息来产生,如果是自动产生的,则下次您更改了Student类,则自动产生的 serialVersionUID也会跟着变更,当反序列化时两个serialVersionUID不相同的话,就会丢出 InvalidClassException,如果您想要维持版本讯息的一致,则要显式宣告serialVersionUID。

    ObjectInputStream、ObjectOutputStream为InputStream、OutputStream加上了可以让使用者写入 对象、读出对象的功能,在写入对象时,我们使用writeObject()方法,读出对象时我们使用readObject()方法,被读出的对象都是以 Object的型态传回,您必须将之转换为对象原来的型态,才能正确的操作被读回的对象,下面这个程序示范了如何简单的储存对象至档案中,并将之再度读 回:

    ObjectStreamDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    import java.util.*;
    public class ObjectStreamDemo {
    	public static void writeObjectsToFile(
    	Object[] objs, String filename) {
    		File file = new File(filename);
    		try {
    			ObjectOutputStream objOutputStream =
    			new ObjectOutputStream(
    			new FileOutputStream(file));
    			for(Object obj : objs) {
    				objOutputStream.writeObject(obj);
    			}
    			objOutputStream.close();
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    	public static Object[] readObjectsFromFile(
    	String filename)
    	throws FileNotFoundException {
    		File file = new File(filename);
    		if(!file.exists())
    		throw new FileNotFoundException();
    		List list = new ArrayList();
    		try {
    			FileInputStream fileInputStream =
    			new FileInputStream(file);
    			ObjectInputStream objInputStream =
    			new ObjectInputStream(fileInputStream);
    			while(fileInputStream.available() > 0) {
    				list.add(objInputStream.readObject());
    			}
    			objInputStream.close();
    		}
    		catch(ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    		return list.toArray();
    	}
    	public static void appendObjectsToFile(
    	Object[] objs, String filename)
    	throws FileNotFoundException {
    		File file = new File(filename);
    		if(!file.exists())
    		throw new FileNotFoundException();
    		try {
    			ObjectOutputStream objOutputStream =
    			new ObjectOutputStream(
    			new FileOutputStream(file, true)) {
    				protected void writeStreamHeader()
    				throws IOException {}
    			};?
    			for(Object obj : objs) {
    				objOutputStream.writeObject(obj);
    			}
    			objOutputStream.close();
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    	public static void main(String[] args) {
    		Student[] students = {new Student("caterpillar", 90),
    		new Student("justin", 85)};
    		// 写入新档
    		writeObjectsToFile(students, "data.dat");
    		try {
    			// 读取档案数据
    			Object[] objs = readObjectsFromFile("data.dat");
    			for(Object obj : objs) {
    				((Student) obj).showData();
    			}
    			System.out.println();
    			students = new Student[2];
    			students[0] = new Student("momor", 100);
    			students[1] = new Student("becky", 100);
    			// 附加至档案
    			appendObjectsToFile(students, "data.dat");
    			// 读取档案数据
    			objs = readObjectsFromFile("data.dat");
    			for(Object obj : objs) {
    				((Student) obj).showData();
    			}
    		}
    		catch(FileNotFoundException e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    

    对象被写出时,会写入对象的类别型态、类别署名(Class signature),static与被标志为transient的成员则不会被写入。

    在这边注意到以附加的形式写入数据至档案时,在试图将对象附加至一个先前已写入对象的档案时,由于ObjectOutputStream在 写入数据时,还会加上一个特别的标示头,而读取档案时会检查这个标示头,如果一个档案中被多次附加对象,那么该档案中会有多个标示头,如此读取检查时就会 发现不一致,这会丢出StreamCorrupedException,为此,您重新定义ObjectOutputStream的writeStreamHeader()方法,如果是以附加的方式来写入对象,就不写入标示头:

    ObjectOutputStream objOutputStream =
    new ObjectOutputStream(
    new FileOutputStream(file, true)) {
    protected void writeStreamHeader()
    throws IOException {}
    };

    对象写出或读入并不仅限于档案存取,您也可以用于网络的数据传送,例如传送整个对象数据或是影像档案

    14、DataInputStream、DataOutputStream

    DataInputStream、DataOutputStream可提供一些对Java基本数据型态写入的方法,像是读写int、double、 boolean等的方法,由于Java的数据型态大小是规定好的,在写入或读出这些基本数据型态时,就不用担心不同平台间资料大小不同的问题。

    这边还是举档案存取来进行说明,有时候您只是要储存一个对象的成员数据,而不是整个对象的信息,成员数据的型态假设都是Java的基本数据型态,您不必要 使用Object输入、输出相关串流对象,而可以使用DataInputStream、DataOutputStream来写入或读出数据,下面这个程序 是个简单的示范:

    Student.java
    package onlyfun.caterpillar;
    public class Student?{
    	private String name;
    	private int score;
    	public Student() {
    		name = "N/A";
    	}
    	public Student(String name, int score) {
    		this.name = name;
    		this.score = score;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public void setScore(int score) {
    		this.score = score;
    	}
    	public String getName() {
    		return name;
    	}
    	public int getScore() {
    		return score;
    	}
    	public void showData() {
    		System.out.println("name: " + name);
    		System.out.println("score: " + score);
    	}
    }
    
    DataStreamDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class DataStreamDemo {
    	public static void main(String[] args) {
    		Student[] students = {new Student("Justin", 90),
    			new Student("momor", 95),
    		new Student("Bush", 88)};
    		try {
    			DataOutputStream dataOutputStream =
    			new DataOutputStream(
    			new FileOutputStream("data.dat"));
    			for(Student student : students) {
    				dataOutputStream.writeUTF(student.getName());
    				dataOutputStream.writeInt(student.getScore());
    			}
    			dataOutputStream.flush();
    			dataOutputStream.close();
    			DataInputStream dataInputStream =
    			new DataInputStream(
    			new FileInputStream("data.dat"));
    			for(int i = 0; i < students.length; i++) {
    				String name = dataInputStream.readUTF();
    				int score = dataInputStream.readInt();
    				students[i] = new Student(name, score);
    				students[i].showData();
    			}
    			dataInputStream.close();
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    

    这个程序在写入档案时,只提取对象的成员数据,而在读出时将这些数据读出,并将读回的数据设定给一个实例,是对象数据还原的一种方式。

    15、SequenceInputStream

    您将一个档案分割为数个档案,接下来要将之再度组合还原为原来的档案,最基本的作法是使用数个 FileInputStream来开启分割后的档案,然后一个一个档案的读取,并连续写入至同一个FileOutputStream中,在这中间,您必须 要自行判断每一个分割档案的读取是否完毕,如果完毕就换读取下一个档案。

    如果您使用SequenceInputStream就不用这么麻烦,SequenceInputStream可以看作是数个 InputStream对象的组合,当一个InputStream对象的内容读取完毕后,它就会取出下一个InputStream对象,直到所有的 InputStream对象都读取完毕为止。

    下面这个程序是SequenceInputStream的使用示范,它可以将指定的档案进行分割,也可以将分割后的档案还原为一个档案:

    SequenceStreamDemo.java
    package onlyfun.caterpillar;
    import java.util.*;
    import java.io.*;
    public class SequenceStreamDemo {
    	public static void main(String[] args) {
    		try {
    			// args[0]: 指定分割(s)或连接(c)
    			switch (args[0].charAt(1)) {
    				case 's':
    				// args[1]: 每个分割档案的大小
    				int size = Integer.parseInt(args[1]);
    				// args[2]: 指定要被分割的文件名称
    				seperate(args[2], size);
    				break;
    				case 'c':
    				// args[1]: 指定要被组合的档案个数
    				int number = Integer.parseInt(args[1]);
    				// args[2]: 组合后的文件名称
    				concatenate(args[2], number);
    				break;
    			}
    		}
    		catch(ArrayIndexOutOfBoundsException e) {
    			System.out.println(
    			"Using: java UseSequenceStream [-s/-c]" +
    			" (size/number) filename");
    			System.out.println("-s: 分割档案\n-c: 组合档案");
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    	// 分割档案
    	public static void seperate(String filename, int size)
    	throws IOException {
    		FileInputStream fileInputStream =
    		new FileInputStream(new File(filename));
    		BufferedInputStream bufInputStream =
    		new BufferedInputStream(fileInputStream);
    		byte[] data = new byte[1];
    		int count = 0;?
    		// 从原档案大小及指定分割的大小
    		// 决定要分割为几个档案
    		if(fileInputStream.available() % size == 0)
    		count = fileInputStream.available() / size;
    		else
    		count = fileInputStream.available() / size + 1;
    		// 开始进行分割
    		for(int i = 0; i < count; i++) {
    			int num = 0;
    			// 分割的档案加上底线与编号
    			File file = new File(filename + "_" + (i + 1));
    			BufferedOutputStream bufOutputStream =
    			new BufferedOutputStream(
    			new FileOutputStream(file));
    			while(bufInputStream.read(data) != -1) {
    				bufOutputStream.write(data);
    				num++;
    				if(num == size) { // 分割出一个档案
    					bufOutputStream.flush();
    					bufOutputStream.close();
    					break;
    				}
    			}
    			if(num < size) {
    				bufOutputStream.flush();
    				bufOutputStream.close();
    			}
    		}
    		System.out.println("分割为" + count + "个档案");
    	}
    	// 连接档案
    	public static void concatenate(String filename,
    	int number) throws IOException {
    		// 收集档案用的List
    		List list =
    		new ArrayList();
    		for(int i = 0; i < number; i++) {
    			// 文件名必须为底线加上编号
    			File file = new File(filename + "_" + (i+1));
    			list.add(i, new FileInputStream(file));
    		}
    		final Iterator iterator = list.iterator();
    		// SequenceInputStream 需要一个Enumeration对象来建构
    		Enumeration enumation =
    		new Enumeration() {
    			public boolean hasMoreElements() {
    				return iterator.hasNext();
    			}
    			public InputStream nextElement() {
    				return iterator.next();
    			}
    		};
    		// 建立SequenceInputStream
    		// 并使用BufferedInputStream
    		BufferedInputStream bufInputStream =
    			new BufferedInputStream(
    				new SequenceInputStream(enumation),
    					8192);
    		BufferedOutputStream bufOutputStream =
    			new BufferedOutputStream(
    				new FileOutputStream(filename), 8192);
    					byte[] data = new byte[1];
    		// 读取所有档案数据并写入目的地档案
    		while(bufInputStream.read(data) != -1)
    		bufOutputStream.write(data);
    		bufInputStream.close();
    		bufOutputStream.flush();
    		bufOutputStream.close();
    		System.out.println("组合" + number + "个档案 OK!!");
    	}
    }
    
    

    分割档案时的范例如下:

    java onlyfun.caterpillar.SequenceStreamDemo -s 1048576 test.zip
    分割为6个档案

    组合档案时的范例如下:

    java onlyfun.caterpillar.SequenceStreamDemo -c 6 test.zip
    组合6个档案 OK!!
    

    16、PrintStream

    之前所介绍过的Stream输出对象,都是直接将内存中的数据写出至目的地(例如一个档案),举个例子来说,如果您将 int 整数 1 使用之前介绍的Stream对象输出至档案,则档案中所储存的是 int 整数 1 在内存中的值,例如:

    FileStream.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class FileStreamDemo {
    	public static void main(String[] args)
    	throws IOException {
    		FileOutputStream file =
    		new FileOutputStream(
    		new File("test.txt"));
    		file.write(1);
    		file.close();
    	}
    }
    

    由于您使用write()方法,这会将 1 在内存中的值之低字节0000001写入档案中,所以如果您使用文字编辑软件(像vi或UltraEdit)观看test.txt的16进位表示,其结果会显示 01(16进位表示)。

    有时候您所想要储存的结果是转换为字符之后的结果,例如若程序的执行结果是3.14159,您会希望使用字符来储存3.14159,也就是俗称的储存为纯文本文件,如此当您使用简单的纯文字编辑器观看时,就可以直接看到程序执行的结果。

    例如您若想使用纯文本文件看到test.txt的显示结果是1,则必须先将内存中的整数1,也就是二进制00000000 00000000 00000000 00000001转换为对应的字符编码,也就是0x31(十进制表示49)并加以储存。

    使用PrintStream可以自动为您进行字符转换的动作,它会使用操作系统的预设编码来处理对应的字符转换动作,直接使用下面这个例子来作示范:

    PrintStreamDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class PrintStreamDemo {
    	public static void main(String[] args)
    	throws FileNotFoundException {
    		PrintStream printStream = new PrintStream(
    		new FileOutputStream(
    		new File("pi.txt")));
    		printStream.print("PI = ");
    		printStream.println(Math.PI);
    		printStream.close();
    	}
    }
    

    执行程序之后使用纯文字编辑器开启pi.txt,其内容会是PI = 3.141592653589793,print()或println()接受int、char、String、double等等数据型态, println()会在输出之后加上换行字符,而print()则不会。

    注意在档案储存上实际并没有二进制档案或是纯文本文件的分别,所有的档案所储存的都是二进制的数据,您俗称的纯文本文件,其实正确的说,是指储存的结果是经过字符转换,例如将 int 整数 1转换为字符 '1' 的编码结果并加以储存。

    17、Reader、Writer

    Reader、Writer支持Unicode标准字符集(Character set)(字节串流则只支持ISO-Latin-1 8-bit),在处理串流时,会根据系统预设的字符编码来进行字符转换,它们是抽象类别,真正您会使用其子类别,子类别通常会重新定义相关的方法。

    在 PushbackInputStream 中,您读入一个含BIG5中文字及ASCII字符的文本文件,这边改写一下这个例子,使用Reader的子类别 InputStreamReader来转换读入的两个字节为汉字字符,并显示在屏幕上:

    ReaderWriterDemo.java
    package onlyfun.caterpillar;
    import java.io.*;
    public class ReaderDemo {
    	public static void main(String[] args) {
    		try {
    			PushbackInputStream pushbackInputStream =
    			new PushbackInputStream(
    			new FileInputStream(args[0]));
    			byte[] array = new byte[2];
    			ByteArrayInputStream byteArrayStream =
    			new ByteArrayInputStream(array);
    			// reader会从已读的位数组中取出数据
    			InputStreamReader reader =
    			new InputStreamReader(byteArrayStream);
    			int tmp = 0;
    			int count = 0;
    			while((count = pushbackInputStream.read(array))
    			!= -1) {
    				// 两个字节转换为整数
    				tmp = (short)((array[0] << 8) |
    				(array[1] & 0xff));
    				tmp = tmp & 0xFFFF;
    				// 判断是否为BIG5,如果是则显示BIG5中文字
    				if(tmp >= 0xA440 && tmp < 0xFFFF) {
    					System.out.println("BIG5: " +
    					(char)reader.read());
    					// 重置ArrayInputStream的读取光标
    					// 下次reader才会再重头读取数据
    					byteArrayStream.reset();
    				}
    				else {
    					// 将第二个字节推回串流
    					pushbackInputStream.unread(array, 1, 1);
    					// 显示ASCII范围的字符
    					System.out.println("ASCII: " +
    					(char)array[0]);
    				}
    			}
    			pushbackInputStream.close();
    		}
    		catch(ArrayIndexOutOfBoundsException e) {
    			System.out.println("请指定文件名称");
    		}
    		catch(IOException e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    

    假设的文本文件中有以下的文字:"这T是e一s个t测试" ,执行结果会是:

    BIG5: 这 
    ASCII: T 
    BIG5: 是 
    ASCII: e 
    BIG5: 一 
    ASCII: s 
    BIG5: 个 
    ASCII: t 
    BIG5: 测 
    BIG5: 试 
    ASCII: ! 
    EOF?

    InputStreamReader可以用字节串流中取出字节数据,并进行字符处理动作,关于Reader、Writer相关子类别,之后会于各相关主题进行介绍。

    其余未看:

    ByteArrayInputStream、ByteArrayOutputStream;

    CharArrayReader、CharArrayWriter;

    PushbackReader;

    http://www.iteedu.com//plang/java/javadiary/92.php

    字节跳动内推

    找我内推: 字节跳动各种岗位
    作者: ZH奶酪(张贺)
    邮箱: cheesezh@qq.com
    出处: http://www.cnblogs.com/CheeseZH/
    * 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    互联网协议入门
    【HTTP】图解HTTPS
    《计算机本科生理想的学习计划》
    VC++ TinyXML
    TinyXML 在vs2010 VC++使用
    Hadoop2.4.1入门实例:MaxTemperature
    xcode6
    Android利用广播监听设备网络连接(断网)的变化情况
    编程算法
    Google的Guava之IO升华
  • 原文地址:https://www.cnblogs.com/CheeseZH/p/2811913.html
Copyright © 2020-2023  润新知