现在的位置: 首页 > 综合 > 正文

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

2013年10月25日 ⁄ 综合 ⁄ 共 9559字 ⁄ 字号 评论关闭
文章目录

------------android培训java培训、期待与您交流!
-------------

1、字节流

字节流的两个基类是InputStream和OutputStream,

相应的缓冲区是BufferedInputStream和BufferedOutputStream
它的操作与字符流类似,可以参与字符流的定义、读取、写入、处理异常的格式
只不过是处理的数据不同,因为对于非字符的数据,   比 如图片、视频、音频文件(例如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 =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因为有可能读取的字节81,值为-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、

转换流

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

上边读取键盘录入的代码,与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.
    }  
}  


9、

知识要点总结:

字符流缓冲区: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)改变标准输入输出设备。

抱歉!评论已关闭.