JAVA之 IO流

一、IO流
流就是字节序列的抽象概念,能被连续读取数据的数据源和能被连续写入数据的接收端就是流,流机制是Java及
C++中的一个重要机制,通过流我们可以*地控制文件、内存、IO设备等数据的流向。而IO流就是用于处理
设备上的数据,如:硬盘、内存、键盘录入等。
IO流根据处理类型的不同可分为字节流和字符流,根据流向的不同可分为输入流和输出流。
 
二、字节流和字符流
字符流,因为文件编码的不同,就有了对字符进行高效操作的字符流对象,它的原理就是基于字节流读取字节时
去查了指定的码表。它和字节流的区别有两点:1.在读取数据的时候,字节流读到一个字节就返回一个字节,
字符流使用了字节流读到一个或多个字节(一个中文对应的字节数是两个,在UTF-8码表中是3个字节)时,
先去查指定的编码表,再将查到的字符返回;2.字节流可以处理所有类型的数据,如jpg、avi、mp3、wav等等,
而字符流只能处理字符数据。所以可以根据处理的文件不同考虑使用字节流还是字符流,
如果是纯文本数据可以优先考虑字符流,否则使用字节流。

JAVA之 IO流

 

三、IO体系,整个java.io的核心都是采用了Decorator(装饰)模式。

JAVA之 IO流

 

1、字符流,Reader(读) Write(写),一个字符二个字节.
Reader中常见的方法:
int read();读取一个字符,并返回读到的这个字符,读到流的末尾则返回-1。
int read(char[]);将读到的字符存入指定的数组中,返回的是读到的字符个数,读到流的末尾则返回-1。
close();读取字符其实用的是window系统的功能,每次使用完毕后,需进行资源的释放。
Writer中常见的方法:
write();将一个字符写入到流中。
write(char[]);将一个字符数组写入到流中。
writer(String);将一个字符写入到流中。
flush();刷新流,将流中的数据刷新到目的地中,流还存在。
close();关闭资源,在关闭前会先调用flush(),刷新流中的数据到目的地。

 

2、字符流的缓冲区:
缓冲区的出现提高了对流的操作效率。原理:其实就是将数组进行封装。
对应的对象
(1)BufferedWriter
特有方法newLine(),跨平台的换行符。
(2)BufferedReader
特有方法readLine(),一次读一行,到行标记时,将行标记之前的字符数据作为字符串返回,读到末尾返回null。
说明:在使用缓冲区对象时,要明确,缓冲的存在是为了增强流的功能而存在,所以在建立缓冲区对象时,
要先有流对象存在。其实缓冲区内部就是在使用流对象的方法,只不过加入了数组对数据进行了临时存储,
为了提高操作数据的效率。

 

3、字节流(一个字节8个二进制位)
InputStream(读)
OutputStream(写)
由于字节是二进制数据,所以字节流可以操作任何类型的数据,值得注意的是字符流使用的是字符数组char[]
而字节流使用的是字节数组byte[]。


4、转换流

(1)特点:是字节流和字符流之间的桥梁,该流对象可以对读取到的字节数据进行指定编码表的编码转换。

(2)何时使用:当字节和字符之间有转换动作时或流操作的数据需要进行编码表的指定时。

(3)具体对象体现:
InputStreamReader: 字节到字符的桥梁
OutputStreamWriter:字符到字节的桥梁
说明:这两个流对象是字符流体系中的成员,它们有转换的作用,而本身又是字符流,所以在new的时候
需要传入字节流对象。
构造函数
InputStreamReader(InputStream),通过该构造函数初始化,使用的是系统默认的编码表GBK。
InputStreamReader(InputStream,String charset),通过该构造函数初始化,
可以通过charset参数指定编码。
OutputStreamWriter(OutputStream),使用的是系统默认的编码表GBK。
OutputStreamWriter(OutputSream,String charset),通过该构造函数初始化,
可以通过参数charset指定编码。


(4)操作文件的字符流对象是转换流的子类
       |--Reader
           |--InputStreamReader(转换流)
              |--FileReader(文件字符流)
       |--Writer
           |--OutputStreamWriter(转换流)
              |--FileWriter(文件字符流)
说明:转换流中的read方法,已经融入了编码表,在底层调用字节流的read方法时将获取的一个或者多个
字节数据进行临时存储,并去查指定的编码表,如果编码没有指定,则使用默认编码表。既然转换流已经完成了
编码转换的动作,对于直接操作的文本文件的FileReader而言,就不用再重新定义了,只要继承该转换流,
获取其方法,就可以直接操作文本文件中的字符数据了。
注意:在使用FileReader操作文本数据时,该对象使用的是默认的编码表,如果要使用指定的编码表,
必须使用转换流。

 

5、流操作的基本规律
(1)明确数据源和数据汇(数据目的),其实是为了明确是输入流还是输出流
(2)明确操作的数据是否是纯文本数据
说明:
数据源:键盘System.in、硬盘、File开头的流对象、内存(数组)。
数据汇:控制台System.out、硬盘、File开头的流对象、内存(数组)。

事例:将键盘录入的数据存储到一个文件中和打印到控制台

(1)数据源System.in
既然是源,使用的就是输入流,可用的体系有InputStream、Reader。
因为键盘录入进来的一定是纯文本数据,所以可以使用专门操作字符数据的Reader。
而System.in对应的流是字节读取流,所以要将其进行转换,将字节转换成字符即可,
所以要使用Reader体系中的InputStreamReader,如果要提高效率,就使用BufferedReader,代码如:
BufferedReader bur=new BufferedReader(newInputStreamReader(Sysem.in));
(2)数据汇:一个文件、硬盘
数据汇一定是输出流,可以用的体系有OutputStream、Writer。往文件中存储的都是文本数据,
那么可以使用字符流较为方便Writer。因为操作的是一个文件,所以使用Writer中的FileWriter,同理,
要提高效率就要使用BufferedWriter。代码如:
BufferedWriter bufr=new BufferedWriter(new FileWriter("test.txt"));
示例代码如下:将键盘录入的数据存储到一个文件中和打印到控制台代码

 

 

private static void test(){ 
    BufferedReader bur=null; 
    OutputStreamWriter osw=null; 
    BufferedWriter bw=null; 
    try{ 
        //数据源 
        bur=new BufferedReader(new InputStreamReader(System.in)); 
        //数据汇 
        osw=new OutputStreamWriter(System.out); 
        //数据汇,因为数据源用的是系统默认编码,所以这里可以直接使用FileWriter 
        //否则必须使用OutputStreamWriter转换流 
        bw=new BufferedWriter(new FileWriter("D:\\test_target.txt")); 
        String line=null; 
        while((line=bur.readLine())!=null){ 
            osw.write(line); 
            osw.flush();//刷新到控制台 
            bw.write(line); 
            bw.flush();//刷新到文本文件 
        }    
    }catch(IOException e){ 
        e.toString(); 
    }finally{ 
        try{ 
            if(osw!=null){ 
                osw.close(); 
            } 
            if(bur!=null){ 
                bur.close(); 
            } 
            if(bw!=null){ 
                bw.close(); 
            } 
        }catch(IOException ex){ 
            ex.toString(); 
        } 
    } 
} 

 

四、IO流操作具体事例

/**
 * 字节流:FileInputStream,FileOutputStream 
 * 字符流:FileReader,FileWriter
 * 缓冲流:BufferedInputStream,BufferedOutputStream(缓冲字节)
 * BufferedReader,BufferedWriter(缓冲字符)
 */
public class JavaIORead {

	/**
	 * 以字节为单位读取文件,常用于读二进制文件,如图片、声音、影像等文件。
	 * (字节流:FileInputStream,FileOutputStream)
	 */
	public static void readFileByBytes(String fileName) {
		File file = new File(fileName);
		InputStream in = null;
		// 以字节为单位读取文件内容,一次读一个字节
		try {
			System.out.println("以字节为单位读取文件内容,一次读一个字节:");
			// 一次读一个字节
			in = new FileInputStream(file);
			int tempbyte;
			// 读取到文件末尾则为-1
			while ((tempbyte = in.read()) != -1) {
				// 直接写入到控制台
				System.out.write(tempbyte);
			}
			in.close();
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
		// 以字节为单位读取文件内容,一次读多个字节,例如:一个汉字占二个字节,可一次读取二个字节
		// 才能正常显示。
		try {
			System.out.println("以字节为单位读取文件内容,一次读多个字节:");
			// 一次读多个字节
			byte[] tempbytes = new byte[100];
			int byteread = 0;
			in = new FileInputStream(fileName);
			JavaIORead.showAvailableBytes(in);
			// 读入多个字节到字节数组tempbytes中,byteread为一次读入的字节数(实际读取的)
			StringBuffer strb = new StringBuffer();
			while ((byteread = in.read(tempbytes)) != -1) {
				// 必须写入0到byteread,否则读取的内容则为整个tempbytes,或包含空串。
				System.out.write(tempbytes, 0, byteread);
				// 转化为字符形式输出(需进行编码转换,编码与文件本身的编码一致)
				String str = new String(tempbytes, "utf-8");
				strb.append(str);
			}
			System.out.println(strb.toString());
		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	/*
	 * 以字符为单位读取文件,常用于读文本,数字等类型的文件 
	 * (字符流:FileReader,FileWriter)	 * 
	 */
	public static void readFileByChars(String fileName) {
		File file = new File(fileName);
		Reader reader = null;
		try {
			System.out.println("以字符为单位读取文件内容,一次读一个字符:");
			// 一次读一个字符,可用FileReader再进行外包装
			reader = new InputStreamReader(new FileInputStream(file), "GBK");
			int tempchar;
			while ((tempchar = reader.read()) != -1) {
				// 对于windows下,这两个字符在一起时,表示一个换行。
				// 但如果这两个字符分开显示时,会换两次行。
				// 因此,屏蔽掉,或者屏蔽。否则,将会多出很多空行。
				// if (((char) tempchar) != ' ') {
				System.out.print((char) tempchar);
				// }
			}
			reader.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			System.out.println("以字符为单位读取文件内容,一次读多个字符:");
			// 一次读多个字符
			char[] tempchars = new char[30];
			int charread = 0;
			// 在inputStreamReader上构造inputStream,并进行编码转换
			reader = new InputStreamReader(new FileInputStream(fileName), "GBK");
			// 读入多个字符到字符数组中,charread为一次读取字符数
			while ((charread = reader.read(tempchars)) != -1) {
				// 同样屏蔽掉不显示
				if ((charread == tempchars.length)
						&& (tempchars[tempchars.length - 1] != ' ')) {
					System.out.print(tempchars);
				} else {
					for (int i = 0; i < charread; i++) {
						if (tempchars[i] == ' ') {
							continue;
						} else {
							System.out.print(tempchars[i]);
						}
					}
				}
			}

		} catch (Exception e1) {
			e1.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	/**
	 * 以行为单位读取文件,常用于读面向行的格式化文件
	 */
	public static void readFileByLines(String fileName) {
		File file = new File(fileName);
		BufferedReader reader = null;
		try {
			System.out.println("以行为单位读取文件内容,一次读一整行:");
			// 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取,
			// 用来包装reader.
			reader = new BufferedReader(new FileReader(file));
			String tempString = null;
			int line = 1;
			// 一次读入一行,直到读入null为文件结束
			while ((tempString = reader.readLine()) != null) {
				// 显示行号
				System.out.println("line " + line + ": " + tempString);
				line++;
			}
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (reader != null) {
				try {
					reader.close();
				} catch (IOException e1) {

				}
			}
		}
	}

	/**
	 * 随机读取文件内容
	 */
	public static void readFileByRandomAccess(String fileName) {
		RandomAccessFile randomFile = null;
		try {
			System.out.println("随机读取一段文件内容:");
			// 打开一个随机访问文件流,按只读方式
			randomFile = new RandomAccessFile(fileName, "r");
			// 文件长度,字节数
			long fileLength = randomFile.length();
			// 读文件的起始位置
			int beginIndex = (fileLength > 100) ? 100 : 0;
			// 将读文件的开始位置移到beginIndex位置。
			randomFile.seek(beginIndex);
			byte[] bytes = new byte[10];
			int byteread = 0;
			// 一次读10个字节,如果文件内容不足10个字节,则读剩下的字节。
			// 将一次读取的字节数赋给byteread
			while ((byteread = randomFile.read(bytes)) != -1) {
				System.out.write(bytes, 0, byteread);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (randomFile != null) {
				try {
					randomFile.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	/**
	 * 缓冲读取文件内容,提升读取效率
	 * 
	 */
	public static void readFileByBuffer(String fileName) throws Exception {
		File file = new File(fileName);
		InputStream is = new BufferedInputStream(new FileInputStream(file));
		long length = file.length();
		if (length > Integer.MAX_VALUE) {
			System.out.println("source file is too large");
			return;
		}
		byte[] bytes = new byte[(int) length];
		int offset = 0, numRead = 0;
		StringBuffer strBuff = new StringBuffer();
		while (offset < bytes.length
				&& (numRead = is.read(bytes, offset, bytes.length - offset))>= 0){
			offset += numRead;
			String str = new String(bytes, "utf-8");
			strBuff.append(str);
		}
		System.out.println(strBuff.toString());
		if (offset < bytes.length)
			throw new IOException("Could not completely read file"
					+ file.getName());
		is.close();
	}

	/**
	 * 重定向标准的按制台输入输出
	 * 
	 */
	public static void setSystemStream() throws FileNotFoundException {
		PrintStream output = new PrintStream(new FileOutputStream("c:/out.log"));
		System.setOut(output);
		PrintStream errOutput = new PrintStream(new FileOutputStream(
				"c:/err.log"));
		System.setErr(errOutput);
		System.out.println("Output redirect test");
		System.err.println("Error redirect test");
		// 原来在控制台输出的文字都将被写入out.log或err.log文件中
	}

	/**
	 * 读取对象,对角的可串行化
	 * 
	 * @throws Exception
	 */
	public static void readFileByObject() throws Exception {
		// User对象需序列化(implements Serializable)
		User user1 = new User("1001", "张三", 25);
		User user2 = new User("1002", "李四", 30);
		FileOutputStream fos = new FileOutputStream("c:/user.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(user1);
		oos.writeObject(user2);
		oos.close();

		FileInputStream fis = new FileInputStream("c:/user.txt");
		ObjectInputStream ois = new ObjectInputStream(fis);
		for (int i = 0; i < 2; i++) {
			User user = (User) ois.readObject();
			System.out.println("userName:" + user.getName() + "  age:"
					+ user.getAge());
		}
		ois.close();
	}

	/**
	 * 复制文件
	 */
	public static void copyFile() {
		BufferedReader bur = null;
		BufferedWriter buw = null;
		try {
			bur = new BufferedReader(new InputStreamReader(new FileInputStream(
					"D:\\test_source.txt"), "UTF-8"));
			buw = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream("D:\\test_target.txt"), "UTF-8"));
			String line = null;
			while ((line = bur.readLine()) != null) {
				buw.write(line);
				buw.flush();// 刷新到文本文件
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (buw != null) {
					buw.close();
				}
				if (bur != null) {
					bur.close();
				}
			} catch (IOException ex) {
				ex.toString();
			}
		}
	}

	/**
	 * 显示输入流中还剩的字节数
	 * 
	 */
	private static void showAvailableBytes(InputStream in) {
		try {
			// available()方法为返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取
			// (或跳过)的估计剩余字节数
			System.out.println("当前字节输入流中的字节数为:" + in.available());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}