java-IO编程

IO基础

io流

IO流是一种顺序读写数据的模式:java

  • 单向流动
  • 以byte为最小单位(字节流)

若是字符不是单子节表示的ASCLL码,Java还提供一下解决方案:linux

  • java还提供了reader、writer表示字符流
  • 字符流传输的最小单位是char
  • 字符流输出byte取决于编码方式

reader、writer本质上是一个能自动编解码的InputStream/OutputStream: 使用reader虽然读入的数据源是子节,可是咱们读入的数据都是char类型的字符 windows

使用InputStream虽然读入的数据和源同样都是byte子节,可是咱们能够根据编码将二进制byte数组转换成字符串达到和reader同样的效果
那么如何选择reader和InputStream?这取决于数据源,若是数据源不是文本就只能使用InputStream,若是是文本使用reader更加方便。

同步io与异步io

同步io:数组

  • 读写io时代码等待数据返回后才继续执行后续代码
  • 代码编写简单,cpu执行效率低

异步io:缓存

  • 读写io时仅发出请求,而后马上执行后续代码
  • 代码编写复杂,cpu执行效率高

JDK提供的java.io是同步io,java.nio是异步io 安全

File对象

java.io.File表示文件系统的一个文件或者目录 建立方法:能够是相对路径或者绝对路径bash

// windows
File f=new File("c:\\win\\note.exe");
// linux
File f=new File("/usr/bin/javac");
复制代码

获取路径/绝对路径/规范路径:getPath() / getAbsolutePath() / getCanonicalPath() 判断文件或目录:异步

  • isFile():是不是文件
  • isDirectory():是不是目录

须要注意的是构造一个File对象,即便咱们传入的文件或目录不存在也不会报错,由于没有对磁盘进行任何操做,只有在调用某些方法的时候若是文件或目录不存在才会报错。
文件操做:ide

  • canRead():是否容许读取该文件
  • canWrite():是否容许写入该文件
  • canExecute():是否容许运行该文件
  • length():获取文件大小
  • createNewFile():建立一个新文件
  • static createTempFile():建立一个临时文件
  • delete():删除该文件
  • deleteOnExit():在JVM退出时删除该文件

目录操做:函数

  • String[] list():列出目录下的文件和子目录名
  • File[] listFiles():列出目录下的文件和子目录名
  • File[] listFiles(FileFilter filter)
  • File[] listFiles(FilenameFilter filter)
  • mkdir():建立该目录
  • mkdirs():建立该目录,并在必要时将不存在的父目录也建立出来
  • delete():删除该目录

Input和Output

InputStream

java.io.InputStream是全部输入流的超类

  • int read()从输入流中读取数据的下一个字节,返回0到255范围内的int字节值。若是由于已经到达流末尾而没有可用的字节,则返回-1。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
  • int read(byte[])读取若干字节并填充到byte[]数组,返回读取的字节数
  • read()方法是阻塞(blocking)的

完整的读取InputStream全部子节

改造后的代码,使用缓存加快读取的速度:

package com.feiyangedu.sample;
import java.io.*;
public class Main {
	public static void main(String[] args) throws IOException {
		try (InputStream input = new FileInputStream("readme.txt")) {
			int n;
			byte[] buffer = new byte[1000];
			while ((n = input.read(buffer)) != -1) {
				System.out.println(n);
			}
		}
	}
}
复制代码

上面代码存在一个问题,若是在读取过程当中发生io错误,InputStream就没法正确的关闭资源得不到释放。改造后的代码以下:

或者(JDK1.7之后的写法)
利用缓冲区一次读取多个子节,提升效率:
ByteArrayInputStream能够在内存中模拟一个InputStream:
他的做用是将一个数组变成一个InputStream,经常用于测试

OutputStream

OutputStream是全部输出流的超类:

  • write(int b)写入一个字节
  • write(byte[])写入byte[]数组的全部字节
  • close():关闭输出流,使用try(resource)能够保证OutputStream正确关闭
  • flush()方法将缓冲器内容输出,一般是自动调用不须要手动调用

package com.feiyangedu.sample;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Main {
	public static void main(String[] args) throws IOException {
		try (OutputStream output = new FileOutputStream("output.txt")) {
			byte[] b1 = "Hello".getBytes("UTF-8");
			output.write(b1);
			byte[] b2 = "你好".getBytes("UTF-8");
			output.write(b2);
		}
	}
}
复制代码

输入流、输出流、字节数组缓冲区的综合实例

InputStream输入流、OutputStream输出流、ByteArrayInputStream输入缓冲区、ByteArrayOutStream输出缓冲区的协调使用。

输入流与输出流能够比喻成水管的两端,当水流经过入口(InputStream)流入经过出口(OutputStream)流出完成文件或者字符串从一个点到另外一个点的移动或者转换,在这个过程当中彷佛不须要缓冲区的介入。那么何时会用到缓冲区呢?当你想在输入流和输出流中间有不少操做,或者将输入流“复制”一份去作另外一个操做的时候就要用到缓冲区了。 好比咱们上传图片,为了更加安全,经过文件的前四个字符串来判断文件的类型: (写一个实例)

// 建立输出流缓冲区
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = file.read(buffer)) > -1 ) {
    baos.write(buffer, 0, len);
}
// 将缓冲区数据写入到数组中
baos.flush();
// 关闭文件
baos.close();

// 建立输出流缓冲区,将输入流缓冲区的数据写入到输入流缓冲区中,这样就实现了一次输入屡次输出的效果
InputStream getType = new ByteArrayInputStream(baos.toByteArray()); // 拿去检测文件类型
InputStream fileImg = new ByteArrayInputStream(baos.toByteArray()); // 若是文件类型合法,拿去上传文件

复制代码

总结

  • OutputStream是全部输出流的超类
  • FileOutputStream实现了文件流输出
  • ByteArrayOutStream在内存中模拟一个字节流输出
  • 使用try(resource)能够保证OutputStream正确关闭

Filter模式

已FileInputStream()为例,他从文件中读取数据,是最终数据源。

在FileInputStream上增长功能的常规作法

若是咱们要给FileInputStream添加缓冲功能: BufferedFileInputStream extends FileInputStream派生一个BufferedFileInputStream 若是给FileInputStream添加计算签名的功能: DigestFileInputStream extends FileInputStream派生一个DigestFileInputStream ..... 若是要添加更多的功能就须要更多的子类去扩展,这样的作的弊端是形成子类爆炸

JDK的Filter模式

JDK为了解决上面的问题把InputStream分为两类:

  • 直接提供数据的的InputStream:(数据真正的来源)
    • FileInputStream
    • ByteArrayInputStream
    • ServletInputStream
    • ...
  • 提供额外附加功能的InputStream:
    • BufferedInputStream
    • DigestInputStream
    • CipherInputStream
    • ...

如何使用

组合:将一个对象复制给另外一个对象的变量,使其持有另外一个的对象的特性和方法 当咱们使用InputStream的时候咱们要根据实际状况组合使用:

上图显示FileInputStream提供数据后通过两次包装后,具备了更多的功能,可是不管通过多少次包装他依然能够向上转型为InputStream而后进行操做。

Filter模式又称Decorator模式,经过少许的类实现了各类功能的组合。

InputStream继承树

FilterInputStream 的做用是用来“封装其它的输入流,并为它们提供额外的功能”。它的经常使用的子类有BufferedInputStream和DataInputStream。 一边是提供数据的实现类,一边是提供附加功能的实现类。经过两边实现类的组合实现更多的功能。

我的理解:提供数据的实现类不能进行同类包装,而功能类能够同类包装。

实例

package com.feiyangedu.sample;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		try (InputStream input = new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt.gz")))) {
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int n;
			while ((n = input.read(buffer)) != -1) {
				output.write(buffer, 0, n);
			}
			byte[] data = output.toByteArray();
			String text = new String(data, "UTF-8");
			System.out.println(text);
		}
	}

}
复制代码

编写自定义工具类计算读取的字节数

import java.io.*;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		try (InputStream input = new CountInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream("test.txt.gz"))))) {
			ByteArrayOutputStream output = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int n;
			while ((n = input.read(buffer)) != -1) {
				output.write(buffer, 0, n);
			}
			byte[] data = output.toByteArray();
			String text = new String(data, "UTF-8");
            // 打印内容
			//System.out.println(text);
			//因为咱们要使用count方法,所以要将InputStream类型向下转型为CountInputStream,由于咱们明确的知道继承关系这里能够不判断是否能够安全的向下转型
			System.out.println(InputStream.class.isAssignableFrom(CountInputStream.class));
			System.out.println(((CountInputStream) input).count);
		}
	}
}
class CountInputStream extends FilterInputStream {
	int count=0;
	public CountInputStream(InputStream in) {
		super(in);
	}

	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		//经过调用父类的read方法获取包装的inputStream的字节长度
		int n = super.read(b, off, len);
		count +=n;
		return n;
	}
}

复制代码

输出

true
11356
复制代码

关于字节(重要)

InputStream的read()与read(byte[] b)

在学习的过程当中发现若是使用read()返回的值表明字节值(0-255),而使用read(byte[] b)则返回的是读取的字节长度,而字节值储存在byte[] b类型的b变量中为何会产生这样的结果?
首先看JDK的解释:InputStream的read()的方法从输入流中读取数据的下一个字节,返回0到255范围内的int字节值。
那么问题来了0到255范围的字节值是什么?
简单解释:00000000一个字节包含8位二进制值,这8位二进制数相互变化,共有2^8 = 256种数字,0~255 那么InputStream的read()每次返回的就是0~255的值,这个int类型10进制的值能够转换成一个8位的二进制数 字符类型:char,由于java的char使用unicode编码,因此可直接赋值给int类型查看他的unicode编码 反过来就是一个int类型(char)int就能够直接转换成char字符

ab中国cdefj
复制代码
import java.io.*;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		InputStream in = null;
		File f = new File("test.txt");
		in = new FileInputStream(f);
		int i = 0;
		while ((i = in.read()) != -1) {
			//String str = new String((char)i);
			System.out.println((char)i);
		}
	}
}
复制代码

输出

a
b
ä
¸
­
å
›
½
c
d
e
f
j
复制代码

上面的程序演示了read()读取单个字节而后返回的int类型的值,经过(char)i强制转型为单个字符而后输出,因为中文是占3个字符的因此中文部分显示乱码

下面的代码演示byte[]读取数据

import java.io.*;
import java.util.zip.GZIPInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		InputStream in = null;
		File f = new File("test.txt");
       //当一次读取3个字节的时候若是凑巧全是中文,或者从第四个字节开始是中文,就不会出现乱码,若是不是就会出现乱码
		byte[] b = new byte[3];

		in = new FileInputStream(f);
		int i = 0;
		while ((i = in.read(b)) != -1) {
        //String函数能够接收一个char数组或者byte数组而后转换成字符串
			String str = new String(b);
			System.out.println(str);
		}
	}
}
复制代码

下面是两种演示数据: 不会出现乱码,由于第三个字节后是中文,每次都三个字节正好读取一个中文字符

abc中国cdefj
复制代码

显示效果

abc
中
国
cde
fje
复制代码

若是没有前面的c

ab中国cdefj
复制代码

就会输出乱码

ab�
���
��c
def
jef
复制代码

操做Zip

ZipInputStream继承自FlaterInputStream实现了ZipConstants接口,虽然ZipInputStream是FlaterInputStream子类可是有些方法是ZipInputStream独有的,在使用这些方法的时候不能向上转型,所以使用ZipInputStream的时候直接建立一个他的实例并且无需向上转型,

ZipInputStream能够读取Zip流。 JarInputStream提供了额外读取jar包内容的能力。 ZipOutputStream能够写入Zip流。 配合FileInputStream和FileOutputStream就能够读写Zip文件。

package com.feiyangedu.sample;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class Main {

	public static void main(String[] args) throws IOException {
		try (ZipInputStream zip = new ZipInputStream(new BufferedInputStream(new FileInputStream("test.jar")))) {
			ZipEntry entry = null;
			while ((entry = zip.getNextEntry()) != null) {
				if (entry.isDirectory()) {
					System.out.println("D " + entry.getName());
				} else {
					System.out.println("F " + entry.getName() + " " + entry.getSize());
					printFileContent(zip);
				}
			}
		}
	}

	static void printFileContent(ZipInputStream zip) throws IOException {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int n;
		while ((n = zip.read(buffer)) != -1) {
			output.write(buffer, 0, n);
		}
		byte[] data = output.toByteArray();
		System.out.println(" size: " + data.length);
	}

}

复制代码

classpath资源 (linux与win的路径有区别)

classpath中能够包含任意类型的文件。 从classpath读取文件能够避免不一样环境下文件路径不一致的问题。 读取classpath资源:

try(InputStream input = getClass().getResourceAsStream("/default.properties")) {
    if (input != null) {
        // Read from classpath
    }
}
复制代码
  • 把资源存储在classpath中能够避免文件路径依赖
  • class对象的getResourceAsStream()能够从classpath读取资源
  • 须要检查返回的InputStream是否为null
package com.feiyangedu.sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;

public class Main {

	public static void main(String[] args) throws IOException {
		// 从classpath读取配置文件:
		try (InputStream input = Main.class.getResourceAsStream("/conf.properties")) {
			if (input != null) {
				System.out.println("Read /conf.properties...");
				Properties props = new Properties();
				props.load(input);
				System.out.println("name=" + props.getProperty("name"));
			}
		}
		// 从classpath读取txt文件:
		String data = "/com/feiyangedu/sample/data.txt";
		try (InputStream input = Main.class.getResourceAsStream(data)) {
			if (input != null) {
				System.out.println("Read " + data + "...");
				BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));
				System.out.println(reader.readLine());
			} else {
				System.out.println("Resource not found: " + data);
			}
		}
	}

}
复制代码

conf.properties

name=Java IO
url=www.feiyangedu.com
复制代码

序列化

序列化是指把一个Java对象变成二进制内容(byte[]) Java对象实现序列化必须实现Serializable接口(空接口) 反序列化是指把一个二进制内容(byte[])变成Java对象 使用ObjectOutputStream和ObjectInputStream实现序列化和反序列化 readObject()可能抛出的异常:

  • ClassNotFoundException:没有找到对应的Class
  • InvalidClassException:Class不匹配
  • 反序列化由JVM直接构造出Java对象,不调用构造方法
  • 可设置serialVersionUID做为版本号(非必需)

Reader与InputStream

Reader与InputStream的区别:

Reader以字符为最小单位实现了字符流输入:

  • int read() 读取下一个字符并返回字符int值(0-65535)
  • int read(char[]) 读取若干字符并填充到char[]数组 经常使用Reader类:
  • FileReader:从文件读取
  • CharArrayReader:从char[]数组读取

完整读取文件实例

利用缓冲区读取文件
FileReader能够从文件中获取Reader: 可是要注意这里使用的是系统默认的编码
CharArrayReader能够在内存中模拟一个Reader:

Reader与InputStream的关系

Reader是基于InputStream构造的,任何InputStream均可指定编码并经过InputStreamReader转换为Reader:

Reader reader = new InputStreamReader(input, "UTF-8")
复制代码

FileReader内部持有一个 FileInputStream

import java.io.*;

public class Main {

	public static void main(String[] args) throws IOException {
		try (Reader reader = new InputStreamReader(new FileInputStream("readme.txt"),"UTF-8")) {
			int n;
			while ((n = reader.read()) != -1) {
				System.out.println((char) n);
			}
		}
	}

}
复制代码

Writer与OutputStream

Writer与OutputStream的区别

Writer以字符为最小单位实现了字符流输出:

  • write(int c) 写入下一个字符(0-65535)
  • write(char[]) 写入char[]数组的全部字符
  • write(char[] c,int off,int len) 写入指定范围的字符
  • write(String s)写入string表示的字符

经常使用Writer类:

  • FileWriter:写入文件
  • CharArrayWriter:写入char[]数组

向Writer写入字符:

FileWriter能够从文件中获取Writer:
CharArrayWriter能够在内存中模拟一个Writer:

Writer与OutputStream的关系

Writer是基于OutputStream构造的,任何OutputStream均可指定编码并经过OutputStreamWriter转换为Writer:

Writer writer = new OutputStreamWriter(output, "UTF-8")
复制代码
相关文章
相关标签/搜索