文章目录
- 有关文件路径的知识
- 数据输入输出流
- DataInputStream数据输入流
- 键盘录入的几种方式
- DataInputStream流的另外几个成员方法
- DataOutputStream数据输出流
- 数据流复制文本文件
- 内存操作流
- ByteArrayOutputStream流与ByteArrayInputStream流
- ByteArrayOutputStream流
- ByteArrayInputStream流
- 歌曲大连唱
- CharArrayWriter流与CharArrayReader流
- CharArrayWriter流
- CharArrayReader流
- StringWriter流与StringReader流
- StringWriter流
- StringReader流
- 打印流
- 字符打印流PrintWriter
- 字节打印流PrintStream
- 随机访问流
- 暂停复制文件
- 把一个文件分割为多份
- 序列化流与反序列化流
- ObjectOutputStream流
- ObjectInputstream流
- 与IO流有关的属性集合
- Properties类的概述
- 序列流SequenceInputStream
- ZIP压缩输入输出流
有关文件路径的知识
- 之前在学习字节流与字符流的时候,知道Java中文件的分隔符可以采用下坡杠(\)也可以采用上坡杠(/),而下坡杠在Java字符串中作为转义符使用,所以要在Windows格式的路径名中使用(\)作为路径分隔符;
- 在Windows系统中,可以使用单独的上坡杠,这是因为大多数Windows文件处理系统将上坡杠作为文件分隔符;但是,我们不推荐这样做——Windows的系统功能可能会改变,并且在其他操作系统上,文件的分隔符可能并不相同;
- 为了程序的可移植性,应该使用正确的文件分隔符,对应的文件分隔符存放在常量字符串
File.separator
中,具体使用如下:
import java.io.File;
public class MyTest2 {
public static void main(String[] args) {
File myFile = new File("C:" + File.separator + "tmp" + File.separator, "test.txt");
System.out.println(myFile);
//C:\tmp\test.txt
}
}
- 因为所有在java.io中的类都是讲相对路径名解释为起始于用户的当前工作目录,所以应该清楚当前的目录,可以通过:
public class MyTest2 {
public static void main(String[] args) {
System.out.println(System.getProperty("user.dir"));
//E:\myJavaProject\流的学习
}
}
数据输入输出流
- Java使用一种很聪明的策略来划分这两种职责,一些流可以从文件一起其他地方接收字节,另一些流可以将字节组合成为更加有用的数据类型;Java程序员采用将一个已经存在的流传递给另一个流的构造器方法,将这两种流结合起来,结合后的流被称为
过滤流
;例如:为了能从文件中读取数值,首先创建一个FileInputStream流,然后将它传递给一个DataInputstream的构造器;
DataInputStream数据输入流
- 首先,来看一下 继承体系:
- DataInputStream流本质还是一个字节流,它的父类是:FilterInputStream 类,包含其他一些输入流,它将这些流用作其基本数据源,可以直接传输数据或提供一些额外的功能
- FilterInputStream 类本身只是简单地重写那些将所有请求传递给所包含输入流的 InputStream 的所有方法,FilterInputStream 的子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段;
- 就像FileInputStream没有读取数值类型的方法一样,数据输入流DataInputStream也没有从文件读取数值的方法;
- 数据流的 存在意义:
经常需要向流写入计算结果,或者从流中读取结果;数据流支持读取所有Java基本类型的方法,写入或者读取一个数值、字符、布尔值或者字符串;
- 数据流DataInputStream的 构造方法:
public DataInputStream(InputStream in)
使用指定的底层 InputStream 创建一个 DataInputStream;
- 数据流DataInputStream的 成员方法:
- 代码演示:
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class MyTest {
public static void main(String[] args) {
DataInputStream dis = null;
try {
dis = new DataInputStream(new FileInputStream("a.txt"));
byte[] bytes = new byte[10];
int read = dis.read(bytes);
System.out.println(read);
//10
System.out.println(new String(bytes));
//package or
byte[] bytes1 = new byte[20];
int read1 = dis.read(bytes1, 0, 10);
System.out.println(read1);
//10
System.out.println(new String(bytes1));
//g.westos.d
byte b = dis.readByte();
System.out.println(b);
//101
char c = dis.readChar();
System.out.println((char) c);
//浯
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
其他几个读取数字的方法没办法通过读取文本文件演示,这里引入另外一个知识点,键盘录入的几种方式,然后演示读取数字的方法;
键盘录入的几种方式
- 键盘录入方式1:
new Scanner对象,传入参数System.in,输入文字,按下Enter键之后,录入结束;
import java.util.Scanner;
public class MyTest2 {
public static void main(String[] args) {
System.out.println("请输入一段文字:");
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
System.out.println(s);
/*请输入一段文字:
nihao你好啊adsfdgfhsdfgh12345
nihao你好啊adsfdgfhsdfgh12345*/
}
}
- 其实System.in是一个标准的输入流,in是System类里面的一个静态字段,它的返回值是一个InputStream流对象,这个输入流对象读取的是键盘的输入;
- Scanner对象的其中一个构造就需要InputStream流的参数;
- 也就是说,之所以我们能用这样一行代码从键盘读取数据,是因为Scanner对象的构造参数在
作怪
,我们看一下Scanner构造所需要的参数:
我们来看一下给定文件读取数据:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class MyTest2 {
public static void main(String[] args) {
try {
Scanner sc = new Scanner(new File("a.txt"));
System.out.println("读取到的数据为:");
String s = sc.nextLine();
System.out.println(s);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
/*读取到的数据为:
package org.westos.demo;*/
}
}
- 根据构造可以知道,只要给定一个InputStream字节输入流,都可以读取数据,之前学习了很多关于该流的子类,都可以作为参数传递进去,我们来看一下效果:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class MyTest2 {
public static void main(String[] args) {
try {
Scanner sc = new Scanner(new FileInputStream("a.txt"));
System.out.println(sc.nextLine());
Scanner sc1 = new Scanner(new BufferedInputStream(new FileInputStream("a.txt")));
System.out.println(sc1.nextLine());
//package org.westos.demo;
//package org.westos.demo;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
- 键盘录入方式2:
public class MyTest2 {
public static void main(String[] args) throws IOException {
//高效的字符流
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入一段文字:");
String s = bfr.readLine();
System.out.println(s);
/*请输入一段文字:
ergf你好asdfgh1234566
ergf你好asdfgh1234566*/
bfr.close();
}
}
后面学习到的流会越来越多,只要是构造方法中的参数需要一个InputStream类型的,给定参数为System.in,就可以从键盘录入数据,中间的这个流作为媒介存在;
DataInputStream流的另外几个成员方法
- 代码演示:
import java.io.DataInputStream;
import java.io.IOException;
public class MyTest3 {
public static void main(String[] args) throws IOException {
DataInputStream dis = new DataInputStream(System.in);
boolean b = dis.readBoolean();
System.out.println(b);
double v = dis.readDouble();
System.out.println(v);
float v1 = dis.readFloat();
System.out.println(v1);
int i = dis.readInt();
System.out.println(i);
/*true
true
3.14
2.2825612953495437E243
2.3
8.579077E-33
23
171062026*/
dis.close();
}
}
DataOutputStream数据输出流
- 继承体系:
- 构造方法:
- 成员方法:
- 代码演示:
import java.io.*;
public class MyTest4 {
public static void main(String[] args) {
try {
writeData();
//怎么写的怎么读,顺序不要乱
readData();
//写入的都看不懂,你直接读出来就好了
} catch (IOException e) {
e.printStackTrace();
}
}
private static void readData() throws IOException{
DataInputStream dis = new DataInputStream(new FileInputStream("c.txt"));
System.out.println(dis.read());//20
System.out.println(dis.readBoolean());//false
System.out.println((char)dis.read());//b
System.out.println(dis.readInt());//100
System.out.println(dis.readDouble());//3.14
//System.out.println(dis.readUTF());
//这两个效果一样,写一个就可以了
System.out.println(DataInputStream.readUTF(dis));//Java
dis.close();
}
private static void writeData() throws IOException {
DataOutputStream dos = new DataOutputStream(new FileOutputStream("c.txt"));
dos.write(20);
dos.writeBoolean(false);
dos.writeByte(98);
//dos.writeBytes("你好啊");
//这个方法虽然名字是写入字节,但是参数接收了一个字符串,它的底层会帮我们转换为字符
dos.writeInt(100);
dos.writeDouble(3.14);
dos.writeUTF("Java");
dos.close();
}
}
数据流复制文本文件
import java.io.*;
public class MyTest5 {
public static void main(String[] args) {
DataInputStream dis = null;
DataOutputStream dos = null;
try {
dis = new DataInputStream(new FileInputStream("a.txt"));
dos = new DataOutputStream(new FileOutputStream("d.txt"));
byte[] bytes = new byte[1024 * 8];
int len = 0;
while ((len = dis.read(bytes)) != -1) {
dos.write(bytes, 0, len);
dos.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (dis != null) {
dis.close();
}
if (dos != null) {
dos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
System.out.println("复制完成!");
}
}
内存操作流
- 特点:
不直接关联文件,它只是在内存中进行数据的读写,总共有3对;
- 内存操作流的引入,为何需要内存操作流?
- 首先我们谈谈虚拟文件的概念,内存虚拟文件,把一块内存虚拟成一个硬盘上的文件,原来该写到硬盘文件上的内容会被写到这个内存中,原来该从一个硬盘上读取的内容可以改为从内存中直接读取;
- 如果程序在运行过程中要产生一些临时文件,可以用虚拟文件的方式来实现。我们不用访问硬盘,而是直接访问内存,会提高应用程序的效率。
- 内存操作流:用于处理临时存储信息的,程序结束,数据就从内存中消失。
- 分类:
字节数组:
ByteArrayInputStream
ByteArrayOutputStream字符数组:
CharArrayReader
CharArrayWriter字符串:
StringReader
StringWriter
- 内存操作流的使用场景
假设已经有一个写好的压缩函数,该函数接收2个参数:一个是输入流对象,一个是输出流对象,它从输入流对象中读取数据,并将压缩后的结果写入输出流对象。程序需要将一台计算机的屏幕图形通过网络不断传送到另外的计算机上,为了节省带宽,我们就需要对图像的像素数据进行压缩后,在通过网络发送出去的。如果没有内存虚拟文件,我们就必须先将一副屏幕图像的像素数据写入到硬盘上的一个临时文件,再以这个文件作为输入流对象去调用那个压缩函数,接着又从压缩函数生成的压缩文件中读取压缩后的数据,再通过网络发送出去,最后删除压缩前后所生成的两个临时文件。可见这样的效率是非常低的。
解决这种低效率的方案:
- 内存操作流的内存虚拟文件功能
JDK中提供了
ByteArrayInputStream
和ByteArrayOutputStream
这两个类可实现类似内存虚拟文件的功能,我们将抓取到的计算机屏幕图像的所有像素数据保存在一个数组中,然后根据这个数组创建一个ByteArrayInputStream流对象,同时创建一个用于保存压缩结果的ByteArrayOutputStream流对象,将这两个对象作为参数传递给压缩函数,最后从ByteArrayOutputStream流对象中返回包含有压缩结果的数组。我们要在程序分配一个存储数据的内存块, 通常都用定义一个字节数组来实现的。
ByteArrayOutputStream流与ByteArrayInputStream流
ByteArrayOutputStream流
- 流的概述
该类继承自OutputStream抽象类,他默认分配的是32个字节元素空间,内存空间不足时动态扩展为原来的2倍;
- 构造方法
public ByteArrayOutputStream()
创建一个新的 byte 数组输出流。缓冲区的容量最初是 32 字节,如有必要可增加其大小;public ByteArrayOutputStream(int size)
创建一个新的 byte 数组输出流,它具有指定大小的缓冲区容量(以字节为单位);
- 成员方法
- 代码演示:
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class MyTest6 {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
//原来我们如果需要将一个字符串写入流当中,需要这样做
String str="好好学习";
byte[] bytes = str.getBytes();
//将字节数组中的数据,写入该流当中
bos.write(bytes);
//取出该流所维护的那个字节数组中的字节数据
byte[] allByte = bos.toByteArray();
//把字节数组还原为字符串
System.out.println(new String(allByte));
//好好学习
//我们可以使用该流当中特有方法,直接转换为字符串
System.out.println(bos.toString());
//好好学习
//由上面的代码可知,这个流可以把字符串拼接起来存放到字节数组中,他自己在底层维护了一个大小为32的字节数组,它可以读取这两个字符串直接将数据统一写入这个流自己维护的字节数组中;
}
}
ByteArrayInputStream流
- 流的概述
该类继承自InputStream抽象类,它的内部缓冲区通过定义一个字节数组实现;
- 构造方法:
public ByteArrayInputStream(byte[] buf)
创建一个 ByteArrayInputStream,使用 buf 作为其缓冲区数组;public ByteArrayInputStream(byte[] buf, int offset, int length)
创建 ByteArrayInputStream,使用 buf 作为其缓冲区数组,pos 的初始值是 offset,count 的初始值是 offset+length 和 buf.length 中的最小值。将该缓冲区的标记设置为指定的偏移量;
- 成员方法:
- 代码演示:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class MyTest7 {
public static void main(String[] args) throws IOException{
byte[] allByte = writeData();
ByteArrayInputStream bis = new ByteArrayInputStream(allByte);
byte[] cachBytes = new byte[1024];
int len=bis.read(cachBytes);
System.out.println(new String(cachBytes,0,len));
//好好学习
bis.close();
}
private static byte[] writeData() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
String str="好好学习";
byte[] bytes = str.getBytes();
bos.write(bytes);
//取出该流所维护的那个字节数组中的字节数据
//我们可以使用该流当中特有方法,直接返回字符串
bos.close();
return bos.toByteArray();
}
}
歌曲大连唱
现在给出两首歌,我想把他们使用上面的流拼接为一首歌曲;
这个流本身不能与文件进行数据传输,但是它可以结合其他流;
思路:结合字节流读取两个音频文件,临时存入字节数组当中,字节数组流读取字节写入最终的文件中;
本质上只使用字节流也可以完成该需求,这里只是演示一下内存流的存在意义;
需要注意的是,虽然字节流本质上什么文件都可以操作,但是不能将这里的音频文件转换为视频文件,视频文件比较特殊;
import java.io.*;
import java.util.ArrayList;
public class MyTest {
public static void main(String[] args) throws IOException {
//定义集合容器,存放需要读取的音频文件输出流
ArrayList<FileInputStream> list = new ArrayList<>();
list.add(new FileInputStream("D:\\桌面图标\\文件程序练习打包\\test\\12\\夜空中最亮的星.mp3"));
list.add(new FileInputStream("D:\\桌面图标\\文件程序练习打包\\test\\12\\亲爱的旅人啊.mp3"));
//定义内存输出流,将音频文件的字节写入内存流当中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (FileInputStream fis : list) {
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = fis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
bos.flush();
}
fis.close();
}
bos.close();
//将内存流中的数据,统一转换为字节存入字节数组当中
byte[] allByte = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(allByte);
//定义文件输出流,将字节写入文件当中
FileOutputStream fos = new FileOutputStream("D:\\桌面图标\\歌曲大连唱.mp3");
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = bis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
fos.flush();
}
bis.close();
fos.close();
System.out.println("合并完成!");
}
}
CharArrayWriter流与CharArrayReader流
- 本质上是一对字符流,和前面的内存流一样,不需要关闭,但是没有前者通用;
CharArrayWriter流
- 构造方法:
- 成员方法:
- write()的执行流程:
CharArrayReader流
- 构造方法
- 成员方法
- 代码演示:
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
public class MyTest1 {
public static void main(String[] args) throws IOException {
CharArrayWriter caw = new CharArrayWriter();
caw.write(100);
caw.write("你好,Java!");
char[] allChars = caw.toCharArray();
CharArrayReader car = new CharArrayReader(allChars);
System.out.println(car.read());//100
char[] chars = new char[20];
System.out.println(car.read(chars,0,9));//8
System.out.println(new String(chars));//你好,Java!
caw.close();
car.close();
}
}
StringWriter流与StringReader流
- 操作起字符串很方便;
StringWriter流
- 构造方法
- 成员方法
toString():特殊的toString(),可以将写入其中的字符串直接展示出来;write():底层是使用StringBuffer来拼接字符串的;
StringReader流
- 构造方法:
- 成员方法
- 代码演示:
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
public class MyTest2 {
public static void main(String[] args) throws IOException {
StringWriter sw = new StringWriter();
sw.write("少年听雨歌楼上,红烛昏罗帐。");
sw.write("壮年听雨客舟中。江阔云低、断雁叫西风。");
sw.write("而今听雨僧庐下。鬓已星星也。悲欢离合总无情。一任阶前、点滴到天明。");
System.out.println(sw.toString());
// 少年听雨歌楼上,红烛昏罗帐。壮年听雨客舟中。江阔云低、断雁叫西风。而今听雨僧庐下。鬓已星星也。悲欢离合总无情。一任阶前、点滴到天明。
String str = sw.toString();
StringReader sr = new StringReader(str);
System.out.println(str);
// 少年听雨歌楼上,红烛昏罗帐。壮年听雨客舟中。江阔云低、断雁叫西风。而今听雨僧庐下。鬓已星星也。悲欢离合总无情。一任阶前、点滴到天明。
sr.close();
sw.close();
}
}
打印流
- 只能往出写数据,没有对应的读取数据的流;
字符打印流PrintWriter
- 进行文本输出时,应该使用
PrintWriter
,该流可以以文本格式打印字符串和数值,只能往出写数据,与数据流DataOutputStream
类似,尽管提供了一些很有用的输出方法,但却没有定义目的地,一个打印流必须与一个目标writer相结合; - 构造方法:
构造方法很多,可以随意组合使用;
- 成员方法:
和之前字符流一样,他有close()与flush();
除此之外,他有很多重载的write()、print()、println(),以及格式化写入文件中的format();
并且需要注意的是:此流的构造可以指定是否开启自动刷新,如果开启了自动刷新,则只有在调用 println、printf 或 format的其中一个方法时才可能完成此操作,而不是所有方法都可以。
- 代码演示:
不开启自动刷新
import java.io.IOException;
import java.io.PrintWriter;
public class MyTest3 {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter("e.txt");
pw.write(100);
pw.write("你好,Java!");
pw.print(3.14);
pw.println(200);
pw.format("%.2f",2.3456754);
pw.flush();
pw.close();
}
}
结果:
开启自动刷新
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
public class MyTest4 {
public static void main(String[] args) throws FileNotFoundException {
PrintWriter pw = new PrintWriter(new FileOutputStream("f.txt"), true);
pw.println("少年听雨歌楼上,红烛昏罗帐。");
pw.printf("%s\n", "壮年听雨客舟中。江阔云低、断雁叫西风。");
pw.format("%s\n", "而今听雨僧庐下。鬓已星星也。悲欢离合总无情。一任阶前、点滴到天明。");
pw.close();
}
}
结果:
字节打印流PrintStream
-
PrintStream
为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式; - 它还提供其他两项功能,与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志;
- 另外,为了自动刷新,可以创建一个 PrintStream;这意味着可在写入 byte 数组之后自动调用 flush 方法,可调用其中一个 println 方法,或写入一个换行符或字节 (’\n’)。
- PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节,在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类;
- 代码演示:
import java.io.IOException;
import java.io.PrintStream;
public class MyTest3 {
public static void main(String[] args) throws IOException {
//PrintStream 字节打印流 单个的,只能输出数据。
/*构造方法有很多,根据需求new对象
PrintStream(File file)
创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out)
创建新的打印流。
PrintStream(String fileName)
创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。*/
//手动new 一个字节打印流,他就是关联一个文件,当你调用println() 方法时,他是把数据写入到所关联的文件中
PrintStream stream = new PrintStream("e.txt");
stream.write("nihao".getBytes());
stream.println("sbc");
stream.println(2000);
stream.println(true);
stream.flush();
stream.close();
//我们通过System类中的静态变量in 可以获取出一个PrintStream字节打印流 调用输出的方法时输出的目的地是屏幕
// System类的属性 out标准"输出流",此流已打开并准备接受输出数据,通常,此流对应于显示器输出。
PrintStream out = System.out;
out.write("asdfasdfasdfasdf".getBytes());
System.out.println("==================================");
//asdfasdfasdfasdf==================================
System.out.println("dfghsadfghjk");
//asdfasdfasdfasdfasdfasdf
}
}
该流可以使用
System.out
自动获取一个流,关联显示器;
- 与其他流的配合复制文件
import java.io.*;
public class MyTest6 {
public static void main(String[] args) throws IOException {
BufferedReader bfr = new BufferedReader(new FileReader("a.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("copy.txt"));
String line = null;
while ((line = bfr.readLine()) != null) {
pw.println(line);
pw.flush();
}
System.out.println("复制文件结束!");
bfr.close();
pw.close();
}
}
- 与Scanner配合复制文件
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Scanner;
public class MyTest {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(new File("a.txt"));
PrintWriter pw = new PrintWriter(new FileWriter("copy.txt"));
while (sc.hasNext()) {
pw.println(sc.nextLine());
pw.flush();
}
System.out.println("复制文件结束!");
sc.close();
pw.close();
}
}
随机访问流
- 什么是 随机访问文件流 RandomAccessFile
- 该类的实例支持读取和写入随机访问文件;
- 随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组,存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过 getFilePointer 方法读取,并通过 seek 方法设置;
- 底层源码:
通过上面的源码,我们可以看到因为RandomAccessFile
实现了数据输入输出流,那么RandomAccessFile
,因此这一个类就可以完成输入输出的功能了,他没有继承什么流,直接继承自Object类;
- 构造方法:
这里面第二个参数:String mode 有以下几种形式:(为什么这里的值是固定的而不弄成枚举形式,不然很容易写错,这是因为随机访问流出现在枚举类型之前,属于Java 历史遗留问题)
- 代码演示:(使用该流
顺序读写数据
,注意:怎么写的怎么读,顺序不要乱;)
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
public class MyTest1 {
public static void main(String[] args) throws IOException {
writeData();
readData();
}
private static void writeData() throws IOException {
RandomAccessFile w = new RandomAccessFile(new File("c.txt"), "rw");
w.write(20);
w.write(new byte[]{97, 98, 99, 100});
w.writeBoolean(true);
w.writeChar('a');
w.writeDouble(3.14);
w.close();
}
private static void readData() throws IOException {
RandomAccessFile r = new RandomAccessFile(new File("c.txt"), "r");
System.out.println(r.read());//20
byte[] bytes = new byte[4];
r.read(bytes);
System.out.println(Arrays.toString(bytes));//[97, 98, 99, 100]
System.out.println(r.readBoolean());//true
System.out.println(r.readChar());//a
System.out.println(r.readDouble());//3.14
r.close();
}
}
- 进行随机读取之前,先来介绍两个方法:
public long getFilePointer() throws IOException
返回此文件中的当前偏移量;public void seek(long pos) throws IOException
设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。偏移量的设置可能会超出文件末尾。偏移量的设置超出文件末尾不会改变文件的长度。只有在偏移量的设置超出文件末尾的情况下对文件进行写入才会更改其长度。- 这里所说的偏移量,也就是字节数。一个文件是有N个字节数组成,那么我们可以通过设置读取或者写入的偏移量,来达到随机读取或写入的目的。
- 代码演示:(使用该流
随机读取数据
)
注意:怎么写的怎么读取,顺序不能乱;使用writeUTF()写入数据的时候,会多写入两个字节,如果你读取的内容中有汉字存在,给定的指针偏移量就不能出错,否则会报错
EOFException
;
import java.io.IOException;
import java.io.RandomAccessFile;
public class MyTest2 {
public static void main(String[] args) throws IOException {
writeData();
RandomAccessFile read = new RandomAccessFile("f.txt", "rw");
//怎么写的就怎么读取,顺序不要乱
System.out.println(read.readInt());//100
System.out.println("文件指针的位置" + read.getFilePointer());//4
System.out.println(read.readBoolean());//true
System.out.println("文件指针的位置" + read.getFilePointer());//5
System.out.println(read.readDouble());//3.14
System.out.println("文件指针的位置" + read.getFilePointer());//13
System.out.println(read.readUTF());//你好
System.out.println("文件指针的位置" + read.getFilePointer());//21
//我们可以移动文件指针
read.seek(0);
System.out.println(read.readInt());//100
read.seek(13); //设置文件指针的位置
System.out.println(read.readUTF());//你好
read.close();
}
private static void writeData() throws IOException {
//rw 可读可写
RandomAccessFile write = new RandomAccessFile("f.txt", "rw");
write.writeInt(100);
write.writeBoolean(true);
write.writeDouble(3.14);
//会多写两个字节
write.writeUTF("你好");
write.close();
}
}
- 此流存在的意义:
我们在复制文件的时候,经常会有这样的需求,下载途中暂停下载,下次接着上次下载的地方继续;
暂停复制文件
我们可以在复制文件的时候,使用代码模拟异常,并记录异常出现时文件的指针到配置文件中,下次复制文件的时候,做严谨性判断,判断文件是否存在并且临时文件的大小是否与上次文件指针的位置一样,如果为否,重新复制文件,否则继续复制;
import java.io.*;
import java.util.Scanner;
public class MyTest3 {
public static void main(String[] args) throws IOException {
//定义输入输出流
RandomAccessFile r = new RandomAccessFile(new File("亲爱的旅人啊.mp3"), "r");
File file = new File("D:\\桌面图标\\copy.mp3");
RandomAccessFile w = new RandomAccessFile(file, "rw");
//第一次读写音频文件
int by = 0;
int i = 0;
try {
while ((by = r.read()) != -1) {
//模拟一个异常,或模拟用户暂停
if (i++ > 3000) {
System.out.println(1 / 0);
}
w.write(by);
}
} catch (ArithmeticException e) {
//捕获异常
//获取当前文件指针
long filePointer = r.getFilePointer();
//将文件指针存入配置文件当中
PrintWriter pw = new PrintWriter("point.txt");
pw.println(filePointer);
pw.flush();
pw.close();
//判断用户是否想要继续复制文件
System.out.println("你想继续复制文件吗?(Y/N)");
//获取用户输入
Scanner sc = new Scanner(System.in);
//如果用户想要继续复制
if (sc.nextLine().equalsIgnoreCase("Y")) {
//获取配置文件中的指针
BufferedReader bfr = new BufferedReader(new FileReader("point.txt"));
Long newPoint = Long.valueOf(bfr.readLine());
//严谨性判断
//判断如果文件不存在或者文件指针不一致
if (!file.exists() || newPoint < filePointer) {
//设置文件指针为初始位置
r.seek(0);
} else {
//设置文件指针为上次位置
r.seek(newPoint);
}
//继续复制文件
copy(r, w);
System.out.println("文件复制完成!");
} else {
System.out.println("文件复制失败!");
file.delete();
}
} finally {
try {
if (r != null) {
r.close();
}
if (w != null) {
w.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void copy(RandomAccessFile r, RandomAccessFile w) throws IOException {
//根据文件指针复制文件
byte[] bytes = new byte[1024 * 8];
int len = 0;
while ((len = r.read(bytes)) != -1) {
w.write(bytes, 0, len);
}
}
}
把一个文件分割为多份
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Scanner;
public class MyTest4 {
public static void main(String[] args) throws IOException {
File file = new File("亲爱的旅人啊.mp3");
RandomAccessFile r = new RandomAccessFile(file, "r");
File parentFile = new File("D:\\桌面图标");
System.out.println("你想把文件分割为多少份?(1-10)");
Scanner sc = new Scanner(System.in);
Integer num = Integer.valueOf(sc.nextLine());
//计算第一次复制完成的文件指针
int point = (int)file.length() / num;
for (int i = 1; i <= num; i++) {
RandomAccessFile w = new RandomAccessFile(new File(parentFile, String.valueOf(i).concat("copy.mp3")), "rw");
long pointer = r.getFilePointer();
byte[] bytes = new byte[point];
int len = 0;
if ((len = r.read(bytes)) != -1) {
w.write(bytes, 0, len);
}
r.seek(pointer + point);
}
System.out.println("文件分割完成!");
}
}
序列化流与反序列化流
- 序列化与反序列化的概念
序列化:将Java对象保存到文件当中;
反序列化:将文件中的对象读取回到内存;
- Java中使用
ObjectOutputStream
与ObjectInputstream
流进行 深克隆,对象的深克隆是采用IO流来实现 使用 ObjectOutputStream 将对象写入文件中,然后再用ObjectInputStream读取回来,也就是说,如果一个对象的内部维护了另外一个对象,这个流也会复制出来
ObjectOutputStream流
- 构造方法
- 成员方法:
该流有很多普通的方法和之前的流类似,这里只介绍写入对象的方法:
ObjectInputstream流
- 构造方法
- 成员方法:
- 代码演示:
测试类:
import java.io.*;
public class MyTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
writeData();
//读取我们保存的Java对象
readData();
}
private static void readData() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("stu.txt"));
//反序列化
Object obj = in.readObject();
Student stu = (Student) obj;
System.out.println(stu);
in.close();
}
private static void writeData() throws IOException {
Student student = new Student("张三", 23);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("stu.txt"));
//把Java对象写到文件中
//当我们去序列化一个Java对象时,要求我们这个对象所对应的类,必须实现一个序列化接口Serializable
out.writeObject(student);
out.close();
}
}
Student类:
public class Student implements Serializable {
private String name;
private int age;
……
}
- 注意:当我们去序列化一个Java对象时,要求我们这个对象所对应的类,必须实现一个序列化接口Serializable;
- 看这样一种现象:
我在执行完writeData()之后,修改了一下Student类中字段的访问修饰符,再去读取该文件中的对象,发现抛出了异常:
Exception in thread "main" java.io.InvalidClassException: www.baidu.demo4.Student; local class incompatible:
stream classdesc serialVersionUID = -3978935709950457914,
local class serialVersionUID = 2257915773317756418
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1880)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1746)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2037)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1568)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:428)
at www.baidu.demo4.MyTest.readData(MyTest.java:16)
at www.baidu.demo4.MyTest.main(MyTest.java:10)
- 为什么会出现错误呢?
这是因为,序列化的时候,给对象实现了一个接口,该接口没有任何内容,它的存在意义和我们之前学习
clone()
方法一样,都只是为了打一个标记,相当于给猪打一个"放心猪肉"的标签,我们在序列化的时候,写入文件的时候打的这样一个标记,读取数据的时候会再次比对这个标记,如果前后的标记不一样,读取失败,报出前后标记不一致的错误;
- 如何解决这个问题?
我们可以在自定义的Student类中将标记值给定,写上这样一行代码:
这行代码可以在Serializable接口当中找到复制,也可以你自己任意给定一个值;
我们可以下载一个
插件GenerateSerialVersionUID
自动提示一个值;
- 再次进行上面的操作,不会抛出异常;
- 假如我现在只想要序列化某个字段,排除某些字段不要序列化进去,给这些不想序列化的字段前面加上关键字
transient
,表示不要序列化,这时候你再打印这个对象,对应的该属性的值为默认值;
测试类:
import java.io.*;
public class MyTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
writeData();
//读取我们保存的Java对象
readData();
}
private static void readData() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("stu.txt"));
//反序列化
Object obj = in.readObject();
Student stu = (Student) obj;
System.out.println(stu);
in.close();
}
private static void writeData() throws IOException {
Student student = new Student("张三", 23);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("stu.txt"));
//把Java对象写到文件中
//当我们去序列化一个Java对象时,要求我们这个对象所对应的类,必须实现一个序列化接口Serializable
out.writeObject(student);
out.close();
}
}
//输出:Student{name='张三', age=0}
Student类:
public class Student implements Serializable {
public String name;
transient private int age;
static final long serialVersionUID = 42L;
……
}
- 如果现在有这样的一个需求,我想序列化多个Java对象,写入的时候很方便,但是读取的时候很不方便,需要挨个读取,不能跳过前几个对象,怎么做?
考虑到序列化与反序列化其实就是深克隆,与浅克隆不一样,把这些对象存储进一个容器对象,将容器进行序列化,反序列化出容器就可以取出里面的对象了;
import java.io.*;
import java.util.ArrayList;
public class MyTest1 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student1 = new Student("王五", 25);
Student student2 = new Student("赵六", 26);
Student student3 = new Student("李四", 24);
ArrayList<Student> list = new ArrayList<>();
list.add(student1);
list.add(student2);
list.add(student3);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("obj.txt"));
//将容器序列化
out.writeObject(list);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("obj.txt"));
ArrayList<Student> myList = (ArrayList) in.readObject();
Student student = myList.get(2);
System.out.println(student);
in.close();
//序列化流和反序列化流,可以进行对象的深克隆。
//Student{name='李四', age=24}
}
}
之所以能序列化ArrayList对象,该类肯定实现了序列化接口Serializable;
与IO流有关的属性集合
Properties类的概述
- Properties 类表示了一个持久的属性集,可保存在流中或从流中加载,属性列表中每个键及其对应值都是一个字符串;
- 一个属性列表可包含另一个属性列表作为它的"默认值";如果未能在原有的属性列表中搜索到属性键,则搜索第二个属性列表;
- 代码演示:
import java.util.Properties;
public class MyTest {
public static void main(String[] args){
Properties properties = new Properties();
properties.put("name","张三");
properties.put("age",20);
properties.put("email","123@qq.com");
System.out.println(properties.get("name"));//张三
System.out.println(properties.get("age"));//20
System.out.println(properties.get("email"));//123@qq.com
}
}
- 上面的存取键值对,它的父类就可以完成,那么Properties 本身存在的意义是什么?因为 Properties 继承于 Hashtable,所以可对 Properties 对象应用 put 和 putAll 方法。但不建议使用这两个方法,因为它们允许调用者插入其键或值不是 String 的项。相反,应该使用 setProperty() 和 getProperty();
- 现在有这样的一个需求,如何将键值对存储到文件当中,并且读取出来,使用原来的知识,一定是先遍历集合,取出键值对,配合PrintWriter流写入文件当中,并开启自动刷新,读取一个键值对,写入一行;但是这个需求,Properties 本身就可以全部做完,使用
load()
可以从输入流中读取属性列表,使用store()
可以将此 Properties 表中的属性列表(键和元素对)写入输出流;
comments - 属性列表的描述,给null值会默认在配置文件上方写一行当前日期时间的注释;
public class MyTest1 {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(new FileInputStream("text.properties"));
properties.store(new FileOutputStream("copy.properties"),null);
System.out.println("写入完成!");
}
}
运行结果:
- 注意:如果你的配置文件写入中文会乱码的话,在Settings里面进行设置,
序列流SequenceInputStream
- 继承体系:
- SequenceInputStream 表示其他输入流的逻辑串联,它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止;该流像串糖葫芦一样给你吧文件流串起来;
- 构造方法:
- 合并两个文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
public class MyTest {
public static void main(String[] args) throws IOException {
SequenceInputStream sis = new SequenceInputStream(new FileInputStream("a.txt"), new FileInputStream("b.txt"));
FileOutputStream fos = new FileOutputStream("all.txt");
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = sis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
fos.flush();
}
sis.close();
fos.close();
}
}
- 合并多个文件
- 本质上,我们还是可以使用上面的办法完成多个文件的合并,因为SequenceInputStream本质上也是一个InputStream,可以将第一个该流对象作为参数传递给第二个该流的对象,但是这样太麻烦;
- SequenceInputStream还有另外一个构造方法,他需要一个Enumeration迭代器对象作为参数,之前学习过的集合Vector有一个方法elements(),可以返回该迭代器类型的对象,我们把这个迭代器对象直接给该流的构造方法,他会帮我们遍历每一个流;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class MyTest1 {
public static void main(String[] args) throws IOException {
Vector<FileInputStream> vector = new Vector<>();
vector.add(new FileInputStream("a.txt"));
vector.add(new FileInputStream("all.txt"));
vector.add(new FileInputStream("b.txt"));
//获取一个迭代器
Enumeration<FileInputStream> enumeration = vector.elements();
//将迭代器传递给该流,它的底层会帮我们遍历每一个流对象
SequenceInputStream sis = new SequenceInputStream(enumeration);
FileOutputStream fos = new FileOutputStream("all2.txt");
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = sis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
fos.flush();
}
sis.close();
fos.close();
}
}
ZIP压缩输入输出流
- ZIP文件(通常)以压缩格式存储一个或更多文件,Java 中可以处理GZIP和ZIP以及JAR格式,操作流程基本上是一致的,这里只讨论ZIP格式;
- 处理ZIP文件的类在
java.util.zip
中而不在java.io
中,尽管不是java.io
中的一部分,但是ZIP类还是java.io.FileInputStream
和java.io.FileOutputStream
的子类; - ZipEntry
- 在每一个压缩文件中都会存在多个子文件,这每一个子文件在Java中就使用ZipEntry表示;
- ZipEntry常用的方法:
- 在实例化ZipEntry 的时候,要设置名称,此名称实际上就是压缩文件中每一个元素的名称;
- ZipOutputStream
- 如果想要完成一个文件或文件夹的压缩,就要使用ZipOutputStream类完成;
- ZipOutputStream常用的方法:
- 此类的功能就是完成ZIP格式输出的
- 继承关系:
java.lang.Object
java.io.OutputStream
java.io.FilterOutputStream
java.util.zip.DeflaterOutputStream
java.util.zip.ZipOutputStream
- 在压缩文件中,每一个压缩的内容都可以用一个ZipEntry 表示,所以在进行压缩之前必须通过putNextEntry 设置一个ZipEntry 即可;
- 压缩文件:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class MyTest2 {
public static void main(String[] args) throws IOException {
File file = new File("a.txt"); // 定义要压缩的文件
File zipFile = new File("D:\\桌面图标\\newFile.zip"); // 定义压缩文件名称
InputStream in = new FileInputStream(file); // 定义文件的输入、输出流
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
out.putNextEntry(new ZipEntry(file.getName())); // 设置ZipEntry对象
out.setComment("aZip.txt"); // 设置注释
int temp = 0;
while ((temp = in.read()) != -1) { // 读取内容
out.write(temp); // 压缩输出
}
in.close(); // 关闭输入流
out.close(); // 关闭输出流
}
}
运行结果:
- 压缩文件夹:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class MyTest3 {
public static void main(String[] args) throws IOException {
File file = new File("D:\\桌面图标\\src");
// 定义要压缩的文件
File zipFile = new File("D:\\桌面图标\\newFile.zip");
// 定义压缩文件名称
//定义文件的输出流
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile));
File[] files = file.listFiles();
//遍历文件夹,压缩文件
for (File f : files) {
//压缩
zip(f, out);
}
System.out.println("压缩完成!");
out.close();
}
private static void zip(File f, ZipOutputStream out) throws IOException {
FileInputStream fis = new FileInputStream(f);
out.putNextEntry(new ZipEntry(f.getName()));
// 设置ZipEntry对象
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = fis.read(bytes)) != -1) {
out.write(bytes, 0, len);
}
fis.close();
//out.close();
//这里不能关闭该流,因为实参与形参中的out指向的是同一对象,这里关闭了该流
//main()中再次调用zip()时,传递的out对象已经被关闭了
}
}
- ZipFile
- 是一个专门表示压缩文件的类;在Java中,每一个压缩文件都可以使用ZipFile表示,还可以使用ZipFile根据压缩后的文件名称找到每一个压缩文件中的ZipEntry并将其进行解压缩操作;
- 常用的方法有:
- ZipFile 在实例化的时候必须接收File 类的实例,此File 类的实例是指向一个压缩 *.zip 文件;
- 解压缩文件夹
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
public class MyTest4 {
public static void main(String[] args) throws IOException {
//压缩文件
File file = new File("D:\\桌面图标\\newFile.zip");
//实例化ZipFile对象
ZipFile zipFile = new ZipFile(file);
File parentFile = new File(file.getParentFile(), file.getName().split("[.]")[0]);
if (!parentFile.exists()) {
parentFile.mkdir();
// 创建文件夹 ,如果这里的有多个文件夹不存在,请使用mkdirs(),如果只是单纯的一个文件夹,使用mkdir()就好了
}
//定义压缩输入流
ZipInputStream zis = new ZipInputStream(new FileInputStream(file));
ZipEntry entry = null;
while ((entry = zis.getNextEntry()) != null) {
System.out.println("解压缩" + entry.getName() + "文件;");
//定义输出的文件路径
File outFile = new File(parentFile, entry.getName());
if (!outFile.exists()) { // 判断输出文件是否存在
outFile.createNewFile(); // 创建文件
}
OutputStream out = new FileOutputStream(outFile);
// 实例化文件输出流
InputStream in = zipFile.getInputStream(entry);
//得到每一个实体的输入流
int len = 0;
byte[] bytes = new byte[1024 * 8];
while ((len = in.read(bytes)) != -1) {
out.write(bytes, 0, len);
}
in.close(); // 关闭输入流
out.close(); // 关闭输出流*/
}
System.out.println("解压缩完成!");
}
}
1、压缩文件中的每一个压缩实体都使用ZipEntry 保存,一个压缩文件中可能包含一个或多个的ZipEntry 对象。
2、在JAVA中可以进行zip、jar、gz、三种格式的压缩支持,操作流程基本上是一样的
3、ZipOutputStream 可以进行压缩输出,但是输出的位置不一定是文件。
4、ZipFile 表示每一个压缩文件,可以得到每一个压缩实体的输入流
5、ZipInputStream 可以得到每一个实体,但是却无法得到每一个实体的输入流。
- JDK中的ZipInputStream与ZipOutputStream 在压缩与解压缩操作中容易出现乱码,建议大家使用Apache的ZipInputStream与ZipOutputStream,因为它可以设置字符编码charset,这样就能够保证在写入/读取文件时,最大限度的避免乱码问题的发生!