JavaSE学习笔记——IO原理

Day12-13

  • File类
    • 概念
    • 方法
    • 示例
      • 递归遍历文件
      • 筛选文件
      • 递归删除目录
      • 文件列表
  • IO原理
    • 流的分类
    • IO流体系
    • 文件字节流
      • FileInputStream
      • FileOutputStream
      • available()方法
      • 练习
    • 文件字符流
      • FileReader
      • FileWriter
      • 练习
    • 注意
    • 缓冲流
      • 缓冲字节流
        • BufferedInputStream
        • BufferedOutputStream
          • 练习
      • 缓冲字符流
        • BufferedReader
          • LineNumberReader
          • 装饰类实现LineNumberReader
        • BufferedWriter
          • 练习
      • 装饰设计模式
    • 转换流
    • 输入输出流
    • IO练习
    • 异常信息日志
    • 打印流
      • 概述
      • 示例
    • 合并流
      • 概述
      • 合并
      • 分割
    • 数据流
    • 对象流
      • 概念
      • 对象的序列化
      • 使用对象流序列化对象
    • 管道流
      • 方法
    • RandomAccessFile类
      • 随机访问
      • 构造器
      • 读写文件举例

File类

概念

  • java.io.File类: 文件和目录路径名的抽象表示形式,与平台无关。
  • File 能新建、删除、重命名文件和目录,但File不能访问文件内容本身。 如果需要访问文件内容本身,则需要使用输入/输出流。
  • File对象可以作为参数传递给流的构造函数。
  • File类的常见构造方法:
    public File(String pathName);
    以pathname为路径创建File对象,可以是绝对路径或者相对路径,如果pathname是相对路径,则默认的当前路径在系统属性user.dir中存储。
    public File(String parent, String child);
    以parent为父路径,child为子路径创建File对象。File的跨平台属性File.separator存储了当前系统的路径分隔符。在UNIX中,此字段为‘/’,在Windows中,为‘\\’。 即,可以使用“\\”或者“/”来表示目录关系。
  • 语法规则:File f = new Flie(“路径名”);
package io;

import java.io.File;
//三种写法相同
public class OpenFile {
	public static void main(String[] args) {
		File f = new File("a.txt");//相对路径(当前文件夹下)
		File f1 = new File("E:\\IO\\a.txt");//绝对路径
		File f2 = new File("E:\\IO", "a.txt");//(目录, 路径), 便于在相同目录下操作不同文件(路径固定, 文件变量)
		//跨平台文件路径分隔符File.separator
		File f3 = new File("E:"+File.separator+"IO"+File.separator+"a.txt");
	}
}

方法

注意:以下方法若无特殊说明,则对文件以及文件夹均有效。

方法 (访问文件名) 意义
getName() 获取File名称
getPath() 获取File路径
getAbsoluteFile() 获取用当前文件或文件夹的绝对路径构成的File对象
getAbsolutePath() 获取File的绝对路径
getParent() 获取上级目录名称
renameTo(File f) 修改File为新File f
方法 (文件检测) 意义
exists() 判断File是否存在
canRead() 判断File是否可读
canWrite() 判断File是否可写
isFile() 判断当前File是否是文件, 使用前必须保证文件存在
isDirectory() 判断当前File是否是目录,使用前必须保证目录存在
方法 (获取常规文件信息) 意义
lastModify() 获取文件最后一次修改时间
length() 获取File长度
方法 (文件相关操作) 意义
createNewFile() 新建文件 (仅可用于文件)
delete() 删除文件 (若文件夹为空,也可以使用delete删除,若非空则删除失败)
方法 (目录相关操作) 意义 (仅可用于文件夹)
mkdir() 创建单级目录,一次只能创建一个文件夹
mkdirs() 创建多级目录
list() 返回当前目录所包含文件以及子目录的String对象
listFiles() 返回当前目录所包含的文件以及子目录的File对象
package io;

import java.io.File;
import java.io.IOException;

public class OpenFile {
	public static void main(String[] args) {
		/**
		 * 访问文件名(文件和文件夹)
		 */
		File f1 = new File("E:\\IO\\1.txt");//打开文件
		File m1 = new File("E:\\IO\\test");//打开文件夹
		System.out.println(f1.getName());//1.txt
		System.out.println(f1.getPath());//E:\IO\1.txt
		File f2 = f1.getAbsoluteFile();//获得一个用当前文件的绝对路径构成的File对象
		System.out.println(f1.getAbsolutePath());//E:\IO\1.txt
		System.out.println(f1.getParent());//E:\IO
		//文件或文件夹重命名,参数是File类型文件,文件的路径要与源文件路径一致
		f1.renameTo(new File("E:\\IO\\1_rename.txt"));//E:\\IO\\1.txt => E:\\IO\\1_rename.txt
		m1.renameTo(new File("E:\\IO\\test_rename"));//E:\\IO\\test => E:\\IO\\test_rename
		/**
		 * 文件检测(文件和文件夹)
		 */
		System.out.println(f1.exists());//false,被改名为1_rename.txt
		File f3 = new File("E:\\IO\\2.txt");
		File m2 = new File("E:\\IO\\test2");
		System.out.println(m2.exists());//该文件夹存在
		System.out.println(f3.canRead());//true
		System.out.println(f3.canWrite());//true
		System.out.println(m2.canRead());//false,目录不可读写
		System.out.println(m2.canWrite());//false,目录不可读写
		System.out.println(f3.isFile());//true
		System.out.println(m2.isDirectory());//true
		/**
		 * 获取常规文件信息
		 */
		System.out.println(f3.lastModified());//1587045172334,获取最后一次修改时间
		System.out.println(f3.length());//2.txt内容为“abc”,每个英文字符占1bit,长度为3bit,每个汉字占3bit
		/**
		 * 文件相关操作
		 */
		File f4 = new File("E:\\IO\\3.txt");
		//先判断要创建的文件是否存在,若不存在,再创建,若存在,则不会创建
		if(f4.exists() == false) {//if(!f4.exists()){...}
			try {
				f4.createNewFile();//只能用来新建文件,不能新建文件夹,文件夹用mkDir()
			} catch (Exception e) {
				e.printStackTrace();
			}
		}//E:\\IO\\3.txt被创建
		f4.delete();//new File("E:\\IO\\3.txt").delete();删除文件
		new File("E:\\IO\\test2\\b\\c").delete();//删除文件夹
		/**
		 * 目录相关操作
		 * mkDir()创建单级目录,mkDirs()创建多级目录
		 * list()返回当前文件夹的下级文件以及文件夹,listFile返回当前文件夹的下级文件以及文件夹的File对象
		 */
		//只能创建文件夹,就算写E:\\IO\\test2\\c\\d\\1.txt也只是新建一个叫做1.txt的文件夹
		//在E:\\IO\\test2下新建文件夹b,在b下新建文件夹c
		//方法一:mkdir()单级创建,每次只能新建一个文件夹
		File f5 = new File("E:\\IO\\test2\\b");
		f5.mkdir();
		File f6 = new File("E:\\IO\\test2\\b\\c");
		f6.mkdir();
		//在E:\\IO\\test2下新建文件夹c,在a下新建文件夹d
		//方法二:mkdirs()多级创建
		File f7 = new File("E:\\IO\\test2\\c\\d");
		f7.mkdirs();
		
		String[] str = m2.list();
		for(String s : str) {
			System.out.println(s);
		}//不包括子文件夹的子文件夹,不包括子文件夹的子文件夹的文件
		
		File[] file = m2.listFiles();
		for(File f : file) {
			System.out.println(f);
		}//不包括子文件夹的子文件夹,不包括子文件夹的子文件夹的文件
		
		//getPath()与getAbsolutePath()所判断的文件可以不存在
		//但若文件不存在, getParent()返回null
		//String[] list()返回指定目录下的所有文件和文件夹, 包含隐藏文件和文件夹, 目录必须存在
	}
}

示例

递归遍历文件

package io;

import java.io.File;

/**
 * 递归遍历文件
 * 判断当前读取的是文件还是文件夹,如果是文件则输出文件的路径,
 * 如果是文件夹,则读取其内部文件,对其内部文件以及文件夹的操作与之前相同
 * @author MCC
 *
 */
public class RTFile {
	public static void main(String[] args) {
		File file = new File("E:\\IO");
		RTFile.rtfile(file);
	}
	
	//递归遍历
	public static void rtfile(File f) {
		if(f.isFile() == true) {
			System.out.println("文件:" + f.getName() + "\t路径:" + f.getAbsolutePath());
		}else {
//			System.out.println("文件夹:" + f.getName() + "\t路径:" + f.getAbsolutePath());
			File[] file = f.listFiles();
			for(File ff : file) {
//				if(ff != null && ff.length() > 0) { //如果想遍历内容不为空的文件,则加上这句话
					rtfile(ff);
//				}
			}
		}
	}
}

筛选文件

package io.file;

import java.io.*;
import java.util.*;
/**
 * 筛选指定名称的文件, 为了演示方便, 本例中筛选java文件
 * @author 14251
 *
 */
public class FileNameFilterDemo {
	public static File[] getFile(String dir, String str) {//传入要查找的目录和什么类型的文件
		File file = new File(dir);
		Set<File> set = new HashSet<File>();
		if(!file.exists()) {
			throw new RuntimeException("File is not exist!");		
		}
		if(!file.isDirectory()) {
			throw new RuntimeException("File is not a file!");
		}else {
			//listFiles()返回当前目录下的所有文件和文件夹的File对象, list()返回String
			File[] fileArray = file.listFiles();
//			for(int x=0; x
			for(File f : fileArray) {
				String name = f.getName();
				if(name.endsWith(".java")) {
					set.add(f);
				}
			}
			return set.toArray(new File[set.size()]);
		}
	}
	//主函数
	public static void main(String[] args) {
		File[] f = getFile("E:\\IO", ".java");
		for(File ff : f) {
			System.out.println(ff.getName()+": "+ff.length());
		}
	}
}

递归删除目录

package io.file;

import java.io.*;
/**
 * 递归删除由内容的目录
 * @author 14251
 *
 */
public class DeleteDirDemo {
	public static boolean deleteFiles(String dir) {
		boolean flag = false;//判断是否删除成功, 内部文件夹都删除成功了, 最外层文件夹才能删除成功
		File fdir = new File(dir);
		if(!fdir.exists()) {
			throw new RuntimeException("Directory is not exist!");		
		}
		if(!fdir.isDirectory()) {
			throw new RuntimeException("It is not a directory!");
		}else {
			File[] file = fdir.listFiles();
			for(File f : file) {
				if(f.isDirectory()) {
					deleteFiles(f.getAbsolutePath());
				}else {
					f.delete();//文件一定能删除成功
				}
			}
			flag = fdir.delete();//文件夹只有在不包含文件时才能被删除, 只需要判断文件夹是否删除成功即可
		}
		return flag;
	}
	//主函数
	public static void main(String[] args) {
		System.out.println(deleteFiles("E:\\11111"));
	}
}

文件列表

package io.file;

import java.io.*;
import java.util.*;
/**
 * 获取指定目录下指定后缀名文件的绝对路径, 并将其保存在一个文件中
 * @author 14251
 * 思路:
 * 1. 对指定目录进行递归, 获取全部符合要求的文件
 * 2. 将文件的绝对路径保存在集合中
 * 3. 将集合数据写入到文件中
 */
public class GetAbsouluteDemo {
	public static void getFileDoc(String dir, String target, String doc) throws IOException{
		//传入目录, 指定文件的后缀名, 要写入的文本路径
		List<String> list = new ArrayList<String>();
		File directory = new File(dir);
		//判断目录是否存在
		if(!directory.exists()) {
			throw new RuntimeException("Directory is not exist!");		
		}
		if(!directory.isDirectory()) {
			throw new RuntimeException("It is not a file!");
		}else {
			//递归遍历文件, 获取符合要求的文件, 并写入集合中
			File[] file = directory.listFiles();
			for(File f : file) {
				if(f.isDirectory()) {
					getFileDoc(f.getAbsolutePath(), target, doc);
				}else {
					if(f.getName().endsWith(target)) {
						list.add(f.getAbsolutePath());
					}
				}
			}
		}
		//将集合内的数据写入到外部文本中
		//输出流
		BufferedWriter br = new BufferedWriter(new FileWriter(doc));
		//遍历集合取出元素
		for(String s : list) {
			br.write(s);
			br.newLine();
			br.flush();
		}
		br.close();
	}
	//主函数
	public static void main(String[] args) {
		try {
			getFileDoc("E:\\IO", ".java", "E:\\IO\\java.txt");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

IO原理

1. 概念:

  • iO流用来处理设备之间-的数据传输。

  • Java程序中,对于数据的输入/输出操作以"流(stream)" 的方式进行。

  • java.io包下提供了各种"流"类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。

2. 输入输出:

  • 输入input:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中。

  • 输出output:将程序(内存)数据输出到磁盘、光盘等存储设备中。

流的分类

  • 按操作数据单位不同分为:字节流(8 bit),字符流(16 bit) 。
  • 按数据流的流向不同分为:输入流,输出流。
  • 按流的角色的不同分为:节点流,处理流。
抽象基类 字节流 字符流
输入流 InputStream Reader
输出流 OutputStream Writer

Java的IO流共涉及40多个类,实际上都是从上面4个抽象基类派生的。由这四个类派生出来的子类名称都是以其父类名作为后缀的。

IO流体系

分类 字节输入流 字节输出流 字符输入流 字符输出流
抽象基类 InputSteam OutputStream Reader Writer
访问文件 FileInputSteam FileOutputStream FlieReader FileWriter
访问数组 ByteArrayInputSteam ByteArrayOutputStream CharArrayReader CharArrayWriter
访问管道 PipedInputSteam PipedOutputStream PipedReader PipedWriter
访问字符串 StringReader StringWriter
缓冲流 BufferedInputSteam BufferedOutputStream BufferedReader BufferedWriter
转换流 InputSteamReader OutputStreamWriter
对象流 ObjectInputSteam ObjectOutputStream
FilterInputSteam FilterOutputStream FilterReader FilterWriter
打印流 PrintStream PrintWriter
推回输入流 PushbackInputSteam PushbackReader
特殊流 DataInputSteam DataOutputStream

文件字节流

  • 语法格式:FileInputStream f = new FileInputStream();//参数为Flie对象或文件路径
  • 文件字节流非常通用,可以用来操作任何类型的文档和文件(图片,压缩包等等),因为字节流直接使用二进制编码。

FileInputStream

注意:

  • 1.使用 read() 读取文件内容。
  • 2.流使用完毕之后一定要关闭。
package io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

/**
 * 操作文件用File类
 * 操作文件内容用输入输出流
 * @author MCC
 *
 */
public class TestFileInputStream {
	public static void main(String[] args) {
		FileInputStream fis = null;
		try {
			/**
			 * 读取文件内容
			 */
//			FileInputStream file = new FileInputStream(new File("E:\\IO\\test2\\xixi.txt"));
			fis = new FileInputStream("E:\\IO\\test2\\xixi.txt");
			byte[] b = new byte[1024];
			int len = 0;
			while((len=fis.read(b))!=-1){
				System.out.println(new String(b));
			}
			fis.close();//流使用完毕之后一定要关闭
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try{
				if(fis!=null){
					fis.close();
				}
			}catch(Exception e){
					e.printStackTrace();
			}
		}
	}
}

FileOutputStream

注意1:

  • 1.使用 write() 将内容写入文件。
  • 2.写入操作完成之后一定要 flush() 刷新到硬盘。
  • 3.流使用完毕之后一定要关闭。

注意2:

writer()方法会覆盖已有文件,具体情况如下:

  • 1.硬盘上已有同名文件时,write()写出后会覆该文件,若不想覆盖,则可以设置append=true。
  • 2.同一个输出流对象,在关闭流操作之前的所有写操作都会默认进行续写操作,不会覆盖文件。
  • 3.流关闭之后,再创建的流对象,即使名称相同,也会覆盖已有文件。
  • 4.即使输出流对象的名称相同,但每次调用时,都通过了new操作,则还是不同对象。
package io;

import java.io.FileOutputStream;

public class TestFileOutputStream {
	public static void main(String[] args) {
		FileOutputStream fos = null;
		try {
			fos = new FileOutputStream("E:\\IO\\test2\\lala.txt");
			String str = "efg";
			fos.write(str.getBytes());//需要byte类型数据,不是引类型,需要调用getXxx()方法转换
			fos.flush();
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally{
			try{
				if(fos!=null){
					fos.close();
				}
			}catch(Exception e){
					e.printStackTrace();
			}
		}
	}
}

available()方法

  • available():InputStream中提供的获取文件大小的方法。
package io;

import java.io.*;

public class AvailiableDemo {
	public static void main(String[] args) {
		//字节输入流的available()方法
		try {
			FileInputStream fis = new FileInputStream("E:\\IO\\test.txt");//test_append
			int num = fis.available();
			System.out.println(num);//11
			byte[] b = new byte[fis.available()];//创建一个刚刚好的数组
			fis.read(b);
			System.out.println(new String(b));//test_append
			fis.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

练习

编写一个程序,把一个文件(或图片、音视频等)复制的到指定文件夹下。

package io;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
 * 编写一个程序,把一个文件复制的到指定文件夹下
 * 1.将E:\IO\test2\xixi.txt复制到E:\IO\test2\a\b下;
 * 2.将E:\IO\QQ截图20200417144142.png复制到E:\IO\test2\b下。
 * @author MCC
 *
 */
public class CopyFile {
	public static void copyFile(String sourcepath, String targetpath) {
		FileInputStream infile = null;
		FileOutputStream outfile = null;
		try {
			//f用于判断文件长度,方便设置byte[]长度
			File f = new File(sourcepath);
			infile = new FileInputStream(f);
			outfile = new FileOutputStream(targetpath);
			//设置数组长度
			byte[] fbyte = new byte[1024];
			//读文件内容到数组
			int len = 0;
			while((len=infile.read(fbyte))!=-1){
				//写文件到目标路径
				outfile.write(fbyte, 0, len);
			}
			//刷新文件
			outfile.flush();
			//关闭流
			outfile.close();
			infile.close();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try{
				if(outfile!=null){
					outfile.close();
				}catch(Exception e){
					e.printStackTrace();
			}
			try{
				if(infile!=null){
					infile.close();
				}catch(Exception e){
					e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		CopyFile.copyFile("E:\\IO\\test2\\xixi.txt", "E:\\IO\\test2\\a\\b\\xixi_copy.txt");
		CopyFile.copyFile("E:\\IO\\QQ截图20200417144142.png", "E:\\IO\\test2\\b\\QQ截图20200417144142_copy.png");
	}
}

文件字符流

注意:字符流只适用于文件内容是字符的文件。

FileReader

读取文件操作步骤:

  • 1.建立一个流对象,将已存在的一个文件加载进流。
    FileReader fr = new FileReader(“File对象或文件路径”);

  • 2.创建一个临时存放数据的数组。
    char[ ] ch = new char[1024];

  • 3.调用流对象的读取方法将流中的数据读入到数组中。
    fr.read(ch);

  • 4.关闭流资源。
    fr.close();

package io;

import java.io.FileReader;

public class TestFileReader {
	public static void main(String[] args) {
		FileReader fr = null;
		try {
			fr = new FileReader("E:\\IO\\3.txt");
			char[] ch = new char[1024];
			fr.read(ch);
			System.out.println(ch);//123abc
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try{
				if(fr!=null){
					fr.close();
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}

FileWriter

写入文件操作步骤:

  • 1.创建流对象,建立数据存放文件。
    FileWriter fw = new FileWriter(“File对象或文件路径”);

  • 2.调用流对象的写入方法,将数据写入流。
    fw.write(“text”);

  • 3.输出流关闭之前需要清空缓存。
    fw.flush();

  • 4.关闭流资源,并将流中的数据清空到文件中。
    fw.close();

package io;

import java.io.FileWriter;

public class TestFileWriter {
	public static void main(String[] args) {
		FileWriter fw = null;
		try {
			fw = new FileWriter("E:\\IO\\4_writer.txt");
			String str = "write 123";
			fw.write(str);
			fw.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try{
				if(fw!=null){
					fw.close();
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}

练习

编写一个程序,把一个文件复制的到指定文件夹下。

package io;

import java.io.*;
/**
 * 演示FileReader和FileWriter
 * @author 14251
 *
 */
public class FileRWDemo {
	public static void main(String[] args) {
		FileReader fr = null;
		FileWriter fw = null;
		try {
			//read(): 一次读一个, 返回读取到的字符的ASCII码, 读取结束返回-1
			//read(char[] ch): 将读取到的字符存入ch中, 返回存入元素的个数, 读取结束返回-1
			fr = new FileReader("E:\\IO\\test.txt");
			fw = new FileWriter("E:\\IO\\test_copy.txt");
			//第一种: 读取到数组中
			char[] ch = new char[1024];
			int len = 0;
			while((len = fr.read(ch)) != -1) {
				fw.write(ch, 0, len);
			}
			fw.flush();
			//第二种: 单个字符读取
//			int ch = 0;
//			while((ch = fr.read()) != -1) {
//				fw.write((char)ch);
//			}
//			fw.flush();
		} catch (IOException e) {
			throw new RuntimeException("文件不存在");
		} finally {
			if(fr!=null) {
				try {
					fr.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if(fw!=null) {
				try {
					fw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

注意

  • 定义文件路径时,注意:可以用 “/” 或者 “\”。

  • 在写入一个文件时,如果目录下有同名文件将被覆盖。如果不想覆盖源文件可以将输出流的append参数设置为true。

  • 在读取文件时,必须保证该文件已存在,否则出异常。

package io;

import java.io.*;
/*
 * write写入后, 磁盘中的原有同名文件会被覆盖, append为true时不覆盖原有文件
 */
public class FileAppend {
	public static void main(String[] args) {
		//续写文件
		//构造函数FileWriter(String address, boolean append);
		FileWriter fw = null;
		try {
			fw = new FileWriter("E:\\IO\\test.txt", true);//OutputStream也可以设置为true
			fw.write("_append");
			fw.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if(fw!=null){
					fw.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

缓冲流

  • 为了提高数据读写的速度,Java API提供了带缓冲功能的流类,在使用这些流类时,会创建一个内部缓冲区数组,因此可以不自定义数组。
  • 根据数据操作单位可以把缓冲流分为:
    BufferedInputStreamBufferedOutputStream(缓冲字节流)
    BufferedReaderBufferedWriter(缓冲字符流)
  • 缓冲流要"套接"在相应的节点流之上,对读写的数据提供了缓冲的功能,提高了读写的效率,同时增加了一些新的方法。
  • 对于输出的缓冲流,写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出。

注意,实质上是把输入输出流包装在缓冲流中,把数据缓冲到内存中,提高速度。

缓冲字节流

BufferedInputStream

缓冲字节输入流

package io;
/**
 * 缓冲字节输入流
 * 读取E:\IO\2.txt文件
 */
import java.io.BufferedInputStream;
//import java.io.File;
import java.io.FileInputStream;

public class TestBufferedByteInput {
	public static void main(String[] args) {
	FileInputStream fs = null;
	BufferedInputStream bf = null;
		try {
//			File f = new File("E:\\IO\\2.txt");
//			FileInputStream fs = new FileInputStream(f);
//			BufferedInputStream bf = new BufferedInputStream(fs);
			fs = new FileInputStream("E:\\IO\\2.txt");
			bf = new BufferedInputStream(fs);
			byte[] b = new byte[1024];
			int len = 0;
			while((len=bf.read(b))!=-1){
				System.out.println(new String(b));
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bf!=null){
					bf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

BufferedOutputStream

缓冲字节输出流

package io;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;

public class TestBufferedByteOutput {
	public static void main(String[] args) {
		BufferedOutputStream bo = null;
		try {
			FileOutputStream fo = new FileOutputStream("E:\\IO\\4.txt");
			bo = new BufferedOutputStream(fo);
			String str = "123abc";
			bo.write(str.getBytes());
			bo.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bo!=null){
					bo.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
练习

缓冲字节流实现文件复制

package io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;

public class BufferedByteCopy {
	public static void main(String[] args) {
		BufferedByteCopy.copyFile("E:\\IO\\2.txt", "E:\\IO\\test1\\2.txt");
		BufferedByteCopy.copyFile("E:\\IO\\QQ截图20200417144142.png", "E:\\IO\\test1\\QQ截图20200417144142.png");
	}
	
	public static void copyFile(String source, String target) {
		BufferedInputStream bf = null;
		BufferedOutputStream bo = null;
		try {
			File f = new File(source);//打开文件,f调用length()函数
			FileInputStream fs = new FileInputStream(f);//输入流
			bf = new BufferedInputStream(fs);//装入输入缓冲流
			
			FileOutputStream fo = new FileOutputStream(target);//输出流
			bo = new BufferedOutputStream(fo);//装入输出缓冲流
			
			byte[] b = new byte[1024];//定义接收数组
			int len = 0;
			while((len=bf.read(b))!=-1){
				bo.write(b, 0, len);//写入目标文件
				bo.flush();//刷新硬盘
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if(bo!=null){
				try{
				bo.close();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
			if(fo!=null){
				try{
				fo.close();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}
	}
}

缓冲字符流

提供了两个新方法:

方法 意义
BufferedReader readLIne() 一次读取一行,返回String
BufferedWriter newLine() 写入一个换行

注意:readLine()方法不返回一行末尾的回车符 '\r\n’

BufferedReader

缓冲字节输入流

package io;

import java.io.*;

public class TestBufferedChar {
	public static void main(String[] args) {
		BufferedReader bf = null;
		try {
			FileReader fs = new FileReader("E:\\IO\\2.txt");
			bf = new BufferedReader(fs);
			String str = null;
			while((str=bf.readLine())!=null){
				System.out.println(str);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bf!=null){
					bf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
LineNumberReader

BufferedReader的子类。
提供了两个新方法:

方法 意义
getLineNumber() 获取当前行号
setLineNumber() 设置当前行号
package io;

import java.io.*;

public class LNReaderDemo {
	public static void main(String[] args) {
		//lineNumberReader()
		try {
			LineNumberReader lnr = new LineNumberReader(new FileReader("E:\\IO\\test.txt"));
			BufferedWriter bw = new BufferedWriter(new FileWriter("E:\\IO\\test_number.txt"));
			char[] ch = new char[1024];
			String str = null;
			lnr.setLineNumber(10);//从第10行开始
			while((str=lnr.readLine())!=null) {
				bw.write(lnr.getLineNumber()+": "+str);
				bw.flush();
			}
			bw.close();
			lnr.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
装饰类实现LineNumberReader
package io;

import java.io.*;
/**
 * 装饰类实现LineNumberReader
 * @author 14251
 *
 */
class MyLineNumberReader{
	private Reader r;//使用Reader类的read()方法
	private int number;//默认为0
	MyLineNumberReader(Reader r){
		this.r = r;
	}
	//readLine()
	public String readLine() throws IOException{
		StringBuilder sb = new StringBuilder();
		int ch = 0;
		while((ch=r.read())!=-1) {
			if(ch == '\r') {
				continue;
			}
//			if(ch == '\n') {
//				return sb.toString();
//			}else {
//				sb.append((char)ch);
//			}
			if(ch == '\n') {
				String res = sb.toString();
				if("over".equals(res)) {
					break;//结束while循环后StringBuilder中还存在字符串over
				}else {
					return res;
				}
			}else {
				sb.append((char)ch);
			}
		}
		//当存在的字符串是over时不需要返回
		if(sb.length()!=0 && !"over".equals(sb.toString())) {
			return sb.toString();
		}
//		sb.delete(0, sb.length());//可以不写, 因为每次调用readLine()方法时都会新建一个StringBuilder
		return null;//break后结束循环, 内部的over不返回, 因此返回null
	}
	//setLineNumber()
	public void setLineNumber(int num) {
		this.number = num;
	}
	//getLineNumber()
	public int getLineNumber() {
		number++;
		return this.number;
	}
	//close()
	public void close() throws IOException{
		r.close();
	}
}

public class MyLineNumberReaderDemo {
	public static void main(String[] args) {
		MyLineNumberReader mnr = null;
		BufferedWriter bw = null;
		try {
//			System.setOut(new PrintStream("C:\\Users\\14251\\Desktop\\MyLineNumberReader.txt"));
			mnr = new MyLineNumberReader(new InputStreamReader(System.in));
			bw = new BufferedWriter(new OutputStreamWriter(System.out));
			String str = null;
			while((str=mnr.readLine())!=null) {//输入over后返回null, 结束该循环
				bw.write("["+mnr.getLineNumber()+"]"+str);
				bw.newLine();
				bw.flush();
			}
		}catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(mnr!=null) {
				try {
					mnr.close();
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
			if(bw!=null) {
				try {
					bw.close();
				}catch(Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

BufferedWriter

缓冲字节输出流

package io;

import java.io.*;

public class TestBufferedChar {
	public static void main(String[] args) {
		FileWriter fs = null;
		BufferedWriter bf = null;
		try {
			FileWriter fs = new FileWriter("E:\\IO\\5.txt");
			bf = new BufferedWriter(fs);
			bf.write("123abcdef");
			bf.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bf!=null){
					bf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
练习

缓冲字符流实现文件复制

package io;

import java.io.*;

public class TestBufferedChar {
	public static void main(String[] args) {
		copyFile("E:\\IO\\2.txt", "E:\\IO\\test1\\2_BufferedWriter.txt");
	}
	
	public static void copyFile(String source, String target) {
		BufferedReader bf = null;
		BufferedWriter bo = null;
		try {
			File f = new File(source);//打开文件,f调用length()函数
			FileReader fs = new FileReader(f);//输入流
			bf = new BufferedReader(fs);//装入输入缓冲流
			
			FileWriter fo = new FileWriter(target);//输出流
			bo = new BufferedWriter(fo);//装入输出缓冲流
			
			String str = null;
			while((str=bf.readLine())!=null){
				bo.write(str);
				bo.newLine();
				bo.flush();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bf!=null){
					bf.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			try {
				if(bo!=null){
					bo.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

装饰设计模式

当想要对已有对象进行功能增强时,可以定义增强类,将已有对象作为参数传入,基于已有功能,进行功能加强,改增强类被称为装饰类,这种设计模式称为装饰设计模式。

实现方法:装饰类通过构造方法接收被装饰的对象,之后提供基于该对象的加强后的功能。

例如,FileReader有read()方法,现在想变成一次读一行,可以基于read()方法提供readLine()方法。

练习1:实现自己的readLine()

package io;

import java.io.*;

class MyBufferedReader{
	private FileReader fr;
	MyBufferedReader(FileReader fr){
		this.fr = fr;
	}
	//readLine()
	public String readLine() throws IOException{
		//定义一个临时存储容器, 将读取到的字符存储在StringBuilder中, 最后在转换成字符串返回
		StringBuilder sb = new StringBuilder();
		int ch = 0;
		while((ch=fr.read())!=-1) {
			if(ch=='\r') {
				continue;
			}
			if(ch=='\n') {
				return sb.toString();
			}else {
				sb.append((char)ch);
			}
		}
		if(sb.length()!=0){
			return sb.toString();
		}
		return null;
	}
	public void close() throws IOException{
		fr.close();
	}
	
}
public class BufferedDemo {
	public static void main(String[] args) {
		try {
			FileReader fr = new FileReader("E:\\IO\\test.txt");
			MyBufferedReader mbr = new MyBufferedReader(fr);
			String str = null;
			while((str=mbr.readLine())!=null) {
				System.out.println(str);
			}
			mbr.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
}

练习2:实现自己的装饰类

package io;
/**
 * 装饰设计模式
 * @author 14251
 *
 */
//Person的eat方法比较单一, 只能吃饭, 现在想在吃饭的基础上添加喝水和收拾碗筷, 这就属于对eat功能的扩展
class Person {
	public void eat() {
		System.out.println("吃饭");
	}
}

class NewPerson {
	private Person p;
	NewPerson(Person p){
		this.p = p;
	}
	public void newEat() {
		System.out.println("喝水");
		p.eat();
		System.out.println("收拾碗筷");
	}
}

public class Demo1 {
	public static void main(String[] args) {
		Person p = new Person();
		NewPerson np = new NewPerson(p);
		np.newEat();
	}
}

转换流

1. 概念:

  • 转换流提供了在字节流和字符流之间的转换。
  • Java API提供了两个转换流:
    InputStreamReaderOutputStreamWriter
  • 字节流中的数据都是字符时,转成字符流操作更高效。

2. InputStreamReader:

  • 用于将字节流中读取到的字节按指定字符集解码成字符。需要和InputStream“套接”。
  • 构造方法
    InputStreamReader(InputStream in)
    public InputSreamReader(InputStream in, String charsetName),第二个参数是文件编码方式。
    如: Reader isr = new InputStreamReader(System.in, ”ISO5334_1”);

3. OutputStreamWriter:

  • 用于将要写入到字节流中的字符按指定字符集编码成字节。 需要和OutputStream“套接”。
  • 构造方法
    public OutputStreamWriter(OutputStream out)
    public OutputSreamWriter(OutputStream out, String charsetName)
    如: Writer osw = new OutputStreamWriter();

4. 编码方式:
文档一般有三种编码方式:

  • ISO8895-1:西欧编码,纯英文编码,不适合汉字。
  • GBK:中英文都适用。
  • UTF-8:中英文都适用。

我们一般使用UTF-8编码。
在字节流转换为字符流时,设置的编码方式要与原文件的编码方式一致,否则会出现乱码。

5. 使用场景:

  • 需要使用BufferReader或BufferedWriter中的readLine()、newLine()方法。
  • 需要知道文件的编码表。

6. 代码举例:

字节输入流转换为字符输入流

package io;

import java.io.FileInputStream;
import java.io.InputStreamReader;

public class TransferStream {
	public static void main(String[] args) {
		BufferedReader br = null;
		try {
			FileInputStream fs = new FileInputStream("E:\\IO\\test1\\2.txt");
//			InputStreamReader isr = new InputStreamReader(fs, "UTF-8");
			InputStreamReader isr = new InputStreamReader(fs);
			br = new BufferedReader(isr);
			String str = null;
			while((str=br.readLine())!=null){
				System.out.println(str);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try{
				if(br!=null){
					br.close();
				}
			}catch(EXception e){
				e.printStackTrace();
			}
		}
	}
}

字节输出流转换为字符输出流

package io;

import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

public class TransferStream {
	public static void main(String[] args) {
	BufferedWriter bw = null;
		try {
			FileOutputStream fo = new FileOutputStream("E:\\IO\\test1\\1.txt");
//			OutputStreamWriter op = new OutputStreamWriter(fo, "UTF-8");
			OutputStreamWriter op = new OutputStreamWriter(fo);
			bw = new BufferedWriter(op);
			
			op.write("123def");
			op.flush();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			try{
				if(bw!=null){
					bw.close();
				}
			}catch(EXception e){
				e.printStackTrace();
			}
		}
	}
}

输入输出流

  • System.inSystem.out分别代表了系统标准的输入和输出设备
    默认输入设备是键盘,输出设备是显示器
  • System.in的类型是InputStream
  • System.out的类型是PrintStream
  • 可以通过System类的setIn(),setOut() 方法对默认设备进行改变。
    public static void setIn(InputStream in)
    public static void setOut(PrintStream out)
  • 键盘录入:
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

练习:把控制台输入的内容写入到指定的txt文件中,当接收的是over时,就结束程序的运行。

package io;

import java.io.*;

public class SystemInDemo {
	public static void main(String[] args) {
//		systemIn();
		mySystemIn();
	}
	//接收键盘输入的数据, 并转换为大写, 当输入over时结束
	public static void systemIn() {
		BufferedReader br = null;
		BufferedWriter bw = null;
		try {
			br = new BufferedReader(new InputStreamReader(System.in));
//			bw = new BufferedWriter(new OutputStreamWriter(System.out));//输出到控制台
			bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("E:\\IO\\SystemIn.txt")));//输出到指定文件
			String in = null;
			while((in=br.readLine()) != null) {
				if("over".equals(in)) {
					break;
				}
				bw.write(in.toUpperCase());
				bw.newLine();
				bw.flush();
			}
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(bw!=null) {
					bw.close();
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
			try {
				if(br!=null) {
					br.close();
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	//不使用转换流的readLine()方法
	public static void mySystemIn() {
		BufferedInputStream bis = null;
		try {
			InputStream in = System.in;
			bis = new BufferedInputStream(in);
			StringBuilder sb = new StringBuilder();
			while(true) {
				int ch = bis.read();
				if(ch == '\r') {
					continue;
				}
				if(ch == '\n') {
					String str = sb.toString();
					if("over".equals(str)) {
						break;
					}
					System.out.println(str.toUpperCase());
					sb.delete(0, sb.length());
				}else{
					sb.append((char)ch);
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				if(bis!=null) {
				bis.close();
				}
			}catch(Exception e) {
				e.printStackTrace();
			}
			
		}		
	}
}
//键盘录入
//BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//输出
//BufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
//详细写法
//InputStream in = System.in;
//InputStreamReader isr = new InputStreamReader(in);
//BufferedReader br = new BufferedReader(isr);

IO练习

  1. 有多名学生,每个学生有三门课的成绩,从键盘输入数据(姓名,性别,成绩1, 2, 3),并计算出总成绩,将学生的信息和计算出的总成绩按照总分由高到低的顺序存储在磁盘 “stuInfo.txt” 中。
package io.practice;

import java.util.*;
import java.io.*;
/*
 * 有多名学生, 每个学生有三门课的成绩, 从键盘输入数据(姓名, 性别, 成绩1, 2, 3), 
 * 并计算出总成绩, 将学生的信息和计算出的总成绩按照总分由高到低的顺序存储在磁盘“stuInfo.txt”中.
 */
//学生类
class Student implements Comparable<Student>{
	private String name;
	private String sex;
	private double chinese;
	private double math;
	private double English;
	private double sum;
	Student(String name, String sex, double chinese, double math, double English){
		this.name = name;
		this.sex = sex;
		this.chinese = chinese;
		this.math = math;
		this.English = English;
		this.sum = chinese+math+English;
	}
	@Override
	//double默认从小到大排序
	public int compareTo(Student s) {
		Double ds1 = this.sum;
		Double ds2 = s.sum;
		Double dc1 = this.chinese;
		Double dc2 = s.chinese;
		int num = ds1.compareTo(ds2);
		if(num==0) {
			num = dc1.compareTo(dc2);
			if(num==0) {
				num = this.name.compareTo(s.name);
			}
		}
		return num;
	}
	@Override
	public int hashCode() {
		return Integer.parseInt(this.name+this.sum*23);
	}
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Student)) {
			throw new RuntimeException("类型错误");
		}else {
			Student s = (Student)obj;
			return this.name.equals(s.name) && this.sum==s.sum;
		}
	}
	@Override
	public String toString() {
		return name+" "+sex+": "+sum+"\t"+chinese+"\t"+math+"\t"+English;
	}
	
	//getter&&setter省略
	
}

//录入学生信息并保存到磁盘的工具类
class StudentInfoTool{
	//录入信息
	//两种排序方法, 不传比较器, 升序排序, 传入比较器, 降序排序
	public static Set<Student> setInfo() throws IOException{
		return setInfo(null);
	}
	public static Set<Student> setInfo(Comparator<Student> cmp) throws IOException{
		Set<Student> set = null;
		if(cmp==null) {
			set = new TreeSet<Student>();
		}else {
			set = new TreeSet<Student>(cmp);
		}
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String line = null;
		while((line=br.readLine())!=null){
			if("over".equals(line)) {
				break;
			}
			String[] str = line.split(",");
			set.add(new Student(str[0], 
					str[1], 
					Double.parseDouble(str[2]), 
					Double.parseDouble(str[3]), 
					Double.parseDouble(str[4])));
		}
		br.close();
		return set;
	}
	//学生信息存储到磁盘文件
	public static void storeInfo(Set<Student> set, String address) throws IOException{
		BufferedWriter bw = new BufferedWriter(new FileWriter(address));
		for(Student stu : set) {
			bw.write(stu.toString());
			bw.flush();
			bw.newLine();
		}
		bw.close();
	}
}
//主方法
public class StudentsInfoTest {
	public static void main(String[] args) {
		try {
			String address = "E:\\IO\\stuInfo.txt";
			Comparator<Student> cmp = Collections.reverseOrder();
//			Set set = StudentInfoTool.setInfo();//不传比较器
			Set<Student> set = StudentInfoTool.setInfo(cmp);//传比较器
			StudentInfoTool.storeInfo(set, address);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
  1. 模拟用户注册和登录的过程。
    注册:用户通过键盘输入信息后保存在code.txt。
    登录:判断账号是否存在,存在时判断密码是否正确。
package io.practice;

import java.util.*;
import java.io.*;

public class CodeDemo {
	//注册信息
	public static void setInfo() throws IOException{
		File file = new File("E:"+File.separator+"IO"+File.separator+"code.txt");//存储和读取的位置
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//键盘录入
		BufferedWriter bw = new BufferedWriter(new FileWriter(file));//写入
		String info = null;
		int transfer = 0;//在账号和密码之间切换
		while((info=br.readLine())!=null) {
			if("over".equals(info)) {
				break;
			}else if(transfer==0) {
				bw.write(info+"=");
				transfer = 1;
				bw.flush();
			}else {
				bw.write(info);
				bw.newLine();
				transfer = 0;
				bw.flush();
			}
		}
		br.close();
		bw.close();
	}
	//登录信息
	public static void logIn() throws IOException{
		File file = new File("E:"+File.separator+"IO"+File.separator+"code.txt");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		//使用Properties获取信息
		Properties prop = new Properties();
		prop.load(new FileReader(file));
		//判断账号是否存在
		Set<String> names = prop.stringPropertyNames();
		String account = br.readLine();//第一次读取的是账号
		if(!(names.contains(account))) {//账号不存在时抛出异常
			throw new RuntimeException("Account is not exist!");
		}else {
			String password = prop.getProperty(account);//账号存在时, 获取账号对应的密码
			String userIn = br.readLine();//第二次读取的是用户输入的密码, 利用了readLine()自动读取下一行的特点
			if(password.equals(userIn)) {//密码正确时输出登录成功
				System.out.println("Successfully log in!");
			}else {//否则抛出异常
				throw new RuntimeException("Password is wrong!");
			}
		}
		br.close();
	}
	//主函数
	public static void main(String[] args) {
		try {
			setInfo();
//			logIn();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

异常信息日志

package io;

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

public class IOExceptionLog {
	public static void main(String[] args) {
		try {
			int arr[] = new int[] {1, 2, 3};
			System.out.println(arr[3]);
		}catch(Exception e) {
			try {
				Date d = new Date();
				SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
				String s = sdf.format(d);
				PrintStream ps = new PrintStream("E:\\IO\\ExceptionLog.log");
				ps.println(s);
				e.printStackTrace(ps);
			}catch(IOException e1) {
				throw new RuntimeException("日志文件创建失败");
			}
			//close();
		}
	}
}
//自动生成日志文件log4j

打印流

概述

  • 在整个IO包中,打印流是输出信息最方便的类。
  • PrintStream (字节打印流) 和 PrintWriter (字符打印流)提供了一系列重载的 print 和 println 方法,用于多种数据类型的输出。
  • PrintStream 和 PrintWriter 的输出不会抛出异常。
  • PrintStream 和PrintWriter 有自动flush功能(通过构造函数实现)。
  • System.out 返回的是 PrintStream 的实例。

示例

package io.printStream;

import java.io.*;
/**
 * PrintWriter, PrintStream读取键盘输入后输出
 * @author 14251
 *
 */
public class PrintStreamDemo {
	public static void main(String[] args) {
		try {
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
			//PrintWriter​(OutputStream out, boolean autoFlush), 可以自动刷新
			PrintWriter pw = new PrintWriter(System.out , true);
//			PrintWriter pw = new PrintWriter(new FileWriter("printWriter.txt") , true);//写到文件中
//   		BufferedWriter bw= new BufferedWriter(pw);
			String line = null;
			while((line=br.readLine())!=null) {
				if("over".equals(line)) {
					break;
				}
				pw.println(line.toUpperCase());//PrintWriter有多种打印方法
			}
			pw.close();
			br.close();
		}catch(Exception e) {
			e.printStackTrace();
		} 
	}
}

合并流

概述

SequenceInputStream是InputStream的子类,可以实现多个字节输入流的合并,例如有多个流操作多个文件,现在想将这些流操作的文件合并,就可以使用SequenceInputStream来完成。

合并

package io.sequenceAndsplit;

import java.io.*;
import java.util.*;
/**
 * 合并流:
 * 假设有三个流, 分别操作三个文件, 现在想将三个文件组成一个文件
 * SequenceInputStream​(InputStream s1, InputStream s2)
 * SequenceInputStream​(Enumeration e)
 * @author 14251
 *
 */
public class SequenceDemo {
	public static void sequenceFiles(File target, File... file) throws IOException{
	//输入要合并的对象, 由于可能有多个, 因此使用可变参数接收
		//定义输入流与集合, 输入流读取文件, 集合存储输入流, 之后转换为Enumeration对象
		List<FileInputStream> list = new ArrayList<FileInputStream>();
		for(File f : file) {
			list.add(new FileInputStream(f));
		}
		//将集合变为枚举对象
		final Iterator<FileInputStream> it = list.iterator();
		Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {
			public boolean hasMoreElements() {
				return it.hasNext();
			}
			public FileInputStream nextElement() {
				return it.next();
			}
		};
		//SequenceInputStream对象
		SequenceInputStream sequence = new SequenceInputStream(en);
		BufferedInputStream bis = new BufferedInputStream(sequence);
		//输出流
		BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target));
		byte[] b = new byte[1024];
		int len = 0;
		while((len=bis.read(b))!=-1) {
			bos.write(b, 0, len);
			bos.flush();
		}
		bos.close();
		bis.close();
	}
	public static void main(String[] args) {
		try {
			sequenceFiles(new File("E:\\IO\\part\\merge.mp4"), 
						  new File("E:\\IO\\part\\1.part"), 
						  new File("E:\\IO\\part\\2.part"),
						  new File("E:\\IO\\part\\3.part"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

分割

package io.sequenceAndsplit;

import java.io.*;
/**
 * 将一个文件分割成(12M)分割成每4M一个文件
 * @author 14251
 *
 */
public class splitDemo {
	public static void splitFile(File sourceFile) throws IOException{
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
		byte[] b = new byte[1024*1024*4];//每4M切割一次
		int len = 0;
		int count = 1;
		while((len=bis.read(b))!=-1) {
			BufferedOutputStream bos = 
					new BufferedOutputStream(new FileOutputStream("E:\\IO\\part\\"+(count++)+".part"));
			bos.write(b, 0, len);
			bos.flush();
			bos.close();
		}
		bis.close();
	}
	//若文件较大, 例如要切割成1.5G的文件, 不能定义大小为500M的数组, 会造成内存溢出,
	//可以设置一个计数器, 让流每次读取5M, 每读取一次计数器加1, 当计数器为100时, 正
	//好切割出500M的碎片文件, 之后切换下一个流读取下一个500M文件
	public static void splitBigFile(File sourceFile) throws IOException{
		BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
		BufferedOutputStream bos = null;
		byte[] b = new byte[1024*1024*5];
		int len = 0;
		int fileName = 0;
		int count = 0;//每100M加1, 用count控制写入的文件, 保证每个文件大小为500M
		while((len=bis.read(b))!=-1) {
			if(count%100==0) {//每500M换一个part文件
				fileName++;
				bos = new BufferedOutputStream(new FileOutputStream("E:\\IO\\part\\"+fileName+".part"));
			}
			bos.write(b, 0, len);
			bos.flush();
			count++;
		}
		bos.close();
		bis.close();
	}
	//主函数
	public static void main(String[] args) {
		try {
//			splitFile(new File("E:\\IO\\DJI_0185.jpg"));
//			SequenceDemo.sequenceFiles(new File("E:\\IO\\part\\DD.jpg"), 
//									   new File("E:\\IO\\part\\1.part"),
//									   new File("E:\\IO\\part\\2.part"), 
//									   new File("E:\\IO\\part\\3.part"),
//									   new File("E:\\IO\\part\\4.part")
//									   );
			splitBigFile(new File("E:\\面向对象方法\\第一周\\0101.mp4"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

数据流

  • DataInputStream和DataOutputStream是专门用来操作基本数据类型的流对象。

  • DataInputStream中的方法:

方法 方法
boolean readBoolean() byte readByte()
char readChar() float readFloat()
double readDouble() short readShort()
long readLong() int readInt()
String readUTF() void readFully(byte[] b)
  • DataOutputStream中的方法
    将上述的方法的read改为相应的write即可。

对象流

概念

  • ObjectInputStreamOjbectOutputSteam
    用于存储和读取对象的处理流。可以把Java对象写入到数据中,也能把对象从数据中还原回来。
  • 序列化(Serialize):用ObjectOutputStream将一个Java对象写入IO流中。
    反序列化(Deserialize):用ObjectInputStream从IO流中恢复该Java对象。
    ObjectOutputStream和ObjectInputStream不能序列化 static 和 transient 修饰的成员变量。

对象的序列化

  • 对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上 (对象的持久化),或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象
  • 序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。
  • 序列化是 RMI(Remote Method Invoke – 远程方法调用)过程的参数和返回值都必须实现的机制,而 RMI 是 JavaEE 的基础,因此序列化机制是 JavaEE 平台的基础。
  • 如果需要让某个对象支持序列化机制, 则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
    Serializable
    Externalizable
  • 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态常量:
    private static final long serialVersionUID;
    用来表明类的不同版本间的兼容性。
  • 如果类没有显示定义UID,它的值是虚拟机自动生成的。若类的源代码作了修改,则serialVersionUID也会发生变化,此时该类之前序列化保存在本地的文件将不能被读取为该类对象。
  • 如果类显示定义了UID,那么当类中的部分代码改变时,因为UID相同,保存在本地的文件仍能转换为该类对象。

使用对象流序列化对象

若某个类实现了 Serializable 接口,该类的对象就是可序列化的,步骤:

  • 1.要序列化的对象的类实现Serializable 接口。
  • 2.显示声明private static final long serialVersionUID;
  • 3.创建ObjectOutputStream。
  • 4.调用ObjectOutputStream 对象的 writeObject(对象) 方法输出可序列化对象。注意写出之后,要使用flush()。

反序列化,步骤:

  • 1.创建ObjectInputStream
  • 调用 readObject() 方法读取流中的对象,该方法每次读取一个Object对象,并会自动指向下一个对象。

注意:如果某个类的字段不是基本数据类型或 String 类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则拥有该类型的 Field 的类也不能序列化。

package io.serialize;

import java.io.*;
import java.util.*;
/**
 * 演示对象序列化
 * 将Person对象存储在本地磁盘中
 * 想要序列化的对象必须实现Serializable接口
 * @author 14251
 *
 */
class Person implements Serializable{
	private static final long serialVersionUID = -9130181056236625591L;
	private String name;
	private int age;
	//static与transient修饰的属性不会被序列化, 但是构造函数中有这些属性, 所以在读数据时候, 读取0或null
	transient String sex;
	static String country = "CN";
	Person(String name, int age, String sex){
		this.name = name;
		this.age = age;
		this.sex = sex;
	}
	//overload toString()
	public String toString() {
		return name+": "+age;
	}
}

public class SerializeDemo {
	public static void main(String[] args) {
		
			File file = new File("E:\\IO\\Person.txt");
//			存入
//			Person p1 = new Person("LiHua", 21);
//			Person p2 = new Person("XiaoMing", 22);
//			try {
//				writeObj(file, p1, p2);
////			writeObj(file, p1, p2, null);
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
			
//			读取
			try {
				Set<Person> set = readObj(file);
//				List set = readObj("E:\\IO\\Person.txt");
				for(Person p : set) {
					System.out.println(p);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
	}
	//写数据
	public static <T> void writeObj(File file, T... t) throws IOException{
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
		for(Object obj : t) {
			oos.writeObject(obj);
			oos.flush();
		}
		oos.close();
	}
	//读数据, 方法1: 捕获EOFException, 使得程序可以读到最后
	public static Set<Person> readObj(File file) throws IOException, ClassNotFoundException{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
		Set<Person> set = new HashSet<Person>();
        try {
            while(true) {
                Person p = (Person)ois.readObject();
                set.add(p);
            }
        } catch(EOFException e) {
        	//不处理
        } finally {
            ois.close();
        }
        return set;
    }
	
	//读数据, 方法2: 不捕获EOFException, 通过返回null判断读到最后, 
	//但要在writeObject()时, 写入一个null, 并且集合要有序, 因为null必须在最后一个
	public static List<Person> readObj(String str) throws IOException, ClassNotFoundException{
		File file = new File(str);
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
		List<Person> list = new ArrayList<Person>();
	    while(true) {
	    	if(ois.readObject()==null) {
	    		break;
	    	}else{
	    		list.add((Person) ois.readObject());
	    	}
		}
		ois.close();
		return list;
    }
	
}	

注意:Java序列化与反序列化使用的类要完全一致,类的包名、类名、类结构等统统都要相同。
如:若序列化时的Person类是其他包中的Person类,如day13.Person,则在反序列化时,接收结果的Person类要指明为day13.Person,不能省略。

管道流

PipedInputStream、PipedOutputStream、PipedReader、Pipedwriter常用于多线程中,特点是输入流与输出流可以直接相连,不需要缓冲区进行间接关联。

方法

方法 意义
PipedInputStream(PipedOutputStream src) 可以相互传入对方对象,进行相互关联
connect​(PipedOutputStream src) 关联流
package io.piped;

import java.io.*;
/**
 * 管道流: 用于多线程技术中
 * @author 14251
 *
 */
class Read implements Runnable{
	private PipedInputStream pis;
	Read(PipedInputStream pis){
		this.pis = pis;
	}
	@Override
	public void run() {
		try {
			byte[] b= new byte[1024];
			int len = 0;
			while((len=pis.read(b))!=-1) {
				System.out.println(new String(b, 0, len));
			}
			pis.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
}

class Write implements Runnable{
	private PipedOutputStream pos;
	Write(PipedOutputStream pos){
		this.pos = pos;
	}
	@Override
	public void run() {
		try {
			pos.write("Piped Stream".getBytes());
			pos.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
}
public class PipedDemo {
	public static void main(String[] args) {
		try {
			PipedInputStream pis= new PipedInputStream();
			PipedOutputStream pos = new PipedOutputStream();
			pis.connect(pos);//将两个管道连结
			Thread t1 = new Thread(new Read(pis));
			Thread t2 = new Thread(new Write(pos));
			t1.start();
			t2.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

RandomAccessFile类

随机访问

RandomAccessFile 类的随机访问实质上是任意访问。

  • RandomAccessFile 类支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件。
  • 支持只访问文件的部分内容。
  • 可以向已存在的文件后追加内容。
  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。 RandomAccessFile 类对象可以自由移动记录指针:
    long getFilePointer():获取文件记录指针的当前位置。
    void seek(long pos):将文件记录指针定位到 pos 位置。
  • 文件的随机写不需要 flush() 操作,且当 seek() 定位在文件内容内部时,会覆盖掉后面的内容。

构造器

  • public RandomAccessFile(File file, String mode)
    public RandomAccessFile(String name, String mode)
  • 创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式:
    r:以只读方式打开。
    rw:打开以便读取和写入。
    rwd:打开以便读取和写入;同步文件内容的更新。
    rws:打开以便读取和写入;同步文件内容和元数据的更新。

读写文件举例

package io;
/**
 * 读操作
 * @author MCC
 *
 */
import java.io.RandomAccessFile;

public class TestRandomAccessFile {
	public static void main(String[] args) {
		try {
			RandomAccessFile raf = new RandomAccessFile("E:\\IO\\4.txt", "r");
			raf.seek(2);//从0开始,4.txt内容为123abc,第2个位置为3
			byte[] b = new byte[1024];
			raf.read(b);
			System.out.println(new String(b));//3abc
			raf.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
}

package io;

import java.io.RandomAccessFile;
/**
 * 读写操作
 * @author MCC
 * RandomAccessFile不需要flush()
 * seek(raf.length());可以定位到文件的最后,追加内容
 * 随机写会覆盖文件内容,例如文件内容为:123456,
 * 此时,seek(3);则文件内容被覆盖为:123xxx
 */
public class TestRandomAccessFile {
	public static void main(String[] args) {
		try {
//			RandomAccessFile raf = new RandomAccessFile("E:\\IO\\4.txt", "r");
//			raf.seek(2);//从0开始,4.txt内容为123abc,第2个位置为3
//			byte[] b = new byte[1024];
//			raf.read(b);
//			System.out.println(new String(b));//3abc
//			raf.close();
			RandomAccessFile raf = new RandomAccessFile("E:\\IO\\5.txt", "rw");
			String str = "Random Access File Write";
//			raf.seek(3);
			raf.seek(raf.length());//在内容的最后写,追加内容
			raf.write(str.getBytes());
//			raf.writeBytes(str);
			raf.close();//RandomAccessFile不需要flush()
		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
}

你可能感兴趣的:(JavaSE学习笔记——IO原理)