2.熟悉Java基本类库系列——Java IO 类库

Java中常用的IO操作基本上可以分为四大部分,分别是:File类操作、RandomAccessFile类操作、字节流操作、字符流操作。只要熟练掌握了本文中所列举的所有例子,基本上对于Java的IO流操作就可以说是掌握了。

下面将以JUnit测试用例的方式,用一个个例子的方式列出这四大部分中常用的操作例子。

一、File类操作

File类操作定义了最基本的、与操作系统的稳健系统相关的操作,可以对文件夹、文件进行一系列的操作。

 1、常用的一些用法

       //1.两个常量
	@Test public void twoConstant(){
		//输出: ‘/‘  ‘:‘
		System.out.println(File.separator);
		System.out.println(File.pathSeparator);
	}	

	//2.创建新文件
	@Test public void createFile(){
		File f = new File("hello");
		try{
			f.createNewFile();
			System.out.println("创建了文件:" + f.getAbsolutePath());
		}catch(Exception e){
			e.printStackTrace();
		}
	}	

	//3.创建一个文件夹
	@Test public void createFolder(){
		try{
			File file = new File("HelloFolder");
			//当一个目录下既没有该名字对应的目录和文件时,才能建立该文件夹
			if(!file.isDirectory() && !file.isFile()){
				file.mkdir();
				//file.mkdirs(); //如果上级目录不存在,那么一并创建上级目录
			}else{
				System.out.println("已存在该名称的文件或文件夹");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	//4.删除一个文件
	@Test public void deleteFile(){
		try{
			File file = new File("hello");
			if(file.exists()){
				file.delete();
			}else{
				System.out.println("文件不存在");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}	

	//5.删除一个文件夹(与删除文件一样)
	@Test public void deleteFolder(){
		try{
			File file = new File("HelloFolder");
			if(file.exists()){
				file.delete();
			}else{
				System.out.println("该文件夹不存在");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}	

	//6.获取指定目录下所有文件的文件名(包括隐藏文件和文件夹)
	@Test public void getAllFileName(){
		try{
			File folder = new File(".");  //当前目录
			String[] fileStrs = folder.list();
			for(String str : fileStrs){
				System.out.print(str + " ");
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	//7.获取指定目录下所有文件的路径
	@Test public void getAllFilePath(){
		try{
			File folder = new File(".");  //当前目录
			File[] files = folder.listFiles();
			for(File file : files){
				System.out.println(file.getCanonicalPath());
			}
		}catch(Exception e){
			e.printStackTrace();
		}
	}    

2、打印指定目录下的所有文件(递归调用)

package com.chanshuyi.io;

/**
 * 列出指定目录的全部内容
 * */

import java.io.*;

class ListAllFile{
    public static void main(String[] args) {
        File f = new File(".");
        print(f);
    }

    //递归打印
    public static void print(File f){
    	if(f != null){
    		if(f.isDirectory()){
    			File[] fileArray = f.listFiles();
    			if(fileArray != null){
    				for(int i = 0; i < fileArray.length; i++){
    					print(fileArray[i]);
    				}
    			}
    		}else{
    			System.out.println(f);
    		}
    	}
    }
} 

二、RandomAccessFile类操作

RandomAccessFile类可以对文件进行随机访问,比如可以指定读写指针到某一个字节处,也可以读写指针指定跳过指定字节数。简单地说,RandomAccessFile类提供了许多方法,使得我们可以对文件进行更加细致的读写操作。

//8.随机读写文件类
@Test public void operateRandom(){
	try{
		//1.跳过两个字节读取文件内容
		//文件内容是:Hello, this is demo File.
		RandomAccessFile randomRW = new RandomAccessFile(new File("demo.txt"), "rw");
		randomRW.skipBytes(2);  //跳过两个字节,即跳过了‘he‘两个英文字符(一个英文字符占用1个字节)
		byte b[] = new byte[100];
		int length = randomRW.read(b);
		System.out.println("总共读取了" + length + "个字节,读取的内容是:" + new String(b));
		//总共读取了23个字节,读取的内容是:llo, this is demo File.

		//2.将读写指针跳回文件头重新读取
		randomRW.seek(0);
		length = randomRW.read(b);
		System.out.println("总共读取了" + length + "个字节,读取的内容是:" + new String(b));
		//总共读取了25个字节,读取的内容是:Hello, this is demo File.

		//3.获取读写指针所在地址
		long pointer = randomRW.getFilePointer();
		System.out.println("文件指针地址:" + pointer);  //文件指针地址:25
		randomRW.seek(2);
		pointer = randomRW.getFilePointer();
		System.out.println("文件指针地址:" + pointer);	//文件指针地址:2
		randomRW.seek(77);
		pointer = randomRW.getFilePointer();
		System.out.println("文件指针地址:" + pointer);	//文件指针地址:77

		randomRW.close();
	}catch(Exception e){
		e.printStackTrace();
	}

三、字节流读写

FileInputStream / FileOutputStream - 实现了对文件的读写操作

ObjectInpuStream / ObjectOutputStream - 实现了对序列化对象的读写操作

ByteArrayInputStream / ByteArrayOutputStream - 实现了对字节数组的读写操作

PipedInputStream / PipedOutputStream - 实现不同线程之间的通信

SequenceInputStream - 实现不同输入流的合并(此对象没有对应的OutputStream类)

BufferedInputStream / BufferedOutputStream - 实现读写缓存层

1、字节流读取 - byte(表示读取的数据类型是byte或byte[])

//15.字节流读取 - byte
@Test public void readByte(){
	try{
		File file = new File("hello.txt");
		InputStream fos = new FileInputStream(file);

		byte[] bytes = new byte[fos.available()];
		fos.read(bytes);
		String str = new String(bytes);
		System.out.println("文件内容是:\n" + str);
		fos.close();

	}catch(Exception e){
		e.printStackTrace();
	}
}

2、字节流读取(缓存) - byte

//字节流读取(缓存) - byte
@Test public void writeByteWithBuffere(){
	try{
		File file = new File("hello1.txt");
		OutputStream fos = new FileOutputStream(file);
		BufferedOutputStream out = new BufferedOutputStream(fos);

		out.write("你好\n".getBytes());
		out.write("吃饭了么!".getBytes());
		out.flush();

		fos.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

3、字节流写入 - byte

//17.字节流写入 - byte
@Test public void writeByte(){
	try{
		File file = new File("hello.txt");
		OutputStream fos = new FileOutputStream(file);
		fos.write("Hello Mac.\n你好,Mac.".getBytes());
		fos.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

4、字节流写入(缓存) - byte

//18.字节流写入(缓存) - byte
@Test public void writeByteWithBuffer(){
	try{
		File file = new File("hello.txt");
		OutputStream fos = new FileOutputStream(file);
		BufferedOutputStream bos = new BufferedOutputStream(fos);

		bos.write("Hello Mac.\n你好,Mac.".getBytes());
		bos.close();
		fos.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

如果你仔细敲过上面4个例子的代码你会发现,其实好像字节流的读取和写入,好像加了缓存和没加缓存,它们的代码好像都差不多啊,至少再写入数据的时候是一样的。而字符流的读取在加了缓存层之后,至少还能直接读取整行数据,字符流的写入加了缓存之后,可以写入换行符。那字节流的缓存究竟有什么必要性呢?

确实,从代码以及其方法上看,其实他们并没有什么区别,但是从官方的API文档来看,缓存最重要的一个地方就是减少程序对于磁盘的IO次数。加了缓存的程序再读取的时候会一次性读取很多个字节,之后提供给程序使用,但如果你不加缓存,那么程序就只会读取代码中指定的字节数。这在你一个字节一个字节从文件中读取数据的时候,其差别就凸现出来了。如果你没有使用缓存进行数据读取,那么你每读一个字节的数据,程序就去磁盘读取一次文件,这样会造成磁盘的频繁IO读取,减少磁盘的寿命。

5、ObjectInputStream / ObjectOutputStream - 序列化一个对象

要被序列化的POJO对象:

package com.chanshuyi.io.po;

import java.io.Serializable;

public class Student implements Serializable{

	/**
	 * 序列化ID
	 */
	private static final long serialVersionUID = 7288449352920655248L;

	private String name;

	private int age;

	private String phone;

	public Student(){

	}

	public Student(String name, int age, String phone){
		this.name = name;
		this.age = age;
		this.phone = phone;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}

	public String toString(){
		return name + "," + age + "," + phone;
	}

}

被序列化的POJO对象需要实现Serializable接口。

序列化对象方法:

//20.ObjectOutputStream - 序列化对象 - 将对象属性序列化保存
@Test public void serialized(){
	try{
		Student std = new Student("Tommy", 13, "18923923876");
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("StudentObject.obj"));
		oos.writeObject(std);
		oos.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

反序列化对象方法:

//21.ObjectInputStream - 反序列化对象 - 读取序列化后的对象
@Test public void deSerialized(){
	try{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("StudentObject.obj"));
		Student std = (Student)ois.readObject();
		System.out.println(std);
		ois.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

6、ByteArrayInputStream / ByteArrayOutputStream - 操作内存字节数据

//19.ByteArrayInputStream - 内存操作流 - 将内存数据转化为流
@Test public void random2Stream(){
	try{
		String str = "你好";
		ByteArrayInputStream input = new ByteArrayInputStream(str.getBytes());
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		int temp = 0;
		while((temp = input.read()) != (-1)){
			char ch = (char)temp;
			output.write(Character.toLowerCase(ch));
		}
		String outputStr = output.toString();
		input.close();
		output.close();
		System.out.println(outputStr);

	}catch(Exception e){
		e.printStackTrace();
	}
}

7、PipedInputStream / PipedOutputStream - 实现进程间的管道通信

接收者:

package com.chanshuyi.io.pineStream;

import java.io.PipedInputStream;

/**
 * 管道流 - 接收者
 * @author yurongchan
 *
 */
public class Receiver implements Runnable{

	private PipedInputStream in = null;

	public Receiver(){
		in = new PipedInputStream();
	}

	public PipedInputStream getIn(){
		return this.in;
	}

	public void run(){
		byte[] b = new byte[1000];
		int length = 0;
		try{
			length = this.in.read(b);
		}catch(Exception e){
			e.printStackTrace();
		}
		System.out.println("接收到的消息:" + new String(b, 0, length));
	}
}

发送者:

package com.chanshuyi.io.pineStream;

import java.io.PipedOutputStream;

/**
 * 管道流 - 发送者
 * @author yurongchan
 *
 */
public class Send implements Runnable{
	private PipedOutputStream out = null;

	public Send(){
		out = new PipedOutputStream();
	}

	public PipedOutputStream getOut(){
		return this.out;
	}

	public void run(){
		String msg = "Hello, I‘m outputer.\n你好,我是发送者。";
		try{
			out.write(msg.getBytes());
			out.close();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}

测试方法:

//22.PipedInputStream - 在不同线程之间进行通信
@Test public void pipeContact(){
	try{
		Send send = new Send();
		Receiver receiver = new Receiver();
		try{
			send.getOut().connect(receiver.getIn());
		}catch(Exception e){
			e.printStackTrace();
		}
		new Thread(send).start();
		new Thread(receiver).start();
	}catch(Exception e){
		e.printStackTrace();
	}
}

8、SequenceInputStream - 合并输入流

//23.SequenceInputStream - 合并几个输入流
@Test public void sequenceInputStream(){
	try{
		InputStream is1 = new FileInputStream(new File("sequence1.txt"));
		InputStream is2 = new FileInputStream(new File("sequence2.txt"));
		OutputStream os = new FileOutputStream(new File("sequence3.txt"));
		SequenceInputStream sis = new SequenceInputStream(is1, is2);
		int temp = 0;
		while((temp = sis.read()) != -1){
			os.write(temp);
		}
		sis.close();
		is1.close();
		is2.close();
		os.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

之后打开sequence3.txt会发现,1、2文本中的内容都到了sequence3.txt中了。

四、字符流读写

字符流的读写实际上还是基于字节流实现的,而且数组存储时更多是以字节为单位存储,因此更多时候还是使用字节流进行读写操作。

因此对于字符流的读写,我们只需要掌握常用的几个操作即可。

InputStreamReader / OutputStreamWriter - 实现字符流的读写

BufferedReader / BufferedWriter - 实现字符流的缓存层

FileReader / FileWriter - 字符流的工具类

1、字符流读取 - char(表示读取的数据类型是char或char[])

//9.字符流读取 - char
@Test public void writeChar(){
try{
    InputStreamReader reader = new InputStreamReader(new FileInputStream("OutputStreamWriter.txt"));
    char[] chars = new char[1000];
    int length = reader.read(chars);
    System.out.println("一共读取了" + length + "个字符,内容是:" + new String(chars));
    reader.close();
  }catch(Exception e){
    e.printStackTrace();
  }
}

2、字符流读取(缓存) - char

//10.字符流读取(缓存)
@Test public void writeCharWithBuffer(){
	try{
		InputStreamReader isr = new InputStreamReader(new FileInputStream("BufferedWriter.txt"));
		BufferedReader reader = new BufferedReader(isr);
		String str = "";
		while((str = reader.readLine()) != null && str.length() != 0){
			System.out.println(str);
		}
		reader.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

3、字符流写入 - char / char[] / String

//11.字符流写入
	@Test public void readChar(){
	try{
	  OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream("OutputStreamWriter.txt"));
	  writer.write("AllFileTest.fileReadUtil -> 字符流写入文件");
	  writer.close();
	}catch(Exception e){
	  e.printStackTrace();
	}
}

4、字符流的写入(缓存)- char/ String

//12.字符流写入(缓存)
@Test public void readCharWithBuffer(){
try{
	OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("BufferedWriter.txt"));
	BufferedWriter writer = new BufferedWriter(osw);
	writer.write("AllFileTest.fileReadUtil -> 字符流写入文件");
	writer.newLine();	//换行
	writer.write("第二行");
	writer.close();
}catch(Exception e){
	e.printStackTrace();
    }
}

总结一下上面四种方式的字符流写入写出,我们会发现两个规律,一个是读写规律,一个是缓存层的规律:

· 读写规律。字符流的读取,其读取的数据都是char(字符型)的单个或者数组。而字符流的写入除了可以写入单个或多个char类型的字符歪,还可以直接写入String(字符串)类型。

· 缓存层规律。字符流的读写,增加了缓存层之后的一个明显区别就是:加了缓存层(Buffer)之后,字符流读取可以实现整行读取(readLine),而字符流写入可以实现写入换行符(writeLine)。

5、文件读取工具类

//13.FileReader - 文件读取工具类(这是加了缓存的。但也可以不加缓存)
@Test public void fileReadUtil(){
    try{
		FileReader fr = new FileReader(new File("demo.txt"));
		BufferedReader reader = new BufferedReader(fr);
		String str = "";
		while((str = reader.readLine()) != null && str.length() != 0){
			System.out.println(str);
		}
		reader.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

将这个例子与上面的第2个例子,即加了缓存的字符流读取例子相比,你会发现其实这两个例子的区别就只是声明FileReader、InputStreamReader的区别而已。声明FileReader只需要再加上File类型参数即可,而声明InputStreamReader则需要再加上一个FileInputStream类,之后才能跟上File类型的参数。因此,我们才说FileReader是一个文件读取工具类。

6、文件写入工具类

//14.FileWriter - 文件写入工具类(这是加了缓存的。但也可以不加缓存)
@Test public void fileWriteUtil(){
	try{
		FileWriter fw = new FileWriter(new File("FileWriteUtil1.txt"));
		BufferedWriter writer = new BufferedWriter(fw);
		writer.write("HelloMan1");
		writer.newLine();
		writer.write("HelloMan2");
		writer.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

同样的,其实FileWriter与OutputStreamWriter也只是声明上的区别而已。

五、其他

这里会收集一些不怎么常用,但是有时也会用到的例子,遇到的时候可以快速的查询到。

1、追加新的内容

//9.字节流 - 追加新内容
//无论是字节流还是字符流,要追加新的内容都是再FileOutputStream中指定第二个参数为true
@Test public void appendFile(){
	try{
		File file = new File("hello.txt");
		OutputStream fos = new FileOutputStream(file, true);

		String str = "\n这是新增加的内容";
		fos.write(str.getBytes());
		fos.close();

	}catch(Exception e){
		e.printStackTrace();
	}
}

2、模拟打印流输出数据

//18.打印流 - 模拟打印的方式输出数据
@Test public void printStream(){
	try{
		PrintStream print = new PrintStream(new FileOutputStream(new File("PrintStream.txt")));
		print.println(true);
		print.println("您好,我是打印输出流");
		print.printf("名字:%s.年龄:%d", "Tom", 32); //格式化输出
		print.close();
		//这里的数据要再PrintStream.txt文件中才能看到
	}catch(Exception e){
		e.printStackTrace();
	}
}

3、使用OutputStream向屏幕输出内容

//19.使用OutputStream向屏幕输出内容
@Test public void systemOutStream(){
	try{
		OutputStream out = System.out;
		out.write("你好".getBytes());
		out.close();
	}catch(Exception e){
		e.printStackTrace();
	}
}

4、标准输出重定向

//20.标准输出重定向
@Test public void redirectOutput(){
	try{
		System.out.println("Print in the Screen. 你好");
		System.setOut(new PrintStream(new FileOutputStream(new File("RedirectOutput.txt"))));
		//下面的输出都将重定向到文件中
		System.out.println("=== 重定向后的输出 ===");
		System.out.println("Hello, Eclipse in Mac.");
	}catch(Exception e){
		e.printStackTrace();
	}
}

5、标准输入重定向

//21.标准输入重定向
@Test public void redirectInput(){
	try{
		File file = new File("RedirectOutput.txt");
		if(!file.exists()){
			System.out.println("文件不存在!");
			return;
		}else{
			System.setIn(new FileInputStream(file));
			byte b[] = new byte[1000];
			int length = System.in.read(b);
			System.out.println("读入的内容为:" + new String(b, 0, length));
		}
	}catch(Exception e){
		e.printStackTrace();
	}
}

6、错误输出重定向

//22.错误输出重定向
@Test public void redirectErrOutput(){
	try{
		System.err.println("Print in the Screen. 你好");
		System.setErr(new PrintStream(new FileOutputStream(new File("RedirectErrOutput.txt"))));
		//下面的输出都将重定向到文件中
		System.err.println("=== 重定向后的错误输出 ===");
		System.err.println("Hello, Eclipse in Mac.");
	}catch(Exception e){
		e.printStackTrace();
	}
}

感谢以下博文的参考:

1、http://www.cnblogs.com/rollenholt/archive/2011/09/11/2173787.html

2、http://www.cnblogs.com/oubo/archive/2012/01/06/2394638.html

时间: 2024-10-07 05:25:03

2.熟悉Java基本类库系列——Java IO 类库的相关文章

4.熟悉Java基本类库系列——Java 正则表达式类库

正则表达式语法结构图: Java正则表达式类库结构图: Java典型例子 1.String类 matches()方法 判断字符串是否符合特定正则表达式 @Test public void testRegex(){ String regex = ".*\\d{3}.*"; String str1 = "11tec34"; String str2 = "285dffd"; String str3 = "bac7736db"; //

Java知识探究一:关于IO类库

经过组织考察,令我忽然发觉自己在最常用的Java中也有很多不明白的地方,实为平身一大憾事,今天特意抽时间将这些点滴记录下来,与大家一起分享 第一批想整理的知识点如下: Java的IO探究,IO的整个结构与发展,顺带附上公司某小工写的断点续传代码学习. Java的异常机制,关于编译时异常和运行时异常的探究. JavaCommon包的理解,尤其是collection包的一些小看法,其实容器嘛,什么样的Utils也逃不出一些基本的范畴,比如存.取.排序.安全性.校验等等等. 闲话不多说,先开始今天的主

Java报错系列——java.util.Date/java.sql.Date/java.sql.Timestamp

前言 前几天,在项目中,遇到一个问题,情况很简单,就是从MYSQL数据库中取出的时间只有年月日,而没有时分秒.然后,第一直觉就是肯定是我导入的包有问题,果然,我导入的是java.sql.Date包,修正为java.util.Date,问题依旧.这是为什么呢?曾经记得,java.util.Date会取出日期+时间,而java.sql.Date只会有日期.这是对的吗?MYSQL的时间类型经常是datetime/timestamp,我们应该怎么做呢? 实验 MYSQL表准备: 代码: 输出: 结论 无

java高并发系列 - 第15天:JUC中的Semaphore,最简单的限流工具类,必备技能

这是java高并发系列第15篇文章 Semaphore(信号量)为多线程协作提供了更为强大的控制方法,前面的文章中我们学了synchronized和重入锁ReentrantLock,这2种锁一次都只能允许一个线程访问一个资源,而信号量可以控制有多少个线程可以访问特定的资源. Semaphore常用场景:限流 举个例子: 比如有个停车场,有5个空位,门口有个门卫,手中5把钥匙分别对应5个车位上面的锁,来一辆车,门卫会给司机一把钥匙,然后进去找到对应的车位停下来,出去的时候司机将钥匙归还给门卫.停车

{Java初阶系列一}--------Java基本简介

{Java初阶系列}--------Java基本简介 本人为自学Java系列,内容来自于中国大学mooc华东师范大学陈育良教授<Java核心技术>,在此感谢老师! 首先说明下Java作为一门编译性语言是如何运行: 首先由程序yuan编写的java程序,由javac.exe编译成字节码文件.class,再通过java.exe在jvm上运行成电脑可以识别的机器语言-------这个也被称为"一次编译,多次运行,跨平台操作",这都是jvm的功劳. 2. 初次学习我碰到疑问:jre

Java基础复习笔记系列 七 IO操作

Java基础复习笔记系列之 IO操作 1. 2.

JDK源码简析--java.lang包中的基础类库

题记 JDK,Java Development Kit. 我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下得rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列所有文章基于的JDK版本都是1.7.16. 本节内容 在本节中,简析java.lang包所包

Java集合干货系列-(一)ArrayList源码解析

前言 今天来介绍下ArrayList,在集合框架整体框架一章中,我们介绍了List接口,ArrayList继承了AbstractList,实现了List.ArrayList在工作中经常用到,所以要弄懂这个类是极其重要的.构造图如下:蓝色线条:继承绿色线条:接口实现 正文 ArrayList简介 ArrayList定义 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA

JDK框架简析--java.lang包中的基础类库、基础数据类型

题记 JDK.Java Development Kit. 我们必须先认识到,JDK不过,不过一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说.还是在一个层级上,它们都是须要被编译成字节码.在JRE中执行的,JDK编译后的结果就是jre/lib下的rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列全部文章基于的JDK版本号都是1.7.16. 源代码下载地址:https://jdk7.jav