Java 基本的IO流

目录


流的分类

4个抽象基类

所有输入输出流

File类的使用

FileInputStream & FileOutputStream

FileReader & FileWriter

BufferedInputStream & BufferedOutputStream

BufferedReader &  BufferedWriter

ByteArrayInputStream & ByteArrayOutputStream

转换流->InputStreamReader & OutputStreamWriter

包装流->DataInputStream & DataOutputStream

对象序列化->ObjectInputStream & ObjectOutputStream

PrintStream

RandomAccessFile类的使用

 

 

流的分类

  按照流向来分类:输入流、输出流,针对于内存来说。

  按照编码格式来分类:字节流、字符流,字节对应byte(1字节8bit),字符对应char(2字节16bit)。

  按照等级来分类:节点流、处理流,节点流是低级流(IO设备、网络之间),处理流是高级流(封装了节点流)。

 

 

4个抽象基类

  Java中的IO流由4个抽象基类派生,分别是:

    输入流:InputStream(字节流)、Reader(字符流)。

    输出流:OutputStream(字节流)、Writer(字符流)。

  对于输入流,InputStream和Reader抽象类都定义了5个方法:

    int read()

    int read(byte[] / char[])

    int read(byte[] / char[], int offset, int length)

    void reset() 将文件指针定位到初始状态

    void skip(long n) 从文件指针当前位置,向文件末尾移动n个字节或者字符。

  对于输出流,OutputStream和Writer抽象类也定义了3个方法:

    void write(int c),c即可是byte对应的ASCII,也可是字符的unicode码。

    void write(byte[] / char[])

    void write(byte[] / char[], int offset, int length)

  Writer抽象类多提供的两个方法:

    void write(String str)

    void write(String str, int offset, int length)

   

 

输入输出流的所有类

  下表(截图来自《疯狂Java 第三版》)

 

  看到上面这么多的类,其实,很有规律,比如FileInputStream、FileReader、FileOutputStream、FileWriter为例。

 

 

File类的使用

  Java提供了File类进行一些文件操作,包括文件信息的查看,文件的操作。

import java.io.File;
import java.io.IOException;

public class UseFile {
	
	public static void useSeparator() {
		System.out.println(File.separator);
		System.out.println(File.pathSeparator);
		
		// 推荐使用格式 
		String path = "E:/a/b/c.txt";
		System.out.println(path);
	}
	
	public static void useFileBasic() {
		String path = "E:/simpleEclipseWordSpace/IO/data.txt";
		File file = new File(path);
		try {
			file.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		file = new File("E:/simpleEclipseWordSpace/", "IO/data.txt");
		file = new File("E:/simpleEclipseWordSpace/IO", "data.txt");
	}
	
	public static void useFileAPI() {
		String path = "E:/simpleEclipseWordSpace/IO/data.txt";
		File file = new File(path);
		
		// 名称信息
		System.out.println("getName -> " + file.getName());
		System.out.println("getPath -> " + file.getPath());
		System.out.println("getAbsolutePath -> " + file.getAbsolutePath());
		System.out.println("getParent -> " + file.getParent());
		
		// 判断状态
		System.out.println("exists -> " + file.exists());
		System.out.println("isFile -> " + file.isFile());
		System.out.println("isDirectory -> " + file.isDirectory());
		
		// 文件信息
		System.out.println("length -> " + file.length()); // 单位字节,length为0,可能是文件不存在,也可能是该路径表示的是文件夹
		System.out.println("canRead -> " + file.canRead());
		System.out.println("canWrite -> " + file.canWrite());
		
		// 文件操作
		File newFile = new File("test.txt");
		boolean created = false;
		try {
			// 不存在则创建成功
			created = newFile.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("success -> " + created);
		
		// 删除文件,已经存在的路径
		boolean deleted = false;
		deleted = newFile.delete();
		System.out.println("deleted -> " + deleted);
	
	}
	
	public static void createDirectory() {
		// 使用mkdir和mkdirs来创建目录,区别如下:
		// 使用mkdir来创建目录时,如果path中任意一个目录不存在,则创建失败
		// 使用mkdirs来创建目录时,如果path中的目录不存在,则会创建目录
		String path = "E:/demo/test";
		File directory = new File(path);
		directory.mkdirs();
		directory.mkdir();
	}
	
	public static void directoryOperation() {
		String path = "E:/eclipseWorkspace";
		File dir = new File(path);
		
		// 使用list()返回下一级的文件名或者目录名
		String[] subNames = dir.list();
		for (String s : subNames) {
			System.out.println(s);
		}
		
		// 使用list()返回下一级的文件或者目录的File对象
		File[] subFiles = dir.listFiles();
		for (File f : subFiles) {
			System.out.println(f.getAbsolutePath());
		}
		
		// 列出所有盘符
		File[] roots = dir.listRoots();
		for (File f : roots) {
			System.out.println(f.getAbsolutePath());
		}
	}
	
	// 扫描目录
	public static void scanDirectory(File src) {
		System.out.println(src.getAbsolutePath());
		if (null == src || !src.exists()) {
			return;
		} else if (src.isDirectory()){
			for (File s : src.listFiles()) {
				scanDirectory(s);
			}
		}
	}
	
	public static void main(String[] args) {
		try {
			System.out.println("aaa");
			return;
		} catch (Exception e) {
			e.getMessage();
		} finally {
			System.out.println("hello");
		}
	}
	
}

 

 

FileOutputStream 与 FileInputStream

  对于FileInputStream和FileOutputStream,需要注意:

  1、都是对文件进行操作的,创建InputStream对象的时候,可以传入File对象,也可以传入文件的路径path。

  2、数据的格式是二进制字节码(byte)格式。

  3、使用输出流的时候, 一定要flush,不要等到关闭输出流的时候,让他自动刷新缓冲区,一定要手动flush,防止数据滞留。

@Test
public void testFileInputStream() throws IOException {
    InputStream inputStream = new FileInputStream(new File("pom.xml"));

    // 一次读一个字节,同时指针后移
    int data = inputStream.read();

    // 创建缓冲数组
    byte[] buf = new byte[10];

    // 一次读取多个数组长度的内容,返回读取的长度
    int length = inputStream.read(buf);

    // 一次读取多个字节
    length = inputStream.read(buf, 0, 5);

    inputStream.close();
}

@Test
public void testFileOutputStream() throws IOException {
    // 默认清空写,可以加第二个参数true表示append写
    OutputStream outputStream = new FileOutputStream("pom.txt");

    // 写入一个字节(注意不是写入99,而是它对应的ASCII码
    outputStream.write(99);

    // 将字节数组中的数据写入
    byte[] data = "hello world".getBytes();
    outputStream.write(data);

    // 将字节数组中的一部分写入文件中
    outputStream.write("123456789".getBytes(), 0, 5);

    outputStream.flush();
    outputStream.close();
}

  

  

FileReader 和 FileWriter类

  FileReader和FileWriter分别是Reader和Writer的子类。在操作数据的时候,是以字符为单位,而不是字节。所以,如果使用FileReader和FileWriter来操作二进制文件,可能会因为编码的不同,导致文件出现错误(导致文件内容修改)。

  使用输出流的时候, 一定要flush,不要等到关闭输出流的时候,让他自动刷新缓冲区,一定要手动flush,防止数据滞留。

@Test
public void testFileReader() throws IOException {
    FileReader reader = new FileReader("pom.xml");

    // 读取一个字符
    int c = reader.read();
    System.out.println((char) c);

    char[] buf = new char[10];
    // 将数据读出后存到字符数组中,返回读取的长度
    int length = reader.read(buf);

    // 读取指定长度的数据(字符),存放到字符数组中,返回读取的长度
    length = reader.read(buf, 0, 5);
    System.out.println(new String(buf));

    reader.close();
}

@Test
public void testFileWriter() throws IOException {
    FileWriter writer = new FileWriter("pom.txt");

    // 写入一个字符
    writer.write('中');

    char[] data = "hello".toCharArray();
    // 将字符数组内容全部写入
    writer.write(data);

    // 将字符数组中的部分内容写入
    writer.write(data, 0, 3);

    // 可以直接写String
    writer.write("hello");

    // 利用append可以多次追加
    writer.append("world").append("ok");

    writer.flush();
    
    writer.close();
}  

  

 

BufferedInputStream 和 BufferedOutputStream

  前面我们使用文件输入输出流,基本都是这样的:

  1、一次读一个字节(字符),或者一次写一个字节(字符)

  2、一次读几个字节(字符), 或者一次写几个字节(字符)

  这样效率很低,为了提高效率,可以使用带有缓冲的输出输出流,当数据长度到达一定长度的时候,再进行一次写入或者写出,这样可以提高效率。

@Test
public void testBufferedInputStream() throws IOException {
    InputStream inputStream = new FileInputStream("pom.xml");
    BufferedInputStream bis = new BufferedInputStream(inputStream);

    byte[] buf = new byte[1024 * 10];

    // 就像使用FileInputStream一样使用
    int length = -1;
    while ((length = bis.read(buf)) != -1) {
        System.out.print(new String(buf));
    }

    // 如果使用了缓冲流,那么不推荐手动关闭节点流
    //inputStream.close();

    // 直接关闭缓冲流,节点流也会被关闭
    bis.close();
}

@Test
public void testBufferedOutputStream() throws IOException {
    OutputStream outputStream = new FileOutputStream("pom.xml", true);
    BufferedOutputStream bos = new BufferedOutputStream(outputStream);

    // 就像使用FileOutputStream一样
    bos.write("hello".getBytes());

    // 直接使用缓冲流的flush即可
    bos.flush();

    // 如果使用了缓冲流,那么不推荐手动关闭节点流
    // outputStream.close();
    bos.close();
}

  

    

 

BufferedReader 和 BufferedWriter

@Test
public void testBufferedReader() throws IOException {
    FileReader reader = new FileReader("pom.xml");
    BufferedReader bufferedReader = new BufferedReader(reader);

    // 使用方式和FileReader方式一样
    String s = bufferedReader.readLine();
    System.out.println(s);

    // 如果使用了缓冲流,那么不推荐手动关闭节点流
    // reader.close();
    bufferedReader.close();
}

@Test
public void testBufferedWriter() throws IOException {
    FileWriter writer = new FileWriter("pom.txt",true);
    BufferedWriter bufferedWriter = new BufferedWriter(writer);

    // 插入新的一行(相当于一个换行)
    bufferedWriter.newLine();
    bufferedWriter.write("hello");
    bufferedWriter.append(" world").append("ok");

    bufferedWriter.flush();

    // 如果使用了缓冲流,那么不推荐手动关闭节点流
    // writer.close();
    bufferedWriter.close();
}

  

 

ByteArrayInputStream 和 ByteArrayOutputStream

  之前,我们操作的数据都是文件中的,文件存储在硬盘中,io效率比较低(硬盘的读取和写入相对于内存来说,很低)。Java提供了ByteArrayInputStream 和 ByteArrayOutputStream,可以让我们操作内存中的数据,如果我们频繁地从一个硬盘中读取一个文件数据,就会导致性能问题; 为此,我们可以将硬盘中的文件读取一次,读到内存中,然后在内容中反复使用,使用的就是上面的ByteArrayInputStream 和 ByteArrayOutputStream。因为内存中的数据都是字节二进制格式,而不是字符格式,所以,只有ByteArrayInputStream和ByteArrarOutputStream,而没有ByteArrayReader 和 ByteArrayWriter。

  注意点:

  1、ByteArrayInputStream 和 ByteArrayOutputStream操作的数据都是字节格式,可以通过getBytes()来转为字节数组。

  2、他们操作的数据都是来自内存的,所以,不需要关闭,即不需要调用close()。

@Test
public void testByteArrayInputStream() throws IOException {
    byte[] data = "hello world".getBytes();
    // 对于ByteArrayInputStream来说,数据的来源是“内存”,也就是上面的data
    InputStream inputStream = new ByteArrayInputStream(data);

    // 读取操作和 FileInputStream的用法相同
    byte[] buf = new byte[5];
    int length = inputStream.read(buf);

    System.out.println(new String(buf));

    // ByteArrayInputStream不需要关闭
}

@Test
public void testByteArrayOutputStream() throws IOException {
    // ByteArrayOutputStream的dest是内存,所以不需要指定dest
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // 要写入内存的数据
    byte[] data = "hello world".getBytes(); 
    
    baos.write(data);
    baos.flush();

    // 获取写入内存的数据
    byte[] res = baos.toByteArray();
    System.out.println(new String(res));
}

  

  

  

InputStreamReader 和 OutputStreamWriter

  前面几个输入输出流,大抵都是这样的,从源点读入数据,可以使用字节流,也可以使用字符流,但是一旦将数据读入到内存,操作数据时就会有所不同:

  1、如果读入的时候使用的是字符流,那么后面就要使用字符流方式来操作内存中数据

  2、如果读入的时候使用的是字节流,那么后面就要使用字节流方式来操作内存中数据

  其实这样是多有不便的,比如我们从文本文件中以字符流形式读入数据,想通过字节流形式操作数据,这就比较麻烦了,因为涉及到字符编码的问题。于是Java提供了转换流,可以将转换流的工作图表示如下:

/**
 * InputStreamReader继承自Reader,是指使用以字节流形式打开资源,但是却使用字符流的方式读取
 */
@Test
public void testInpuStreamReader() throws IOException {
    // 以字节流的方式打开资源
    FileInputStream inputStream = new FileInputStream("pom.txt");

    // 创建InputStreamReader需要接受字节流对象
    Reader reader = new InputStreamReader(inputStream);

    // 还可以指定字符集
    // InputStreamReader isr = new InputStreamReader(inputStream, "utf-8");

    // 注意,每一次是读一个char,而不是一个byte
    int c = reader.read();

    System.out.println((char)c);

    // 不用关闭节点流
    reader.close();
}

/**
 * OutputStreamWriter继承自Writer,是指 将 字符串(字符数组)写入到 用字节流方式打开的资源中
 */
@Test
public void testOutputStreamWriter() throws IOException {
    // 资源使用字节流方式打开
    OutputStream outputStream = new FileOutputStream("pom.txt", true);
    // 接收一个OutputStream
    Writer writer = new OutputStreamWriter(outputStream);

    // 还可以指定字符集
    // Writer writer = new OutputStreamWriter(outputStream, "utf-8");
    writer.write("hello world");
    writer.append("ok").append("fine");
    writer.flush();

    writer.close();
}

  

 

DataInputStream 和 DataOutputStream

  读取和写入的数据是“有类型”的,所以在使用这两个数据流的时候,需要主要读取的顺序要和写入时的顺序相对应。

    /**
     * DataInputStream和DataOutputStream是装饰流,读写的数据是有"类型"的
     * DataInputStream是以字节流的形式读取数据
     * DataOutputStream是将数据以字节流形式输出
     * DataInputStream和DataInputStream需要配合使用,并且两者操作的顺序需要对应
     */
    @Test
    public void testDataStream() throws IOException {
        /*
         * 写数据
         */
        OutputStream outputStream = new FileOutputStream("pom.txt");
        DataOutputStream das = new DataOutputStream(outputStream);
        // 开始写入数据,注意数据是什么类型,就需要调用writeXxx
        das.writeBoolean(true);
        das.writeDouble(9.99);
        das.writeUTF("hello world");
        das.flush();
        das.close();

        /**
         * 读数据
         */
        InputStream inputStream = new FileInputStream("pom.txt");
        DataInputStream dis = new DataInputStream(inputStream);
        // 注意读数据时,调用的API要和写入时的API对应,顺序不要颠倒
        boolean flag = dis.readBoolean();
        double d = dis.readDouble();
        String s = dis.readUTF();
        System.out.println(flag);   // true
        System.out.println(d);      // 9.99
        System.out.println(s);      // hello world
    }

  

  

ObjectInputStream 和 ObjectOutputStream

  这两个对象流和DataInputStream、DataOutputStream用法相同,但是他还有另外一个很重要的功能:实现对象的序列化。

  当一个对象需要序列化的时候,这个对象所属的类,需要实现Serializable接口。如果类中某些属性不希望序列化的时候显式(想隐藏属性和属性值),可以在属性名之前加transient关键字。

/**
 * ObjectOutputStream和ObjectInputStream这两个对象流,与DataOutputStream和DataInputStream使用方法相同
 * 不过ObejctOutputStream和ObjectInputStream可以完成对象的序列化。
 */
@Test
public void testObjectStream() throws IOException, ClassNotFoundException {
    /**
     * 写入数据
     */
    OutputStream outputStream = new FileOutputStream("pom.txt");
    ObjectOutputStream oos = new ObjectOutputStream(outputStream);

    oos.writeBoolean(true);
    // 对象序列化,要进行序列化的类必须实现Serializable接口
    oos.writeObject(new Person(1,"ganlixin"));
    oos.flush();
    oos.close();


    /**
     * 读取数据
     */
    InputStream inputStream = new FileInputStream("pom.txt");
    ObjectInputStream ois = new ObjectInputStream(inputStream);

    // 读取顺序和写入时的顺序相同
    boolean flag = ois.readBoolean();
    Object o = ois.readObject();
    if (o instanceof Person) {
        System.out.println(o); //Person{id=1, name='ganlixin'}
    }
}

  

 

PrintStream

import java.io.BufferedOutputStream;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

public class Use_PrintStream {
	public static void main(String[] args) throws FileNotFoundException {
		/**
		 * PrintStream 打印流,比如System.out
		 */
		
		/**
		PrintStream out = System.out;
		out.println("hello");
		*/
		
		// 指定输出端
		PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("demo.txt")));
		out.println("yes");
		
		// 可以通过System.setOut来指定输出端
		System.setOut(out);
		System.out.print("该内容会输出到新指定的地方");
		
		// FileDescriptor.out;// 标准输出
	}
}

  

 

RandomAccessFile类

  这个类,其实和PHP中的fopen的功能差不多,指定需要操作的文件名以及操作的类型(模式)。

  测试读文件

import java.io.IOException;
import java.io.RandomAccessFile;

public class Test_RandomAccessFile_Read{
	public static void main(String[] args) throws IOException {
		
		// 注意RandomAccessFile处理文件的编码格式是字节流格式,而不是字符流
		
		// RandomAccessFile.RandomAccessFile(String name, String mode)
		RandomAccessFile raf = new RandomAccessFile("data.txt", "r");
		// data.txt的内容:0123456789abcdefghijklmnopqrstuvwxyz
		
		System.out.println(raf.length()); //35
		// 返回文件内容的大小(字节),注意文件大小不等同于文件内容大小
		
		raf.seek(3); // 定位到文件内容的第n字节出
		byte[] buf = new byte[5];    
		raf.read(buf);
		System.out.println(new String(buf));// 34567
		
		//一次性读取一行
		System.out.println(raf.readLine());  // 89abcdefghijklmnopqrstuvwxyz
	}
}

  

  测试写文件

import java.io.IOException;
import java.io.RandomAccessFile;

public class Test {
	public static void main(String[] args) throws IOException {
		// RandomAccessFile.RandomAccessFile(String name, String mode)
		RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
		// data.txt的内容:0123456789
		
		//RandomAccessFile.write(int)
		//RandomAccessFile.write(byte b[])
		//RandomAccessFile.write(byte b[], int off, int len)
		raf.write("abcd".getBytes());
		
		//raf.writeXxx
//		raf.writeInt(5);
//		raf.writeChar('Y');
//		raf.writeChars("hello");
		raf.close();
		
		//在写文件的时候,如果没有使用seek来移动文件指针,那么会先清空文件内容,在填入内容,填入内容的格式是二进制字节流
	}
}

  

 

posted @ 2018-10-27 16:58  寻觅beyond  阅读(369)  评论(0)    收藏  举报
返回顶部