Java基础——IO(一)——IO流概述、字节流、字符流、流操作规律

第一讲 IO概述

一、概述

1、IO:Input Stream的缩写。

2、特点:

      1)IO可以用来处理设备间的数据传输;

      2)Java对数据的操作是通过流的方式;

      3)Java用于操作流的对象都在io包中;

      4)流按操作数据分为两种:字节流、字符流;

      5)流按流向分为:输入流、输出流;

      6)IO流只能操作数据,不能操作文件(如:通过IO流可以向一个文件写入数据,却无法删除这个文件)。

3、IO流常用基类

      1)字节流的抽象基类:InputStream、OutputStream;

      2)字符流的抽象基类:Reader、Writer;

      3)由这四个基础类派生的子类,名称都是以其父类名作为子类名的后缀,以其操作对象(或功能)作为前缀(例如:InputStream的子类FileInputStream,Reader的子类FileReader)。


第二讲 字符流

一、概述

1、字符流有两个基类:Reader、Writer;

2、字符流中的对象融合了编码表,默认使用当前系统的编码;

3、字符流只用于处理文字数据,字节流可以处理多媒体数据(如图片、视频文件等);

4、既然IO流是用于操作数据的,那么数据的最常见体现形式就是文件。通过查阅API,找到一个专门用于操作文件的Writer子类对象:FileWriter。后缀是父类名,前缀是流对象的功能。该流对象一被初始化就必须有被操作的文件存在。


二、字符流的基本操作

1、写入字符流步骤
      1)创建一个FileWriter对象,该对象一被初始化,就必须要明确指定被操作的文件。如果该目录下已有同名文件,则同名文件将被覆盖。其实这一步就是在明确数据要存放的目的地。
      2)调用write(String s)方法,将字符串写入到流中(写入内存)。
      3)调用flush()方法,刷新该流的缓冲,将内存(流)中的数据刷新到目的地(文件)中。flush刷新后,流可以继续使用。
      4)调用close()方法,关闭流资源。执行close()方法在关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。close会关闭流,因此调用close方法后将不能再写入数据。
      备注
      1)java自身不能写入数据,而是调用系统内部方式完成数据的写入,使用系统资源后,一定要关闭资源,否则很可能会造成资源泄漏。
      2)文件的数据的续写是通过构造函数FileWriter(String s, boolean append),在创建对象时,传递一个true参数,代表不覆盖已有的文件,而是在已有文件的末尾处进行数据续写,如果文件不存在就会在内部调用File的API创建文件(Windows系统中的文件内换行用\r\n两个转义字符表示,在Linux系统中只用\n表示换行)。
      3)由于在创建对象时,需要制定创建文件的位置,如果指定的位置不存在,就就会发生IOException,所以在整个步骤中,需要对IO异常进行try-catch处理。

使用示例:

package com.dawndaybreak.ioexample;

import java.io.FileWriter;
import java.io.IOException;

/**
 * 
 * 通过FileWriter实现对文件的写入、续写操作
 * 
 * @author dawn
 * 
 */
public class WriterDemo {

	public static void main(String[] args) {
		String filePath = "E:/demo.txt";
		String[] arrays = { "Hello World!", "dd" };
		FileWriterUtil.writerData(filePath, arrays);
		FileWriterUtil.writerData(filePath, arrays, true);
	}

}

class FileWriterUtil {

	/**
	 * 添加多行字符到指定文件
	 * 
	 * @param filePath
	 *            文件目录
	 * @param arrays
	 *            写入数据,每个不等于null的对象为一行数据
	 */
	public static void writerData(String filePath, Object[] arrays) {
		FileWriter writer = null;
		try {
			writer = new FileWriter(filePath);
			for (Object line : arrays) {
				if (line != null) {
					writer.write(String.valueOf(line));
					writer.write(getLineSeparator());
					writer.flush();
				}
			}
		} catch (IOException e) {
			throw new RuntimeException("文件写入失败,可能缺失权限或没有指定的盘符", e);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 添加多行字符到指定文件
	 * 
	 * @param filePath
	 *            文件目录
	 * @param arrays
	 *            写入数据,每个不等于null的对象为作为一行数据
	 * @param append
	 *            追加,append为true则表示不覆盖原文件,只追加内容;否则覆盖原文件
	 */
	public static void writerData(String filePath, Object[] arrays,
			boolean append) {
		FileWriter writer = null;
		try {
			writer = new FileWriter(filePath, append);
			for (Object line : arrays) {
				if (line != null) {
					writer.write(String.valueOf(line));
					writer.write(getLineSeparator());
				}
			}
		} catch (IOException e) {
			throw new RuntimeException("文件写入失败,可能缺失权限或没有指定的盘符", e);
		} finally {
			if (writer != null) {
				try {
					writer.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	private static String getLineSeparator() {
		String osName = System.getProperty("os.name");
		if (osName != null && osName.contains("Windows")) {
			return "\r\n";
		}
		return "\n";
	}
}

2、读取字符流步骤

      1)创建一个文件读取流对象(FileReader Object),和指定名称的文件相关联。如果指定的文件不存在,将会抛出FileNotFoundException异常。

      2)调用read()方法。read():一次读一个字符;read(char[] cbuffer):通过字符数组读取,每次读取都返回填充到数组的字符量

      3)为避免资源泄漏,读取后必须调用close()关闭流。

使用示例:

package com.dawndaybreak.ioexample;

import java.io.FileReader;
import java.io.IOException;

/**
 * 
 * 通过FileReader实现对指定文件的读取操作
 * 
 * @author dawn
 * 
 */
public class ReaderDemo {

	public static void main(String[] args) {
		String filePath = "E:/demo.txt";
		String content = FileReaderUtils.readData(filePath);
		System.out.println(content);
		System.out.println(FileReaderUtils.readDataByBuffer(filePath));
	}

}

class FileReaderUtils {
	/**
	 * 逐字符读取
	 * 
	 * @param filePath
	 *            文件目录
	 * @return 文件内容
	 */
	public static String readData(String filePath) {
		FileReader reader = null;
		StringBuffer buffer = new StringBuffer();
		try {
			reader = new FileReader(filePath);
			int ch = -1;
			while ((ch = reader.read()) != -1) {
				buffer.append((char) ch);
			}
		} catch (IOException e) {
			throw new RuntimeException("读取失败,可能引起的元:指定文件不存在或其他因素", e);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return buffer.toString();
	}

	/**
	 * 通过数组读取数据
	 * 
	 * @param filePath
	 *            文件目录
	 * @return 文件内容
	 */
	public static String readDataByBuffer(String filePath) {
		FileReader reader = null;
		StringBuffer buffer = new StringBuffer();
		try {
			reader = new FileReader(filePath);
			int len = -1;
			char[] cbuffer = new char[1024];
			while ((len = reader.read(cbuffer)) != -1) {
				buffer.append(new String(cbuffer, 0, len));
			}
		} catch (IOException e) {
			throw new RuntimeException("读取失败,可能引起的元:指定文件不存在或其他因素", e);
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return buffer.toString();
	}
}

文件的拷贝:

文件的拷贝其实就是将一个目的地的数据读取写入到另一个目的地

package com.dawndaybreak.ioexample;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;

/**
 * 复制文件,可复制图片、文本、压缩包等
 * 
 * @author dawn
 * 
 */
public class CopyFile {

	public static void main(String[] args) {
		// copy file
		String sourcePath = "E:/demo.txt";
		String outputPath = "E:/demo-new.txt";
		FileUtil.getInstance().copyFile(sourcePath, outputPath);
	}

}

class FileUtil {

	private Reader reader;
	private Writer writer;

	public static FileUtil getInstance() {
		return new FileUtil();
	}

	private FileUtil() {
	}

	/**
	 * 复制文件到另一个目录
	 * 
	 * @param sourcePath
	 *            源文件目录,即被复制文件目录
	 * @param outputPath
	 *            输出文件目录,即复制出来的文件存放目录
	 */
	public void copyFile(String sourcePath, String outputPath) {
		try {
			initStream(sourcePath, outputPath);

			copyFileToDir();
		} catch (Exception e) {
			throw new RuntimeException("文件复制失败", e);
		} finally {
			close();
		}
	}

	private void close() {
		if (reader != null) {
			try {
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (writer != null) {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void copyFileToDir() throws IOException {
		int ch = -1;
		while ((ch = reader.read()) != -1) {
			writer.write(ch);
		}
		writer.flush();
	}

	private void initStream(String sourcePath, String outputPath)
			throws IOException {
		reader = new FileReader(sourcePath);
		writer = new FileWriter(outputPath);
	}
}

三、字符流的缓冲区——BufferReader、BufferWriter

概述

1、缓冲区的出现是为了提高流的读写效率,所以在缓冲区创建之前,要先创建流对象,并将流对象作为参数传递给缓冲区的构造函数。

2、缓冲技术原理:在此对象中封装一个数字,每次将数据写入数字,在数组装满数据会调用flush方法后将数据一次性取出写入文件。

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

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

            如:FileWriter fw = new FileWriter("E:/demo.txt");

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

           如:BufferedWriter bw = new BufferedWriter(fw);

      3)调用write方法写入数据到目标文件

           如:bw.write("Hello World!");

      4)刷新缓冲区(只要用到缓冲区,就要记得刷新。关闭流同样会刷新,为了排除意外事故,保证数据存在,建议写入一次就刷新一次)

            如:bw.flush();

      5)关闭缓冲区(缓冲区操作的是流对象,关闭缓冲区其实就是关闭缓冲区中的流对象)

           如:bw.close();

技巧:BufferedWriter提供了一个跨平台的换行符newLine()方法,可以在不同操作系统上调用,用作数据换行。

            如:bw.newLine();

4、读取流缓冲区BufferedReader

       该缓冲区提供了一个一次读一行的方法readLine,方便于堆文本数据的获取,当返回null时表示读到文件末尾。readLine方法返回的时候,只返回回车符之前的数据内容。并不返回回车符。

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

读取缓冲区BufferedReader的步骤

      1)创建一个读取流对象和文件相关联
         如:FileReader fr = new FileReader("E:\demo.txt");
      2)为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲区对象的构造函数。
         如:BufferedReader br = new BufferedReader(fr);
      3)调用该缓冲区提供的readLine方法一行一行读取,如果到达文件末尾,则返回null
         如:String sLine = br.readLine();
      4)关闭流资源
         如:br.close();

使用示例(复制一个文本文件):

package com.dawndaybreak.ioexample;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
 * 复制文本文件,通过缓冲技术
 * 
 * @author dawn
 * 
 */
public class BufferedCopyFile {

	public static void main(String[] args) {
		// // copy file
		// String sourcePath = "E:/demo.txt";
		// String outputPath = "E:/demo-new.txt";
		// FileUtil.getInstance().copyFile(sourcePath, outputPath);
		// copy image file
		String sourcePath = "E:/img1.jpg";
		String outputPath = "E:/img2.jpg";
		FileUtil.getInstance().copyFile(sourcePath, outputPath);
	}

}

class BufferedFileUtil {

	private BufferedReader reader;
	private BufferedWriter writer;

	public static BufferedFileUtil getInstance() {
		return new BufferedFileUtil();
	}

	private BufferedFileUtil() {
	}

	/**
	 * 复制文件到另一个目录
	 * 
	 * @param sourcePath
	 *            源文件目录,即被复制文件目录
	 * @param outputPath
	 *            输出文件目录,即复制出来的文件存放目录
	 */
	public void copyFile(String sourcePath, String outputPath) {
		try {
			initStream(sourcePath, outputPath);

			copyFileToDir();
		} catch (Exception e) {
			throw new RuntimeException("文件复制失败", e);
		} finally {
			close();
		}
	}

	private void close() {
		if (reader != null) {
			try {
				reader.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if (writer != null) {
			try {
				writer.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	private void copyFileToDir() throws IOException {
		String sLine = null;
		while ((sLine = reader.readLine()) != null) {
			writer.write(sLine);
		}
		writer.flush();
	}

	private void initStream(String sourcePath, String outputPath)
			throws IOException {
		reader = new BufferedReader(new FileReader(sourcePath));
		writer = new BufferedWriter(new FileWriter(outputPath));
	}
}

5、自定义缓冲区

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

实现步骤:

       1)初始化自定义类,加入流对象;

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

实现示例(以BufferedReader为例):

package com.dawndaybreak.ioexample;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

/**
 * 测试自定义BufferedReader
 * 
 * @author dawn
 *
 */
public class MyBufferedReaderTest {
	public static void main(String[] args) throws Exception {
		MyBufferedReader br = new MyBufferedReader(new FileReader("E:/demo.txt"));
		String sLine = null;
		while ((sLine = br.readLine()) != null) {
			System.out.println(sLine);
		}
		br.close();
	}
}

/**
 * 实现自定义BufferedReader
 * 
 * @author dawn
 *
 */
class MyBufferedReader extends Reader {
	
	private Reader reader;
	
	public MyBufferedReader(Reader reader) {
		this.reader = reader;
	}
	
	public String readLine() throws IOException {
		StringBuilder sb = new StringBuilder();
		int ch = -1;
		while ((ch = reader.read()) != -1) {
			if (ch == '\r') {// 遇到换行符继续
				continue;
			} else if (ch == '\n') {// 遇到回车符表示读取完毕
				return sb.toString();
			} else {
				sb.append((char) ch);
			}
		}
		if (sb.length() != 0) {
			return sb.toString();
		}
		return null;
	}

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

	@Override
	public void close() throws IOException {
		if (reader != null) {
			reader.close();
		}
	}

}

6、LineNumberReader

      在BufferedReader中有个直接子类LineNumberReader,LineNumberReader提供了特有的方法设置开始行号或获取每行行号:

              public void setLineNumber(int lineNumber)// 设置开始行号

              public int getLineNumber()// 获取每行行号

使用示例:

package com.dawndaybreak.ioexample;

import java.io.FileReader;
import java.io.LineNumberReader;

/**
 * 
 * @author 通过LineNumberReader特有的方法,设置/获取行号
 * 
 */
public class LineNumberReaderDemo {

	public static void main(String[] args) throws Exception {
		LineNumberReader lineReader = new LineNumberReader(new FileReader(
				"E:/demo.txt"));
		lineReader.setLineNumber(9);// 设置开始行号
		while (lineReader.readLine() != null) {
			// 输出每一行行号
			System.out.println(lineReader.getLineNumber());
		}
		lineReader.close();
	}

}

四、装饰者设计模式

1、当想对已有对象进行功能增强时,可定义类:将已有对象传入,基于已有对象的功能,并提供加强功能,那么自定义的该类称之为装饰类。例如:Java IO体系。
2、装饰类通常都会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
3、装饰和继承的区别:
      1)装饰模式比继承要灵活。避免了继承体系的臃肿,且降低了类与类之间的关系。
      2)装饰模式比继承要灵活。避免了继承体系的臃肿,且降低了类与类之间的关系。
      3)从继承结构转为组合结构。
      4)在定义类的时候,不要以继承为主;可通过装饰设计模式进行增强类功能。灵活性较强,当装饰类中的功能不适合,可再使用被装饰类的功能。


第三讲 字节流

一、概述

1、字节流和字符流的基本操作是相同的,但字节流还可以操作其他媒体文件。
2、由于媒体文件数据中都是以字节存储的,所以,字节流对象可直接对媒体文件的数据写入到文件中,而可以不用再进行刷流动作。
3、读写字节流:InputStream(输入流,用于读操作)、OutputStream(输出流,用于写操作)
4、为何不用进行刷流动作:因为字节流操作的是字节,即数据的最小单位,不需要像字符流一样要进行转换为字节。所以可直接将字节数据写入到指定文件中。
5、InputStream特有方法:
      public int available();// 返回文件中的字节个数(即有效内容长度)
6、一般在文件较小时可用available方法来指定数组长度,从而省去循环判断。但是如果文件较大,此时数组长度过长,很有可能就造成内存溢出。

使用示例(copy图片):

package com.dawndaybreak.ioexample;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 复制图片,也可复制其他文件
 * 
 * @author dawn
 * 
 */
public class CopyImage {

	public static void main(String[] args) {
		String filePath = "E:/img1.jpg";
		String outputPath = "E:/img2.jpg";
		String outputPathNext = "E:/img3.jpg";
		getInstance().byByteArrayCopy(filePath, outputPath);
		getInstance().byAvailableCopy(filePath, outputPathNext);
	}

	private InputStream is = null;
	private OutputStream os = null;

	public static CopyImage getInstance() {
		return new CopyImage();
	}

	/**
	 * 使用available方法一次性读取所有内容,写入目的文件,当文件较大时,极有可能造成内存溢出
	 * 
	 * @param filePath
	 * @param outputPath
	 */
	public void byAvailableCopy(String filePath, String outputPath) {
		try {
			initStream(filePath, outputPath);
			// 一次读取所有字节内容,如果文件较大,极有可能会造成内存溢出
			byte[] buffer = new byte[is.available()];
			is.read(buffer);
			os.write(buffer);
		} catch (Exception e) {
		} finally {
			close();
		}
	}

	private void initStream(String filePath, String outputPath)
			throws FileNotFoundException {
		is = new FileInputStream(filePath);
		os = new FileOutputStream(outputPath);
	}

	/**
	 * 普通复制方法,通过字节数组复制
	 * 
	 * @param filePath
	 *            目标文件路径
	 * @param outputPath
	 *            输出目标路径
	 */
	public void byByteArrayCopy(String filePath, String outputPath) {
		try {
			initStream(filePath, outputPath);
			// 一次读取1024个字节
			byte[] buffer = new byte[1024];
			int len = -1;
			while ((len = is.read(buffer)) != -1) {
				os.write(buffer, 0, len);
				os.flush();
			}
		} catch (Exception e) {
		} finally {
			close();
		}
	}

	private void close() {

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

二、字节流缓冲区

与字符流缓冲区一样,为了提高流的读写效率。

1、读写特点:
      read():会将字节byte型值提升为int型值
      write():会将int型强转为byte型,即保留二进制数的最后八位。
2、原理:将数据拷贝一部分,读取一部分,循环,直到数据全部读取完毕。
      1)先从数据中抓取固定数组长度的字节,存入定义的数组中,再通过然后再通过read()方法读取数组中的元素,存入缓冲区。
      2)循环这个动作,直到最后取出一组数据存入数组,可能数组并未填满,同样也取出包含的元素。
      3)每次取出的时候,都有一个指针在移动,取到数组结尾就自动回到数组头部,这样指针在自增。
      4)取出的时候,数组中的元素在减少,取出一个,就减少一个,直到减到0即元素取完。
      5)当文件中的全部数据都被读取出时,read()方法就返回-1。
3、自定义读取字节流缓冲区
      需求:根据字节流缓冲区的原理,自定义一个字节流缓冲区。
      注意:
              1、字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
              2、因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
              3、所以,为了避免这种情况将读到的字节进行int类型的提升。并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。而在写入数据时,只写该int类型数据的最低8位。
              4、byte类型的-1提升为int类型时还是-1。原因:因为在bit8个1前面补的全是1导致的。如果在bit8个1前面补0,即可以保留原字节数据不变,又可以避免-1的出现。这时将byte型数据&0xff即255即可。

自定义示例:

package com.dawndaybreak.ioexample;

import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 自定义字节缓冲,实现文件复制
 * 
 * @author dawn
 *
 */
public class MyBufferedInputStream extends InputStream {

	public static void main(String[] args) {
		MyBufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		try {
			bis = new MyBufferedInputStream(new FileInputStream("E:/img1.jpg"));
			bos = new BufferedOutputStream(new FileOutputStream("E:/img2.jpg"));
			int by = -1;
			while ((by = bis.read()) != -1) {
				bos.write(by);
			}
		} catch (Exception e) {
		} finally {
			try {
				if (bis != null) {
					bis.close();
				}
				if (bos != null) {
					bos.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	private InputStream is;
	private byte[] buffer = new byte[1024];
	private int count = 0, pos = 0;

	public MyBufferedInputStream(InputStream is) {
		this.is = is;
	}

	@Override
	public int read() throws IOException {
		if (count == 0) {// 第一次调用read方法
			count = is.read(buffer);
			if (count < 0) {
				return -1;
			}
			// 初始化指针
			pos = 0;
			byte b = buffer[pos];

			count--;// 每读取一个字节,表示数组中的字节数就少一个
			pos++;// 指针+1,下次放访问buffer数组,指向下个位置
			// 将返回的byte数据提升为int类型,字节数增加,且高24位被补1,原字节数据改变。
			// 通过&255,主动将byte类型提升为int类型,提高24位补0,原字节数据不变。
			// 在输出字节流写入数据时,只写该int数据的最低8位。
			return b & 255;
		} else if (count > 0) {// 不是第一次调用,且buffer中数据没有读取完时
			byte b = buffer[pos];
			count--;
			pos++;
			return b & 0xff;
		}
		return -1;
	}

	@Override
	public void close() throws IOException {
		if (is != null) {
			is.close();
		}
	}

}

第四讲 操作流规律

一、键盘录入

1、标准输入输出流
      System.in:对应的标准输入设备:键盘;对应的类型:InputStream。
      System.out:对应的标准输出设备:控制台;对应的类型:PrintStream,是OutputStream的子类FileOutputStream的子类。
2、整行录入
      当使用输入流进行键盘录入时,只能一个字节一个字节进行录入。为了提高效率,可以自定义一个数组进行存储。当一行录入完毕,再将一行数据进行显示。这种整行录入的方式,和字符流读一行数据的原理是一样的。也就是readLine()。
      那么能不能直接使用readLine()来完成键盘录入的一行数据的读取呢?readLine()是字符流BufferedReader类中方法。而键盘录入的read()是字节流InputStream的方法。
      那么能不能将字节流转成字符流再使用字符流缓冲区的readLine()呢?这就需要用到转换流了。
3、转换流
3.1、转换流的由来:
      1)字符流与字节流直接的桥梁;
      2)方便了字符流与字节流之间的操作;
      3)字节流中的数据都是字符时,转成字符流操作更高效。
3.2、InputStreamReader将字节流通向字符流
      1)获取键盘录入对象
         InputStream in = System.in;
      2)将字节流对象转成字符流对象,使用转换流
         InputStreamReader isr = new InputStreamReader(in);
      3)为了提高效率,使用BufferedReader将字符串进行缓冲区技术高效操作。
         BufferedReader br = new BufferedReader(isr);
      4)上面三个步骤的合并写法
         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
3.3、OutputStreamWriter字符流通向字节流
      字符流通向字节流:录入的是字符,存到硬盘上的是字节。步骤和InputStreamReader转换流一样。

使用示例:

package com.dawndaybreak.ioexample;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

/**
 * 将键盘录入的数据显示在控制台,当输入0时,退出程序
 * 
 * @author xuHaiLiang
 * 
 */
public class TransformStreamDemo {

	public static void main(String[] args) throws IOException {
		// 将键盘录入,字节流通向字符流
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		// 控制输出,字符流通向字节流
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
				System.out));

		String s = null;
		while ((s = br.readLine()) != null) {
			if ("0".equals(s)) {
				break;
			}
			bw.write(s);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
	}

}

二、操作流规律
1、
      源:键盘录入。
      目的:控制台。
2、需求:想把键盘录入的数据存储到一个文件中。
      源:键盘
      目的:文件。
      使用字节流通向字符流的转换流(桥梁):InputStreamReader
3、需求:想要将一个文件的数据打印在控制台上。
      源:文件
      目的:控制台
      使用字符流通向字节流的转换流(桥梁):OutputStreamWriter
4、流操作的基本规律:
      最痛苦的就是流对象有很多,不知道该用哪一个。
通过三个明确来完成:
4.1、明确源和目的。
        源:输入流。InputStream  Reader
        目的:输出流。OutputStream  Writer
4.2   操作的数据是否是纯文本。
        是:字符流
        否:字节流
4.3   当体系明确后,再明确要使用哪个具体的对象。通过设备来进行区分:
        源设备:内存,硬盘,键盘
        目的设备:内存,硬盘,控制台
5、规律体现
5.1 将一个文本文件中数据存储到另一个文件中。复制文件。
        1)源:因为是源,所以使用读取流:InputStream和Reader
             明确体系:是否操作文本:是,Reader
             明确设备:明确要使用该体系中的哪个对象:硬盘上的一个文件。Reader体系中可以操作文件的对象是FileReader
             是否需要提高效率:是,加入Reader体系中缓冲区 BufferedReader.
             FileReader fr = new FileReader("a.txt");
             BufferedReader bufr = new BufferedReader(fr);
        2)目的:输出流:OutputStream和Writer
             明确体系:是否操作文本:是,Writer
             明确设备:明确要使用该体系中的哪个对象:硬盘上的一个文件。Writer体系中可以操作文件的对象FileWriter。
             是否需要提高效率:是,加入Writer体系中缓冲区 BufferedWriter
             FileWriter fw = new FileWriter("b.txt");
             BufferedWriter bufw = new BufferedWriter(fw);
练习:将一个图片文件中数据存储到另一个文件中。复制文件。要按照以上格式自己完成三个明确。
        1)源:输入流,InputStream和Reader
            是否是文本?否,InputStream
            源设备:硬盘上的一个文件。InputSteam体系中可以操作文件的对象是FileInputSteam
            是否需要提供效率:是,BufferedInputStream
            BufferedInputSteambis=newBufferedInputStream(newFileInputStream("c:/users/asus/desktop/1.jpg"));
        2)目的:输出流,OutputStream和Writer
             是否是文本?否,OutputStream
             源设备:硬盘上的文件,FileOutputStream
             是否需要提高效率:是,加入BufferedOutputStream
             BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream("c:/users/asus/desktop/2.jpg"));
5.2 需求:将键盘录入的数据保存到一个文件中。
        1)源:InputStream和Reader
              是不是纯文本?是,Reader
              设备:键盘。对应的对象是System.in。——为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转换成Reader。用Reader体系中转换流,InputStreamReader
              InputStreamReaderisr = new InputStreamReader(System.in);
             需要提高效率吗?需要,BufferedReader
             BufferedReaderbufr = new BufferedReader(isr);
       2)目的:OutputStream  Writer
             是否是存文本?是!Writer。
             设备:硬盘。一个文件。使用 FileWriter。
             FileWriter fw = newFileWriter("c.txt");
            需要提高效率吗?需要。
            BufferedWriter bufw = new BufferedWriter(fw);
5.3   扩展:想要把录入的数据按照指定的编码表(UTF-8)(默认编码表是GBK),将数据存到文件中。
        目的:OutputStream  Writer
        是否是存文本?是!Writer。
        设备:硬盘上的一个文件。使用 FileWriter。——但是FileWriter是使用的默认编码表:GBK。而存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。
        该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流:FileOutputStream
        OutputStreamWriter osw =new OutputStreamWriter(newFileOutputStream("d.txt"),"UTF-8");
        需要高效吗?需要,BufferedWriter
        BufferedWriter bufw = new BufferedWriter(osw);
记住:
       转换流什么使用?
       字符和字节之间的桥梁。通常,涉及到字符编码转换时,需要用到转换流。
练习:将一个文本数据打印在控制台上。要按照以上格式自己完成三个明确。
        1)源:InputStreamReader
            是文本?是:Reader
            设备:硬盘。上的文件:FileReader
            是否需要提高效率?是:BufferedReader
             BufferedReader br=new BufferedReader(newFileReader("1.txt"));
       2)目的:OutputStream Writer
            是文本?是:Writer
            设备:控制台。对应对象System.out。由于System.out对应的是字节流,所以利用OutputSteamWriter转换流
            是否提高效率?是:BufferedWriter
            BufferedWriter bw =new BufferedWriter(newOutputStreamWriter(system.out));

示例(将键盘录入写入文件):

	public static void main(String[] args) throws IOException {
		// 将键盘录入,字节流通向字符流
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		// 控制输出,字符流通向字节流
//		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
//				System.out));
		// 将键盘录入内容存储到文件
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/demo.txt")));

		String s = null;
		while ((s = br.readLine()) != null) {
			if ("0".equals(s)) {
				break;
			}
			bw.write(s);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
	}

示例(将文件内容显示在控制台):

public static void main(String[] args) throws IOException {
		// 将键盘录入,字节流通向字符流
//		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("E:/demo.txt")));
		// 控制输出,字符流通向字节流
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
				System.out));
//		 将键盘录入内容存储到文件
//		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:/demo.txt")));

		String s = null;
		while ((s = br.readLine()) != null) {
			if ("0".equals(s)) {
				break;
			}
			bw.write(s);
			bw.newLine();
			bw.flush();
		}
		br.close();
		bw.close();
	}

示例(异常信息输出到文件):

package com.dawndaybreak.ioexample;

import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Log {

	public static void main(String[] args) {
		try {
			System.out.println(Integer.parseInt("dd"));
		} catch (Exception e) {
			try {
				PrintStream pw = new PrintStream("E:/log.log");
				System.setOut(pw);
				System.out.println(new SimpleDateFormat("yyyy-MM-dd")
						.format(new Date()));
			} catch (FileNotFoundException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace(System.out);
		}
	}

}

示例(将系统属性存入文件):

	public static void main(String[] args) throws FileNotFoundException {
		Properties p = System.getProperties();
		p.list(new PrintStream("E:/systemProperties.txt"));
	}

通过System.setIn()、System.setOut(0可以对默认设备进行更改

System.setIn(newFileInputStream("demo.txt"));//将源改成文件demo.txt
System.setOut(newFileOutputStream("demo2.txt"));//将目的改成文件demo2.txt

流的基本应用小结:

       1)流是用来处理数据的。
        2)处理数据时,一定要先明确数据源,与数据目的地(数据汇)。
        3)数据源可以是文件,可以是键盘。
        4)数据目的地可以是文件、显示器或者其他设备。
        5)而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理.转换处理等。




你可能感兴趣的:(Java学习笔记)