Java基础笔记(IO)

1 IO

IO 是指 Input / Output,即输入和输出。

  • Input 是指从外部读入数据到内存并以Java提供的某种数据形式表示,如读文件,从网络读取等。因为代码是在内存中运行的,所以数据也必须读取在内存中,最后表现的具体形式无非是 byte 数组,字符串等等。
  • output 指的是把数据从 内存 输出到外部,例如,写文件,输出到网络等等。

即可以看作输入,输出的参照物是 内存

IO 流是一种顺序读写数据的模式,它是单向流动的。

Java提供了 InputStreamOutputStream 表示字节流,它的最小单位为 byte

如果读写的文件是文本,那Java还提供了 ReaderWriter 表示字符流,字符流读取数据时得到的却是 char 字符,归咎原因还是 Reader 内部把读到的 byte 进行了编码。
ReaderWriter 可以看作是一个能自动编解码的 InputStreamOutputStream,传输的最小单位为 char,而它输出的 char 取决于编码方式。
当如果数据源不是文本那只能使用 InputStreamOutputStream,如果是文本那么使用 ReaderWriter 就更方便一些。

1.1 File 对象

常用的 File 构造函数是传入 pathName 字符串,这个 pathName 是与操作系统有关的,window 下是使用两个反斜线 \\ 分隔目录(或者直接使用 /),而 Linus 使用一个一个反斜线 \

java.io.File 表示文件系统的一个文件或目录,创建 File 对象本身并不涉及 IO 操作,通过 isFile() 实例方法判读 File 对象是否为文件,通过 isDirectory() 实例方法判断 File 对象是否为目录。
File 对象获取路径信息,可通过以下 实例方法,这三个方法一般对 文件的 File 对象 操作返回都是统一个字符串:

  • getPath():获取路径,一般的获取结果是创建目录 File 对象时,传入的目录字符串
  • getAbsolutePath():获取绝对路径
  • getCanonicalPath():获取规范路径

eg:

File filePath = new File("./");
System.out.println("获取 filePath 的路径:" + filePath.getPath());
System.out.println("获取 filePath 的绝对路径:" + filePath.getAbsolutePath());
System.out.println("获取 filePath 的规范路径:" + filePath.getCanonicalPath());

// 获取 filePath 的路径:.
// 获取 filePath 的绝对路径:F:\javacode\ForLearningJava\.
// 获取 filePath 的规范路径:F:\javacode\ForLearningJava

1.1.1 文件 File 对象的操作

常用的实例方法(标了 static 的是静态方法):

  • canRead():是否可读
  • canWrite():是否可写
  • canExceute():是否可运行
  • length():返回文件大小
  • createNewFile():创建文件,返回一个布尔值,创建成功返回 true,如果存在与此文件名相同的文件,则返回 false
  • delete():删除文件
  • static createTempFile():创建临时文件
  • deleteOnExit():退出 JVM 时删除该文件

1.1.2 目录 File 对象的操作

常用的实例方法:

  • String[] list():返回全部文件名和子目录的名称数组
  • File[] listFiles():返回全部文件名和子目录的 File 数组
  • File[] list(FilenameFilter filter):返回过滤后的文件名和子目录的名称数组
  • File[] listFiles(FilenameFilter filter):返回过滤后的文件名和子目录的名称 File 数组
  • mkdir():创建该目录
  • mkdirs():创建该目录,必要时把父目录也创建出来
  • delete():删除该目录

eg:

File filepath1 = new File("F:\\");
// 输出文件后缀为 pdf 的文件名
for (String item : filepath1.list(new FilenameFilter() {
	
	@Override
	public boolean accept(File dir, String name) {
		if (name.endsWith("pdf")) {
			return true;
		}
		return false;
	}
})) {
	System.out.println(item);
}

1.2 IO 接口

Java的IO流的接口和实现是分离的,同步 IO 接口包括(它们都是抽象类):

  • 字节流:
    • InputStream:对应的实现类有 FileInputStream 等等
    • OutputStream:对应的实现类有 FileOutputStream 等等
  • 字符流:
    • Reader:对应的实现类有 FileReader 等等
    • Writer:对应的实现类有 FileWriter 等等

1.2.1 InputStream

InputStream 是一个抽象类,是所有输入流的超类:

  • abstract int read():抽象 read() 方法是该类最重要的一个方法,它的作用是读取下一个字节,并返回字节代表的整数(0 ~ 255),如果已经读到末尾,就返回 -1
  • int read(byte[] b)read() 方法的重载,利用缓冲区一次接受多个字节,它接受一个 byte 数组,读取若干个字节后填充到 byte 数组中,返回读取的字节个数,当已经读到末尾,就返回 -1
  • int read(byte[] b, int off, int len):指定 byte 数组的最大偏移量和最大填充量。
  • void close():关闭输入流

1.2.2 OutputStream

OutputStream 是一个抽象类,是所有输入流的超类,与 InputStream 类似:

  • abstract write(int b):写入一个字节
  • void write(byte[] bytes):写入 byte 数组中的所有字节
  • void write(byte[] bytes, int off, int len):指定写入 byte 数组中的范围
  • void flush():将缓冲区内容输出,像磁盘和网络写入数据的时候,出于效率的考虑很多时候并不是输出一个字节就写入一个字节,因为对于很多设备来讲,一次输入一个字节和一次输入一千个字节,花费的时间是一样的,所以通常情况下它会将字节放进缓冲区里,等缓冲区满了再一次性写入
  • void close():关闭输出流,关闭输入流之前会自动调用将缓冲区内容输出

eg:

public static void main(String[] args) throws FileNotFoundException, IOException {

	// jdk1.7 支持的 try(resource) 语法自动调用 close() 方法
	// 写入数据,使用 UTF-8 编码
	try (OutputStream output = new FileOutputStream("src/forIO.txt")) {
		byte[] outputString = "中文".getBytes("UTF-8");
		output.write(outputString, 0, outputString.length);
	}
	// jdk1.7 支持的 try(resource) 语法自动调用 close() 方法
	// 使用缓冲区读取字节
	// FileInputStream 的路径参数可以使绝对路径或者是相对路径,相对路径以项目的根目录出发,如 F:/javaCode/ForLearningJava
	try (InputStream fileInputStream = new FileInputStream("src/forIO.txt")) {
		int n;
		byte[] bytes = new byte[20]; // new byte[1024],一个中文占3个字节
		while ((n = fileInputStream.read(bytes)) != -1) {
			System.out.println("read " + n + " bytes");
			for (byte item:bytes) {
				System.out.println(item);
			}
		}
	}
	System.out.println("==========================");
	// 使用 try finally 关闭资源
	// 使用read直接读取字节
	InputStream input = null;
	try {
		input = new FileInputStream("src/forIO.txt");
		int n;
		while ((n = input.read()) != -1) {
			System.out.println(n);
		}
	} finally {
		if (input != null) {
			input.close();
		}
	}
}

1.2.3 FilterInputStream

Java IO使用组合功能而非继承的设计模式即Filter 模式(也称 Decorator 模式)为 InputStreamOutputStream 增加功能,这是为了解决因想要增加功能依赖继承而新增的子类个数失控的问题。最终其实就是使用类的组合的方式去添加功能,而不是使用继承类的方式去添加功能。

InputStream 为例,JDK 把 InputStream 继承树分为两类:

  • 一种是可以直接提供数据的 InputStream,如 FileInputStreamServletInputStream
  • 另一种是提供额外附加功能的 FilterInputStream(抽象类),其实现类如 BufferedInputStreamGZIPInputStream
    可以将一个 InputStream 和任意 FilterInputStream 组合,OutputStream 同理

其实查看 FilterInputStream 抽象类的源码就明了了

实现这样的添加功能的 InputStream 只需继承 FilterInputStream 类,然后像俄罗斯套娃一样,将代表数据源的 InputStream 作为构造函数的参数传入即可,比方下例创建一个可以返回文件字节总数的 InputStream 类():

public class CountInputStream extends FilterInputStream {
	// 定义构造函数
	public CountInputStream(InputStream in) {
		super(in);
	}
	// 用于存储字节总数
	public int count = 0;
	// 不用重写 read(byte[] b),因为那个方法是调用这个方法实现的
	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		int n = super.read(b, off, len);
		count += n;
		return n;
	}
	public static void main(String[] args) throws FileNotFoundException, IOException {
		try (CountInputStream countInputStream = new CountInputStream(new FileInputStream("src/forIO.txt"))) {
			byte[] bs = new byte[30];
			countInputStream.read(bs);
			System.out.println(countInputStream.count);
		}
	}

}
1.2.3.1 ZipInputStream

ZipInputStreamFilterInputStream 的子类,它用于读取 ZIP 文件。ZipInputStream 的基本用法:循环使用 getNextEntry 方法获取 ZipEntry,直至返回 null,其中 ZipEntry 表示一个压缩文件或目录,此时可通过调用它的 isDirectory 获取是否为目录,下面是解压一个加压了一份 TXT 文件的压缩包的代码:

public static void main(String[] args) throws FileNotFoundException, IOException {
    try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream("src/forIO.zip"))) {
        ZipEntry entry = null;
        // 循环调用 getNextEntry 获取 ZipEntry,直至返回 null
        while ((entry = zipInputStream.getNextEntry()) != null) {
            // 解压
            // 每个 entry 表示着一个压缩文件或目录
            if (!entry.isDirectory()) {
                try (OutputStream outputStream = new FileOutputStream("src/zipTotxt.txt")) {
                    int n;
                    byte[] bt = new byte[1024];
                    // 此时这个 zipInputStream 会随着循环获取 entry 而变化
                    while ((n = zipInputStream.read(bt)) != -1) {
                        outputStream.write(bt, 0, n);
                    }
                }
            }
        }
    }
}
1.2.3.2 ZipOutputStream

ZipInputStreamFilterInputStream 的子类,它用于制作 ZIP 文件。
它的基本用法:

try (ZipOutputStream zip = new ZipOutputStream("src/fotIO.zip")) {
	File[] files = ...
	for (File file:files) {
		// 在 zip 中创建压缩文件区域
		zip.putNextEntry(new ZipEntry(file.getName)));
		// 压缩文件
		zip.write(getFileDataAsBytes(file));
		// 结束当前文件的打 包,继续循环
		zip.closeEntry();
	}
}

上述方法没有实现文件的层次结构,如果想要实现就需要传入相对路径。

1.2.4 Reader

Reader 类是所有字符输入流的父类,它是一个抽象类,它以字符 char 为最小单位实现字符流输入。
它是基于 InputStream 构造的,,任何 InputStram 都可以 指定编码 并通过 InputStreamReader 转换为 Reader,注意 Reader 不是 InputStream 的子类,即不能向上转型为 InputStream
eg:

public static void main(String[] args) throws FileNotFoundException, IOException {
	// 使用 char 数组接受读取字符
	// 指定编码
	try (Reader reader = new InputStreamReader(new FileInputStream("src/forIO.txt"), "UTF-8")) {
		char[] chars = new char[20];
		int n;
		while ((n = reader.read()) != -1) {
			System.out.println((char) n);
		}
	}
	// 直接逐个读取字符的字节码,并转换为字符
	try (Reader reader = new InputStreamReader(new FileInputStream("src/forIO.txt"), "UTF-8")) {
		char[] chars = new char[20];
		int n;
		while ((n = reader.read(chars)) != -1) {
			for (char item:chars) {
				System.out.println(item);
			}
		}
	}
}

1.2.5 Writer

Reader 类是所有字符输出流的父类,它是一个抽象类,它以字符 char 为最小单位实现字符流输出。
它是基于 OutputStream 构造的,,任何 OutputStram 都可以 指定编码 并通过 OutputStreamReader 转换为 Writer,注意 Writer 不是 OutputStream 的子类,即不能向上转型为 OutputStream
主要用法其实与 OutputStream 写入字节大同小异,其中 Writer 有一个 void write(String s) 方法用于写入字符串。

1.3 classpath 资源

classpath 资源中可以包含任意类型的文件,从 classpath 资源读取文件可以避免不同环境下文件路径不一致的问题。
读取 classpath 资源(以下代码在 AboutClasspathDemo.java):

// classpath 其实就是 buildpath
// 注意路径前要添加 /
try (InputStream inputStream = AboutClasspathDemo.class.getResourceAsStream("/log4j2.xml")) {
	if (inputStream != null) {
		System.out.println("找到log4j2.xml");
	}
}
// classpath
System.out.println(AboutClasspathDemo.class.getResource(""));
//输出 file:/E:/javaCode/ForLearningJava/target/classes/top/Seiei/forIO/ 
System.out.println(AboutClasspathDemo.class.getResource("/log4j2.xml"));
//输出 file:/E:/javaCode/ForLearningJava/target/classes/log4j2.xml
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值