Java基础<十五>---> IO

第一  IO流概述

一、概述:

IO流是来处理设备间的数据传输

1、特点:

1)流操作按照数据可分为字节流(处理所有的数据)和字符流(处理文字,其中包含编码表,可以指定编码表防止了编码表不同而产生乱码的现象)

2)按照流向分可以分为输出流和输入流。

字节流的抽象基类:InputStream(读)、OutputStream(写)

字符流的抽象基类:Reader(读)、Writer(写)

注:此四个类派生出来的子类名称都是以父类名作为子类名的后缀,以前缀为其功能;


第二  字符流

一、简述:

1、字符流中的对象融合了编码表。使用的是默认的编码,即当前系统的编码。

2、字符流只用于处理文字数据,而字节流可以任何数据。

3、既然IO流是用于操作数据的,那么数据的最常见体现形式是文件。专门用于操作文件的子类对象:FileWriter、FileReader

二、字符流的读写操作:

1、写入字符流:

1)创建一个FileWriter对象,该对象一被初始化,就必须要明确被操作的文件。且该目录下如果已有同名文件,则同名文件将被覆盖。其实该步就是在明确数据要存放的目的地。

2)调用write()方法,将字符串写入到流中。这里他本身没有特定的写方法都是继承自父类的方法有写单个字符:write(int c),写入字符数组:write(char[] cbuf)这里的数组一般定义成1024的整数倍,不宜过大,过大容易造成内存溢出。写入字符数组的某一部分:write(char[] cbuf, int off, int len),写入字符串:write(String str),写入字符串的某一部分:write(String str, int off, int len)

3)调用flush()方法,刷新该流的缓冲,将数据刷新到目的地中。

4)调用close()方法,是关闭流资源。但是关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。

注:

①、close()和flush()区别:flush()刷新后,流可以继续使用;而close()刷新后,将会关闭流,流不能使用。

②、其实java自身不能写入数据,而是调用系统内部方式完成数据的书写,使用系统资源后,一定要关闭资源。

③、数据的续写是通过构造函数 FileWriter(String s,boolean append),根据给定文件名及指示是否附加写入数据的boolean值来构造FileWriter对象。为true时就是续写,为false就是不续写。

2、读取字符流:

1)创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生异常FileNotFoundException。

2)调用读取流对象的read()方法。read():一次读一个字符,且会继续往下读。

read()读取单个字符。read(char[] cbuf)将字符读入数组。其实都是按照每次只读取一个字符的方式读取的,只是读到数组中会把读取到的数据存放在数组中,起到一个临时缓存的作用,提高了读取效率。

3)读取后要将流资源关闭。

示例:

/*
 * 既然IO流是用于操作数据的,那么数据的最常见体现形式是:文件。
 * 
 * 需求:在硬盘上,创建一个文件并写入一些文字数据。
 * 找到一个专门用于操作文件的Writer子类对象。FileWriter。  后缀名是父类名。 前缀名是该流对象的功能。
 */
public class FileWriterDemo {
	public static void main(String[] args) {
		method();//写内容
		method2();//续写内容
	}
	
	/*
	 * 写数据
	 */
	public static void method(){
		FileWriter fw = null;
		try {
			//创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。
			//该文件会被存放在指定的目录下,如果该文件已经存在,会被覆盖
			//其实该步就是在明确数据要存放的目的地。
			fw = new FileWriter("testwrite.txt");// C:\\testwrite.txt
			
			//调用write方法,将字符串写入到流中
			fw.write("asdsadafsd");
			
			//刷新流对象中的缓冲中的数据。
			//将数据刷到目的地中。
//			fw.flush();
			
			//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。
			//将数据刷到目的地中。
			//和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
//			fw.close();
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				if(fw != null)
					fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/*
	 * 在文件原有内容的基础上续写内容
	 */
	public static void method2(){
		FileWriter fw = null;
		try {
			fw = new FileWriter("testwrite.txt", true);
			fw.write("\r\nnihao\r\nxiexie");//\r\n是换行符\n是linux下的换行
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(fw != null){
				try {
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

三、文件的拷贝:

原理:其实就是将磁盘下的文件数据读取出来,然后写入磁盘的一个文件中
步骤:
1、在硬盘上创建一个文件,用于存储读取出来的文件数据
2、定义读取流和文件关联
3、通过不断读写完成数据存储
方式一:读取一个字符,存入一个字符
方式二:先将读取的数据存入到内存中,再将存入的字符取出写入硬盘
4、关闭流资源:输入流资源和输出流资源。


示例:

/*
 * 将一个文件复制到指定目录下,这里就采用读取到数组中的方式
 */
public class CopyTest {
	public static void main(String[] args) {
		copy();
	}
	
	public static void copy(){
		FileReader fr = null;
		FileWriter fw = null;
		char[] buf = new char[1024];
		int num = 0;
		try {
			fr = new FileReader("testwrite.txt");
			fw = new FileWriter("C:\\testwrite.txt");
			while((num = fr.read(buf)) != -1){
//				fw.write(new String(buf,0,num));//写入字符串
				fw.write(buf,0,num);//写入数组
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(fr != null){
				try {
					fr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(fw != null){
				try {
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

四、字符流缓冲区:BufferedWriter和BufferedReader

1、缓冲区的出现:提高了流的读写效率,所以在缓冲区创建前,要先创建流对象,即先将流对象初始化到构造函数中。

2、缓冲技术原理:此对象中封装了数组,将数据存入,在一次性取出。

3、写入流缓冲区BufferedWriter的步骤:

1)创建一个字符写入流对象

2)为提高字符写入流效率,加入缓冲技术,只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。

      注意,只要用到缓冲去就需要刷新。

3)其实关闭缓冲区就是在关闭缓冲区中的流对象。

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

4、读取流缓冲区BufferedReader的步骤:

1)创建一个读取流对象和文件关联

2)为提高效率,加入缓冲技术,将字符读取流对象作为参数传递给缓冲对象的构造函数。

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

      readLine()方法返回的时只返回回车符之前的数据内容,并不返回回车符,即读取的内容中不包含任何行终止符(回车符和换行符)。

--->readLine()方法原理:无论是读一行,或读取多个字符,其实最终都是在硬盘上一个一个读取。所以最终使用的还是read方法一次读一个。

原理图:


写实例:

/*
 * 缓冲区的出现就是为了提高操作流的效率
 * 所以在创建缓冲区之前必须要先有流对象
 * 
 * 缓冲区提供了一个跨平台的换行符 new Line();
 */
public class BufferedWriterDemo {
	public static void main(String[] args) {
		method();
	}

	public static void method() {
		// 创建一个字符写入流对象
		FileWriter fw = null;
		BufferedWriter bfw = null;
		try {
			fw = new FileWriter("buf.txt");
			// 为了提高字符写入流效率。加入了缓冲技术。其实缓冲区就是封装了数组,不用自己定义数组,用起来更方便
			// 只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
			bfw = new BufferedWriter(fw);

			for (int x = 0; x < 10; x++) {
				bfw.write("abcds" + x);
				bfw.newLine();// 换行在windows中相当于\r\n,在linux中相当于\n
			}
			// 记住,只要用到缓冲区,就要记得刷新。
			// bufw.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				bfw.close();// 关闭缓冲区就是在关闭缓冲区中的流对象,关闭前刷新
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

读实例:

/*
 * 字符读取流缓冲区:该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。
 * 当返回null时,表示读到文件末尾。
 * 
 * readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
 */
public class BufferedReaderDemo {
	public static void main(String[] args) {
		method();
	}
	
	public static void method(){
		//创建一个读取流对象
		FileReader fr = null;
		BufferedReader bfr = null;
		try {
			fr = new FileReader("buf.txt");
			//为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。
			bfr = new BufferedReader(fr);
			
//			char[] buf = new char[1024];
//			bfr.read(buf);
//			int len = 0;
//			while((len = bfr.read(buf)) != -1){
//				System.out.println(new String(buf,0,len));
//			}
			
			String line = null;
			while((line = bfr.readLine()) != null){
				System.out.println(line);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			try {
				bfr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

通过缓冲技术提高效率复制文件:

/*
 * 通过缓冲区复制一个文件。
 */
public class CopyByBuffer {
	public static void main(String[] args) {
		copyByBuf();
	}

	public static void copyByBuf() {
		FileWriter fw = null;
		FileReader fr = null;
		BufferedWriter bfw = null;
		BufferedReader bfr = null;

		try {
			fw = new FileWriter("C:\\buffertest.txt");//创建写文件对象
			fr = new FileReader("buffertest.txt");//创建读文件对象
			bfw = new BufferedWriter(fw);//使用缓冲区关联读写对象
			bfr = new BufferedReader(fr);

			String line = null;
			while ((line = bfr.readLine()) != null) {//通过读一行的方式提高效率
				bfw.write(line);
				bfw.newLine();//换行,夸平台
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (bfw != null) {
				try {
					bfw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (bfr != null) {
				try {
					bfr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

4、自定义BufferedReader的readLine方法,模拟BufferedReader

原理:可根据BufferedReader类中特有发那个发readLine()的原理,自定义一个类中包含相同功能的方法

步骤:a.初始化自定义的类,加入流对象。

           b.定义一个临时容器,原BufferedReader封装的是字符数组,此类中可定义一个StringBuilder的容器,最终可实现字符串的提取。

/*
 * 自定义readLine方法,模拟BufferedReader
 */
public class ImitateBufferReader {
	public static void main(String[] args) {
		MyBufferedReader mbfr = null;
		try {
			FileReader fr = new FileReader("buffertest.txt");
			mbfr = new MyBufferedReader(fr);

			String line = null;
			while ((line = mbfr.myReadLine()) != null) {
				System.out.println(line);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				mbfr.myClose();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

/*
 * 装饰设计模式理念 对需要功能增强的类传进来然后进行装饰
 */
class MyBufferedReader extends Reader {
	private Reader r;

	MyBufferedReader(Reader r) {
		this.r = r;
	}

	public String myReadLine() throws IOException {
		// 定义一个临时容器,原BufferedReader封装的是字符数组,这里定义StringBuilder演示原理
		StringBuilder sb = new StringBuilder();

		int ch = 0;
		while ((ch = r.read()) != -1) {
			if (ch == '\r') {//如是'\r'继续往下读
				continue;
			}
			if (ch == '\n') {//读到'\n'就停止读,然后将这些数据返回
				return sb.toString();
			} else {
				sb.append((char) ch);//读取操作
			}
		}
		if (sb.length() != 0) {
			return sb.toString();//这里是防止最后一行没有回车符的情况
		}
		return null;
	}

	public void myClose() throws IOException {
		r.close();
	}

	@Override
	public int read(char[] cbuf, int off, int len) throws IOException {
		return r.read(cbuf, off, len);
	}

	@Override
	public void close() throws IOException {
		r.close();
	}
}


5、LineNumberReader
在BufferedReader中有个直接的子类LineNumberReader,其中有特有的方法获取和设置行号:

setLineNumber()和getLineNumber()

看实例吧:

/*
 * LineNumberReader可以打印出行号,并操作指定行号的内容
 */
public class LineNumberReaderDemo {
	public static void main(String[] args) {
		LineNumberReader lnr = null;
		try {
			FileReader fr = new FileReader("buffertest.txt");
			lnr = new LineNumberReader(fr);

			String line = null;
//			lnr.setLineNumber(100);// 设置起始行号
			while ((line = lnr.readLine()) != null) {
				System.out.println(lnr.getLineNumber() + ":" + line);// 带行号的输出
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				lnr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

自定义LineNumberReader:

/*
 * 模拟一个带行号的缓冲区对象。
 */
public class MyLineNumberReader {
	public static void main(String[] args) {
		MyLineNumReader mlr = null;
		try {
			FileReader fr = new FileReader("buffertest.txt");
			mlr = new MyLineNumReader(fr);

			String line = null;
			while ((line = mlr.myReadLine()) != null) {
				System.out.println(mlr.getLineNumber() + ":" + line);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

// 优化后
class MyLineNumReader extends MyBufferedReader {
	private int lineNumber = 0;

	public int getLineNumber() {
		return lineNumber;
	}

	public void setLineNumber(int lineNumber) {
		this.lineNumber = lineNumber;
	}

	MyLineNumReader(Reader r) {
		super(r);
	}

	public String myReadLine() throws IOException {
		lineNumber++;
		return super.myReadLine();
	}
}

/*
 * class MyLineNumReader { private Reader r; private int lineNumber = 0;
 * 
 * MyLineNumReader(Reader r) { this.r = r; }
 * 
 * public int getLineNumber() { return lineNumber; }
 * 
 * public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; }
 * 
 * public String myReadLine() { lineNumber++; StringBuilder sb = new
 * StringBuilder(); int ch = 0; try { while ((ch = r.read()) != -1) {
 * 
 * if (ch == '\r') { continue; } if (ch == '\n') { return sb.toString(); } else
 * { sb.append((char) ch); } } } catch (IOException e) { e.printStackTrace(); }
 * 
 * if (sb.length() != 0) { return sb.toString(); } return null; } }
 */


第三  装饰设计模式

一、概述:

当想要对对已有的对象进行功能增强时,可以定义类将已有对象传入,基于已有的功能,并提供增强的功能,那么自定义的该类称为装饰类。装饰类通常会通过构造函数接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。

装饰和继承的区别:

1、装饰模式比继承要灵活,通过避免了继承体系的臃肿,且降低了类与类间的关系。

2、装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能,所以装饰类和被装饰的类通常都是属于一个体系。

3、从继承结构转为组合结构。

注:在定义类的时候,不要以继承为主;可通过装饰设计模式进行增强类功能。灵活性较强,当装饰类中的功能不适合,可再使用被装饰类的功能。

        要继承相应的父类,就需要将所有的抽象方法实现,或交给子类实现。

啥也不说,直接上实例:

/*
 * 装饰设计模式:
 * 当想要对对已有的对象进行功能增强时
 * 可以定义类将已有对象传入,基于已有的功能,并提供增强的功能
 * 那么自定义的该类称为装饰类
 * 
 * 装饰类通常会通过构造函数接收被装饰的对象
 * 并基于被装饰的对象的功能,提供更强的功能
 */
public class PersonDemo {
	public static void main(String[] args) {
		PersonDem p = new PersonDem();
		SuperPerson sp = new SuperPerson(p);
		sp.superChifan();
	}
}

/*
 * 刚开始的时候的人,只具备基本的功能
 */
class PersonDem{
	public void chifan(){
		System.out.println("吃饭");
	}
}

/*
 * 装饰后的人,功能更强
 */
class SuperPerson{
	private PersonDem p;
	
	SuperPerson(PersonDem p){
		this.p = p;
	}
	
	public void superChifan(){
		System.out.println("水果");
		p.chifan();
		System.out.println("甜点");
	}
}


第四  字节流

一、概述:

1、字节流和字符流的原理是相似的,而字符流是基于字节流的,字节流可以操作如媒体等其他数据,如媒体等

2、由于媒体数据中都是以字节存储的,所以,字节流对象可直接对媒体进行操作,而不用再进行刷流动作。

3、读写字节流:InputStream     --->  输入流(读)  OutputStream  --->  输出流(写)

4、为何不用进行刷流动作:因为字节流操作的是字节,即数据的最小单位,不需要像字符流一样要进行转换为字节。可直接将字节写入到指定文件中,但是需要在写代码的时候,如果有字符串,要将字符串转为字节数组再进行操作。

5、FileInputStream特有方法:int available() --->  返回数据字节的长度,包含终止符

在定义字节数组长度的时候,可以用到这个方法:byte[] = new byte[fos.available()]   (fos为字节流对象)但是,对于这个方法要慎用,如果字节过大,超过jvm所承受的大小(一般内存为64M),就会内存溢出。

上实例:

/*
 * 字节流操作
 * InputStream OutputStream
 * 
 */
public class FileStreamDemo {
	public static void main(String[] args) {
		// writeFile();
		// readFile_1();
		// readFile_2();
		readFile_3();
	}

	/*
	 * 对字节流读操作的第一种方式 通过read()方法读取一个字节
	 */
	public static void readFile_1() {
		FileInputStream fis = null;// 定义字节输入流
		try {
			fis = new FileInputStream("fos.txt");// 指定输入流和文件关联
			int ch = 0;
			while ((ch = fis.read()) != -1) {// 读取操作
				System.out.println((char) ch);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fis != null)// 判空,提高程序的健壮性
					fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * 对字节流读操作的第二种方式 通过read(byte[])方法读取字节数组
	 */
	public static void readFile_2() {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("fos.txt");
			byte[] byf = new byte[1024];
			int len = 0;
			while ((len = fis.read(byf)) != -1) {
				System.out.println(new String(byf, 0, len));
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fis != null)
					fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * 对字节流读操作的第三种方式 通过字节流的available()方法获取到文件大小,定义一个大小刚刚好的数组,无需循环
	 * 但是,这种方式操作较大数据时容易内存溢出,所以要慎用 首选的还是定义1024的整数倍数组
	 */
	public static void readFile_3() {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream("fos.txt");
			// 通过这样的方式定义一个刚刚好的数组
			// 这种方法慎用,如果文件不大,可以用这个方法
			byte[] byf = new byte[fis.available()];// fis.available()获取文件大小
			fis.read(byf);
			System.out.println(new String(byf));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/*
	 * 对字节流进行写操作
	 */
	public static void writeFile() {
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream("fos.txt");//定义字节写出流和文件关联
			fos.write("abcds".getBytes());// str.getBytes()将String转化成字节数组
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (fos != null)
				try {
					fos.close();// 因为字节流没有用到缓冲区,所以不需要刷新,但必须关闭资源
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
}

二、通过字节流复制媒体文件:

思路:

1、用字节流读取流对象和媒体文件相关联

2、用字节写入流对象,创建一个媒体文件,用于存储获取到的媒体文件数据

3、通过循环读写,完成数据的存储

4、关闭资源

/*
 * 通过字节流拷贝图片
 */
public class CopyPicture {
	public static void main(String[] args) {
		copyPic();
	}

	public static void copyPic() {
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try {
			fis = new FileInputStream("1.jpg");
			fos = new FileOutputStream("C:\\1.jpg");

			byte[] byf = new byte[1024];
			int len = 0;
			while ((len = fis.read(byf)) != -1) {// 将数据读取到数组中
				fos.write(byf, 0, len);// 写入数组中的有效数据
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			// 有多个流不能一起关闭,要分别关闭
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

三、字节流缓冲区:

1、概述:有了前面字符缓冲区的经验,相信这里也就很好理解了,缓冲区的出现无非就是提高了流的读写效率,当然也就是因为这样,所以缓冲区的使用频率也是相当高的,所以要做必要性的掌握。

2、字节流缓冲区读写的特点:

这里没有什么特殊的读写方法,就是read()读一个字节,read(byte[])读数组的方法。写write(int b)写一个自己,write(byte[])写数组的方法。

注:需要注意个地方,write方法只写出二进制的最后八位。

原理:将数据拷贝一部分,读取一部分,循环,直到数据全部读取完毕。

1)先从数据中抓取固定数组长度的字节,存入定义的数组中,再通过然后再通过read()方法读取数组中的元素,存入缓冲区

2)循环这个动作,知道最后取出一组数据存入数组,可能数组并未填满,同样也取出包含的元素

3)每次取出的时候,都有一个指针在移动,取到数组结尾就自动回到数组头部,这样指针在自增

4)取出的时候,数组中的元素再减少,取出一个,就减少一个,直到减到0即数组取完

5)到了文件的结尾处,存入最后一组数据,当取完数组中的元素,就会减少到0,这是全部数据就取完了

3、自定义字节缓冲区:

/*
 * 自定义字节流缓冲区
 */
public class MyBufferedInputStream {
	private InputStream in;
	private byte[] buf = new byte[1024];
	private int pos;// 定义数组的指针
	private int count;// 定义计数器

	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;// &255是因为存放的是二进制,也就是可能前八位是11111111,提升为int还是-1,就直接return了,
			// &255就是让前面补0
			/*
			 *  11111111 11111111 11111111 11111111 
			 * &00000000 00000000 00000000 11111111 
			 * ------------------------------------ 
			 *  00000000 00000000  00000000 11111111
			 */
		}else if(count>0){
			byte b = buf[pos];
			
			count--;
			pos++;
			return b&255;//&0xff
		}
		return -1;
	}
	
	public void myClose() throws IOException{
		in.close();
	}
}
注:取出的是byte型,返回的是int型,这里存在提升的动作,
当byte中的八位全为1的时候是byte的-1,提升为int类型,就变为int型的-1,为-1时程序就停止循环了,read循环条件就结束了,变为-1的原因是由于在提升时,将byte的八位前都补的是1,即32位的数都是1,即为int型的-1了。如何保证提升后的最后八位仍为1呢?就需要将前24位补0,就可以保留原字节数据不变,又可以避免转为int型出现-1的情况;
那么要如何做呢?
这就需要将提升为int的数据和前24位为0,后八位仍为原字节数据的这个值做与运算。即和255做与运算即可。说到了这里应该也明白了为什么Read方法返回值为int类型了。

4、获取键盘输入:键盘输入都是字节,所以用到字节流与标准输入流相关联就可以把输入的数据获取到流中,以达到数据的操作效果。

/*
 * 获取键盘录入
 */
public class ReadIn {
	public static void main(String[] args) {
		// method_1();
		// method_2();
		InputStreamReaderDemo();
	}

	/*
	 * 获取键盘录入
	 */
	public static void method_1() {
		InputStream in = System.in;// 定义输入流与键盘输入流想关联
		int ch = 0;
		try {
			while ((ch = in.read()) != -1) {
				System.out.println(ch);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/*
	 * 需求:通过键盘录入数据。 当录入一行数据后,就将该行数据进行打印。 如果录入的数据是over,那么停止录入。
	 */
	public static void method_2() {
		InputStream in = System.in;
		StringBuilder sb = new StringBuilder();// 定义一个临时容器

		while (true) {
			int ch = 0;
			try {
				ch = in.read();
				if (ch == '\r')// windows中换行符为\r\n
					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);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// **************************************************************************

	/*
	 * 通过上面录入一行数据,发现其方法类似于readLine方法 但是readLine方法是字符流缓冲区的方法,所以需要把字节流转换成字符流进行操作
	 * 需要用到InputStreamReader方法进行转换,InputStreamReader字节通向字符的桥梁
	 * 
	 * 转换流
	 */
	public static void InputStreamReaderDemo() {
		// //获取键盘录入对象
		// InputStream in = System.in;
		//
		// //将字节流对象转换成字符流对象
		// InputStreamReader isr = new InputStreamReader(in);
		//
		// //将字符流对象用缓冲技术高效处理
		// BufferedReader bufr = new BufferedReader(isr);

		// 键盘录入最常见的写法
		// BufferedReader bufr = new BufferedReader(new
		// InputStreamReader(System.in));

		BufferedReader bufr = null;
		try {
			// 源为文件
			bufr = new BufferedReader(new InputStreamReader(
					new FileInputStream("buf.txt")));
		} catch (FileNotFoundException e2) {
			e2.printStackTrace();
		}

		// 操作输出
		// 获取输出流
		// OutputStream out = System.out;
		// //将字符流转换成字节流,OutputStreamWriter字符流通向字节流的桥梁
		// OutputStreamWriter osw = new OutputStreamWriter(out);
		// BufferedWriter bfw = new BufferedWriter(osw);//高效缓冲区
		// 目的地是控制台
		BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(
				System.out));

		// 目的地是文件
		// BufferedWriter bfw = null;
		// try {
		// bfw = new BufferedWriter(new OutputStreamWriter(new
		// FileOutputStream("out.txt")));
		// } catch (FileNotFoundException e1) {
		// e1.printStackTrace();
		// }

		String line = null;

		try {
			while ((line = bufr.readLine()) != null) {
				if ("over".equals(line)) {
					break;
				}
				// System.out.println(line.toUpperCase());
				bfw.write(line.toUpperCase());
				bfw.newLine();// 换行
				bfw.flush();// 数据存放在缓冲区,所以需要刷新
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				bufr.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

5、转换流:转换流可以实现字节数据和字符数据的相互转换方便与操作,而且在转换的时候可以指定编码,这也是该流最具特色的地方。在上面的程序中已经看到了使用转换流的效果,下面就简单的介绍下该流:

InputStreamReader 是字节流通向字符流的桥梁

OutputStreamWriter 是字符流通向字节流的桥梁

//获取键盘录入对象
		 InputStream in = System.in;
		
		 //将字节流对象转换成字符流对象
		 InputStreamReader isr = new InputStreamReader(in);
		
		 //将字符流对象用缓冲技术高效处理
		 BufferedReader bufr = new BufferedReader(isr);
// 键盘录入最常见的写法
		 BufferedReader bufr = new BufferedReader(new
		 InputStreamReader(System.in));
字符流转字节流:

// 操作输出
		// 获取输出流
		 OutputStream out = System.out;
		 //将字符流转换成字节流,OutputStreamWriter字符流通向字节流的桥梁
		 OutputStreamWriter osw = new OutputStreamWriter(out);
		 BufferedWriter bfw = new BufferedWriter(osw);//高效缓冲区


6、保存异常信息:在程序运行时异常信息和重要,但是异常信息显示给客户是没有任何意义,这时我们就需要将运行时的一唱歌信息保存起来给开发人员看,就需要用到该技术了,下面的只是个实例,说明保存异常信息的原理,在实际开发中我们一般用log4j来处理异常信息。

/*
 * 把错误信息存储在文件中
 * 
 * log4j专门用来处理日志打印的工具
 */
public class ExceptionInfo {
	public static void main(String[] args) {
		try{
			int[] arr = new int[2];
			System.out.println(arr[3]);
		}catch(Exception e){
			Date d = new Date();
			SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			String s = sf.format(d);
			
			try {
				PrintStream ps = new PrintStream("exception.log");
				ps.println(s);
				System.setOut(ps);//改变标准输出流的位置
			} catch (FileNotFoundException e1) {
				throw new RuntimeException("日志文件创建失败");
			}
			e.printStackTrace(System.out);
		}
	}
}


7、获取系统信息,并保存:
步骤:
1)获取系统信息:
Properties getProperties()
2)将信息输出到指定输出流中
void  list(PrintStream out)
3)将输出流中数据存入指定文件中
new PrintStream("systeminfo.txt")
实例:
/*
 * 获取系统属性信息
 */
public class SysInfo {
	public static void main(String[] args) {
		Properties pro = System.getProperties();

		try {
			pro.list(new PrintStream("sysinfo.txt"));// list方法将属性列表输出到指定的输出流。
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}

第五  流操作规律

一、概述:

流操作的基本规律:最痛苦的就是流对象有很多,不知道该用哪一个。

通过三个明确来完成判定:

1、明确源和目的

源:输入流  InputStream  Reader

目的输出流 outputStream Writer

2、操作的数据是否是纯文本

是:字符流

不是:字节流

3、当体系明确后再明确需要使用哪个具体对象

通过设备进行区分

源设备:内存、硬盘、键盘

目的设备:内存、硬盘、控制台

分析实例:

/*
 * 需求:将键盘录入的数据保存到一个文件中。
 * 
 * 源:InputStream  Reader
 * 是否是纯文本:是用Reader
 * 
 * 设备:键盘 对象的对象是System.in
 * 不是选择Reader吗,System.in对应的不是字节流吗?
 * 为了操作键盘文本数据方便,转换成字符流按照字符流操作最为方便
 * 所以既然明确了Reader,那么就将System.in转换成Reader
 * 使用到了Reader体系中的转换流InputStreamReader
 * 
 * InputStreamReader isr = new InputStreamReader(System.in);
 * 
 * 需要提高效率吗: 需要 BufferedReader
 * BufferedReader bufr = new BufferedReader(isr);
 * 
 * 目的地:OutputStream Writer
 * 是否是纯文本:是  Writer
 * 设备是硬盘上的一个文件:使用FileWriter
 * FileWriter fw = new FileWriter("c.txt");//创建存放的文件
 * 是否需要提高效率:需要  BufferedWriter
 * BufferedWriter bfw = new BufferedWriter(fw);
 * 
 * ***********************************************
 * 扩展:想要在录入数据的时候指定编码表(UTF-8),将数据存放在文件当中
 * 
 * 目的地:OutputStream Writer
 * 是否是纯文本:是  Writer
 * 设备是硬盘上的一个文件:使用FileWriter
 * 但是FileWriter是使用默认的编码表GBK
 * 
 * 在存储是需要指定编码表UTF-8,而指定编码表只有转换流可以指定
 * 所以使用OutputStreamWriter进行转换,并指定码表
 * 而该转换流对象要接收一个字节输出流。而且还可以操作文件的输出流。FileOutputStream
 * 
 * OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
 * 
 * 需要提高效率吗:需要 BufferedWriter
 * BufferedWriter bfw = new BufferedWriter(osw);
 * 
 * ******  转换流的使用,字符和字节之间的桥梁,通常涉及到字符编码时,需要用到转换流
 * 
 */
public class TransStreamDemo2 {
	public static void main(String[] args) {
		method();
	}
	
	 public static void method(){
		 //录入对象
		 InputStream in = System.in;
		 InputStreamReader isr = new InputStreamReader(in);
		 BufferedReader bfr = new BufferedReader(isr);
		 OutputStreamWriter osr = null;
		 BufferedWriter bfw = null;
		 try {
			 //写出对象,因为需要指定编码,所以需要转换
			 osr = new OutputStreamWriter(new FileOutputStream("q.txt"), "UTF-8");
			 bfw = new BufferedWriter(osr);
		 
		 String line = null;
		 
			while((line = bfr.readLine()) != null){
				if("over".equals(line)){
					break;
				}
				 bfw.write(line);
				 bfw.newLine();
			 }
		} catch (IOException e) {
			e.printStackTrace();
		}finally{
			if(bfr != null){
				try {
					bfr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(bfw != null){
				try {
					bfw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	 }
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值