IO流特点
IO流属于物理连接,需要手动显示close(),否则内存泄漏
File对象必须是文件路径而不能是文件目录,否则在使用流时会报错java.io.FileNotFoundException: D:\io\io1\io2 (拒绝访问。)
IO流的分类
- 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit)
- 按数据流的流向不同分为:输入流,输出流
- 按流的角色的不同分为:节点流,处理流
为什么这样分类?
1,数据单位
- 字节流(8bit = 255位):适合图像视频等(.jpg .mp3 .mp4 .avi .doc .ppt…)传输,因为电脑像素点的灰度值范围是[0,255]
- 字符流(16bit = 2字节):适合传输文本(.java .c .cpp .txt),Java语言规范规定,Java的char类型是UTF-16的code unit,也就是一定是16位(2字节)
2,流向
输出流: 程序(内存)——>存储设备
输入流:键盘,磁盘等——>程序(内存)
3,角色
节点流:直接读写数据,最简单的一类流
处理流:以节点流对象为形参,为节点流提供更好的性能的流
- 基类(抽象类)给定了节点流的规范
- 子类的命名后缀规则一致,虽然有40多个类,但都很规范
字符流:Reader和Writer(以FileReader和FileWriter为例)
(1)FileWriter
- 主要方法
方法 | 解释 |
---|---|
构造器 FileWriter(File路径or对象,可选项append) | append默认为false true表示write方法追加写入 false表示write方法覆盖写入 |
write(“写入的内容”) write(String或char[],offset,len) | 可以传入String或char[ ] offset表示从索引offset号开始传入 len表示传入的长度,注意角标越界问题 |
flush(空参) | 刷新,不刷新的话,虽然写入了,但是read不到,文件打开也看不到写入 |
close() | 显示关闭流,防止内存溢出 |
-
示例
准备工作:先创建好fileFile file = new File("text.txt");//在当前project下创建一个text.txt文件 if(!file.exists()){ file.createNewFile();//防止FileNotFoundException: text.txt (系统找不到指定的文件。) } /***************Writer操作*******************/ FileWriter fileWriter = new FileWriter(file,true);//流的实例化 //append为true则多个write是追加写入 //append为false则write是覆盖写入 fileWriter.write("hello world\n"); fileWriter.flush();//刷新,不刷新的话,虽然写入了,但是read不到,文件打开也看不到写入 fileWriter.write("it's a test\n"); fileWriter.flush();//刷新 //该程序运行几遍就write几遍,下面read就会重复几遍 fileWriter.close();
(2)FileReader
- 主要方法
方法 | 解释 |
---|---|
构造器 FileReader(File对象或路径) FileReader(FileDescriptor fd) | / |
read(空参) | 空参迭代返回该文件的一个字符的ASCII码值data,需配合强转char输出 |
read(char[] cbuf) | 迭代返回本次流读取的长度len,并把读取的数去填入cbuf数组中 |
read(char[] cbuf,偏移量offset,最大读取长度length) | length为0时线程阻塞,length为装填长度 read方法返回值 若某段流没有可读字符,read返回-1 |
当FileReader流能read到字符时,返回值len=实际读取长度>0
读取不到返回-1,所以可以利用len!=-1创建循环条件
注意区别read返回值 和 对cbuf赋值的差别
read()是一个迭代器,无论是read(空参)还是read(cbuf)都会导致迭代器指针移动
- 解释:什么叫“目的缓冲区”和“装填长度”?为什么read方法最好不要带offset和len参数?
1.char[] cbuf数组就是目的缓冲区,把cbuf传入read()的形参,则会把目标文件中cbuf.length()个连续的字符读入cbuf,然后遍历cbuf数组,故称“缓冲”目标缓冲区(cbuf数组)设置过大则内存占用大,过小则运行速度慢。
2.而read(cbuf,offset,len)中的len就是装填长度,装填长度不影响最后read输出结果,但可能造成角标越界
3.len为0线程阻塞,offset会影响读取结果,所以通常不带这两个参数
-
示例
/*************Reder操作**************/ FileReader fileReader = new FileReader(file);//流的实例化 char[] cbuf = new char[6];//设置“目的缓冲区” int len ; //定义“装填长度” while((len=fileReader.read(cbuf,0,6)) != -1){//最好是不带后面两个形参 //当FileReader流能read到字符时,返回值len=实际读取长度>0 //形参解析:目的缓冲区数组,offset索引偏移量(为0时保证从cbuf[0]开始装填), // len最大装填长度:len+offset之和不能超过cbuf.length()否则报错指针越界 //输出的方式1:注意len和length区别 for(int i = 0 ; i<len ; i++){ //必须是i<len“装填长度”,而不能是i<cbuf.length“缓冲区长度” System.out.print(cbuf[i]); } } fileReader.close();
开发规范
为了防止各种报错,开发时最好满足以下规范
- close()防止内存溢出
- try-catch-finally而不是简单的throws IOException
- close()放在finally中,以便出问题时及时关闭流
- 在finally中的close()还需要if判断是否存在,并嵌套try-catch
- 使用IDEA的alt+insert调try-catch时,会自动在外边生成FileWriter fw = null;和FileReader fr = null;
字节流:InputStream和OutputStream
- 步骤规范:创建File对象——创建流对象——操作——关闭流
我们把刚刚创建的“C:\Users\Administrator\IdeaProjects\ZJHindi\text.txt”复制到“D:\io\io1\io2”下
- 关掉QQ(热键冲突)Alt+Ctrl+T调用try-catch-finally
- 第二步
- 第三步,两个close()分别try -catch(注意去掉throws IOException)
完整代码:
package zjh;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyTest {
public static void main(String[] args){
FileReader fr = null;//从源文件读取
FileWriter fw = null;//写入目的文件
try {
File scrfile = new File("C:\\Users\\Administrator\\IdeaProjects\\ZJHindi\\text.txt");
File destfile = new File("D:\\io\\io1\\io2\\desttext.txt");
fr = new FileReader(scrfile);
fw = new FileWriter(destfile);
char[] cbuf = new char[5];
int len;
while((len=fr.read(cbuf))!=-1){
fw.write(cbuf);
}
fw.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(fw!=null){//先判断fw非空
try {
fw.close();//防止关闭时出错
} catch (IOException e) {
e.printStackTrace();
}
}
if(fr!=null) {//判断fr非空
try {
fr.close();//关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
处理流:缓冲流:BufferedInputStream和BufferedOutputStream
- 把节点流作为形参new缓冲流,套接在已有的节点流之外
- 传统的节点流是线程阻塞的,而缓冲流则通过在内存中开辟一块缓冲区用于各节点流缓冲,缓冲满之后执行,利用空间换时间
- 外层处理流close()的时候,内层字节流也会关闭,因此只需对外层流finally{close()}
如下是一个封装好的图片复制方法:
public static void BufferedStreamCopy(String srcPath,String destPath) {//缓冲流字节流的复制方法
//形参直接放入源文件路径 和 目的文件路径,文件自动复制
BufferedInputStream bis = null;//写的时候按正常步骤写
BufferedOutputStream bos = null;//这两句是try-catch-finally自动生成的
try {
//File对象的创建,导入路径
File srcfile = new File(srcPath);
File destfile = new File(destPath);
//节点流的创建,用于复制图像视频等
FileInputStream fis = new FileInputStream(srcfile);
FileOutputStream fos = new FileOutputStream(destfile);
//处理流(缓冲流)的创建,用于加速
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
//原本对节点流的操作照搬给处理流
byte[] bbuf = new byte[1024];
int len;
while((len=bis.read(bbuf)) != -1){
//每从bis读一次,就往bos写一次
bos.write(bbuf);
}
//因为有处理流自动flush,所以不用显示的flush
//缓冲区满了之后自动会flush,默认长度1024x8
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流,只需要关闭外层
if(bis!=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bos!=null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
调用一下:
public static void main(String[] args) {
BufferedStreamCopy("D:\\io\\io1\\io2\\QQ截图20220115002227.png"
,"D:\\io\\io2\\io3\\新建文件夹\\复制后的图.png");//目的路径也必须是文件,并且格式(.png)最好相同
}
字符流处理文本——>字节流处理图片视频
- 把FileReader——>FileInputStream,把FileWriter——>FileOutputStream
- 把char[] cbuf——>byte[] bbuf
其实字节流也可以复制(仅仅是复制)文本文件,只是中途不能控制台输出,否则中文会被切分
应用
加密算法:适用于所有文件,加密解密方法相同(^5)
public static void MethodEncrpt(String srcpath,String destpath){//定义方法:加密,解密
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
bis = new BufferedInputStream(new FileInputStream(new File(srcpath)));
bos = new BufferedOutputStream(new FileOutputStream(new File(destpath)));
int data;
while((data= bis.read()) != -1){
bos.write(data^5);//异或加密,加密解密方式都相同(M^n^n=M)
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/********************加密解密方法使用示例********************/
public static void main(String[] args) {
//加密
MethodEncrpt("C:\\Users\\Administrator\\Desktop\\数据结构文档\\00_课程介绍.pdf","D:\\io\\加密\\字节流文件\\加密数据结构课程介绍.pdf");
//解码
MethodEncrpt("D:\\io\\加密\\字节流文件\\加密数据结构课程介绍.pdf","D:\\io\\解密\\字节流文件\\解密1.pdf");
MethodEncrpt("D:\\io\\io1\\io2\\desttext.txt","D:\\io\\io1\\io3\\加密1.txt");
MethodEncrpt("D:\\io\\io1\\io3\\加密1.txt","D:\\io\\io1\\io3\\解密1.txt");
}
字数统计算法:加入了转换流
注意:实际开发中应该用try-catch-finally,此处为了方便直接throws IOException
public static void MethodCount(String path) throws IOException {
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
InputStreamReader isr = new InputStreamReader(bis);
HashMap<Character,Integer> hsm1 = new HashMap<>();
int len;
char[] cbuf = new char[1024];
while((len=isr.read(cbuf)) != -1){//读取到cbuf
for(int i=0 ; i<len ; i++){
if(hsm1.containsKey(cbuf[i])){
hsm1.replace(cbuf[i],hsm1.get(cbuf[i])+1);
}
else{
hsm1.put(cbuf[i],1);
}
}
}
//输出遍历hsm1数组
Set<Map.Entry<Character, Integer>> entries = hsm1.entrySet();
Iterator<Map.Entry<Character, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
Map.Entry<Character, Integer> next = iterator.next();
Character key = next.getKey();
Integer value = next.getValue();
switch (key) {
case ' ':
System.out.println("空格:"+value+"次");
break;
case '\t':
System.out.println("tab:"+value+"次");
break;
case '\r':
System.out.println("回车:"+value+"次");
break;
case '\n':
System.out.println("换行:"+value+"次");
break;
default:
System.out.println(key+":"+value+"次");
break;
}
}
}
加密解密GUI
public class EncrptDir {
public static void EncrptMethod(String dirpath) throws IOException {//文件目录
File srcfile = new File(dirpath);
File destfile = new File("D:\\加密照片\\加密照片5");//加密照片存储地址
if(!destfile.exists()){
destfile.mkdirs();
}
File[] files = srcfile.listFiles();//罗列所有照片的File对象
for(int i = 0 ; i<files.length ; i++){//处理每个文件
String s = files[i].getName().split("\\.")[1];//s表示文件名后缀
//split方法是正则表达式,所以要用\\.
String d = "加密图片"+i+"." + s;
File file_jm = new File(destfile,d);
//创建节点流
FileInputStream fis = new FileInputStream(files[i]);//输入原图
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(file_jm);//输出加密
BufferedOutputStream bos = new BufferedOutputStream(fos);
//读写操作
int data;
while((data=bis.read())!=-1){
bos.write(data^5);//加密操作
}
//关闭流
bos.close();
bis.close();
System.out.println("正在加密第 "+i+" 张图");
}
System.out.println("加密完成,文件位于D:\\加密照片\\加密照片5");
}
public static void DecrptMethod(String dirpath) throws IOException {//文件目录
File srcfile = new File(dirpath);
File destfile = new File("D:\\解密照片\\解密照片5");//解密照片存储地址
if(!destfile.exists()){
destfile.mkdirs();
}
File[] files = srcfile.listFiles();//罗列所有照片的File对象
for(int i = 0 ; i<files.length ; i++){//处理每个文件
String s = files[i].getName().split("\\.")[1];//s表示文件名后缀
//split方法是正则表达式,所以要用\\.
String d = "加密图片"+i+"." + s;
File file_jm = new File(destfile,d);
//创建节点流
FileInputStream fis = new FileInputStream(files[i]);//输入原图
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(file_jm);//输出解密
BufferedOutputStream bos = new BufferedOutputStream(fos);
//读写操作
int data;
while((data=bis.read())!=-1){
bos.write(data^5);//解密操作
}
//关闭流
bos.close();
bis.close();
System.out.println("正在解密第 "+i+" 张图");
}
System.out.println("解密完成,文件位于D:\\解密照片\\解密照片5");
}}
package ZJHindi;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.*;
public class EncrptGUI {
public static void main(String[] args) {
JFrame jf = new JFrame("文件加密工具");
jf.setLayout(new FlowLayout(FlowLayout.LEFT));
jf.setBounds(460,300,350,200);
/*********逻辑部分**********/
JLabel label1 = new JLabel("需要加密or解密的文件源路径:");
JTextField srcfilepath = new JTextField("",30);
JButton jButton1 = new JButton("点击加密");
jButton1.setSize(20,30);
JLabel jLabe2 = new JLabel("输出位置D:\\加密照片\\加密照片5");
jf.add(label1);
jf.add(srcfilepath);
jf.add(jButton1);
jf.add(jLabe2);
JButton jButton2 = new JButton("点击解密");
jf.add(jButton2);
JLabel jLabe3 = new JLabel("输出位置D:\\解密照片\\解密照片5");
jf.add(jLabe3);
/****************************/
jf.setVisible(true);
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JLabel outter = new JLabel("执行信息:");
jf.add(outter);
JTextArea jta = new JTextArea();
jf.add(jta);
/****************************加密*********************/
jButton1.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
String path = srcfilepath.getText();//获取输入的路径
try {
EncrptDir.EncrptMethod(path);
} catch (IOException e1) {
e1.printStackTrace();
}
jta.append("加密完成\n");
}
});
/******************解密*****************/
jButton2.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
String path = srcfilepath.getText();//获取输入的路径
try {
EncrptDir.DecrptMethod(path);
} catch (IOException e1) {
e1.printStackTrace();
}
jta.append("解密完成\n");
}
});
}
}
转换流:字节——>字符
转换流只有继承于Reader和Writer的,即只能从字节转到字符
InputStreamReader写入流转换:解码
因为读取的文件有一个确定的编码集,所以解码时必须按该编码集,否则会乱码
- 构造函数默认为UTF-8
- 自定义构造new InputStreamReader(内层字节流 , "GBK"或"UTF-8"等等);
OutputStreamWriter导出流转换:编码
编码即导出到硬盘中,写入到文件中,所以用什么编码都一样,只需要保证后续解码的时候对应的编码集相同。
“GBK”“UTF-8”底层都是用unicode存储的