目录
一、IO流
1.1 IO流概述及分类
IO流是用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,
Java用于操作流的类都在IO包中,流按照流向可以分为输入流(InputStream)和输出流(OutputStream),
按照操作类型可以分为:字节流和字符流,
- 字节流:字节流可以操作任何数据,因为在计算机中任何数据都是以字节的形式存储的
- 字符流:字符流只能操作纯字符数据,比较方便
1.2 IO流常用父类
- 字节流抽象父类
- InputStream
- OutputStream
- 字符流抽象父类
- Reader
- Writer
1.3程序书写
- 使用前,导入IO包中的类
- 使用时,进行IO异常处理
- 使用后,释放资源
二、字节流
字节流主要分为InputStream和OutputStream,因为这两个是抽象类,我们在使用的时候只能使用它的子类,
JAVA中针对文件的读写操作设置了一系列的流,
其中主要有FileInputStream、FileOutputStream、FileReader、FileWriter四种最为常用的流,
我们首先学习一下字节流FileInputStream和FileOutputStream。
2.1FileInputStream
FileInputStream是从文件系统中的某个文件中获得输入字节,用于读取诸如图像数据之类的原始数据流,
我们一般使用read方法来读取字节,read()可以从输入流中读取一个数据字节,并且以int类型返回,当文件到末尾时会返回-1,
我们利用这个特点来读取一个txt文件测试一下,
import java.io.FileInputStream;
import java.io.IOException;
public class IOTest {
public static void main(String[] args) throws IOException {
FileInputStream f=new FileInputStream("data.txt");
int ch;
while((ch=f.read())!=-1){
System.out.println(ch);
}
f.close();
}
}
这里有一个小问题,既然read方法每次读取的是一个字节,为什么不用byte类型接收而用int类型,
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式存储的,
如果每次读取返回的类型都是byte,有可能读到11111111,这个二进制补码代表的数字为-1,而程序碰到-1后就会停止读文件,
后面的数据就没有办法读到了,所以在读取的时候用int类型接收,如果是11111111,那么前面也会补足24个0凑够4个字节,然后返回
这样就可以保证正常读取后面的数据了。
2.2FileOutputStream
FileOutputStream是文件输出流,是用于将数据写入File或者FileDescriptor的输出流。
FileOutputStream用于写入诸如图像数据之类的原始字节的流,我们一般用的是write方法,
void write(byte b[])//将b.length个字节从指定的byte数组写入文件输出流中
void write(byte b[],int off,int len)//将指定byte数组从偏移量off开始的len个字节写入文件输出流
void write(int b)//将指定字节写入文件输出流
我们往txt文件写入字符串"java"试一下,
FileOutputStream f=new FileOutputStream("dataOutput.txt");
f.write(106);//字符j(虽然写入的是一个int数,但是会自动去除前3个八位,变为byte一个字节)
f.write(97);//字符a
f.write(118);//字符v
f.write(97);//字符a
f.close();
2.3FileOutputStream追加
当我们想保留原来文件的内容,并在原来内容的基础上添加一些字符,这时就不能只用文件名构造FileOutputStream()了,
因为用文件名构造FileOutputStream对象,在创建对象的时候如果没有文件会帮我们创建文件,如果有则会将文件清空再重新写入,
我们需要添加一个额外的布尔参数append,append如果为true ,则字节将被写入文件的末尾而不是开头,也就是不会覆盖前面的内容
FileOutputStream(File file, boolean append)//创建文件输出流以写入由指定的 File对象表示的文件
FileOutputStream(String name, boolean append)//创建文件输出流以指定的名称写入文件
我们来测试一下,接着上面的内容"java",继续再写入一个"java"。
FileOutputStream f=new FileOutputStream("dataOutput.txt",true);
f.write(106);
f.write(97);
f.write(118);
f.write(97);
f.close();
2.4拷贝文件
在java文件流中,拷贝文件一般有四种方式。
2.4.1按字节逐个拷贝
FileInputStream fis=new FileInputStream("luffy.jpg");
FileOutputStream fos=new FileOutputStream("luffyCopy.jpg");
int b;
while((b=fis.read())!=-1){
fos.write(b);//逐个字节拷贝,复制图片
}
fis.close();
fos.close();
可以看到图片已经拷贝成功了。
但是这种速度非常慢,是逐个字节进行拷贝的,如果碰到比较大的文件效率就会很低。
2.4.2字节数组整体拷贝
如果遇到字节数比较多的文件,拷贝的时候时间消耗就会比较大,这里我们可以用到available方法,
int availabe()//返回文件可以读取的字节个数
int read(byte[] b)//从输入流中将最多b.length个字节的数据读取到byte数组中,并返回读取的有效字节个数
int read(byte[] b,int off, int len)//从输出流中将最多len个字节的数据读取到byte数组中,开始位置为off,并返回读取的有效字节个数
int write(byte[] b)//将b.length个字节从指定byte数组写入文件输出流,并返回写入的有效字节个数
int write(byte[] b,int off, int len)//将指定byte数组从偏移量off开始的len个字节写入文件输出流,并返回写入的有效字节个数
那么我们看看如何使用available方法拷贝字节数组,
FileInputStream fis=new FileInputStream("testVideo.avi");
FileOutputStream fos=new FileOutputStream("testVideoCopy.avi");
byte arr[]=new byte[fis.available()];
fis.read(arr);
fos.write(arr);
fis.close();
fos.close();
这种方式相比于一个字节一个字节读取,效率快了很多,但是在开发中这种方式还是不常用的,
因为如果文件大小过大的话,一次将文件读取到程序中,JVM可能会出现内存溢出的错误,所以在开发中这两种方法都不常用。
2.4.3小数组拷贝
小数组拷贝就是定义一个容量相对小的数组,让数组一次无法接收完文件流中的数据,
然后不断读取写入,读取写入直到文件流中没有数据了,我们来看看代码如何实现,
FileInputStream fis=new FileInputStream("testVideo.avi");
FileOutputStream fos=new FileOutputStream("testVideoCopy.avi");
byte arr[]=new byte[1024];//这里一般定义为1024,也可以自己设置为其他较小的数字(最好为1024的整数倍)
int len;
while((len=fis.read(arr))!=-1){//当文件流中还有数据时
fos.write(arr,0,len);//写入读取进来的有效数据
}
fis.close();
fos.close();
2.4.4缓冲字节流拷贝
缓冲字节流就是用到了BufferedInputStream和BufferedOutputStream,其构造方法如下,
BufferedInputStream(InputStream in)//使用输入流in创建一个BufferedInputStream对象,供以后使用
BufferedInputStream(InputStream in, int size)//使用输入流in创建BufferedInputStream,具有指定缓冲区大小size
BufferedOutputStream(OutputStream out)//创建一个新的缓冲输出流,以将数据写入指定的底层输出流
BufferedOutputStream(OutputStream out, int size)//创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流
当创建BufferedInputStream时,将创建一个内部缓冲区数组,大小一般为1024*8个字节,装满后一次刷新到文件上,
当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次有多个字节,
然后我们使用缓冲字节流对象进行读写操作,其功能与前面的读写相同,我们看看如何具体使用的,
FileInputStream fis=new FileInputStream("data.txt");
FileOutputStream fos=new FileOutputStream("dataCopy.txt");
BufferedInputStream bis=new BufferedInputStream(fis);
BufferedOutputStream bos=new BufferedOutputStream(fos);
int len;
while((len=bis.read())!=-1){//当文件流中还有数据时
bos.write(len);//写入读取进来的有效数据
}
bis.close();
bos.close();
fis.close();
fos.close();
2.5flush和close方法
- flush()方法
- 用来刷新缓冲区的,将缓冲区的字节全部都刷新到文件上,刷新之后可以再次写出数据
- close()方法
- 用于关闭流释放资源的,如果是带缓冲区的流对象使用close方法,会先刷新缓冲区再关闭流,将缓冲区的字节全部都刷新到文件上,关闭之后不可以写出数据
2.6字节流操作中文
字节流在读取中文的时候,因为一个中文用两个字节表示,字节流一个字节一个字节读取时有可能会读到半个中文,造成乱码,
如果我们用小数组来读取,那么怎么设置小数组的大小也是一个问题,如果大小设置为两个,那么两个中文之间要是有一个标点符号一样还是会乱码,
所以我们一般使用字符流读取中文,在用字节流读取中文时可能会出现乱码的情况,
在字节流写出中文时,字节流直接操作的是字节,所以写出中文也必须将字符串转换为字节数组再写出,
比如write("你好".getBytes()),再比如写回车换行为write("\r\n".getBytes())
2.7流的异常处理
2.7.1老版本(1.6版本及以前)
import java.io.*;
public class IOTest {
public static void main(String[] args) throws IOException {
FileInputStream fis=null;
FileOutputStream fos=null;
try{//try finally语句保证了创建输出流有异常时,输入流可以正常关闭
fis=new FileInputStream("data.txt");
fos=new FileOutputStream("dataCopy.txt");
int b;
while((b=fis.read())!=-1){
fos.write(b);
}
}finally {//里面的try finally嵌套保证fis关闭异常时可以正常关闭fos
try{
if(fis!=null)
fis.close();
}finally {
if(fos!=null)
fos.close();
}
}
}
}
2.7.2新版本(1.7版本)
import java.io.*;
public class IOTest {
public static void main(String[] args) throws IOException {
try(
FileInputStream fis=new FileInputStream("data.txt");
FileOutputStream fos=new FileOutputStream("dataCopy.txt");
){
int b;
while((b=fis.read())!=-1) {
fos.write(b);
}
}
//不用显示写close关闭流,在try后的小括号中创建流对象,
//执行完大括号的代码后,流对象会自动关闭
}
}
注意,小括号中不能创建不具备自动关闭功能的对象(需要实现AutoCloseable接口)。
2.8给图片加密
给图片加密实现起来比较简单,将读出来的字节通过自己设计的编码规则对应到另外的一个数,再将结果保存输出,
解码的时候按照编码规计算出原来的字节码,即可得到原来的照片了,我们看看具体如何实现,
import java.io.*;
public class IOTest {
public static void main(String[] args) throws IOException {
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("luffy.jpg"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("luffyEncrypt.jpg"));
int b;
while((b=bis.read())!=-1){
//异或操作使用两次就会得到数本身,解码的时候再异或一次即可得到原字节码
bos.write(b^123);//将得到的字节码异或123
}
bis.close();
bos.close();
Decode();
}
public static void Decode() throws IOException {
BufferedInputStream bis=new BufferedInputStream(new FileInputStream("luffyEncrypt.jpg"));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("luffyDecode.jpg"));
int b;
while((b=bis.read())!=-1){
bos.write(b^123);//异或123进行解码
}
bis.close();
bos.close();
}
}
我们看看原图片,加密后图片和解密后的图片(顺序为依次从左至右),
2.9拷贝文件
要求在控制台录入文件的路径,将文件拷贝到当前项目下,
我们定义一个方法,获取键盘录入的文件路径,并且封装成File对象返回,
import java.io.*;
import java.util.Scanner;
public class IOTest {
public static void main(String[] args) throws IOException {
File file = getFile();//获取文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file.getName()));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
bis.close();
bos.close();
System.out.println("拷贝成功");
}
public static File getFile() {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件路径名:");
while (true) {
String fileDir = scanner.nextLine();
File file = new File(fileDir);
if (!file.exists()) {
System.out.println("路径下不存在该文件,请重新输入文件路径名:");
} else if (file.isDirectory()) {
System.out.println("输入的路径为文件夹路径,请重新输入文件路径名:");
} else {
return file;//返回文件
}
}
}
}
我们检查一下当前目录下有没有刚刚的ico.ico文件,可以看到正确拷贝过来了。
2.10录入数据拷贝到文件
要求将键盘录入的数据拷贝到当前项目下的data.txt文件中,键盘录入的数据当遇到quit时退出,
import java.io.*;
import java.util.Scanner;
public class IOTest{
public static void main(String[] args) throws IOException {
Scanner sc=new Scanner(System.in);
FileOutputStream fos=new FileOutputStream("data.txt");
System.out.println("请键盘输入数据,输入quit结束:");
while(true){
String line=sc.nextLine();
if("quit".equals(line)){
break;
}else{
fos.write(line.getBytes());//字符串写出必须转换为字节数组
fos.write("\r\n".getBytes());//写入换行
}
}
fos.close();
System.out.println("程序已退出");
}
}
我们检查一下文本文件的内容,发现正确写入了内容,