Day20 --IO流对象 字节流
来源:互联网 发布:陕西广电网络是国企嘛 编辑:程序博客网 时间:2024/05/16 14:57
a.
IO流
概述
* IO流用于处理设备之间的数据传输 --局域网、互联网之间的数据传输底层都是用的IO流
* Java中对于数据的操作,就是通过流的方式
* Java操作的流对象都是在IO包中
* 流按流向分为:输入流 输出流
* 流按操作分为:
* 字节流:
* 字节流可以操作任何数据,因为计算机中的数据都是以字节的形式存储的。
* 因为不同国家语言不同,就存在不同的码表,码表不同读取字节数据就必须转换成字符来读写,这样方便阅读。
* 比如不同国家照片的,在不同国家的显示是不同的,虽然照片相同,但照片的字是不同,这个时候就需要有字节来操作,将字节转字符。
* 字符流:
* 字符流只能操作纯字符数据,比较方便
IO流中常用父类
字节流抽象父类
* InputStream --字节输入流的顶层抽象父类
* 读(从硬盘中读取数据:内存 - 硬盘)创建FileInputStream,如果没有就创建该文件后,再做其他操作
* OutputStream --字节输出流的顶层抽象父类
* 写(往硬盘中写入数据:硬盘 - 内存) 创建FileOutputStream。如果没有该文件,会先帮我们创建,前提可以没有该文件
字符流抽象父类
* Reader 字符输入流 读
* Writer 字符输出流 写
IO流的书写过程
* 使用前,导入IO包中的类
* 使用时,对IO流进行异常处理
* 使用后,释放IO流资源
IO流就相当于一个管道,操作的是 硬盘 和 内存 之间的数据。
从硬盘中读数据 到 内存中。 InputStream
从内存中写数据 到 硬盘中。OutputStream
可以这样理解写的数据: 在写数据的时候,没有保存之前在内存中,当一旦保存,该数据就到硬盘中了。
b.
FileInputStream --字节输入流 过程:硬盘到内存 显示给我们看(控制台),虚拟显示。
概述
* 从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。
方法
* read(); 读数据(从 硬盘 中读一个字节数据到 内存 上) --从硬盘文件中读取一个字节数据
* 每read()一次,指针就往后走一位,当读到末尾数据(不存在的数据)就返回-1,所以可以将-1 作为文件内容的结束标记,同时可以作为循环判断的一个子条件。
* 当读到-1,就表示文本内容数据已经结束。
* 读的时候,该文件必须存在,如果不存在就报异常。
FileNotFoundException 表示文件找不到异常
FileNotFoundException 是 IOException 的一个子类,所以自己抛IOException即可
代码
String url = "数据.txt";
FileInputStream fis = null;
try {
File file = new File(url); // 创建File对象,用于获取文件路径
fis = new FileInputStream(file); //创建输入流对象,如果该文件不存在,就报异常
int read = fis.read(); // 从硬盘文件中读取字节数据
System.out.println(read); //97 此时只能读出一个字节数据
/**
* 为什么文本中是a,而输出的是97呢?
* 首先,我们用的是FileInputStream 字节输入流来操作
* 其二,因为计算机中存储的数据都是以0101这种二进制的形式进行存储的
* 97在ascii码表中代表a, 97就是a的二进制表现形式。更确切的是是GBK码表(win系统用的就是GBK码表)。因为ascii码表攘扩到每一种码表中(因为每个码表都需要数字和英文字母)
* 而我们在文本中的写的a 就是计算机通过码表翻译成自己能看得懂的97的二进制表现形式。(a在计算机中代表的就是97的二进制表现形式)
* */
// 如果想将文件中的数据全都读出来,就用while循环来做
int r ; //定义临时变量r,让其记录每次读到的文本数据
while((r = fis.read()) !=-1){ //当读到-1,就表示文本内容已经结束
// r = fis.read() 将每次读到的文本数据赋值给r
System.out.println(r); //97 98 99
}
} catch (Exception e) {
e.printStackTrace();
}finally {
fis.close(); //关闭IO流资源
}
分析:为什么read()的返回值类型是int,而不是byte?
我的说法
注意:int(占4个字节) byte(占1个字节) int转byte会损失精度。
* 字节输入流可以读取任意文件(如:音频,图片,文本等),这些文件底层都是以二进制的形式进行存储的。
* 当使用字节流的read()方法时,读到文件末尾都会有一个结束标记-1,遇到-1系统就以为该文件已经读取完毕。 如:一个图片文件占4个字节,byte的表现形式就是: 00000111 00110000 11111111 00100011,而11111111刚好是byte-1的表现形式。如果采用byte类型来接收,当读到11111111的时候,系统就以为读取到该文件的末尾处,那么该数据就会读取不完整。为了避免情况,系统采用int来作为接收类型。如果读到该图片文件的其中一个字节正好是11111111,就会在前面补上24个0, 24个0等等于3个8个比特位。(24+8[读取到的当前一个字节]=32)/4=8。而11111111刚好是正的255,而255!=-1,就会继续往后读取该文件的其他字节数。而结束标记-1就是一个int类型。
问?当补上24个0的时候,该文件数据就改变了,那该怎么办?
答:字符流还有一个write()写的方法,在写出该数据时,会将其24个0给砍掉,来保证原有数据的原样性。
所以设计用int来接收,而结束标记-1就是一个int类型,无需补上24个0了。
注意:int类型占4个字节,每个字节都会在前面补上24个0, 24个0等等于3个8个比特位。(24+8[读取到的当前一个字节]=32)/4=8,即int占4个字节。
老师说法
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111
那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上
24个0[每8个比特位是一个字节]凑足4个字节,那么byte类型的-1就变成int类型的255。这样可以保证整个数据读完,而结束标记的-1就是int类型
c.
FileOutputStream --字节输出流 过程:内存到硬盘 存储到硬盘中,实际存在了。
概述
* 文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
* 将数据写入到硬盘中(内存 - 硬盘)。
方法
* write(int b); 写数据(将数据从 内存 写到 硬盘中)。
* 写的时候,可以没有该文件,会在写的过程中给自动创建。
* 前提:写到磁盘中的路径必须存在。
代码
FileOutputStream fos = null;
try {
String pathName = "write.txt";
File file = new File(pathName);
fos = new FileOutputStream(file); //创建字节输出流对象,如果没有该文件,系统会自动创建
fos.write(97); // 往硬盘中写入数据 a 虽然写出的是一个int数,但实际写到文件上的是一个字节,会自动去除前3个8位,也就是一个byte。
fos.write(98); // 往硬盘中写入数据 b [写入数据后,刷新该项目(其实刷新的是该文本write.txt)]
} catch (Exception e) {
e.printStackTrace();
}
fos.close(); //关闭资源
// 因为操作的该系统的GBK码表,所以写的时候会翻译成对应GBK中的ascii码表中对应的数据。
FileOutputStream的追加?
概述
* 就是在原有数据的文本上再添加数据。
实现
* 操作的是 构造方法
* FileOutputStream fos = new FileOutputStream(路径,true);
* 如果想续写文件数据,在构造方法中添加第二个参数,true。
* 如果没有参数true,就是将原有文件数据清空后,再重写写入数据。
代码
String name ="write.txt";
File file = new File(name);
// FileOutputStream fos = new FileOutputStream(file); 如果没有参数true,就是将原有文件数据清空,再重写写入数据。
FileOutputStream fos = new FileOutputStream(file, true); //构造参数添加第二个参数true后,才能在文本中追加数据。
fos.write("11".getBytes()); //如果想添加 非数字的数据,就要用getBaytes()将其转成字节。
fos.close();
d.
拷贝图片
概述
* 分析:
* 拷贝图片,是将原有图片重新生成一份。
代码
拷贝图片
String pathName = "美女.jpeg";
File file = new File(pathName);
FileInputStream fis = new FileInputStream(file); //创建字节输出流对象
FileOutputStream fos = new FileOutputStream("copy美女.jpeg"); //创建字节输出流对象
int r;
while((r = fis.read()) != -1){ //首先读取原图片文件数据
//然后写出原图片数据到copy美女.jpeg中
fos.write(r);
}
// 关流操作
fis.close();
fos.close();
--但是不推荐这样做,因为如果操作音频等大一些的文件的时候,效率太慢,所以使用数组的方式读取并写入。
拷贝音频
Date d = new Date();
long start = d.getTime();
String pathname ="music.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("copyMusic.mp3");
int r ;
while((r = fis.read()) != -1){
fos.write(r);
}
long end = System.currentTimeMillis();
long time = end - start;
long resultTime = time / 60 / 60;
System.out.println("花费时间:"+resultTime+" 秒"); //19秒
字节数组拷贝之available()方法 --available:adj. 可获得的;可购得的;可找到的;有空的
概述
* available 表示一次性读取该文件的所有大小
方法
* available(); 获取读的文件的所有字节个数
* int read(byte[] b); 一次读取一个字节数组
* wirte(byte[] b); 一次写入一个字符数组
存在弊端:有可能内存溢出,如果读取的文件太大,那么可能导致内存异常。
代码
long start = new Date().getTime();
String pathname = "张静波 - 这支烟灭了以后.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("copy张静波 - 这支烟灭了以后.mp3");
// 定义数组来设置每次读取的大小
byte[] arr = new byte[fis.available()]; //该方法是获取该文件的数据大小
System.out.println("该文件大小:"+fis.available());
fis.read(arr); //一次性全部读取。 --将文件上的字节读取到内存中
fos.write(arr); //一次性全部写入到 copy张静波 - 这支烟灭了以后.mp3 中。 --将字符数组中的数据全部写入到copy文件中
long end = System.currentTimeMillis();
long time = (end - start) / 60 / 60;
fis.close();
fos.close();
System.out.println("使用available方法读取后,花费的时间是:"+time+" 秒"); //0秒
// 原来一个一个字节的读取,花费7秒
// 使用available后读取,花费0秒,速度超快,但是这样做有弊端,如果遇到很大的文件,可能导致内存溢出异常
// 所以采用自定义的数组来记录每次读取的大小。
c.
定义小数组
概述
* 因为 一个个读取字节个数 和 一次性读取字节个数都存在弊端,所以建议自己 使用定义小数组的方式 来自定义的每次读取和写入的字节个数。
方法
* 系统考虑到这一点,就为我们提供了这样的方法供我们使用。
* write(byte[] b);
* wirte(byte[] b, int off, int len); 写出有效的字节个数
* 参数说明
* byte[] b; 自定义的字节数组大小
* int off; 表示数组中当前数据的索引,一般定义为0
* 0 表示当前字节数组每个数据的索引(即根据索引拿数据就不会出错)
* int len; 每一次读取到的有效字节个数,当没有数据就返回-1
定义小数组的标准格式
* 为什么字节数组定义1024的整数倍?
* 即: byte[] b = new byte[1024*8];
* 因为计算机中的机制就是1024。即2的10次方。
* 注意:如果read()中没有传入参数,那么返回的不是读取的字节个数,而是字节的码表值
代码
long start = new Date().getTime(); //记录当前时间
String pathname ="Dima Bilan.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file); // 创建字节输入流
FileOutputStream fos = new FileOutputStream("copyDima Bilan.mp3"); // 创建字节输出流
byte[] b = new byte[1024 * 8]; // 定义小数组的大小 建议是1024的整数倍
int len; //记录每次读取字节数组的数据大小
while(( len = fis.read(b)) !=-1){
fos.write(b, 0, len); //0表示当前字节数组每个数据的索引(即根据索引拿数据就不会出错)
}
long end = System.currentTimeMillis(); //记录拷贝完后的时间
long resultTime = (end - start) / 60 /60; //将两个时间相减,拿到拷贝音乐一共用了多久
System.out.println("拷贝一首歌曲花费:"+ resultTime +"秒"); //:0秒
fis.close(); //关闭IO流资源
fos.close(); //关闭IO流资源
d.
BufferedInputStream 和 BufferedOutputStream 拷贝文件(缓冲区的方式copy)
概述
* 字节流一次读写一个数组明显要比一次读写一个字节的速度要快,
* 这是加入数组这样的缓冲区效果,Java本身在设计的时候,也考虑到这样的设计思想(装饰设计模式),所以提供了字节缓冲区流。
* BufferedInputStream 字节输入缓冲区流 --对FileInputStream进行包装
* BufferedInputStream内置了一个缓冲区(数组)
* 从BufferedInputStrem中读取一个字节时,BufferedInputStream会一次性从文件中读取8192个,存入到缓冲区中,返回给程序一个字节
* 程序再次读取时,就不用从文件中拿去,而是直接从缓冲区中获取
* 直到缓冲区中的8192个被全部使用过以后,才会重新从文件中读取8192个
* BufferedOutputStream 字节输出缓冲区流 ----对FileOutputStream进行包装
* BufferedOutputStream 同样内置了一个缓冲区(数组)
* 程序向流中写出字节时,不会直接写到文件中,而是先写到缓冲区中,
* 直到缓冲区写满8192个,BufferedOuputStream才会把缓冲区中的数据一次性写到文件里
* 过程
* 读的时候一次读取8192个字节给BufferedInputStream,但int r 是一个一个从bufferedInputStream拿每一个字节, r再一个一个的写入到BuffereOutputStream中,直到写出了8192个后,再给copy
* 源文件 FileInputStream - BufferedInputStream - int r - BuffereOutputStream - FileOutputStream - copy文件
注意:
缓冲区就是操作内存, 内存之间传输数据要比硬盘的速度要高,所以降低到硬盘的读写次数就会提高效率。
* 使用缓冲区
源文件 FileInputStream - BufferedInputStream - int r - BuffereOutputStream - FileOutputStream - copy文件 --其实操作的就是 内存与内存之间的读写数据(减少对硬盘的访问读写次数,从而提高效率。但是这是操作的是两个数组)
* 定义小数组
源文件 - int r(一次性记录自定义的数组大小) - FileOutputStream - copy文件 --其实操作的就是 硬盘到内存与内存硬盘之间的读写数据 (并没有减少对硬盘的读写次数,但是也是使用到了缓冲区,但是使用的缓冲区的次数并没有那么多。这是操作的是一个数组)
代码
String pathname ="是阿涵阿 - 不想.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file); // 创建字节输入流
BufferedInputStream bif = new BufferedInputStream(fis); // 创建缓冲流对象,对字节输入流进行保证,让其变得更加强大。
FileOutputStream fos = new FileOutputStream("copy是阿涵阿 - 不想.mp3"); // 创建字节输出流
BufferedOutputStream bos = new BufferedOutputStream(fos); // 创建缓冲流对象,对字节输出流进行保证,让其变得更加强大。
int r ;
while((r = bif.read()) != -1){
bos.write(r);
}
// 关流 只关Buffere类的流对象,因为以后的书写形式直接Buffere传入匿名内部类的方式,所以只关Buffere的
bif.close();
bos.close();
自定义小数组和带Buffere的读写速度哪个更快。
* 如果定义小数组8192个字节大小和Buffere自带的8192个字节相比较的话
* 定义小数组的能比Buffere的快那么一点点,因为读写操作的都是同一个数组
* (FileInputStream 和 FileOutputStream)
* 而Buffered操作的是两个数组
* (FileInputStream 和 BufferedInputStream, FileOutputStream 和 BufferedOuputStream)
e.
flush 和 close 方法区别?
* flush(); 是用来刷新缓冲区的,刷新完后还可以继续使用流对象。
* 好处:做QQ聊天记录,使用缓冲区的输出流)关闭聊天窗口后,下次打开还能看到原先聊天记录,而不是将QQ下线后,再能看原先的聊天记录。(即使用流对象)
* close(); 是关闭释放资源的,但是在关闭资源之前会刷新一次缓冲区,将缓冲区中的数据全部输出到文件中,再关闭。刷新完后就不能再使用流对象了。
* 使用close()确保copy数据的完整性。
// 不关流,可能导致copy后数据的不完整性
// 源文件数据大小: 10,881,245 bytes
// copy后的数据大小: 10,878,976 bytes
不使用close(),有可能导致copy后的数据时不完整性,因为自定义的小数组中还剩余一部分字节,而刷新的只是自定义数组缓冲区的整数倍数据,可能源文件数据大小不是缓冲区的整数倍大小,所以导致copy后的数据是不完整的。
f.
字节流读取 和 写出中文
字节流读取中文的问题 --读取中文会乱码,只能用字符流来解决。
* 字节流在读写中文的时候,可能有时候会读到半个中文,而导致文件乱码。所以读中文只能用字符流来读取。
字节流写出中文的问题 --写中文可以用getBytes()将其转成字节数组来解决。
* 因为字节流字节操作的是字节,所以在写出中文的时候要将其转换为字节数组,使用getBytes()方法。
* 如果想写出回车换行,就用write("/r/n".getBytes());
g.
流的标准处理异常代码 --分1.6及1.6之前 和 1.7版本 【1.6面试必问】
1.6及1.6之前处理异常代码 --try{}...finally 采用finally嵌套的方式
代码
public static void demo01_jdk6即6之前_流异常代码的处理() throws IOException{
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("aaa.txt");
fos = new FileOutputStream("copyaaa.txt");
int r;
while( (r = fis.read()) != -1){
fos.write(r);
}
}finally{
try {
if(fis != null) //如果fis关闭不了,就关闭fos
fis.close();
} finally { // try..catch 嵌套的目的是能关一个就尽量关一个
if(fos != null)
fos.close();
}
}
1.7处理异常代码 --try(){}...close(此处的close并没有写,会自动关闭资源。因为实现了InputStream 间接的实现了 AutoCloseable接口)
代码
public static void demo02_jdk7_流异常代码的处理() throws IOException{
try(
FileInputStream fis = new FileInputStream("aaa.txt");
FileOutputStream fos = new FileOutputStream("copyaaa2.txt");
MyClose c = new MyClose();
){
int r;
while( ( r = fis.read()) != -1){
fos.write(r);
}
}
}
注意:
原理
* 在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用,流对象的close方法将流关掉
*【如果一个自定义类想实现自动关闭功能,必须实现 AutoCloseable 接口。】
f.
图片加密 (简单方法) --改变的是每个字节的大小,从而完成对该文件的加密 和 解密 过程。
概述
* 图片加密不是对源文件进行操作,而是根据源文件生成一个加密后的文件,解密也是拿加密后的文件进行解密,从而生成一个新的解密的文件。
加密
* 将写出的每个字节都 异或 一个数,就是对源文件进行加密。
* 将加密后的文件也 异或 加密前的那个数,就是对copy后的文件进行解密(会重新生成一个解密后的文件)
注意:一个数被 异或 两次,还是该原数本身(原来的数),根据这个思想对图片文件进行加密和解密操作。
加密和解密过程:
* 加密的时候 对源文件数据进行了 一次异或 这就是加密。
* 操作的是 源文件数据 和 copy_原图片文件_(加密)
* 解密的时候 对加密的文件数据进行又 一次异或 相当于 对源文件数据 异或两次 还是源文件数据。
* 操作的是 copy_原图片文件_(加密) 和 copy_原图片文件_(加密)_[解密过程].jpg
g.
拷贝文件
案例:在控制台录入文件的路径,将文件拷贝到当前项目路径下
代码:Day20_IO流对象 字节流\src\com\day20\b\test\io\Test02_控制台上录入路径_将文件copy到当前项目路径下.java
案例:将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到quit时就退出
代码
FileOutputStream fos = new FileOutputStream("键盘录入数据copy到该文件中.txt");
Scanner sc = new Scanner(System.in);
System.out.println("请录入:");
while(true){
String str = sc.nextLine();
if(str.equals("quit")){
System.out.println("遇到quit,输出结束");
break; // break结束该循环
}
fos.write(str.getBytes()); // 字符串写出 必须转换成字节数组
fos.write("\r\n".getBytes()); //换行输出
}
fos.close();
IO流
概述
* IO流用于处理设备之间的数据传输 --局域网、互联网之间的数据传输底层都是用的IO流
* Java中对于数据的操作,就是通过流的方式
* Java操作的流对象都是在IO包中
* 流按流向分为:输入流 输出流
* 流按操作分为:
* 字节流:
* 字节流可以操作任何数据,因为计算机中的数据都是以字节的形式存储的。
* 因为不同国家语言不同,就存在不同的码表,码表不同读取字节数据就必须转换成字符来读写,这样方便阅读。
* 比如不同国家照片的,在不同国家的显示是不同的,虽然照片相同,但照片的字是不同,这个时候就需要有字节来操作,将字节转字符。
* 字符流:
* 字符流只能操作纯字符数据,比较方便
IO流中常用父类
字节流抽象父类
* InputStream --字节输入流的顶层抽象父类
* 读(从硬盘中读取数据:内存 - 硬盘)创建FileInputStream,如果没有就创建该文件后,再做其他操作
* OutputStream --字节输出流的顶层抽象父类
* 写(往硬盘中写入数据:硬盘 - 内存) 创建FileOutputStream。如果没有该文件,会先帮我们创建,前提可以没有该文件
字符流抽象父类
* Reader 字符输入流 读
* Writer 字符输出流 写
IO流的书写过程
* 使用前,导入IO包中的类
* 使用时,对IO流进行异常处理
* 使用后,释放IO流资源
IO流就相当于一个管道,操作的是 硬盘 和 内存 之间的数据。
从硬盘中读数据 到 内存中。 InputStream
从内存中写数据 到 硬盘中。OutputStream
可以这样理解写的数据: 在写数据的时候,没有保存之前在内存中,当一旦保存,该数据就到硬盘中了。
b.
FileInputStream --字节输入流 过程:硬盘到内存 显示给我们看(控制台),虚拟显示。
概述
* 从文件系统中的某个文件中获得输入字节。哪些文件可用取决于主机环境。
方法
* read(); 读数据(从 硬盘 中读一个字节数据到 内存 上) --从硬盘文件中读取一个字节数据
* 每read()一次,指针就往后走一位,当读到末尾数据(不存在的数据)就返回-1,所以可以将-1 作为文件内容的结束标记,同时可以作为循环判断的一个子条件。
* 当读到-1,就表示文本内容数据已经结束。
* 读的时候,该文件必须存在,如果不存在就报异常。
FileNotFoundException 表示文件找不到异常
FileNotFoundException 是 IOException 的一个子类,所以自己抛IOException即可
代码
String url = "数据.txt";
FileInputStream fis = null;
try {
File file = new File(url); // 创建File对象,用于获取文件路径
fis = new FileInputStream(file); //创建输入流对象,如果该文件不存在,就报异常
int read = fis.read(); // 从硬盘文件中读取字节数据
System.out.println(read); //97 此时只能读出一个字节数据
/**
* 为什么文本中是a,而输出的是97呢?
* 首先,我们用的是FileInputStream 字节输入流来操作
* 其二,因为计算机中存储的数据都是以0101这种二进制的形式进行存储的
* 97在ascii码表中代表a, 97就是a的二进制表现形式。更确切的是是GBK码表(win系统用的就是GBK码表)。因为ascii码表攘扩到每一种码表中(因为每个码表都需要数字和英文字母)
* 而我们在文本中的写的a 就是计算机通过码表翻译成自己能看得懂的97的二进制表现形式。(a在计算机中代表的就是97的二进制表现形式)
* */
// 如果想将文件中的数据全都读出来,就用while循环来做
int r ; //定义临时变量r,让其记录每次读到的文本数据
while((r = fis.read()) !=-1){ //当读到-1,就表示文本内容已经结束
// r = fis.read() 将每次读到的文本数据赋值给r
System.out.println(r); //97 98 99
}
} catch (Exception e) {
e.printStackTrace();
}finally {
fis.close(); //关闭IO流资源
}
分析:为什么read()的返回值类型是int,而不是byte?
我的说法
注意:int(占4个字节) byte(占1个字节) int转byte会损失精度。
* 字节输入流可以读取任意文件(如:音频,图片,文本等),这些文件底层都是以二进制的形式进行存储的。
* 当使用字节流的read()方法时,读到文件末尾都会有一个结束标记-1,遇到-1系统就以为该文件已经读取完毕。 如:一个图片文件占4个字节,byte的表现形式就是: 00000111 00110000 11111111 00100011,而11111111刚好是byte-1的表现形式。如果采用byte类型来接收,当读到11111111的时候,系统就以为读取到该文件的末尾处,那么该数据就会读取不完整。为了避免情况,系统采用int来作为接收类型。如果读到该图片文件的其中一个字节正好是11111111,就会在前面补上24个0, 24个0等等于3个8个比特位。(24+8[读取到的当前一个字节]=32)/4=8。而11111111刚好是正的255,而255!=-1,就会继续往后读取该文件的其他字节数。而结束标记-1就是一个int类型。
问?当补上24个0的时候,该文件数据就改变了,那该怎么办?
答:字符流还有一个write()写的方法,在写出该数据时,会将其24个0给砍掉,来保证原有数据的原样性。
所以设计用int来接收,而结束标记-1就是一个int类型,无需补上24个0了。
注意:int类型占4个字节,每个字节都会在前面补上24个0, 24个0等等于3个8个比特位。(24+8[读取到的当前一个字节]=32)/4=8,即int占4个字节。
老师说法
因为字节输入流可以操作任意类型的文件,比如图片音频等,这些文件底层都是以二进制形式的存储的,如果每次读取都返回byte,有可能在读到中间的时候遇到111111111
那么这11111111是byte类型的-1,我们的程序是遇到-1就会停止不读了,后面的数据就读不到了,所以在读取的时候用int类型接收,如果11111111会在其前面补上
24个0[每8个比特位是一个字节]凑足4个字节,那么byte类型的-1就变成int类型的255。这样可以保证整个数据读完,而结束标记的-1就是int类型
c.
FileOutputStream --字节输出流 过程:内存到硬盘 存储到硬盘中,实际存在了。
概述
* 文件输出流是用于将数据写入 File 或 FileDescriptor 的输出流。
* 将数据写入到硬盘中(内存 - 硬盘)。
方法
* write(int b); 写数据(将数据从 内存 写到 硬盘中)。
* 写的时候,可以没有该文件,会在写的过程中给自动创建。
* 前提:写到磁盘中的路径必须存在。
代码
FileOutputStream fos = null;
try {
String pathName = "write.txt";
File file = new File(pathName);
fos = new FileOutputStream(file); //创建字节输出流对象,如果没有该文件,系统会自动创建
fos.write(97); // 往硬盘中写入数据 a 虽然写出的是一个int数,但实际写到文件上的是一个字节,会自动去除前3个8位,也就是一个byte。
fos.write(98); // 往硬盘中写入数据 b [写入数据后,刷新该项目(其实刷新的是该文本write.txt)]
} catch (Exception e) {
e.printStackTrace();
}
fos.close(); //关闭资源
// 因为操作的该系统的GBK码表,所以写的时候会翻译成对应GBK中的ascii码表中对应的数据。
FileOutputStream的追加?
概述
* 就是在原有数据的文本上再添加数据。
实现
* 操作的是 构造方法
* FileOutputStream fos = new FileOutputStream(路径,true);
* 如果想续写文件数据,在构造方法中添加第二个参数,true。
* 如果没有参数true,就是将原有文件数据清空后,再重写写入数据。
代码
String name ="write.txt";
File file = new File(name);
// FileOutputStream fos = new FileOutputStream(file); 如果没有参数true,就是将原有文件数据清空,再重写写入数据。
FileOutputStream fos = new FileOutputStream(file, true); //构造参数添加第二个参数true后,才能在文本中追加数据。
fos.write("11".getBytes()); //如果想添加 非数字的数据,就要用getBaytes()将其转成字节。
fos.close();
d.
拷贝图片
概述
* 分析:
* 拷贝图片,是将原有图片重新生成一份。
代码
拷贝图片
String pathName = "美女.jpeg";
File file = new File(pathName);
FileInputStream fis = new FileInputStream(file); //创建字节输出流对象
FileOutputStream fos = new FileOutputStream("copy美女.jpeg"); //创建字节输出流对象
int r;
while((r = fis.read()) != -1){ //首先读取原图片文件数据
//然后写出原图片数据到copy美女.jpeg中
fos.write(r);
}
// 关流操作
fis.close();
fos.close();
--但是不推荐这样做,因为如果操作音频等大一些的文件的时候,效率太慢,所以使用数组的方式读取并写入。
拷贝音频
Date d = new Date();
long start = d.getTime();
String pathname ="music.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("copyMusic.mp3");
int r ;
while((r = fis.read()) != -1){
fos.write(r);
}
long end = System.currentTimeMillis();
long time = end - start;
long resultTime = time / 60 / 60;
System.out.println("花费时间:"+resultTime+" 秒"); //19秒
字节数组拷贝之available()方法 --available:adj. 可获得的;可购得的;可找到的;有空的
概述
* available 表示一次性读取该文件的所有大小
方法
* available(); 获取读的文件的所有字节个数
* int read(byte[] b); 一次读取一个字节数组
* wirte(byte[] b); 一次写入一个字符数组
存在弊端:有可能内存溢出,如果读取的文件太大,那么可能导致内存异常。
代码
long start = new Date().getTime();
String pathname = "张静波 - 这支烟灭了以后.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream("copy张静波 - 这支烟灭了以后.mp3");
// 定义数组来设置每次读取的大小
byte[] arr = new byte[fis.available()]; //该方法是获取该文件的数据大小
System.out.println("该文件大小:"+fis.available());
fis.read(arr); //一次性全部读取。 --将文件上的字节读取到内存中
fos.write(arr); //一次性全部写入到 copy张静波 - 这支烟灭了以后.mp3 中。 --将字符数组中的数据全部写入到copy文件中
long end = System.currentTimeMillis();
long time = (end - start) / 60 / 60;
fis.close();
fos.close();
System.out.println("使用available方法读取后,花费的时间是:"+time+" 秒"); //0秒
// 原来一个一个字节的读取,花费7秒
// 使用available后读取,花费0秒,速度超快,但是这样做有弊端,如果遇到很大的文件,可能导致内存溢出异常
// 所以采用自定义的数组来记录每次读取的大小。
c.
定义小数组
概述
* 因为 一个个读取字节个数 和 一次性读取字节个数都存在弊端,所以建议自己 使用定义小数组的方式 来自定义的每次读取和写入的字节个数。
方法
* 系统考虑到这一点,就为我们提供了这样的方法供我们使用。
* write(byte[] b);
* wirte(byte[] b, int off, int len); 写出有效的字节个数
* 参数说明
* byte[] b; 自定义的字节数组大小
* int off; 表示数组中当前数据的索引,一般定义为0
* 0 表示当前字节数组每个数据的索引(即根据索引拿数据就不会出错)
* int len; 每一次读取到的有效字节个数,当没有数据就返回-1
定义小数组的标准格式
* 为什么字节数组定义1024的整数倍?
* 即: byte[] b = new byte[1024*8];
* 因为计算机中的机制就是1024。即2的10次方。
* 注意:如果read()中没有传入参数,那么返回的不是读取的字节个数,而是字节的码表值
代码
long start = new Date().getTime(); //记录当前时间
String pathname ="Dima Bilan.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file); // 创建字节输入流
FileOutputStream fos = new FileOutputStream("copyDima Bilan.mp3"); // 创建字节输出流
byte[] b = new byte[1024 * 8]; // 定义小数组的大小 建议是1024的整数倍
int len; //记录每次读取字节数组的数据大小
while(( len = fis.read(b)) !=-1){
fos.write(b, 0, len); //0表示当前字节数组每个数据的索引(即根据索引拿数据就不会出错)
}
long end = System.currentTimeMillis(); //记录拷贝完后的时间
long resultTime = (end - start) / 60 /60; //将两个时间相减,拿到拷贝音乐一共用了多久
System.out.println("拷贝一首歌曲花费:"+ resultTime +"秒"); //:0秒
fis.close(); //关闭IO流资源
fos.close(); //关闭IO流资源
d.
BufferedInputStream 和 BufferedOutputStream 拷贝文件(缓冲区的方式copy)
概述
* 字节流一次读写一个数组明显要比一次读写一个字节的速度要快,
* 这是加入数组这样的缓冲区效果,Java本身在设计的时候,也考虑到这样的设计思想(装饰设计模式),所以提供了字节缓冲区流。
* BufferedInputStream 字节输入缓冲区流 --对FileInputStream进行包装
* BufferedInputStream内置了一个缓冲区(数组)
* 从BufferedInputStrem中读取一个字节时,BufferedInputStream会一次性从文件中读取8192个,存入到缓冲区中,返回给程序一个字节
* 程序再次读取时,就不用从文件中拿去,而是直接从缓冲区中获取
* 直到缓冲区中的8192个被全部使用过以后,才会重新从文件中读取8192个
* BufferedOutputStream 字节输出缓冲区流 ----对FileOutputStream进行包装
* BufferedOutputStream 同样内置了一个缓冲区(数组)
* 程序向流中写出字节时,不会直接写到文件中,而是先写到缓冲区中,
* 直到缓冲区写满8192个,BufferedOuputStream才会把缓冲区中的数据一次性写到文件里
* 过程
* 读的时候一次读取8192个字节给BufferedInputStream,但int r 是一个一个从bufferedInputStream拿每一个字节, r再一个一个的写入到BuffereOutputStream中,直到写出了8192个后,再给copy
* 源文件 FileInputStream - BufferedInputStream - int r - BuffereOutputStream - FileOutputStream - copy文件
注意:
缓冲区就是操作内存, 内存之间传输数据要比硬盘的速度要高,所以降低到硬盘的读写次数就会提高效率。
* 使用缓冲区
源文件 FileInputStream - BufferedInputStream - int r - BuffereOutputStream - FileOutputStream - copy文件 --其实操作的就是 内存与内存之间的读写数据(减少对硬盘的访问读写次数,从而提高效率。但是这是操作的是两个数组)
* 定义小数组
源文件 - int r(一次性记录自定义的数组大小) - FileOutputStream - copy文件 --其实操作的就是 硬盘到内存与内存硬盘之间的读写数据 (并没有减少对硬盘的读写次数,但是也是使用到了缓冲区,但是使用的缓冲区的次数并没有那么多。这是操作的是一个数组)
代码
String pathname ="是阿涵阿 - 不想.mp3";
File file = new File(pathname);
FileInputStream fis = new FileInputStream(file); // 创建字节输入流
BufferedInputStream bif = new BufferedInputStream(fis); // 创建缓冲流对象,对字节输入流进行保证,让其变得更加强大。
FileOutputStream fos = new FileOutputStream("copy是阿涵阿 - 不想.mp3"); // 创建字节输出流
BufferedOutputStream bos = new BufferedOutputStream(fos); // 创建缓冲流对象,对字节输出流进行保证,让其变得更加强大。
int r ;
while((r = bif.read()) != -1){
bos.write(r);
}
// 关流 只关Buffere类的流对象,因为以后的书写形式直接Buffere传入匿名内部类的方式,所以只关Buffere的
bif.close();
bos.close();
自定义小数组和带Buffere的读写速度哪个更快。
* 如果定义小数组8192个字节大小和Buffere自带的8192个字节相比较的话
* 定义小数组的能比Buffere的快那么一点点,因为读写操作的都是同一个数组
* (FileInputStream 和 FileOutputStream)
* 而Buffered操作的是两个数组
* (FileInputStream 和 BufferedInputStream, FileOutputStream 和 BufferedOuputStream)
e.
flush 和 close 方法区别?
* flush(); 是用来刷新缓冲区的,刷新完后还可以继续使用流对象。
* 好处:做QQ聊天记录,使用缓冲区的输出流)关闭聊天窗口后,下次打开还能看到原先聊天记录,而不是将QQ下线后,再能看原先的聊天记录。(即使用流对象)
* close(); 是关闭释放资源的,但是在关闭资源之前会刷新一次缓冲区,将缓冲区中的数据全部输出到文件中,再关闭。刷新完后就不能再使用流对象了。
* 使用close()确保copy数据的完整性。
// 不关流,可能导致copy后数据的不完整性
// 源文件数据大小: 10,881,245 bytes
// copy后的数据大小: 10,878,976 bytes
不使用close(),有可能导致copy后的数据时不完整性,因为自定义的小数组中还剩余一部分字节,而刷新的只是自定义数组缓冲区的整数倍数据,可能源文件数据大小不是缓冲区的整数倍大小,所以导致copy后的数据是不完整的。
f.
字节流读取 和 写出中文
字节流读取中文的问题 --读取中文会乱码,只能用字符流来解决。
* 字节流在读写中文的时候,可能有时候会读到半个中文,而导致文件乱码。所以读中文只能用字符流来读取。
字节流写出中文的问题 --写中文可以用getBytes()将其转成字节数组来解决。
* 因为字节流字节操作的是字节,所以在写出中文的时候要将其转换为字节数组,使用getBytes()方法。
* 如果想写出回车换行,就用write("/r/n".getBytes());
g.
流的标准处理异常代码 --分1.6及1.6之前 和 1.7版本 【1.6面试必问】
1.6及1.6之前处理异常代码 --try{}...finally 采用finally嵌套的方式
代码
public static void demo01_jdk6即6之前_流异常代码的处理() throws IOException{
FileInputStream fis = null;
FileOutputStream fos = null;
try{
fis = new FileInputStream("aaa.txt");
fos = new FileOutputStream("copyaaa.txt");
int r;
while( (r = fis.read()) != -1){
fos.write(r);
}
}finally{
try {
if(fis != null) //如果fis关闭不了,就关闭fos
fis.close();
} finally { // try..catch 嵌套的目的是能关一个就尽量关一个
if(fos != null)
fos.close();
}
}
1.7处理异常代码 --try(){}...close(此处的close并没有写,会自动关闭资源。因为实现了InputStream 间接的实现了 AutoCloseable接口)
代码
public static void demo02_jdk7_流异常代码的处理() throws IOException{
try(
FileInputStream fis = new FileInputStream("aaa.txt");
FileOutputStream fos = new FileOutputStream("copyaaa2.txt");
MyClose c = new MyClose();
){
int r;
while( ( r = fis.read()) != -1){
fos.write(r);
}
}
}
注意:
原理
* 在try()中创建的流对象必须实现了AutoCloseable这个接口,如果实现了,在try后面的{}(读写代码)执行后就会自动调用,流对象的close方法将流关掉
*【如果一个自定义类想实现自动关闭功能,必须实现 AutoCloseable 接口。】
f.
图片加密 (简单方法) --改变的是每个字节的大小,从而完成对该文件的加密 和 解密 过程。
概述
* 图片加密不是对源文件进行操作,而是根据源文件生成一个加密后的文件,解密也是拿加密后的文件进行解密,从而生成一个新的解密的文件。
加密
* 将写出的每个字节都 异或 一个数,就是对源文件进行加密。
* 将加密后的文件也 异或 加密前的那个数,就是对copy后的文件进行解密(会重新生成一个解密后的文件)
注意:一个数被 异或 两次,还是该原数本身(原来的数),根据这个思想对图片文件进行加密和解密操作。
加密和解密过程:
* 加密的时候 对源文件数据进行了 一次异或 这就是加密。
* 操作的是 源文件数据 和 copy_原图片文件_(加密)
* 解密的时候 对加密的文件数据进行又 一次异或 相当于 对源文件数据 异或两次 还是源文件数据。
* 操作的是 copy_原图片文件_(加密) 和 copy_原图片文件_(加密)_[解密过程].jpg
g.
拷贝文件
案例:在控制台录入文件的路径,将文件拷贝到当前项目路径下
代码:Day20_IO流对象 字节流\src\com\day20\b\test\io\Test02_控制台上录入路径_将文件copy到当前项目路径下.java
案例:将键盘录入的数据拷贝到当前项目下的text.txt文件中,键盘录入数据当遇到quit时就退出
代码
FileOutputStream fos = new FileOutputStream("键盘录入数据copy到该文件中.txt");
Scanner sc = new Scanner(System.in);
System.out.println("请录入:");
while(true){
String str = sc.nextLine();
if(str.equals("quit")){
System.out.println("遇到quit,输出结束");
break; // break结束该循环
}
fos.write(str.getBytes()); // 字符串写出 必须转换成字节数组
fos.write("\r\n".getBytes()); //换行输出
}
fos.close();
阅读全文
0 0
- Day20 --IO流对象 字节流
- JAVASE基础-day20(IO(字节流))
- 字节流(day20)
- day20(IO流)
- day20<IO流>
- 黑马程序员-day20-字节流
- Day20第二十天 java基础 -------IO流
- 黑马程序员-day20-IO流(File)
- 黑马程序员-day20-IO流(Properties)
- 黑马程序员-day20-IO流(其他类)
- IO流+JAVA学习笔记-DAY20
- 黑马程序员-day20字节流及字节流缓冲区
- day20 Io
- IO流(字节流)
- IO流字节流
- IO流字节流
- IO流-字节流
- IO流--字节流
- Day18 --集合框架 Map集合 固定下边界
- Day19 --异常 File类
- 第一次总结与反思
- Slim研读笔记八之路由(上)
- python函数嵌套
- Day20 --IO流对象 字节流
- Day21 --IO流对象 字符流 递归
- OJ题目--数字整除
- Day22 --序列流 内存输出流 随机访问流 对象操作流 数据输入输出流 打印流 标准输入输出流 Properties
- posix线程的误区: 线程是否启动
- Day23 --递归
- 第三章 ALDS1_1_A:Insertion Sort 插入排序法
- 事件分发和NestedScrolling(一)
- Day24 --多线程(上)