Java基础之IO操作(上)

IO流

概述:

  • 用来处理设备之间的数据传输。Java对数据的操作是通过流的方式。Java用于操作流的对象都在IO包中。

分类(按操作数据):

  • 字符流的抽象基类:
    • Reader
      • BuffedReader(字符读取流缓冲区)
      • InputStreamReader(转换流)
        • FileReader
    • Writer
      • PrintWrite
      • BuffedWriter(字符输出流缓冲区)
      • OutputStreamWriter(转换流)
        • FileWriter
  • 字节流的抽象基类:

    • InputStream

      • PipedInputStream(管道流)
      • ObjectInputStream(对象流)
      • SequenceInputStream(合并流)
      • FileInputStream
      • FilterInputStream
        • BufferedInputStream(字节读取流缓冲区)
    • OutputStream

      • PipedOutputStream(管道流)
      • ObjectOutputStream(对象流)
      • FileOutputStream
      • FilterOutputStream
        • BufferedOutputStream(字节输出流缓冲区)
        • PrintStream
  • 特点:四个类派生出来的子类名称都是以其父类名作为子类名的后缀

字符流

字节流+编码表

字符流两个基类:

字符流中的输出流:Writer

  • 继承体系:

    • Writer
      • PrintWrite
      • BuffedWriter(字符输出流缓冲区)
      • OutputStreamWriter(转换流)
        • FileWriter
  • 特点:文件不存在,则会自动创建;如果文件存在,则会被覆盖

  • 基本方法:

    • void write(char[] cbuf) 写入字符数组。
    • abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
    • void write(String str) 写入字符串。
    • void write(String str, int off, int len) 写入字符串的某一部分。
  • 步骤:

    • 创建一个可以往文件中写入字符数据的字符输出流对象
    • 调用Writer对象中的write方法,写入数据,数据被写入到临时存储缓冲区中
    • 进行刷新,将数据直接写入到目的地中
    • 关闭流,关闭资源,在关闭前会先调用flush刷新缓冲中的数据到目的地
  • 说明:

    • close方法只能用一次。流关闭以后不能再调用write方法;
    • 如果构造函数中加入true,代表不覆盖已有的文件,可以实现对文件进行续写
  • IO流的异常处理方式:为防止代码异常导致流无法关闭,因此在finally中对流进行关闭。

    • 代码示例:
FileWriter fw = null;

try {
	fw = new FileWriter("demo.txt");
	fw.write("abcdefg");
}
catch (IOException e) {
	System.out.println(e.toString());
}
finally {
	try {
		if(fw!=null)
			fw.close();
	}
	catch (IOException e) {
		throw new RuntimeException("关闭失败");
	}

字符流中的输入流:Reader

  • 继承体系:

    • Reader
      • BuffedReader(字符读取流缓冲区)
      • InputStreamReader(转换流)
        • FileReader
  • 特点:最后带Reader的均为其子类

  • 基本方法:

    • int read() 读取单个字符,而且会自动往下读;返回作为整数读取的字符,范围在 0 到 65535 之间 (0x00*0xffff),如果已到达流的末尾,则返回 *1
    • int read(char[] cbuf) 将字符读入数组。返回:读取的字符个数(数组长度),如果已到达流的末尾,则返回 *1
    • abstract int read(char[] cbuf, int off, int len) 将字符读入数组的某一部分。
    • int read(CharBuffer target) 试图将字符读入指定的字符缓冲区。
  • 步骤:

    • 创建一个文件读取流对象,和指定名称的文件相关联(要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException)
    • 调用读取流对象的read方法
    • 关闭流,关闭资源

字符流的缓冲区

  • 概述:
    • 缓冲区的出现提高了对数据的读写效率
    • 缓冲区要结合流才可以使用
  • 作用:
    • 提高了对数据的读写效率
    • 在流的基础上对流的功能进行了增强
      • 写入换行:使用BufferedWriter类中的 newLine() 方法;
      • 读取一行数据:使用BufferedReader类中的 readLine() 方法

对应类(2种):

  • 字符读取流缓冲区: BufferedReader

    • 基本方法:
      • BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。
      • int read() 读取单个字符。 覆盖了父类中的read方法。
      • String readLine() 读取一个文本行。读到文件末尾时,返回null。可采用返回是否为null作为循环条件。

      特点:使用了读取缓冲区的read方法,将读取到的字符进行缓冲并判断换行标记,将标记前的缓冲数据变成字符串返回。

  • 代码示例:

	/*
	明白了BufferedReader类中特有方法readerLine的原理后,
	可以自定义一个类中包含一个功能和readLine一致的方法。
	来模拟一下BufferedReader
	*/
	import java.io.*;
	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')
					return sb.toString();//通过回车符来标识每一行的数据返回;若文件的最后一行没有回车符,该行将不会返回,因此需在循环结束时,增加判断语句。
				else
					sb.append((char)ch);
			}
			if(sb.length()!=0)
				return sb.toString();
			return null;
		}

		/*
		覆盖Reader类中的抽象方法
		*/

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

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

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


	class MyBufferedReaderDemo {
		public static void main(String[] args) throws IOException{
			FileReader fr = new FileReader("buf.txt");
			MyBufferedReader myBuf = new MyBufferedReader(fr);

			String line = null;
			while ((line=myBuf.myReadLine())!=null) {
				System.out.println(line);
			}

			myBuf.myClose();
		}
	}
  • LineNumberReader 类:BufferedReader的子类。跟踪行号的缓冲字符输入流。

    • 此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。
  • 字符输出流缓冲区:BufferedWriter

    • 基本方法:
      • BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流
      • void newLine() 写入一个行分隔符。
      • void flush() 刷新该流的缓冲。

字节流

基本操作与字符流类相同。但它不仅可以操作字符,还可以操作其他媒体文件。

分类:

  • 字节流两个基类:
    • InputStream

      • ByteArrayInputStream
      • SequenceInputStream
      • FileInputStream
      • FilterInputStream
        • DataInputStream
        • BufferedInputStream(字节读取流缓冲区)
    • OutputStream

      • ByteArrayOutputStream
      • FileOutputStream
      • FilterOutputStream
        • DataOutputStream
        • BufferedOutputStream(字节读输出流缓冲区)
        • PrintStream

字节输入流InputStream

  • 常用方法:
    • int available() 返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。
  • 三种读取方法示例:
	//读取方式一
	public static void readFile_1() throws IOException {
		//创建一个读取流对象,和指定文件关联
		FileInputStream fis = new FileInputStream("fos.txt");

		//打印刚好字符字节大小,不过要少用,当文件太大时,可能内存溢出
		byte[] buf = new byte[fis.available()];//定义一个刚刚好的缓冲区,不用再循环了
		fis.read(buf);
		System.out.println(new String(buf));

		fis.close();
	}

	//读取方式二
	public static void readFile_2() throws IOException {
		FileInputStream fis = new FileInputStream("fos.txt");
		//建议使用这种方式
		byte[] buf = new byte[1024];
		int len =0;
		while ((len=fis.read(buf))!=*1) {
			System.out.println(new String(buf,0,len));
		}
		fis.close();
	}

	//读取方式三
	public static void readFile_3() throws IOException{
		FileInputStream fis = new FileInputStream("fos.txt");
		//一次读取一个字节
		int ch = 0;
		while ((ch=fis.read())!=*1) {
			System.out.println((char)ch);
		}
		fis.close();
	}

字节输出流OutputStream

  • 常用方法:与字符输出流基本相同

转换流

  • 概述:字符流与字节流之间的桥梁,方便了字符流与字节流之间的操作

  • 应用:字节流中的数据都是字符时,转成字符流操作更高效。

    • InputStreamReader:字节**>字符的桥梁,解码。
      • 通过构造方法:InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。
    • OutputStreamWriter:字符**>字节的桥梁,编码。
      • 通过构造方法:OutputStreamWriter(OutputStream out) 创建使用默认字符编码的 OutputStreamWriter。
      • OutputStreamWriter(OutputStream out, String charsetName) 创建使用指定字符集的 OutputStreamWriter。可使用指定的编码将要写入流中的字符编码成字节。

        注:FileWriter fw = new FileWriter("a.txt");等同OutputStreamWriter osw = new OutputStreamWriter(newFileOutputStream("a.txt"),"GBK");其实就是转换流指定了本机默认码表的体现,而且这个转换流的子类对象,可以方便操作文本文件。简单说:操作文件的字节流+本机默认的编码表。是按照默认码表来操作文件的便捷类;如果操作文本文件需要明确具体的码表必须用转换流。

  • 说明:使用字节流读取一个中文字符需要读取两次,因为一个中文字符由两个字节组成,而使用字符流只需读取一次。

键盘录入最常见方法:

  • 步骤:
    • 获取键盘录入对象:InputStream in = System.in;
    • 将字节流对象转换成字符流对象:InputStreamReader isr = new InputStreamReader(in);
    • 为了提高效率,将字符串进行缓冲区技术高效操作:BufferedReader bufr =new BufferedReader(isr);
  • 简写示例:
	BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
	BufferedWriter bufw =new BufferedWriter(new OutputStreamWriter(System.out));

字节流的缓冲区

  • 同样是为了提高字节流的读写效率
  • BufferedInputStream和BufferedOutputStream

流操作的基本规律

通过三个明确来完成。

  1. 明确源和目的。
    • 源:输入流(InputStream、Reader)
    • 目的:输出流(OutputStream、Writer)
  2. 操作的数据是否是纯文本。
    • 源:
      • 是:字符流(Rea der)
      • 否:字节流(InputStream)
    • 目的:
      • 是:字符流(Writer)
      • 否:字节流(OutputStream)
  3. 当体系明确后,再明确具体的设备。
    • 源设备:
      • 硬盘:File
      • 键盘:System.in
      • 内存:数组(ArrayStream)
      • 网络:Socket流
    • 目的设备:
      • 硬盘:File
      • 控制台:System.out
      • 内存:数组(ArrayStream)
      • 网络:Socket流
  4. 是否需要额外功能
    • 是否需要高效(缓冲区)
      • 是,就加上buffer
  • 代码示例:
	import java.io.BufferedReader;
	import java.io.BufferedWriter;
	import java.io.FileOutputStream;
	import java.io.IOException;
	import java.io.InputStreamReader;
	import java.io.OutputStreamWriter;
	import java.io.PrintStream;

	/*
	 * 需求:把键盘录入的数据按照指定的编码表(utf*8),存到文件中。		
	 * 分析:
	 *		源:键盘(System.in)
	 * 		目的:FileWriter
	 * 		
	 *  	纯文本 Writer
	 * 		
	 *		设备:硬盘,一个文件,使用FileWriter
	 *		但是FileWriter是使用的默认编码表:GBK,存储时,需要加入指定的编码表,而指定的编码表只有转换流可以指定
	 *		所以要使用的对象是OutputStreamWriter。
	 *		而该转换流对象需要接收一个字节输出流,而且还可以操作的文件的字节输出流:FileOutputStream
	 *		OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF*8");
	 *
	 *		需要提高效率吗?需要
	 *		BufferedWriter bufw = new BufferedWriter(osw);
	 */
	public class TransStreamDemo {

		public static void main(String[] args) throws IOException {

			// 更改源
			// System.setIn(new FileInputStream("PersonDemo.java"));

			// 更改目标,将会生成一个空文件(若不存在,存在则会覆盖)
			System.setOut(new PrintStream("zz.txt"));

			// 键盘录入
			BufferedReader bufr = new BufferedReader(new InputStreamReader(
					System.in));

			// 按照指定的编码表(utf*8),将数据存到文件中
			BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream("d1.txt"), "UTF*8"));

			String line = null;

			while ((line = bufr.readLine()) != null) {
				if ("over".equals(line))
					break;
				// System.out.println(line.toUpperCase());
				bufw.write(line.toUpperCase());
				bufw.newLine();// newLine()为BufferedReader特有方法,跨平台
				bufw.flush();
			}
			bufr.close();
		}
	}

File类

概述:

用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。 File对象可以作为参数传递给流的构造函数。

常见方法:

构造方法

  • File(String pathname) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
  • File(String parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。
  • File(File parent, String child) 根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。
//可以将一个已存在的,或者不存在的文件或者目录封装成file对象,不会创建文件
//方式一:
File f1 = new File("d:\\demo\\a.txt" );
//方式二:
File f2 = new File("d:\\demo" ,"a.txt" );
//方式三:
File f = new File("d:\\demo" );
File f3 = new File(f,"a.txt" );
/*考虑到Windows和Linux系统通用(File.separator是与系统有关的默认名称分隔符。
在 UNIX 系统上,此字段的值为 '/';在 MicrosoftWindows 系统上,它为 '\\'。):
*/
File f4 = new File("d:" + File.separator + "demo" +File.separator + "a.txt" );

常用方法:

  • 创建:

    • boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立创建文件,而且文件已经存在,会覆盖,输出流如果继续想该文件写入数据,可在构造方法中添加true
    • boolean mkdir():创建文件夹。
    • boolean mkdirs():创建多级文件夹。
  • 删除:

    • boolean delete():删除失败返回false。如果文件夹中有文件或者文件正在被使用,则删除不了返回falsel。
    • void deleteOnExit();在程序退出时删除指定文件。
  • 判断:

    • boolean exists() :文件是否存在。
    • isFile(): 测试此抽象路径名表示的文件是否是一个标准文件。
    • isDirectory(): 测试此抽象路径名表示的文件是否是一个目录。
    • isHidden(): 测试此抽象路径名指定的文件是否是一个隐藏文件。
    • isAbsolute(): 测试此抽象路径名是否为绝对路径名。
  • 获取信息:

    • String getName():返回由此抽象路径名表示的文件或目录的名称。
    • String getPath():将此抽象路径名转换为一个路径名字符串。
    • String getParent():该方法返回的是绝对路径中的父目录。如果获取的是相对路径,返回null。如果相对路径中有上一层目录那么该目录就是返回结果。
    • getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
    • long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。
    • long length():返回此抽象路径名表示的文件的长度,以字节为单位;如果文件不存在,则返回 0L。如果此路径名表示一个目录,则返回值是不确定的。
    • static File[] listRoots():列出可用的文件系统根。
    • long getTotalSpace(): 返回此抽象路径名指定的分区大小。
    • long getFreeSpace(): 返回此抽象路径名指定的分区中未分配的字节数。
    • long getUsableSpace(): 返回此抽象路径名指定的分区上可用于此虚拟机的字节数。
    • String[] list() 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。

      注:该方法的File对象中封装的必须是目录,否则会产生NullPointerException,如果访问的是系统级目录也会发生空指针异常;如果目录存在但是没有内容,会返回一个数组,但是长度为0。

    • String[] list(FilenameFilter filter) 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。

      实现步骤:先定义一个类实现FilenameFilter接口,然后复写accept方法(boolean accept(File dir, String name) 测试指定文件是否应该包含在某一文件列表中)。

    • File[] listFiles() 返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。
  • 重命名:

    • boolean renameTo(File dest)重新命名此抽象路径名表示的文件。
      //示例:
      File f1 = new File("d:\\code\\day21\\0.mp3" );
      File f2 = new File("d:\\code\\day21\\1.mp3" );
      boolean b = f1.renameTo(f2);
      

代码示例:

import java.io.File;
import java.io.FilenameFilter;

/*
 * 需求:获取指定目录下,任意一个指定类型文件的列表
 * 分析:可以使用 String[] list(FilenameFilter filter)方法
 * 		自定义一个类,实现FilenameFilter接口,覆盖其accept方法
 */
public class FileListDemo {

	public static void main(String[] args) {
		// 建立一个指定目录的File对象
		File dir = new File("D:\\Java\\Day20");
		dirList(dir);
	}

	private static void dirList(File dir) {
		// 调用传入File对象的list方法,并传入一个自定义文件名过滤器类
		String[] dirname = dir.list(new MyTypeFilter(".java"));
		for (String s : dirname) {
			System.out.println(s);
		}
	}
}

// 自定义文件名过滤器类,并实现FilenameFilter接口,覆盖accept方法
class MyTypeFilter implements FilenameFilter {
	private String type;

	// 定义构造器
	public MyTypeFilter(String type) {
		this.type = type;
	}

	@Override
	public boolean accept(File dir, String name) {
		return name.endsWith(type);
	}
}

递归

简介:

  • 函数自身直接或者间接的调用到了自身。

使用时机:

  • 一个功能在被重复使用,并每次使用时,参与运算的结果和上一次调用有关。这时可以用递归来解决问题。

注意:

  • 限定条件,要注意递归的次数,尽量避免内存溢出。

代码示例:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/*
 *需求:
 *	将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。
 *	建立一个java文件列表文件。
 *
 *思路:
 *	1,对指定的目录进行递归
 *	2,获取递归过程所有的java文件路径。
 *	3,将这些路径存储到集合中。
 *	4,将集合中的数据写入到一个文件中。
 */
class JavaFileListDemo {
	public static void main(String[] args) {
		// 将指定目录封装成对象
		File dir = new File("D:\\Java");
		// 建立集合用于存放路径
		List<File> list = new ArrayList<File>();
		// 调用自定义获取文件列表方法
		fileToList(dir, list);
		// System.out.println(list.size());

		File file = new File(dir, "javalist.txt");
		// 调用自定义将集合数据,写入文件的方法
		writeToFile(list, file.toString());
	}

	// 自定义获取文件列表方法
	public static void fileToList(File dir, List<File> list) {
		File[] files = dir.listFiles();
		for (File f : files) {
			if (f.isDirectory())
				// 当路径仍为目录时,进行递归
				fileToList(f, list);
			else {
				// 直到是java文件时,将路径存储到集合中
				if (f.getName().endsWith(".java"))
					list.add(f);
			}
		}
	}

	// 定义一个将集合数据,写入文件的方法
	public static void writeToFile(List<File> list, String javaListFile) {
		BufferedWriter bufw = null;
		try {
			bufw = new BufferedWriter(new FileWriter(javaListFile)); // 传入一个用于存放数据的文件字符串名"D:\\Java\\javalist.txt"
			for (File f : list) {
				String path = f.getAbsolutePath();
				bufw.write(path);
				bufw.newLine();
				bufw.flush();
			}
		} catch (IOException e) {
			throw new RuntimeException("文件写入失败");
		} finally {
			try {
				if (bufw != null)
					bufw.close();
			} catch (IOException e) {
				throw new RuntimeException("写入关闭失败");
			}
		}
	}
}