------------android培训、java培训、期待与您交流!
-------------
1、字节流
字节流的两个基类是InputStream和OutputStream,
相应的缓冲区是BufferedInputStream和BufferedOutputStream
它的操作与字符流类似,可以参与字符流的定义、读取、写入、处理异常的格式
只不过是处理的数据不同,因为对于非字符的数据, 比 如图片、视频、音频文件(例如mp3)等,这些文件只能用字节流 对之进行操作。
如果想在已有的文件中追加数据,怎样呢?依旧在创建对象是:fos
=new FileOutputStream(file,true);
字节流的写操作,不用刷新动作,也能写入目的地,这是为什么?
字符流底层用也的字节流,因为涉及编码,比如写一个汉字,它有两个字节,你不能读一个字节就写入,而要等到两个字节都读取完后,才写 入,所以必须要有flush机制。而字节流,不涉及编码,可以读一个就写一个而不出现问题,所以不用刷新也能写入。
字节流的写方法:
public class OutputStreamDemo { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub File file = new File("d:"+File.separator+"def.txt"); if(!file.exists()){ try { file.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } FileOutputStream fos = null; try { fos = new FileOutputStream(file); //定义一个字符串,因为字节流只能以字节或字节数组的形式读取 String s = "我要进黑马,实现一个跨越"; byte [] by =s.getBytes(); fos.write(by);//不用刷新的 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(fos!=null){ try { fos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
2、字节流的读方法:
但注意:如果字节说过大,会造成内存溢出。
部分代码:fis =new FileInputStream(file);
byte [] ch =newbyte[fis.available()];
fis.read(ch);
System.out.println(new
String(ch));
字节流特有的读取方式:available(
方法可以知道文件的所有字节数,以此可以定义一个刚刚好的字节数组,只用读取一次,然后写入来完成文件复制
方法一:按字节读取 public class InputStreamDemo1 { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub File file = new File("d:"+File.separator+"abc.txt"); if(!file.exists()){ try { file.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } FileInputStream fis = null; try { //实例化字节流对象 fis = new FileInputStream(file); int x = 0; while((x = fis.read())!=-1){ System.out.println((char)x); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(fis!=null){ fis.close();//一定要执行的关闭流,释放资源 } }}} 方法二:按字节数据读取 部分代码: try { fis = new FileInputStream(file); // int x = 0; byte [] ch = new byte[1024];//定义一个容量为1024的byte数组 int len =0; while((len = fis.read(ch))!=-1){ System.out.println(new String(ch,0,len)); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ if(fis!=null){ fis.close(); }
2、 练习1:复制图片(字节文件输入输出流)
注意:字符流也可以复制图片,但是它读取一段字节后就会查表进行编码,如果表中有对应的编码值那么没有问题,
如果没有对应的编码,则会在编码表的未知区域找一个类似的编码进行替代,这时就改变了原来的字节,
导致图片格式不对,复制的图片无法打开。
/*
需求:复制一个图片。
思路:
1.定义一个字节读取流对象和图片关联
2.定义一个字节写入流对象,用于创建和存储图片。
3.通过循环读写,完成数据的存储。
4.关闭资源。
*/
public class CopyPic { /** * @param args */ public static void main(String[] args) { long start = System.currentTimeMillis(); copy_pic(); long end = System.currentTimeMillis(); System.out.println(end-start); } public static void copy_pic(){ File pic = new File("d:\\我和女友.jpg"); File pic_copy = new File("d:"+File.separator+"我和女友_复件.jpg"); //判断两个文件是否存在; if(!pic.exists()){ throw new RuntimeException("找不到图片"); } if(!pic_copy.exists()){ try { pic_copy.createNewFile(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } BufferedInputStream bis = null; BufferedOutputStream bos = null; try{ bis = new BufferedInputStream(new FileInputStream(pic)); bos = new BufferedOutputStream(new FileOutputStream(pic_copy)); byte[] ch = new byte[1024]; int len = 0; while((len = bis.read(ch))!=-1){//一次读一个字节数组 bos.write(ch, 0, len); } }catch(Exception e){ e.printStackTrace(); }finally{//必须关流 if(bis!=null){ try { bis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(bos!=null){ try { bos.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
4、
练习2:复制mp3文件(使用字节缓冲区)
复制媒体文件要注意的问题:
1、复制MP3因为有可能读取的字节8个1,值为-1,,返回值是int类型,类型提升后结果仍为-1,这会导致数据并未读完,
但myRead()结果却为-1,从而使得复制mp3文件失败。
为避免这种情况,可以采取这种方法,读取的字节类型提升后前24位补的是1,我们让它补0。
怎么补零?
类型提升的过程我们是没法控制的,那么可以将提升后的结果&255,
结果就转化成最后一个字节不变,而前三个字节位都变成了0,
&255后的值作为read方法的返回值。
这其实也是为什么read()方法的返回值是int而不是byte的原因。
2、字节写入流的write(intb)方法,在写入时,有个截取动作,即把最低8位为保留,而把高24位舍弃。
自定义的字节流缓冲:
两种方法,一种是java自己的缓冲区,一种是自定义的缓冲区。
class MyBufferedInputStream { private InputStream in; private byte[] buf = new byte[1024*4]; private int pos = 0,count = 0; MyBufferedInputStream(InputStream in) { this.in = in; } //一次读一个字节,从缓冲区(字节数组)获取。 public int myRead()throws IOException { //通过in对象读取硬盘上数据,并存储buf中。 if(count==0) { count = in.read(buf); if(count<0) return -1; pos = 0; byte b = buf[pos]; count--; pos++; return b&255; }else if(count>0){ byte b = buf[pos]; count--; pos++; return b&0xff; } return -1; } public void myClose()throws IOException{ in.close(); } } /*毕老师的讲解: 11111111-111111110000000000101001001010100101010010101001010 byte: -1 ---> int : -1; 00000000 00000000 00000000 11111111 255 11111111 11111111 11111111 11111111 11111111 -->提升了一个int类型 那不还是-1吗?是-1的原因是因为在8个1前面补的是1导致的。 那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。 怎么补0呢? 11111111 11111111 11111111 11111111 &00000000 00000000 00000000 11111111 ------------------------------------ 00000000 00000000 00000000 11111111 0000-0001 1111-1110 000000001 1111-1111 -1 结论: 字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。 因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1. 那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。 所以,为了避免这种情况将读到的字节进行int类型的提升。 并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。 而在写入数据时,只写该int类型数据的最低8位。 */ Import ........ public class CopyMp3 { public static void main(String[] args)throws IOException {//异常全抛出去 // TODO Auto-generated method stub long start =System.currentTimeMillis(); copyMp3(); long end = System.currentTimeMillis(); Print.sop((end-start)+"毫秒"); } public static void copyMp3()throws IOException{ BufferedInputStream in = new BufferedInputStream(new FileInputStream("D:\\突然好想你.mp3")); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("D:\\突然好想你_copy.mp3")); int len =0; while((len = in.read())!=-1){ out.write(len); } if(in!=null){ in.close(); } if(out!=null){ out.close();} } public static void copy_2() throwsIOException { MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("突然好想你.mp3")); BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("突然好想你_1.mp3")); int by = 0; //System.out.println("第一个字节是:"+bufis.myRead());//结果为-1。 int len = 0; while((len = bufis.myRead())!= -1) { bufos.write(len);//write其实在做一个强转动作 //bufos.flush();//这里不要写flush,否则复制速度会大幅降低。 //System.out.println(len); } bufis.myClose(); bufos.close(); } }
5、
读取键盘录入
读取键盘录入。
System.out:对应的是标准输出设备,控制台。
System.in:对应的是标准的输入设备,键盘。
通过键盘录入数据。
当录入一行数据后,就将改行数据打印。
如果录入的数据是over,那么停止录入。*/
/*
import java.io.InputStream; public class SystemIn { /** * @param args * @throws Exception */ public static void main(String[] args) throws Exception { // TODO Auto-generated method stub // 新建一个键盘输入流 InputStream in = System.in; //创建一个缓冲区,将数据存入缓冲区中 StringBuilder sb = new StringBuilder(); int ch = 0; while(true){ ch = in.read(); /* read()是一个阻塞式的方法,它每次读取一个字节,如果没有数据,它会一直等待。 1. 为什么它读硬盘上的文件文件时,没有阻塞? 因为硬盘上的文件就算是空,为0字节,因为文件有结束标记(windows系统给每个磁盘文件都加有结束标记),会返回-1,而不阻塞。 而在dos命令行窗口下,启动后,既没有数据,也没有结束标记,所以一直阻塞,而“ctrl+c”命令,其实就是输入一个结束标记。 2. 为什么dos命令窗口下,只用我敲回车键后,控制台才打印,而不是我录入一个字符它就打印? 因为,录入的字符,只有在敲回车键后才写入in关联的流里边,这时流里边才有数据,从而被read()方法读取到。 3.为什么我敲入一个字符回车后,却打印出来3个数字? 因为,windows中,敲回车键,就是加入换行符,而windows中的换行符是用\r\n两个字符表示的,所以会有3个。 */ if(ch == '\r') continue; if(ch == '\n'){ String s = sb.toString(); if("over".equals(s)){ break; } //大写输出 System.out.println(s.toUpperCase()); //清空缓冲区,否则,结束标记失效、之前打印的将载累加 sb.delete(0, sb.length()); }else{//如果没有遇到回车符,就继续往缓冲中添加 sb.append((char)ch); } } } }
6、
转换流
在接下来会讲到指定编码表的问题。
上边读取键盘录入的代码,与BufferedReader的readLine()方法很相似,那么能不能用readLine()来代替?因为System.in是字节流,它要使用字符流缓冲区的方法,这就需要使用到两个转换流:InputStreamReader和OutputStreamWriter。
class TransStreamDemo { public static void main(String[] args) throws IOException { //获取键盘录入对象。 //InputStream in = System.in; //将字节流对象转成字符流对象,使用转换流。InputStreamReader //InputStreamReader isr = new InputStreamReader(in); //为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader //BufferedReader bufr = new BufferedReader(isr); //键盘的最常见写法。 BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in)); BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out)); String line = null; while((line=bufr.readLine())!=null) { if("over".equals(line)) break; bufw.write(line.toUpperCase()); bufw.newLine(); bufw.flush(); } bufr.close(); } }
7、流操作的基本规律
1.明确源和目的。
源:输入流,InputStream和Reader;
目的:输出流,OutputStream和Writer
2.明确操作的数据是否是纯文本。
是:字符流
不是:字节流。
3.当体系明确后,再明确要使用哪个具体的对象。
通过设备来区分:
源设备:内存、硬盘、键盘
目的设备:内存、硬盘、控制台。
此外:
如果需要提高效率,则换用对应的缓冲区。
如果需要指定编码,则换用对应的转换流。
System类中的setIn()、setOut()、setError()方法,可以改变标准输入流、输出流和错误输出流。
8、异常信息的保存
IO操作最头痛的是如何其体系中选择需要的类,这个过程可以通过三个步骤来完成:
小知识点:
class ExceptionInfo { Public static void main(String[] args) throws IOException { try { int [] arr = newint[2]; System.out.println(arr[3]); //空指针异常 } catch (Exception e) { try { Date d= new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-DDHH:mm:ss"); String s =sdf.format(d); //保存异常信息到文件中,可以续写。 PrintStream ps = new PrintStream(new FileOutputStream("exception.log",true)); ps.println(s); //打印异常的日期 System.setOut(ps); } catch (IOException ex) { throw new RuntimeException("日志文件创建失败"); } e.printStackTrace(System.out); } } } 异常信息序列化 class SystemInfo { public static void main(String[]args) throws IOException { Properties prop = System.getProperties(); System.out.println(prop); //数组的形式打印 prop.list(System.out);//不用像在集合时一样,用遍历集合 //将属性列表输出到指定的输出流 prop.list(new PrintStream("d:"+File.separator+"sysinfo.txt")); //列表的形式打印到sysinfo.txt. } }
9、
知识要点总结:
字符流缓冲区:BufferReader和BufferWriter
a.提高了对数据读写的效率。
b.必须结合流使用。
c.在流的基础上对流的功能进行了增强。
BufferReader字符读取流缓冲区:
该缓冲区提供了一个一次读一行的方法readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符
BufferWriter
该缓冲区中提供了一个跨平台的换行符。newLine();
1)练习:通过缓冲区复制一个.java文件。
2)练习:Bufferreader的MyreadLine方法
3)装饰设计模式;自定义装饰类;读取行数的LineNumnberReader。MyLineNumberReader的设计
4)字节流
InputStream和OutputStream
5)缓冲字节流
6)拷贝MP3,拷贝图片。
7)自定义字节流的缓冲区
8)读取键盘录入
9)读取转换流
10)写出转换流
11)流操作规律总结。
12)改变标准输入输出设备。