黑马程序员—字节流、自定义缓冲区、异常信息处理



黑马程序员——字节流、自定义缓冲区、异常信息处理

1、字节流

字节流的两个基类是InputStreamOutputStream

相应的缓冲区是BufferedInputStreamBufferedOutputStream
它的操作与字符流类似,可以参与字符流的定义、读取、写入、处理异常的格式

只不过是处理的数据不同,因为对于非字符的数据,   如图片、视频、音频文件(例如mp3)等,这些文件只能用字节流   对之进行操作。
        
如果想在已有的文件中追加数据,怎样呢?依旧在创建对象是:fos =new FileOutputStream(filetrue);

字节流的写操作,不用刷新动作,也能写入目的地,这是为什么?

 字符流底层用也的字节流,因为涉及编码,比如写一个汉字,它有两个字节,你不能读一个字节就写入,而要等到两个字节都读取完后,才写入,所以必须要有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 =newFileInputStream(file);
             byte [] ch =newbyte[fis.available()];
             fis.read(ch);
          System.out.println(newString(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();
			}

练习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();
				}
			}
		}
	}
}




练习2:复制mp3文件(使用字节缓冲区)


       复制媒体文件要注意的问题:
  1
、复制MP3因为有可能读取的字节81,值为-1,返回值是int类型,类型提升后结果仍为-1,这会导致数据并未读完,
    
myRead()结果却为-1,从而使得复制mp3文件失败。
     
为避免这种情况,可以采取这种方法,读取的字节类型提升后前24位补的是1,我们让它补0
   
怎么补零?
  
类型提升的过程我们是没法控制的,那么可以将提升后的结果&255
  
结果就转化成最后一个字节不变,而前三个字节位都变成了0
  &255
后的值作为read方法的返回值。
   
这其实也是为什么read()方法的返回值是int而不是byte的原因。
       2
、字节写入流的write(intb)方法,在写入时,有个截取动作,即把最低8位为保留,而把高24位舍弃。
自定义的字节流缓冲:

代码:


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类型的提升。


并在保留原字节数据的情况前面了补了240,变成了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、转换流


   在接下来会讲到指定编码表的问题。


上边读取键盘录入的代码,与BufferedReaderreadLine()方法很相似,那么能不能用readLine()来代替?因为System.in是字节流,它要使用字符流缓冲区的方法,这就需要使用到两个转换流:InputStreamReaderOutputStreamWriter

代码

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.明确源和目的。

    源:输入流,InputStreamReader;
         
目的:输出流,OutputStreamWriter

   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.
    }  
} 

要点总结:


字符流缓冲区:BufferReaderBufferWriter


           a.提高了对数据读写的效率。


           b.必须结合流使用。


           c.在流的基础上对流的功能进行了增强。


BufferReader字符读取流缓冲区:


该缓冲区提供了一个一次读一行的方法readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。


readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符


BufferWriter


该缓冲区中提供了一个跨平台的换行符。newLine();


1)练习:通过缓冲区复制一个.java文件。


2)练习:BufferreaderMyreadLine方法


3)装饰设计模式;自定义装饰类;读取行数的LineNumnberReaderMyLineNumberReader的设计


4)字节流


InputStreamOutputStream


5)缓冲字节流


6)拷贝MP3,拷贝图片。


7)自定义字节流的缓冲区


8)读取键盘录入


9)读取转换流


10)写出转换流


11)流操作规律总结。


12)改变标准输入输出设备

---------------ASP.Net+Android+IOS开发、.Net培训、期待与您交流! -----------




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值