一、集合的流式处理
在JDK8以后,提供对集合的流式操作,对集合的元素可以像“流水”一样,依次访问,遍历,排序等,它是“不可逆的”(访问后面元素之后不能再次返回前面元素),根据流的处理方式,可以分为串行流和并行流,串行流表示同一时间只能有一个流式操作,而并行流可以与多个流式操作
流返回的结果包括中间操作和最终操作
中间操作:它的返回值依然是流对象,例如排序、过滤、去重
最终操作:返回值是特定结果类型,例如遍历、取最大值、最小值或返回新的集合
常用方法:
stream():将一个集合流式化
filter():按条件过滤,里面使用lambda表达式
sort():排序集合元素
distinct():过滤重复元素
reduce():将集合的所有元素累加或拼接
map():映射一个新的集合,对集合元素变更输出
collect():返回一个新的集合
min():找到最小值
max():找到最大值
get():获取集合计算的结果
forEach():遍历每个元素
public static void main(String[] args) {
List<Integer> list = new ArrayList();
for(int i = 0 ;i<7;i++){
list.add(i+1);
}
// 1、过滤 filter() 过滤掉偶数
list.stream().filter( param ->param%2==1 )
.forEach(System.out::println); //遍历元素
// 2、排序 sort() 降序
list.stream().sorted((o1,o2)->o2-o1).forEach(System.out::println);
// 3 map() 映射一个新的集合 , 如果是奇数 输出奇数 ,否则偶数
list.stream().map(
param -> param%2==1?"这个元素是奇数":"这是偶数"
).forEach(System.out::println);
list.add(1);
list.add(1);
System.out.println("去重元素");
// 4 distinct() 去除重复元素
list.stream().distinct().forEach(System.out::println);
// 5 reduce() 将集合的所有元素 累加(或拼接)
int sum = list.stream().reduce((o1,o2)->o1+o2).get();
System.out.println("总和:"+sum);
// 6 collect 返回一个新的集合
List<Integer> list2= list.stream().filter(param->param%2==1).collect(Collectors.toList());
System.out.println("遍历新集合");
list2.stream().forEach(System.out::println);
// 7、最大和最小
int max = list2.stream().max((o1,o2)->o1-o2 ).get();
System.out.println("最大值:"+max);
int min = list2.stream().min((o1,o2)->o1-o2 ).get();
System.out.println("最小值:"+min);
}
二、Java的I/O流
2.1、File类
File类用于表示文件类,可以操作与文件相关的功能,File类属于java.IO,提供了大量的文件操作方法。
- java.io.File类:文件和文件目录路径的抽象表示形式,与平台无关
- File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。
- 想要在Java程序中表示一个真实存在的文件或目录,那么必须有一个File对象,但是Java程序中的一个File对象,可能没有一个真实存在的文件或目录。
- File对象可以作为参数传递给流的构造器
创建一个文件类/文件路径类
File file = new File();
pathname 可以是文件路径: d://aaa.txt ,也可以写目录的路径 : d://myfile
常用构造器:
-
public File(String pathname):以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
-
绝对路径:是一个固定的路径,从盘符开始
-
相对路径:是相对于某个位置开始
-
public File(String parent,String child):以parent为父路径,child为子路径创建File对象。
-
public File(File parent,String child):根据一个父File对象和子文件路径创建File对象
-
路径中的每级目录之间用一个路径分隔符隔开。
-
路径分隔符和系统有关:
-
windows和DOS系统默认使用“\”来表示
-
UNIX和URL使用“/”来表示
-
Java程序支持跨平台运行,因此路径分隔符要慎用。
-
为了解决这个隐患,File类提供了一个常量:public static final String separator。根据操作系统,动态的提供分隔符。
举例:
File file1 = new File("d:\\atguigu\\info.txt"); File file2 = new File("d:" + File.separator + "atguigu" + File.separator + "info.txt"); File file3 = new File("d:/atguigu");
常用方法:
craeteNewFile() : 创建新文件,要求构造的file对象指定的是文件路径。
delete() :删除文件或文件目录 删除成功返回true
exists() :测试此文件或目录是否存在。 存在返回true 不存在返回false
getAbsoluteFile(): 返回绝对路径的文件对象,返回值File
getAbsolutePath() : 返回绝对路径的字符串地址 ,返回
String getName() :获取文件或目录的文件名
getParent() : 返回此文件对象 的 父目录(上一级)的字符串地址。返回值String
getParentFile(): 返回次文件的父目录的文件对象, 返回值File
getPath() : 返回文件对象的相对路径
isDirectory() :判断该对象是否为目录, 目录: true 否则: false
isFile() :判断该对象是否为文件: 文件: true: 否则 false
lastModified() : 获取该文件的后修改实际 ,返回毫秒数
length : 获取该文件的 字节长度 list() : 获取该目录下的所有清单(文件和文件夹) ,返回字符串数组 , 清单地址
listFile(): 同上: 获取该目录下的所有清单(文件和文件夹),返回文件对象的数组
mkdirs : 创建多层目录, 根据文件对象(目录)创建有层级关系的目录 。
字节流(Stream) 字符流(Reader/Writer)
输入流(In) InputStream Reader
输出流(Out) OutputStream Writer
mkdir:创建一层目录。
2.2、什么是I/O?
在生活中,你需要将U盘的文件,拷贝到电脑或者将电脑的文件拷贝到其它设备,文件是通过数据流的方式一次到达另一个设备中,文件的靠背就是一个输入(Input)和输出(Output)的过程
Java中提供对应的API支持对文件的输入和输出,java.io.*
什么是流?
生活中也存在流的概念,例如管道中的流水,从管道的入口到达管道的出口,一滴水可以从入口流到出口,可以将“水”比作“字节数据或字符数据”,数据也可以从一端流到另一端。
输入(Input):Java中,以“应用程序(内存)”为中心,将磁盘文件(设备端)到达内存中的过程,称为输入
输出(Output):以“应用程序(内存)”为中心,将数据从内存到达磁盘文件(设备端)的过程称为输出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S9dwtqyy-1604833819131)(D:\ziliao\java2008\01 java基础\笔记\assets\1604295414903.png)]
根据文件操作方式不同可以将IO分为三类
1、按照读写的方向不同:输入流(InputStream)和输出流(OutputStream)
2、按照数据类型不同:字节流(Byte)和字符流(Char)
3、按照读写效率不同:单一流和包装流(Buffered等缓冲流)
关于流的分类Java提供四个顶级抽象类,分布构建它们的子类
输入流 | 输出流 | |
---|---|---|
字节流 | InputStream(字节输入流) | OutputStream(字节输出流) |
字符流 | Reader(字符输入流) | Writer(字符的输出流) |
常见的流
1、文件字节输入流和文件字节输出流:FileInputStreanm和 FileOutputStream
2、文件字符操作流:FileReader和FileWriter
3、缓冲字节输入流和缓冲字节输出流:BufferedInputStream 和 BufferedOutputStream
4、缓冲字符输出流和缓冲字符输出流: BufferedReader 和 BufferedWriter
5、数据输入流和数据输出流:DataInputStream 和 DataOutputStream
6、字节数组输入流和字节数组输出流: ByteArrayInputStream 和 ByteArrayOutputStream
7、字符数组输入流和字符数组输出流: BufferedReader 和 BufferedWriter
8、转换流:(字节流转成字符流):InputStreamReader和OutputStreamWrite
9、对象流(序列化流):ObjectInputStream和ObjectOutputStream
10、随机访问流(这个流既可以读,也可以写):RandomAccessFile
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mUFWlKAU-1604833819143)(D:\ziliao\java2008\01 java基础\笔记\assets\22-1604295486965.png)]
字节流
定义:文件的输入输出以一个“字节”为单位,进行流处理
FileInputStream和FileOutputStream
读入:将文件中的数据读到内存中
常用方法:
int read():一个字节的读取,返回字节的ASCII码,对于汉字会分3次读取
int read(byte):按一个数组长度读取,返回实际读取的字节长度,数据存放在数组中
int read(byte,offset,len):读取流中指定长度的数据,并存放在指定位置
available():返回流中剩余的字节长度,如果已读完则返回0
skip(long n):丢弃指定的字节长度,从下一个开始读取
`**available**()`
File file = new File("d:/aaa.txt");
FileInputStream fis = new FileInputStream(file);
//
byte [] b= new byte[10];
StringBuffer sb = new StringBuffer();
//每次读取的长度, b: 存放数据的数组
int len = 0;
while((len = fis.read(b)) !=-1){
sb.append( new String(b,0,len));
}
System.out.println(sb);
fis.close();
public static void read2() throws IOException {
// InputStream是抽象类
InputStream is = new FileInputStream("d:/aaa.txt");
//丢弃前两个字节
is.skip(2);
System.out.println((char)is.read());
System.out.println("还剩下多少个字节:"+ is.available());
// 将后面的字节继续使用字节数组读
byte [] b = new byte[10];
int len = is.read(b,1,4);
// 显示数组中读取的所有数据
System.out.println(Arrays.toString(b));
//将数组的内容转成字符串 对于空内容不会转换
System.out.println(new String(b));
is.close();
}
文件写出:将内存的数据写出到磁盘中
构造方法:
new FileOutputStream(File/String):构造文件对象的写出流,默认覆盖写出
new FileOutputStream(File/String,append):构造文件对象的写出流
append:表示在原有文件上追加数据, false:覆盖
常用方法:
void write(int):写出一个字节
void write(byte []):写出一个字节数组,这里需要知道数组的编码格式“UTF-8”
void write(byte [], offerset,len):写出一个字节数组,指定数组的长度和下标。 从数组的下标开始写出,len表示写出长度
flush() :清空缓存,对于使用缓冲流时,将缓冲强制清空。
//将内存的数据写出到文件 如果文件不存在,会自动创建, 默认覆盖写入 true:追加
FileOutputStream fos = new FileOutputStream("d://aaa.txt" ,true);
String str="今天天气还不错";
fos.write(99);
//写出一个字符串 字符串可以转成字节数组 或字符数组
fos.write(str.getBytes("UTF-8"));
// 写出指定长度
fos.write(str.getBytes("UTF-8"),0,3); // 写出这个数组的前2个字节
// 清空缓存
fos.flush();
// 关闭流
fos.close();
System.out.println("写出成功");
文件复制:
将文件(图片,文本,视频)从一个目录复制到另一个目录, 其中数据长度不变,通过文件读写的方式完成复制
复制过程:从源文件读取数据,然后将数据再出到目标文件中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0BJ2tsaz-1604833819145)(D:\ziliao\java2008\01 java基础\笔记\assets\1604295753095.png)]
/**
* 单个字节复制
* @param srcFile 源文件
* @param disFile 目标文件
*/
public static void copyFile(File srcFile, File disFile){
FileInputStream fis=null;
FileOutputStream fos =null;
try {
// 源文件输入流
fis = new FileInputStream(srcFile);
// 目标文件输出流
fos = new FileOutputStream(disFile);
int n=0;
while( (n =fis.read()) !=-1){
//将读到的n写出到 目标文件中
fos.write(n);
}
System.out.println("复制成功。。");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
//无论是否发生异常 都会关闭流
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 一个字节数组的赋值
* @param src 源地址
* @param disc 目标地址
*/
public static void copyFile(String src,String disc){
FileInputStream fis = null;
FileOutputStream fos = null;
try {
//创建 字节输入流
fis=new FileInputStream(src);
fos = new FileOutputStream(disc);
int len=0;
byte [] b = new byte[1024];
while( (len= fis.read(b)) !=-1){
// 写出 实际读取的长度 ,为了避免在最后一次写出时出现多余字节
fos.write(b,0,len);
}
System.out.println("复制成功");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
fos.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流
字符流用于读写存储字符的文件,以一个字符为单位,依次读取字符文件,常用以Reader或Writer为父类,对文件的操作使用java.io.FileReader和java.io.FileWriter
读文件:FileReader
常用方法:
- new FileReader(path):通过文件路径构建字符输入流
- new FileReader(File):通过文件对象构建字符输入流
- int read():读取一个字符,返回字符的int类型
- int read(char):读取字符数组长度的数据,返回实际读取字符长度,数据存放在字符数组中
- int read(char offerset len):读取指定字符长度的数组,返回实际读取字符的长度,数据存放在字符数组中
- mark(int):标记流中当前位置(读取到哪里了)
- markSupported():判断次流是否支持mark操作
- reset():重置数据流,(又可从头开始读取)
- skip(long):丢弃指定长度字符
读字符文件
// 1、创建字符输入流
try {
FileReader reader = new FileReader("d:/myfile.txt");
// 丢弃字符
reader.skip(1);
//读一个字符
System.out.println((char)reader.read());
System.out.println((char)reader.read());
//读一个字符数组长度
char [] c = new char[10];
System.out.println("实际长度:"+reader.read(c));
System.out.println(new String(c));
//继续读
int len = reader.read(c,0,5);
System.out.println("字符数组:"+ Arrays.toString(c));
System.out.println("读指定长度字符个数:"+new String(c,0,len));
// 将字符流重置
// reader.reset();
// System.out.println("重置后继续读:"+ reader.read());
//System.out.println("是否支持标记字符:"+reader.markSupported());
//关闭流,后面就不能使用该对象
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
写文件:将内存数据写出到文件中,再写出过程中可以覆盖写出也可以追加写出,FileWriter类创建对象过程
new FileWriter(String):指定写出文件地址
new FileWriter(String,append):指定写出文件地址,设置是否追加写出,true表示追加,false表示覆盖
new FileWriter(File):指定写出文件对象
new FileWriter(File,append):指定写出文件对象,设置是否可追加
常用方法:
write(int):写出一个字符
write(String):写出一个字符串
write(char []):写出一个字符数组
write(char [] offerset len):写出一个指定长度的字符数组
flush():刷新缓冲
close():关闭缓冲
append©:将指定字符添加到此流中
// 1、创建文件写出流 FileWriter
try {
// 文件不存在,可自动创建,但是不会创建目录
File file = new File("d://myabc/aaa.txt");
//判断文件目录不存在, 先创建目录
if(!file.getParentFile().exists()){
//创建该目录
file.getParentFile().mkdirs();
}
FileWriter writer = new FileWriter("d://myabc/aaa.txt");
// 写一个字符的asci
writer.write(98);
//写字符串
writer.write("hello");
//写指定长度的字符串
writer.write("abcdef",0,3); //写abc
char [] c = {'L','O','L'};
//写字符数组
writer.write(c);
System.out.println("写出成功");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
关闭和刷新:
对于带有缓冲功能的写出流,需要先刷新缓冲区,才能将数据写出,如果不刷新则最后不能正常写出。写出流如果刷新后还可以继续写,而关闭了则不能继续写
面试题:flush和close的区别?
flush:刷新缓冲,流可以继续使用
close:先刷新缓冲区,然后再释放系统资源,关闭后不能继续使用
try {
FileWriter writer = new FileWriter("1.txt");
writer.write("刷");
writer.flush();
writer.write("新");
writer.flush();
writer.write("关");
writer.close();
writer.write("闭"); // 这里抛出异常 , Stream closed
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
关于换行符
回车符 \r 和换行符 \n :
回车符:回到一行的开头(return)。
换行符:下一行(newline)。
系统中的换行:
Windows系统里,每行结尾是 回车+换行 ,即 \r\n ;
Unix系统里,每行结尾只有 换行 ,即 \n ;
Mac系统里,每行结尾是 回车 ,即 \r 。从 Mac OS X开始与Linux统一。
2.3、包装流
定义:在原始字节流或字符流的基础上,为了提高读写效率进行再次处理的流,称为包装流/处理流
1、缓存字节流 BufferedInputStream、BufferedOutputStream
由于原始流在文件读写时效率比较低(操作文件本身占用资源比较多),可以通过创建缓冲区的方式,提高读写效率,将读取/写出的数据先放入缓冲区,到达一定数量后再次从缓冲区读写/取出
mark(readLimit)和reset()用法
用法参考链接:https://blog.csdn.net/u010120301/article/details/49741757
其中reset不能单独使用,必须mark(readLimit),readLimit表示标记后最多读取的上限,但是这里标记后读取的内容与BufferedInputStream的缓冲大小有关,由上限决定,也就是说读取的内容超出上限可以继续重置到mark的位置
public static void main(String[] args) throws IOException {
//创建缓冲流
InputStream is = new FileInputStream("d:/myfile.txt");
BufferedInputStream bis = new BufferedInputStream(is);
//是否支持mark 或 reset
System.out.println(bis.markSupported());
System.out.println((char)bis.read());//97
//重置
bis.mark(3); // pos标记往后退三个 最多可以读取字节上限
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
System.out.println("再次读取:"+(char)bis.read());
bis.reset(); // 这里 重置后 退回到3个以前的位置
// 重置后输出
int n =0;
while( (n = bis.read()) !=-1){
System.out.println("重置后;"+(char)n);
}
//关闭流
bis.close();
is.close();
}
2、缓存字符流 (BufferedReader 、BufferedWriter)
public static void main(String[] args) throws IOException {
// 缓冲字符流 可以一行一行读取 、写出
BufferedReader br = new BufferedReader(new FileReader("d:/小众网站.txt"));
//读一行
// System.out.println(br.readLine());
// System.out.println(br.readLine());
// System.out.println(br.readLine());
String s = null; //读的数据为空 则不需要读
while( (s = br.readLine()) !=null){
System.out.println(s);
}
br.close();
//缓冲写出流
FileOutputStream pw = new FileOutputStream("d:/abcd.txt");
//由于字节流不能直接放入 字符缓冲区,需要将它转成字符流 使用转换流并可以指定编码格式
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(pw));
bw.newLine();// 开启新一行(换行)
bw.write("这是测试转换流的方式");
bw.close();
}
3、打印流(输出流) PrintWriter 、PrintStream
public static void main(String[] args) throws FileNotFoundException {
// 打印流 ,提供一些打印输出方法
PrintWriter pw = new PrintWriter("d:/abcd.txt");
pw.print(100);
pw.println('a');//换行打印
pw.print("hello");
pw.close();
//System.out 字节打印流 PrintStream
4、数据字节流(DataInputStream、DataOutrutStream)
它们用于读入写出Java基本数据类型的数据到文件或其它设备端
DataOutputStream常用方法:
- writeByte(byte):写一个字节到设备或文件
- writeChar(char):写一个字符到设备或文件
- writeInt(int):写一个4个字节的int到设备或文件
- writeBoolean(boolean):写一个boolean类型到设备或文件
- writeDouble(double):写一个double类型到设备或文件
- WriteFloat(float):写一个float类型到设备或文件
- writeLong(long):写一个long类型到设备或文件
- writeShort(short):写一个short类型到设备或文件
- wroteUTF(string):写一个字符串类型到设备或文件
DataInputStream:读指定文件的数据,可以读数据类型
-
int readInt():读一个int类型
-
short readShort():读一个short类型
-
readByte():读一个byte类型
-
read():读一个字节类型
-
readDouble():读一个double类型
-
readFloat():读一个float类型
-
readChar():读一个char类型
-
readBoolean():读一个boolean类型
-
readLong():读一个long类型
public static void main(String[] args) throws IOException { //创建数据写出流 DataOutputStream dos = new DataOutputStream( new FileOutputStream("d:/data.txt")); //写一个int类型 依次写出4个字节 dos.writeInt(100); dos.writeBoolean(true); //关闭 dos.close(); //读取文件 创建数据读入流 ,需要按写的顺序读进来 DataInputStream dis = new DataInputStream( new FileInputStream("d:/data.txt")); //读一个int类型 (依次读4个字节) int num = dis.readInt(); System.out.println("读取的数据:"+ num); System.out.println("读的数据:"+dis.readBoolean()); dis.close(); }
2.4、转换流
转换流是将字符流转成字符流的桥梁,也可以在转换时指定编码格式。InputStreamReader和OutputStreamReader
public static void main(String[] args) throws IOException {
// 字节流转成字符流
InputStream is = new FileInputStream("d://小众网站.txt");
InputStreamReader isr = new InputStreamReader(is);
//缓冲流 读取数据
BufferedReader br = new BufferedReader(isr);
//读一行
String str =null;
while( (str= br.readLine()) !=null){
System.out.println(str);
}
//关闭流
br.close();
isr.close();
is.close();
}
public static void main(String[] args) throws IOException {
// 创建 字节转成字符的 写出流 FileOutputStream os =
FileOutputStream fos = new FileOutputStream("d://data.txt");
//指定编码 GBK 格式一个汉字占2个字节 UTF-8 格式一个汉字占3个字节
OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
//缓冲形式的
BufferedWriter bw = new BufferedWriter(osw);
bw.write("你好");
bw.newLine();
bw.write("我不好");
bw.close();
}
2.5、随机字节流
RandomAccessFile是随机字节流,它是一个可读可写的流,在文件操作时指定该对象的模式(mode)后,可以读数据或写数据
实现 DataInputStream和DataOutputStream类
构造器:
RandomAccessFile rm = new RandomAccessFile(File,mode);
RandomAccessFile rm = new RandomAccessFile(String,mode);
mode:表示对象的模式
r:表示该对象只能读 不能写
rw/rws/rwd:表示该对象是可读可写的;
public static void main(String[] args) throws IOException {
//创建可读 的流
RandomAccessFile reader = new RandomAccessFile("d://data.txt","r");
//创建可读可写的 的流
RandomAccessFile writer = new RandomAccessFile("d://data-1.txt","rw");
// 读和写的过程和之前一样
byte [] b= new byte[10];
int len=0;
while( (len = reader.read(b)) !=-1){
writer.write(b , 0 , len);
}
System.out.println("复制成功");
//关闭流
writer.close();
reader.close();
}
skipByte 和 seek的区别
// 跳字节读取
RandomAccessFile raf = new RandomAccessFile("d:/data.txt","rw");
// 跳过2个字节
raf.skipBytes(2);
System.out.println((char)raf.readByte()); //3
System.out.println("当前偏移量:"+raf.getFilePointer());//3
// 又丢弃1个字节 从当前位置 往后偏移1位
raf.skipBytes(1);
System.out.println("修改后的偏移量"+raf.getFilePointer());//4
System.out.println("偏移后的读取数据:"+(char)raf.readByte()); //5
raf.close();
// seek用法
RandomAccessFile raf2 = new RandomAccessFile("d:/data.txt","rw");
// 设置当前读取的位置 ,从0开始计算 ,指定n ,就从n的下一个字节 读取
raf2.seek(2);
System.out.println("seek后的数据:"+(char)raf2.readByte());//3
raf2.seek(1); // 又从0开始 设置偏移量为1
System.out.println("修改后的偏移量"+raf.getFilePointer());//1
System.out.println("seek后的数据:"+(char)raf2.readByte())//2
raf2.close();
2.6、对象序列化流
对象流也称为序列化流,用于存储对象和读取对象的字节流,也是属于包装流
序列化和反序列化
将内存中的对象(Object,集合类等)保存到磁盘、网络介质、其他设置的过程,并在合适的时间能获取磁盘文件/网络的数据,这个过程就是对象的序列化和反序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mI39bH4y-1604833819147)(D:\ziliao\java2008\01 java基础\笔记\assets\44.png)]
为什么需要序列化和反序列化呢?
在之前文件中存储的文本信息,这样不便于对数据的分类和操作,如果可以做到直接对对象的读和写,这样可大大提高编程效率,并最大程度保证对象的完整性
Java-IO中实现对象序列化的两种方式:
1、实现Serializable接口
2、实现Externalizable接口
2.6.1、Serializable接口
对象需要实现该接口,但是他没有任何需要实现的方法,只有一个用于标记该类可序列化的唯一标识。任何类需要序列化都必须标记该变量
public class User implements Serializable {
// 对于能区分其他类的唯一表示
private static final long serialVersionUID = 1L;
private int uid;
private String name;
private String password;
// 有一部分属性不能序列化
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
//创建序列化的对象流 从内存到文件
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/user.txt"));
User user= new User();
user.setUid(1001);
user.setName("admin");
user.setPassword("123456");
//序列化对象
oos.writeObject(user);
//关闭流
oos.close();
// 反序列化: 将文件中的数据 再读入到内存中 ,需要一个读的流 ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d://user.txt"));
// 反序列化尽量只读一次 (也可以读多次, 如何写出就如何读入)
Object obj = ois.readObject();
if(obj instanceof User){
User u = (User)obj;
System.out.println("反序列化的结果:"+u);
}
//关闭流
ois.close();
问题: 能否自定义序列化的属性 ,这里可以采用方式二,实现Externalizable,并重写两个方法 接口继承而来,在其基础上新增了两个未实现方法:readExternal(ObjectInputStream)和 writeExternal(ObjectOutputStreawm) ,自定义需要序列化的属性
public interface Externalizable extends java.io.Serializable
2.6.2、Externalizable接口
public class Student implements Externalizable {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
// 自定义可序列化的属性
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(this.id);
out.writeUTF(this.name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.id = in.readInt();
this.name = in.readUTF();
}
public Student(int id, String name, String sex) {
this.id = id;
this.name = name;
this.sex = sex;
}
public Student( ) {
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 创建序列化类
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:/stu.txt"));
//创建学生
List<Student> list = new ArrayList<>();
list.add(new Student(1001,"张飞","男"));
list.add(new Student(1002,"刘备","男"));
list.add(new Student(1003,"小乔","女"));
// 将集合序列化
oos.writeObject(list);
//关闭
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:/stu.txt"));
//读
Object obj = ois.readObject();
if(obj instanceof List){
List<Student> list2 = (List<Student>)obj;
for(Student s : list2){
System.out.println(s);
}
}
//关闭流
ois.close();
}
问题:哪些属性不能序列化?
1、类中的static修饰的属性不能序列化
2、类中属性被transient修饰的不能序列化,例如transient private Integer age = null;
3、实现Externalizable接口的类的属性不能全部序列化,必须手动写可序列化的属性
2.7、文件的压缩流和解压流
文件压缩使用场景:在文件上传或下载中需要操作多个文件时,如果一个一个文件复制需要花较长时间,而且比较繁琐,javaAPI提供一种压缩/解压文件方式,可以将多个文件打包成一个文件(.zip)
包:java.util.zip
常用类:ZipEntry:表示压缩文件中的每一个实体文件
ZipFile:表示压缩文件对象
ZipOutputStream:表示压缩文件输出流,用于将普通文件写出到zip文件中
ZipInputStream:表示解压文件的输入流,用于读zip文件中的每一个实体ZipEntry
2.7.1、压缩文件的步骤
a、创建需要压缩文件的输入流(InputStream)
b、创建压缩包所在的路径,并指定压缩文件名,同时创建ZipOutputStream输出流
c、将文件对象添加到ZipOutputStream中的实体中(也可以指定压缩后的实体名称)
d、文件复制
e、关闭流
public static void main(String[] args) throws IOException {
// 1、创建文件对象
File file = new File("d:/小众网站.txt");
// 2、创建文件的输入流
FileInputStream fis = new FileInputStream(file);
// 3、创建文件压缩流(输出流)
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry("小众网站.txt "));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
zos.close();
fis.close();
}
压缩多个文件
/**
* 压缩一个文件夹 myfile
* @param args
*/
public static void main(String[] args) throws IOException {
//构建压缩包的输出流
ZipOutputStream zos = new ZipOutputStream(
new FileOutputStream("d:/myfile.zip"));
File file=new File("d:/myfile");
File [] files = file.listFiles();
for(File f : files){
//构造每一个文件的输入流
FileInputStream fis = new FileInputStream(f);
putZipFile(fis, zos ,f.getName());
System.out.println(f.getName()+"文件压缩成功" );
}
//关闭压缩流
zos.flush();
zos.close();
}
/**
* 将文件放入压缩流中
* @param fis
* @param zos
* @param entryName
* @throws IOException
*/
public static void putZipFile(InputStream fis ,
ZipOutputStream zos,
String entryName) throws IOException {
// 给压缩包中添加文件,并可自定义文件名
zos.putNextEntry(new ZipEntry(entryName));
// 给压缩包设置注释
zos.setComment("这是压缩包的注释。。。。");
// 文件复制
int len = 0;
byte [] b = new byte[1024];
while( (len = fis.read(b)) !=-1){
zos.write(b,0,len);
}
System.out.println("文件压缩成功");
fis.close();
}
2.7.2、解压文件的步骤:
解压文件是将一个.zip文件的内容,复制到文件下,需要使用ZipInputStream
解压文件的关键点: 获取解压文件的每一个条目ZipEntry的输入流 ,将输入流写出到指定位置。
如何获取输入流: ZipFile对象 表示一个zip文件
步骤:
a、根据文件路径创建ZipInputStream
b、根据文件路径创建ZipFile对象,用于获取输入流
c、循环遍历每一条目,得到它的ZipEntry
d、获取ZipEntry的输入流
e、将文件复制到指定位置
public static void main(String[] args) throws IOException {
//1、创建ZipInputStream
ZipInputStream zis = new ZipInputStream(
new FileInputStream("d:/myfile.zip"));
// 2、创建ZipFile对象
ZipFile zipFile = new ZipFile("d:/myfile.zip");
// 3、获取zip中的实体
ZipEntry en = null;
while( (en= zis.getNextEntry())!=null){
System.out.println(en.getName()+"--"+en.isDirectory());
//4、获取每一个en的输入流 (关键)
InputStream is = zipFile.getInputStream(en);
copyFile(is ,"d:/my",en.getName());
}
}
/**
* 通过输入流 复制文件到指定的目录下
* @param is 输入流
* @param path 存放文件的路径
* @param fileName 文件名
*/
public static void copyFile(InputStream is , String path , String fileName) throws IOException {
File file = new File(path);
//判断目录是否存在, 不存在就 创建
if(!file.exists()){
file.mkdirs();
}
FileOutputStream fos = new FileOutputStream(path+File.separator+fileName);
int len = 0 ;
byte [] b = new byte[1024];
while( (len = is.read(b)) !=-1){
fos.write(b,0,len);
}
System.out.println("解压成功");
fos.close();
is.close();
}