1.背景介绍
什么是流
在编程里流是一组有序的数据序列,JAVA里IO流用来处理设备之间的数据传输
最开始的时候,数据是要在各种端之间传递,从而需要各种源/端之间的操作,而且还需要多种不同的方式进行通信(比如顺序、随机存取、缓冲、二进制、按符号、行、字等等),于是java类库的设计者就通过创建大量的类来解决这个问题,于是就有了JAVA中的IO流(但JAVAI/O设计的初衷就是为了避免过多的类,好在现在我们只需要通过封装好的接口就可以很容易的使用)。
- jdk1.0的时候只有InputStream和OutputStream这两个接口和它们的包装类
- 在jdk1.1引入了Reader和Writer(字符流)来适应国际化,并且解决了原先IO流对传递16进制的字符串不友好的问题,而且它们的效率比传统的字节流更高
- 在jdk1.4版本为了改进性能及功能又引入了nio类(也就是包含Channel(通道),Buffer(缓冲区),Selector(选择器)这几个主要概念)
2.知识剖析
流的操作对象:File类
File类是java.io包中唯一代表磁盘文件本身的对象,通过File来实现对磁盘文件的操作,包括创建、删除、获取信息等等
流的模式:输入流和输出流
输入流:InputStream
输出流:OutputStream
输入流:InputStream
它的作用是用来表示那些从不同数据源产生输入的类,这些数据源包括:
1)字节数组
2)String对象
3)文件对象
4)“管道”,工作方式与实际生活中的管道类似,即从一端输入,从另一端输出
5)由其它种类的流组成的序列,以便我们可以将它们收集合并到一个流内
6)其它数据源,如Internet连接等
每种数据源都有对应的InputStream子类,还有一种单独的FileInputStream也是属于一种InputStream
InputStream 对应的子类:
输出流:OutputStream
它决定输出所要去的目标:
1)字节数组
2)文件对象
3)管道
输出目标也有对应的子类
OutputStream 对应的子类:
Reader和Writer(字符输入和输出流)
java 1.1对基本的IO流类库进行了重大的修改,当中最大的改动就是出现了Reader和Writer类。这两个类不是用来替代InputStream和OutputStream的,而是提供Unicode和面向字符的IO功能,也就是现在我们说的字符流。它的继承层次结构主要是为了国际化,而且在此之前的io流不能很好的处理16位的Unicode字符,在我们需要字符和字节结合起来使用的时候很不方便,它们也可以通过适配器(InputStreamReader/OutputStreamWriter)和InputStream/OutputStream互相转换。
而Java中的字符是Unicode是双字节的,InputStream是用来处理字节,并不适合处理字符串文本,所以在处理字符上应该尽量使用它们。
缓存流
BufferedInputStream和BufferedOutputStream
BufferedReader和BufferedWriter
缓存是IO的一种性能优化。缓存流为IO流增加了内存缓存区,大大增加了流的执行效率缓存流本身不具IO功能,只是在别的流上加上缓冲提高效率,像是为别的流装上一种包装。在对文件或其他目标频繁读写或操作的时候可能效率低、效能差,这时候使用缓冲流可以更高效的读写信息。因为缓冲流先将数据缓存起来,然后一起写入或读取出来。所以说,缓冲流还是很重要的,在IO操作时记得加上缓冲流提升性能。
Zip压缩输入/输出流
ZipInputStream和ZipOutputStream
JarInputStream和JarOutputStream
也有linux下的文件压缩流:GZIPOutputStream/GZIPInputStream
在日常的使用中经常会使用到WinRAR或WinZIP等压缩文件,通过这些软件可以把一个很大的文件进行压缩以方便传输。在Java中为了减少传输时的数据量也提供了专门的压缩流,可以将文件或文件夹压缩,而且它的压缩类型也非常丰富,更可以实现JAR及GZIP文件格式的压缩
压缩流的注意事项
1、压缩文件中的每一个压缩实体都使用ZipEntry保存,一个压缩文件中可能包含一个或多个ZipEntry对象。
2、在JAVA中可以进行zip、jar、gz三种格式的压缩支持,操作流程基本上是一致的。
3、ZipOutputStream可以进行压缩的输出,但是输出的位置不一定是文件。
4、ZipFile表示每一个压缩文件,可以得到每一个压缩实体的输入流。
3.常见问题
字符流和字节流的区别
- 字节流操作的基本单元为字节(8bit),可以通过字节操作所有数据;字符流操作的基本单元为Unicode码元,只能处理字符类 数据。
- 字节流默认不使用缓冲区;字符流使用缓冲区。
- 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。
4.编码实战
字节流的基础操作:
package com.xio.util;
import org.junit.Test;
import java.io.*;
public class InputTest {
// 字节输入流 读取文件展示
@Test
public void inputToFile() throws IOException{
File file= new File( "f:/out.txt");
// 自定义长度获取字节 字节流是按照字节读取 utf-8编码一个汉字占3个字节
// byte[] byteArray = new byte[10];
// 获取文件的长度
byte[] byteArray= new byte[(int) file.length()];
InputStream is = new FileInputStream(file);
int size;
// 1、read () 方法,这个方法 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。如果因为已经到达流末尾而没有可用的字节,则返回值 -1 。
// 2、read (byte[] b,int off,int len) 方法, 将输入流中最多 len 个数据字节读入 byte 数组。尝试读取 len 个字节,但读取的字节也可能小于该值。以整数形式返回实际读取的字节数。
// 3、read (byte[] b) 方法, 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。以整数形式返回实际读取的字节数。
size = is.read(byteArray);
System. out.println( "大小:"+ size +";内容:" + new String(byteArray));
is.close();
}
// 字节输出流 读取String输出到文件
@Test
public void outToFile() throws IOException {
String str = "超人大战面条怪,战了九九八十一天不曾想面条怪被孙猴子一棍子挑去做了碗炸酱面下了肚";
//写好要输出的文件路径
File file = new File("f:/o1ut.txt");
// 输出流把要输出到的文件拿到
OutputStream os= new FileOutputStream(file);
byte[] byteArray= str.getBytes();
// 写入文件
os.write(byteArray);
// 关闭流
os.close();
}
// 字符输出流 输出文件
@Test
public void writeCharToFile() throws IOException{
String str= new String( "hello hello hello hello hello hello How are you?");
File file= new File( "f:/test1.txt");
// Writer os= new FileWriter(file);
// 续写
Writer os= new FileWriter(file,true);
os.write(str);
os.flush();
os.close();
}
// 字符输入流 读取文件
@Test
public void File() throws IOException{
File file= new File( "f:/test1.txt");
Reader reader= new FileReader(file);
int date ;
// 通过单个字母读取
// while ((date=reader.read())!=-1){
// System.out.println((char) date);
// }
// 通过数组读取
char[] chars = new char[8];
date = reader.read(chars);
System.out.println(new String(chars,0,date));
while ((date = reader.read(chars))!=-1){
System.out.println(new String(chars,0,date));
}
reader.close();
}
@Test
public void upload() {
// 需要拷贝的文件
String input = "C:\\Users\\GhostSugar\\20180611_162654.mp4";
// 写入的文件
String Output = "F:\\2.mp4";
// 文件输入流
FileInputStream in = null;
// 文件输出流
FileOutputStream out = null;
// 缓冲输入输出流 是FileInputStream和FileOutputStream的包装类
BufferedInputStream bin = null;
BufferedOutputStream bout = null;
try {
in = new FileInputStream(input);
out = new FileOutputStream(Output);
bin = new BufferedInputStream(in);
bout = new BufferedOutputStream(out);
int data;
// read读取内容 读取不到的时候返回-1
while ((data = bin.read())!= -1){
// 输出data长度的文件
bout.write(data);
}
}catch (IOException e){
e.fillInStackTrace();
}finally {
try {
if(bin!=null)bin.close();
if (bout!=null)bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
缓存流
package com.xio.util;
import org.junit.Test;
import java.io.*;
import java.nio.Buffer;
public class BufferIOTest {
// 缓存字节输出流
@Test
public void bufferInputTest(){
try {
//创建字节输出流实例
OutputStream out=new FileOutputStream("F:\\test.txt");
//根据字节输出流构建字节缓冲流
BufferedOutputStream buf=new BufferedOutputStream(out);
String data="从字节输入流中读取信息,缓冲各个字符,从而实现高效读取。";
//写入缓冲区
buf.write(data.getBytes());
//刷新缓冲区,即把内容写入
buf.flush();
//关闭流
buf.close();//关闭缓冲流时,也会刷新一次缓冲区
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 缓存字节输入流
@Test
public void bufferOutputTest(){
try {
//创建字节输入流实例
InputStream in=new FileInputStream("F:\\test.txt");
//根据字节输入流构建字节缓冲流
BufferedInputStream buf=new BufferedInputStream(in);
byte[] bytes=new byte[1024*10];
//数据读取
int len=-1;
StringBuffer sb=new StringBuffer();
while((len=buf.read(bytes))!=-1)
{
sb.append(new String(bytes,0,len));
}
System.out.println("读取内容:"+sb);
//关闭流
buf.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 缓存字符输出流
@Test
public void bufferReader(){
try {
Writer w=new FileWriter("F:\\test.txt");
//根据字符输出流创建字符缓冲流
BufferedWriter buf=new BufferedWriter(w);
//写入数据
buf.write("只要功夫深铁杵磨成针,磨成针了做什么,嘿嘿嘿,你不懂");
//刷新流
buf.flush();
//关闭流
buf.close();
w.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 缓存字符输入流
@Test
public void bufferWriter(){
try {
Reader r=new FileReader("F:\\test.txt");
//根据字符输入流创建字符缓冲流
BufferedReader buf=new BufferedReader(r);
char [] data=new char[512];
//数据读取
int len=-1;
StringBuilder sb=new StringBuilder();
while((len=buf.read(data))!=-1){
sb.append(new String(data,0,len));
}
System.out.println("读取内容: "+sb);
//关闭流
buf.close();
r.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Zip压缩流:
package com.xio.util;
import org.junit.Test;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class ZipTest {
@Test
public void zipTest() throws Exception {
// zip("F:/Test.zip",new File("f:/1.mp4"));
//
ZipFile file = new ZipFile("F:\\test\\Test.zip");
DecZip(file,"f:\\test\\");
}
// 压缩
private void zip(String zipFileName, File inputFile) throws Exception {
ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
zip(out, inputFile, inputFile.getName());
out.close();
}
/**
* @Description: 压缩文件
* @Param: ZipOutputStream out, File file, String fileName
* @return: void
* @Author: Mr.Zhang
* @Date: 2018/6/23
*/
private void zip(ZipOutputStream out, File file, String fileName) throws Exception {
System.out.println("开始压缩...");
// 路径名是否是目录 否则为文件
if (file.isDirectory()) {
System.out.println("找到文件夹,开始分配");
// 获取路径数组
File[] fl = file.listFiles();
// 写入此目录的entry
out.putNextEntry(new ZipEntry(fileName + "/"));
// 判断参数是否为空
fileName = fileName.length() == 0 ? "" : fileName + "/";
// 遍历数组中文件
for (int i = 0; i < fl.length; i++) {
zip(out, fl[i], fileName + fl[i]);
}
} else {
System.out.println("直接压缩文件:"+fileName);
// 创建新的进入点
out.putNextEntry(new ZipEntry(fileName));
// 创建FileInputStream对象
FileInputStream in = new FileInputStream(file);
int b;
// 循环读取 将字节写入当前ZIP条目
while ((b = in.read()) != -1) {
out.write(b);
}
System.out.println("压缩完毕");
in.close();
}
}
/**
* @Description: 解压缩文件
* @Param: ZipFile zipFile 待解压文件
* @Param: String dir 解压目录
* @return: void
* @Author: Mr.Zhang
* @Date: 2018/6/23
*/
public void DecZip(ZipFile zipFile,String dir) throws IOException {
File outFile = null ;
ZipInputStream zipInput = null;
OutputStream out = null;
InputStream input = null;
ZipEntry entry = null;
// 获取ZIp输入流
zipInput = new ZipInputStream(new FileInputStream(zipFile.getName())) ;
// 获得压缩文件内的子文件
while((entry = zipInput.getNextEntry())!=null){
System.out.println("解压" + entry.getName() + "文件") ;
// 定义输出路径
outFile = new File(dir + File.separator + entry.getName());
// 判断文件夹和文件是否存在
if(!outFile.getParentFile().exists()){
outFile.getParentFile().mkdir() ;
}
if(!outFile.exists()){
outFile.createNewFile() ;
}
// 得到每一个实体的输入流
input = zipFile.getInputStream(entry);
// 实例化文件输出流
out = new FileOutputStream(outFile);
int temp = 0 ;
while((temp=input.read())!=-1){
out.write(temp) ;
}
input.close();
out.close();
}
input.close();
}
public void DecZip1(File zipFile,String dir) throws IOException {
ZipInputStream zin = null;
try {
// File zipFile = new File("F:\\Test.zip");
zin = new ZipInputStream(new FileInputStream(zipFile));
// entry代表的是压缩包中的文件 每个子文件都由一个zipEntry表示
ZipEntry entry = zin.getNextEntry();
// 如果entry不为空,且不为目录
while (((entry = zin.getNextEntry()) != null) && !entry.isDirectory()) {
System.out.println("ces2");
File file = new File(dir + entry.getName()); // 获取文件目录
if (!file.exists()) { // 如果该文件不存在
file.mkdirs();// 创建文件所在文件夹
file.createNewFile(); // 创建文件
}
zin.closeEntry(); // 关闭当前entry
System.out.println(entry.getName() + "解压成功");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (zin!=null)zin.close();
}
}
}
5.扩展思考
Spring框架中的流读取图片上传到本地服务器
6.参考文献
JAVA编程思想
JAVA从入门到精通(第三版)
百度:JAVA IO流
7.更多讨论
Q:缓存流的默认缓存大小是多少,可以手动设置大小吗。数组长度和缓存大小相同的情况下,字节数组做缓存和使用字节缓存流 哪个效率高?
Q:性能有区别吗?
张强:使用字节缓存流效率高,至于缓存默认大小和手动设置未了解。下面:
A 常雷雷:IO流在web端的应用场景有哪些
Q 张强:一般是在使用文件上传的时候使用,或者数据端之间传输文件、流数据的时候可以使用
A:使用哪种方式效率最高:
Q:使用缓存流的效率是最高的
A:上传图片的时候使用什么方式上传的?
Q:我用的spring封装的flit类操作的,它可以读取到request里的数据流从而完成对文件的储存
分享到此结束
谢谢大家!