勿以恶小而为之,勿以善小而不为--------------------------刘备
劝诸君,多行善事积福报,莫作恶
上一章简单介绍了File类的使用(一),如果没有看过,请观看上一章
一. OutputStream
OutputStream 是字节输出流, 用于从程序中往文件里写入内容。 常常使用其子类, FileOutputStream.
一.一 OutputStream 接口方法
方法名 |
作用 |
abstract void write(int b) |
写入单个字节,写入内容为 b |
void write(byte[] b) |
写入整个字节数组, 写入内容为 字节数组 |
void write(byte[] b, int off, int len) |
写入字节数组,从off 到off+len 的内容 |
void close() |
关闭此输出流并释放与此流相关联的任何系统资源。 |
void flush() |
刷新此输出流并强制任何缓冲的输出字节被写出 |
一定要关闭流。
一.二 FileOutputStream 类
对于文件的字节操作,常常使用 FileOutputStream 类。
一.二.一 构造方法
一.二.一.一 方法
方法 |
作用 |
FileOutputStream(File file) |
传入文件, 输出时重写文件内容 |
FileOutputStream(File file, boolean append) |
传入文件,append为true时,追加文件内容,为false时,重写文件内容。 |
FileOutputStream(String name) |
传入文件的路径,相对路径和绝对路径均可以,输出时重写文件内容 |
FileOutputStream(String name, boolean append) |
传入文件的路径,append为true时,追加文件内容,为false时,重写文件内容 |
实际上,常用这两种方式,一种是传入文件,另外一种是传入文件路径。
如果传入的文件不存在的话,那么构造时会创建该文件。
老蝴蝶建议, 传入文件。
一.二.一.二 演示
@Test
public void conTest() throws Exception{
//有两种,一种是传入文件File, 一种是传入路径字符串
File file=new File("E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
//传入File,重写文件
OutputStream outputStream=new FileOutputStream(file);
//传入File, 追加文件
// OutputStream outputStream=new FileOutputStream(file,true);
String path="E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello.txt";
//传入路径, 重写文件
OutputStream outputStream1=new FileOutputStream(path);
//传入路径,追加文件
// OutputStream outputStream1=new FileOutputStream(path,true);
}
一.二.二 写入和关闭方法
重写父类的方法, 主要是 write() 方法。
一.三 OutputStream 写入文件
一.三.一 write(int b) 单个写入
写入int 类型,如果写入的是字符串的话,需要将字符串转换成字节数组,然后遍历字节数组,一个个写入。
@Test
public void writeContentTest() throws Exception{
File file=new File("E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
//这个文件不存在
OutputStream outputStream=new FileOutputStream(file);
//输入单个内容
outputStream.write(100);
//输入单个内容
outputStream.write((int)'a');
//输入数组
String str="Hello,My Name is TwoButterfly\r\n";
//一次次写入
byte[] bytes=str.getBytes("UTF-8");
//一个一个写入
for(byte b:bytes){
//byte 类型会自动转换成 int 类型
outputStream.write(b);
}
System.out.println("写入内容成功");
outputStream.close();
}
运行程序,查看文件内容
会将100 转换成字节, 转换后为 d. (97+3, 97为小写字母a)
会发现,当写入字符串时,需要一个一个的写,效率太低。 能不能直接写入字符串呢?
一.三.二 write(byte[] bytes) 字节数组形式写入
当写入字符串时,因为是字节输出,所以是不能直接写入字符串的, 但却可以直接写入字节数组。 就是将一个个字节先封装起来,封装成一个字节数组,然后以字节数组为单位,进行写入。 建议都采用写字节数组的形式。
@Test
public void writeContent2Test() throws Exception{
File file=new File("E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
//不是追加,是重写。
OutputStream outputStream=new FileOutputStream(file);
//输入字符串
String str="Hello,My Name is TwoButterfly";
//将字符串转换成字节数组
byte[] bytes=str.getBytes("UTF-8");
//写入字节数组, 也可以 后面添加 0, len 形式的。
outputStream.write(bytes);
System.out.println("写入文件成功");
outputStream.close();
}
运行程序,查看内容
发现,将以前的内容,先删除,再进行写入。 那么,能不能保留以前的内容呢?
一.三.三 追加内容,构造方法时令append 参数为true
在构造方法 FileOutputStream 时,令参数 append为true 即可。
@Test
public void writeContent3Test() throws Exception{
File file=new File("E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
//令参数为true,追加内容
OutputStream outputStream=new FileOutputStream(file,true);
//输入字符串, 追加内容
String str="Thank you";
//将字符串转换成字节数组
byte[] bytes=str.getBytes("UTF-8");
//写入字节数组
outputStream.write(bytes);
System.out.println("写入文件成功");
outputStream.close();
}
运行程序:
会发现,格式非常不好,都连在一起了, 能不能换行呢?
一.三.四 换行,写入 \r\n
换行,只需要写入换行符就可以了。 换行符是 \r\n
@Test
public void writeContent4Test() throws Exception{
File file=new File("E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
//令参数为true,追加内容
OutputStream outputStream=new FileOutputStream(file,true);
//输入字符串, 追加内容,并且换行
String str="\r\n I change Line"+"\r\n I change Line too";
//将字符串转换成字节数组
byte[] bytes=str.getBytes("UTF-8");
//写入字节数组
outputStream.write(bytes);
System.out.println("写入文件成功");
outputStream.close();
}
运行程序:
现在写入的都是英文字符,能不能写入我们伟大的中国汉字呢?
一.三.五 写入汉字
@Test
public void writeContent5Test() throws Exception{
File file=new File("E:"+File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
//这个文件不存在
OutputStream outputStream=new FileOutputStream(file,true);
//输入汉字
String str="\r\n你好,我是两个蝴蝶飞";
//写入数组
byte[] bytes=str.getBytes("UTF-8");
//换一种形式
outputStream.write(bytes,0,bytes.length);
outputStream.close();
}
运行程序:
正常写入汉字。 能够将内容写入到文件里面,那么就可以将文件内容读取出来。
二. InputStream
InputStream 是字节输入流, 用于读取文件中的内容。常常使用其子类 FileInputStream.
二.一 InputStream 接口方法
方法 |
作用 |
abstract int read() |
只读取一个内容, 并且返回该内容 |
int read(byte[] b) |
一次读取多个内容,并且将内容放置到 b 字节数组里面。返回读取的长度 |
int read(byte[] b, int off, int len) |
一次读取多个内容,读的内容是从 off 到 off+len, 然后将内容放置到b 字节数组里面 |
void close() |
关闭此输入流并释放与流相关联的任何系统资源。 |
二.二 FileInputStream
二.二.一 构造方法
二.二.一.一 方法
方法 |
作用 |
FileInputStream(File file) |
放置进去文件 |
FileInputStream(String name) |
放置进去文件的路径 |
可以传入文件,也可以传入文件的路径, 注意,文件必须要存在,否则会抛出 FileNotFoundException 异常。
老蝴蝶建议,传入文件的形式。
二.二.一.二 演示
@Test
public void conTest() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello.txt");
//传入文件
InputStream inputStream=new FileInputStream(file);
String path="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello.txt";
//传入文件的路径
InputStream inputStream1=new FileInputStream(path);
}
二.三 InputStream 读取文件
二.三.一 read() 一个一个读取
@Test
public void read1Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
//读一个,需要转换成 char 字符进行展示
System.out.println("第一个:"+(char)inputStream.read());
System.out.println("第二个:"+(char)inputStream.read());
System.out.println("第三个:"+(char)inputStream.read());
int r=-1;
byte[] b=new byte[(int)file.length()];
int i=0;
//需要一个个放置到字节数组里面
while((r=inputStream.read())!=-1){
b[i]=(byte)r;
i++;
}
System.out.println("读出文件内容:"+new String(b,0,i));
inputStream.close();
}
运行程序,控制台打印输出:
发现,中文目前可以正常的打印出来。
注意: read() 读取之后,无法再重新返回去读。
读内容是一个一个的读,那么能不能读完之后,封装到一个数组里面呢, 这样便于获取?
二.三.二 read(byte[] b ) 读取内容后封装到数组里面
@Test
public void read2Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
//根据文件大小,设置字节数组的大小
byte[] bytes=new byte[(int)file.length()];
//读取之后,封装到数组里面
inputStream.read(bytes);
System.out.println("读出内容:"+new String(bytes));
inputStream.close();
}
控制台打印输出:
中文也可以正常的显示出来,换行也能显示。
但发现有一个问题,创建字节数组时,用的是 new byte[(int)file.length()]; 也就是先统计一下文件的大小,然后根据文件大小,去构建数组,需要先查询一下。 这样效率会低一些。
二.三.三 read(byte[] b) 数组长度设置为 1024
@Test
public void read3Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
//设置长度 为1024
byte[] bytes=new byte[1024];
inputStream.read(bytes);
System.out.println("读出内容:"+new String(bytes));
inputStream.close();
}
运行程序:
会发现,后面有很多很多的空格, 浪费了 bytes 数组的空间。 可以利用 read(bytes) 方法的返回值进行控制。
二.三.四 read(byte[] b) 返回值
@Test
public void read4Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
//设置长度 为1024
byte[] bytes=new byte[1024];
//返回的内容,就是读取的长度
int len=inputStream.read(bytes);
System.out.println("读出内容:"+new String(bytes,0,len));
inputStream.close();
}
运行程序,控制台打印输出:
你以为这样就成功了吗? 不,还没有。 现在我们固定长度为 1024, 也就是可以读取 1KB的内容, 但是如果长度超过了 1KB, bytes字节数组是放不下的,会造成文件的内容读取不全的, 那该怎么办呢?
老蝴蝶可以演示一下,这种效果。 我们可以设置长度为 10, 这个长度肯定小于文件的大小
@Test
public void read5Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
//设置长度 为10
byte[] bytes=new byte[10];
//返回的内容,就是读取的长度
int len=inputStream.read(bytes);
System.out.println("读出内容:"+new String(bytes,0,len));
inputStream.close();
}
这个时候,运行程序:
会发现,文件内容没有读全,只读取了前10个字节。
要想全部读完,我们可以设置循环读取。
二.三.五 read(byte[]b ,int offset, int len) 循环读取内容
@Test
public void read6Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
StringBuilder sb=new StringBuilder();
byte[] bytes=new byte[10];
int len=-1;
while((len=inputStream.read(bytes))!=-1){
//依次读取的内容
String temp=new String(bytes,0,len);
sb.append(temp);
}
System.out.println("输出读取的内容:"+sb.toString());
inputStream.close();
}
控制台打印输出:
可以读取全部的内容信息,但是发现,产生了中文乱码的问题。
这是由于在读取中文时,准确地说在读取到 汉字’个’时, 正好将个这个词的字节给分开了, 一半在 bytes[9], 另一半在下一次bytes数组的 的bytes[0]位置。
如果将字节数组bytes的长度扩大,如扩大到 1024, 就不会产生乱码问题了。
二.四 字节读取时,中文乱码问题
在fileSrc目录下,新创建一个 rz.txt 文件, 填充内容为:
你好我是两个蝴蝶飞你好我是两个蝴蝶飞
我们读取这个文件,来演示中文乱码问题。
二.四.一 字节数组长度为奇数时
字节数组为奇数时,很容易造成中文乱码。
@Test
public void read7Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"rz.txt");
InputStream inputStream=new FileInputStream(file);
StringBuilder sb=new StringBuilder();
byte[] bytes=new byte[5];
int len=-1;
while((len=inputStream.read(bytes))!=-1){
//依次读取的内容
String temp=new String(bytes,0,len);
sb.append(temp);
}
System.out.println("输出读取的内容:"+sb.toString());
inputStream.close();
}
运行程序:
有很大概率,会将中文进行拆分。
这个例子时,将 byte[5] 转换成 byte[6],换成偶数时:
这个时候,就可能不乱码了。
二.四.二 字节数组长度不被3整除时
上面的字节长度为6时,不乱码,为10时就乱码了。
为12时,就不乱码了,为 14时乱码了,为18时不乱码了。
老蝴蝶建议,接收的字节数组的长度最好是能被3整除的偶数。 读取时,为了方便,常常是1024的整数被。
所以,综合起来就是: 能同时被1024和3整除的偶数。
为了方便,老蝴蝶还是以 1024为主。
二.五 读取文件内容综合
@Test
public void read8Test() throws Exception{
File file=new File("E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"Hello3.txt");
InputStream inputStream=new FileInputStream(file);
//长度设置成1024
byte[] bytes=new byte[1024];
int len=-1;
while((len=inputStream.read(bytes))!=-1){
//依次读取的内容
System.out.println("读取的内容为:"+new String(bytes,0,len));
}
inputStream.close();
}
三. OutputStream和InputStream的应用: 文件复制
文件复制,实际上就是将 InputStream和 OutputStream 结合起来使用, 两者共同使用同一个 字节数组, InputStream将读出的内容批量写入到字节数组里面, OutputStream将字节数组批量写入到文件里面。
三.一 文件复制方法
/**
* 复制字节文件
* @param src 源文件
* @param desc 目标文件
* @return
*/
public static boolean copyBin(String src,String desc) throws Exception{
//定义两个文件
File srcFile=new File(src);
File descFile=new File(desc);
if(!srcFile.exists()||!srcFile.isFile()){
throw new RuntimeException("源文件不存在或者源文件是目录");
}
//定义InputStream 和 OutputStream
InputStream inputStream=new FileInputStream(srcFile);
OutputStream outputStream=new FileOutputStream(descFile);
//1M 1M的读取
byte[] bytes=new byte[1024];
int len=-1;
//将内容写入到 bytes字节数组里面
while((len=inputStream.read(bytes))!=-1){
//将bytes字节数组写入到文件里面
outputStream.write(bytes,0,len);
}
outputStream.flush();
//关闭流
outputStream.close();
inputStream.close();
return true;
}
三.二 测试文件复制
复制文件和复制图片类型都可以。
@Test
public void copyTest(){
//复制普通文件
/* String src="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"rz.txt";
String desc="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"rzcopy.txt";*/
//复制图片
String src="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"129.png";
String desc="E:"+ File.separator+"ideaWork"+File.separator+"Java2"+File.separator+"fileSrc"
+File.separator+"129copy.png";
try {
CopyUtils.copyBin(src,desc);
System.out.println("文件复制成功");
} catch (Exception e) {
e.printStackTrace();
}
}
运行程序,查看文件系统