IO输入输出

结构图:(来源:http://www.cnblogs.com/zemliu/archive/2013/08/19/3269015.html)

Reader-Writer:

 

InputStream-OutputStream:

两种结构相对应。IO最重要的是Reader、Writer、InputStream、OutputStream和另外的File类,Serializable序列化接口等。

 

 

将数据写入文件,或者从终端读取用户输入。Java的IO建立在对流的使用之上。流是数据的一个序列,可读可写。

流可能有源(终端)——输入流,目标(文件系统)——输出流。通过流载送数据格式的不同,进一步对流划分:字符流和字节流。

 

字符流:类名中有“Reader”或者“Writer”是字符流。通常用字符流处理可读的文本。用字符流来处理2字节的UniCode字符数据(Java使用UniCode的UTF-16编码)。

字节流:处理8位的二进制数据。类名包含Stream。通常用字节流处理非文本的数据,如图像和编译生成的字节码文件。

 

底层Java流支持如每次读/写一个字节的基本概念。提供聚合的、附加的功能简化工作,这些高级别流称为包装流(Wrapper)。Wrapper对象可能包含一个底层流对象的引用,即包装了一个底层流对象。过程:交互->Wrapper->底层流对象。

Java.io含有很多类,管理使用数据流、序列化文件和文件系统的输入输出操作。需要理解包的结构和类的命名。定义了一组接口和类,是IO的基石。包中含有很多特定流(类)的实现,包括实现的过滤(filtered)、缓冲(buffered)、管道(piped)流及对象流。也提供了一组类来操作底层的文件系统。

 

最后必须关闭文件资源,否则面临文件被锁住的问题。

 

 

1、File类:

不是基于流的类。除了与流一起使用,File类还提供访问底层文件系统中文件和目录结构的接口。包含文件或目录的各种元数据信息及很多针对文件的实用方法。如文件名、文件长度、最后修改时间、是否可读、创建临时文件createTempFile(String prefix,String suffix)、获取当前文件的路径名,获得当前目录中的文件列表、判断指定文件是否存在、删除文件和目录delete()等方法。

它有两个常量:

pathSeparator:与系统有关的路径分隔符,为了方便,它被表示为一个字符串。“;”

separator:与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。“\”(windows下输出)

跨系统可以使用这两个常量。exp列出文件和目录:

 

String fileName="data"+File.separator;
File f=new File(fileName);
String[] str=f.list();
for(int i=0; i<str.length; i++){
     System.out.println(str[i]);
}
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。若要返回一个抽象完整路径名数组,使用listFiles(),返回File数组,这些路径名表示此抽象路径名表示的目录中的文件。

 

 

2、字符流

Reader、Writer类是字符流的抽象基类。

Writer类方法write(~):写入字符。

StringWriter:一个字符流,可以用其回收在字符串缓冲区中的输出来构造字符串。关闭 StringWriter 无效。此类中的方法在关闭该流后仍可被调用,而不会产生任何IOException。toString()返回以字符串的形式返回该缓冲区的当前值。

InputStreamReader:字节到字符的桥梁;OutputStreamWriter:字符到字节的桥梁。

 

(1)字符流写入(输出至)文件:

String pathname = "data\\FileInWriter.txt";
File file = new File(pathname);
String inString = "你怎么不上天?";
Writer writer = new FileWriter(file);
writer.write(inString);
writer.close();

FileWriter用来向文件写入字符流的便捷类。

BufferedWriter类将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。该类提供了newLine()方法,它使用平台自己的行分隔符概念,此概念由系统属性line.separator定义。并非所有平台都使用新行符 ('\n') 来终止各行。因此调用此方法来终止每个输出行要优于直接写入新行符。

通常Writer将其输出立即发送到底层字符或字节流。除非要求提示输出,否则建议用BufferedWriter包装所有其write()操作可能开销很高的Writer(如FileWriters和OutputStreamWriters)。例如:

PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));

将缓冲PrintWriter对文件的输出。如果没有缓冲,则每次调用print()方法会导致将字符转换为字节,然后立即写入到文件,而这是极其低效的。

 

缓冲信息在关闭Writer之前不会保存到文件中,所以要牢记关闭Writer。还可使用Writer.flush()方法强制Writer将内容写至目标中。

 

(2)读取文件中的字符:

StringBuffer sb = new StringBuffer();
String line="";
BufferedReader br = new BufferedReader(new FileReader(file));
while((line=br.readLine())!=null){
    sb.append(String.format(line+"%n"));
}
br.close();

BufferedReader类从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。通常,Reader所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用BufferedReader包装所有其read()操作可能开销很高的Reader(如FileReader和InputStreamReader)。

如果没有缓冲,则每次调用read()或readLine()都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。通过用合适的BufferedReader替代每个DataInputStream,可以对将DataInputStream用于文字输入的程序进行本地化。

上述也可以通过FileReader类实现,FileReader类用来从文件读取字符流的便捷类。不过BufferedReader更有效率,它会将读取的字符缓冲起来。

 

 

3、字节流

System.in和System.out都是字节流。前者指向InputStream,后者PrintSteam(继承OutputStream简化各种对象类型的写入)

输入字节流InputStream是所有的输入字节流的抽象父类。ByteArrayInputStream、StringBufferInputStream(已过时)、FileInputStream、PipedInputStream是其中四种子类,前三个分别从Byte数组(内部缓冲区)、StringBuffer、和文件中读取输入字节。最后一个在不同的线程间进行安全的基于IO的数据通讯,与其它线程共用的管道中读取数据,以成对的方式工作(输入/输出管道流),不建议使用单个线程,因为这样可能死锁线程。

(1)字节流写入(输出至)文件:

String fileName = "data"+File.separator+"FileInWriter.txt";
File file = new File(fileName);
FileOutputStream fos = new FileOutputStream(file);
String string = "大王叫我来巡山,挥着小旗,唱着歌";
byte[] byts = string.getBytes();
fos.write(byts, 0, byts.length);
fos.close();
FileOutputStream用于写入诸如图像数据之类的原始字节的流。

(2)读取文件中的字节:

FileInputStream fis = new FileInputStream(file);
byte[] bytr = new byte[100];
while(fis.read(bytr, 0, bytr.length)!=-1){
        fis.read(bytr);
}
System.out.println(new String(bytr));
fis.close();
FileInputStream用于读取诸如图像数据之类的原始字节流。 FileOutputStream用于写入诸如图像数据之类的原始字节的流。   数据流DataOutputStream:

是filtered流中的一种,继承FilteredOutputStream。过滤流包装另一个流(对应的输出流),来提供附加的功能,或者以此种方式更改数据。filtered类还包含FilteredInputStream、FilteredWriter和FilteredReader。DataOutputStream的过滤器提供了对每个java基础类型的输出方法:writeBoolean、writeDouble等,也提供了字符串的输出:writeUTF。

 

顺序流SequenceInputStream:

SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。

 

对象流ObjectOutputStream、ObjectInputStream:

java可以直接向流中读写对象的能力(用于读/写入文件)。借由对象的序列化(serialization)将对象写入到对象输出流中。将对象转换成一个字节的序列,称之为序列化--Java RMI技术(能够让对象在同远程系统中的对象交互时,即像访问本地对象一样)的基础。RMI也是JAVA EJB基于组件计算技术的基础。

使用ObjectOutputStream和ObjectInputStream来写入和读取对象。

Example:

public class ObjectStream {

	@SuppressWarnings("unchecked")
	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
           List<Student> students = new ArrayList<Student>();
           students.add(new Student(1, "王大侠"));
           students.add(new Student(2, "王小侠"));
           
           String fileName = "data"+File.separator+"objectstream.txt";
           ObjectOutputStream oos=null;
           oos=new ObjectOutputStream(new FileOutputStream(fileName));
           oos.writeObject(students);
           oos.close();
           ObjectInputStream ois=null;
           ois=new ObjectInputStream(new FileInputStream(fileName));
           students = (List<Student>)ois.readObject();
           ois.close();
	}

}

必须强调写入对象流的对象必须实现序列化(即可序列化的),即实现Serializable接口:

public class Student implements Serializable{
        ......
}

当抽象父类标注成可序列化时,那么它的子类也能够被序列化。Serializable接口没有任何方法,这样的接口被称为标记式接口。所以有时为了某种特殊用途,我们可以创建标记式接口以显式地标记一个类。可以自定义序列化格式,重写方法。

 

另外可以通过Transient修饰符指示在序列化时跳过,如:

List<Student> transient students = new ArrayList<Student>();

上述学生列表将不会被序列化。

 

如果已经持久化一个被序列化的对象,随后更改了类的定义(如添加删减字段、方法、接口),再去读取已序列化的对象,则会得到异常。因为Java会判断在输出流中保存的对象,和现在类定义的差异性。但增加了Transient修饰符的字段则会被忽略。有一个解决办法即经常看到的序列化版本serialVersionUID(根据类的定义特定生成):

private static final long serialVersionUID = 1L;

这样即使我们的字段等定义有所改变,但类的版本ID是相同的。

 

对于可能经常变化的类,处理序列化版本通常不便,最佳策略:1、序列化使用最小化 2、尽可能使用transient的成员变量 3、使用serialVersionUID 4、自定义序列化格式。

 

 

4、字符流与字节流的转换:

InputStreamReader是字节流通向字符流的桥梁。它包装了InputStream,它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定(例如提供一个解码类定义之间的映射),或者可以接受平台默认的字符集。

类似,OutputStreamWriter包装了OutputStream,将字符转换成字节。

这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来。

转换Example:

(1)字节输入流转换为字符输入流:

String fileName = "data\\FileInWriter.txt";
File file = new File(fileName);
Reader reader = new InputStreamReader(new FileInputStream(file), "UTF-8");
char[] cbuf = new char[100];
int length = reader.read(cbuf);
System.out.println(length+new String(cbuf, 0, length));
reader.close();
InputStreamReader是字节流通向字符流的桥梁:它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。每次调用InputStreamReader中的一个read()方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。为了达到最高效率,可要考虑在BufferedReader内包装InputStreamReader。例如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

 

(2)字符输出流转换为字节输出流

String fileName = "data\\FileInWriter.txt";
File file = new File(fileName);         
Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
writer.write("大王叫我来巡山");
writer.close();
OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。每次调用write()方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给write()方法的字符没有缓冲。为了获得最高效率,可考虑将OutputStreamWriter包装到BufferedWriter中,以避免频繁调用转换器。例如:
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));

5、RandomAccessFile类

随机读取文件可以快速地定位到文件的特定位置,并从该位置读取或者写入。

随机访问文件的行为类似存储在文件系统中的一个大型byte数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过getFilePointer方法读取,而seek方法快速地将其移动到底层文件相应的位置,length返回文件总共的字节数。

public RandomAccessFile(File file,String mode) throws FileNotFoundException

RandomAccessFile提供了四种构造模式mode:

"r"以只读方式打开。调用结果对象的任何 write 方法都将导致抛出IOException。

"rw"打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。

"rws"打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。

"rwd"  打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。

后两种同步模式,“rws”确保内容和元数据(即文件最近修改时间戳等)的持久化,“rwd”只确保内容的更新。这两种会有额外的开销,不过对于数据的完整性有必要。

RandomAccessFile类也提供了很多read和write来读取写入数据。

 

RandomAccessFile写入和读取对象:

写入:RandomAccessFile并不直接支持保存对象。将对象持久化到RandomAccessFile,必须将其转换成一个字节数组。将ByteArrayOutputStream包装到ObjectOutputStream。这一意味着所有写入到ObjectOutputStream中的对象,都将被管接到底层的ByteArrayOutputStream。此时可以调用toByteArray方法从ByteArrayOutputStream取出所有字节内容。

读取:相反。以适当的长度创建一个字节数组,并使用readFully从RandomAccessFile中读取并填充字节。将填充的字节数组包装在ByteArrayInputStream中。然后再将它包装在ObjectInputStream。再从中读取,就会使用底层的字节数据重建已持久化的对象。

		   //写入对象持久化到文件
		   File file = new File("data"+File.separator+"data.txt");
		   RandomAccessFile  randomAccessFile = new RandomAccessFile(file, "rwd");
		   ByteArrayOutputStream baos = new ByteArrayOutputStream();
		   //包装ObjectOutputStream
		   ObjectOutputStream oos = new ObjectOutputStream(baos);
		   Student student = new Student(1, "王大侠");
		   //写入对象,该对象必须序列化
		   oos.writeObject(student);
		   byte[] byts = baos.toByteArray();
		   randomAccessFile.write(byts, 0, byts.length);
		   baos.close();
		   oos.close();
		  
		   
		   //从文件中读取重建已持久化对象
		   byte[] bytr = new byte[byts.length];
		   System.out.println(bytr.length);
		   //此时已到文件末尾
		   randomAccessFile.getFilePointer();
		   //重置文件开始处
		   randomAccessFile.seek(0);
		   //读取文件字节填充数组
		   randomAccessFile.readFully(bytr);
                   //填充的字节数组包装
		   ByteArrayInputStream bais = new ByteArrayInputStream(bytr);
		   //包装ByteArrayInputStream
		   ObjectInputStream ois = new ObjectInputStream(bais);
		   //重建已持久化对象
		   Student student2 = (Student) ois.readObject();
		   System.out.println(student2.getName());
		   bais.close();
		   ois.close();
		   randomAccessFile.close();

 

再如网络编程,也设计IO,服务器端:

//服务器端程序
public class Server{

	public void send()  throws UnknownHostException, IOException{
			//监听8080端口,等待用户请求的ServerSocket
			ServerSocket serverSocket = new ServerSocket(8080);
			//服务器端与客户端通信用的Socket,一对Socket
			Socket socket = serverSocket.accept();
			//写入响应输出串流
			//建立连接Socket的PrintWriter(每次写入一个String最标准的做法)写入输出串流
			//socket.getOutputStream()获取写入字节,PrintWriter包装转换成字符(这个构造方法创建必要的中间 OutputStreamWriter,后者使用默认字符编码将字符转换为字节。)
			PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
			printWriter.print("大王来了!");
			printWriter.close();
	}
	
	public static void main(String[] args){
		try {
                          new Server().send();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
}

客户端程序:

public class Client {

	public void receive()  throws UnknownHostException, IOException{		
			//客户端对服务器端建立Socket连接
			Socket socket = new Socket("127.0.0.1", 8080);
			//服务器端发送,客户端读取输入串流 
			//BufferedReader读取Scoket上数据
			//建立连接Socket的InputStreamReader
			//socket.getInputStream()获取输入字节,并用InputStreamReader包装转成输入字符,转换成字符Characters
			InputStreamReader inputStreamReader = new InputStreamReader(socket.getInputStream());
			//BufferedReader包装inputStreamReader,转换成缓冲区字符buffered Characters,可以读取字符并转换成字符串
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String info = bufferedReader.readLine();
			bufferedReader.close();
			System.out.println(info);
	}
	
	public static void main(String[] args){
		try {
			new Client().receive();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

 

InputStreamReader是字节流通向字符流的桥梁:它使用指定的charset读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。每次调用InputStreamReader中的一个read()方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。为了达到最高效率,可要考虑在BufferedReader内包装InputStreamReader。例如: OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。每次调用write()方法都会导致在给定字符(或字符集)上调用编码转换器。在写入底层输出流之前,得到的这些字节将在缓冲区中累积。可以指定此缓冲区的大小,不过,默认的缓冲区对多数用途来说已足够大。注意,传递给write()方法的字符没有缓冲。为了获得最高效率,可考虑将OutputStreamWriter包装到BufferedWriter中,以避免频繁调用转换器。例如:
Writer out = new BufferedWriter(new OutputStreamWriter(System.out));

猜你喜欢

转载自wwy0612.iteye.com/blog/2321456
今日推荐