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

原文地址为: 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


转载请注明本文地址: 2.熟悉Java基本类库系列——Java IO 类库

猜你喜欢

转载自blog.csdn.net/dearbaba_8520/article/details/80324248