Java学习之IO流

一、IO的概述

当需要把内存中的数据存储到持久化设备上这个动作称为输出(写)Output操作。

当把持久设备上的数据读取到内存中的这个动作称为输入(读)Input操作。

我们把输入和输出的动作叫做IO操作,或者也可以叫OI操作,看你心情

想把Java程序操作完的数据保存硬盘等持久化设备上,这时需要把这些数据通过JVM,调用操作系统底层的读写技术才能把数据保存在持久设备上,同样的如果我们要从持久设备上读取数据,也要借助操作系统底层。而Java是面向对象的语言,它把这些操作和系统底层的相关的命令已经给我们封装成相应的对象,我们需要读写操作时,找到对应的对象即可完成。

 二、File类

2.1、File类概述

File文件和目录路径名的抽象表示形式。

Java中把文件或者目录(文件夹)都封装成File对象。也就是说如果我们要去操作硬盘上的文件,或者文件夹只要找到File这个类即可。

2.2、File类的构造函数和分隔符

public class FileDemo {
	public static void main(String[] args) {
		//File构造函数演示
		String pathName = "e:\\java_code\\day22e\\hello.java";
		File f1 = new File(pathName);//将Test22文件封装成File对象。注意;有可以封装不存在文件或者文件夹,变成对象。
		System.out.println(f1);

		File f2 = new File("e:\\java_code\\day22e","hello.java");
		System.out.println(f2);

		//将parent封装成file对象。
		File dir = new File("e:\\java_code\\day22e");
		File f3 = new File(dir,"hello.java");
		System.out.println(f3);

		File类中几个静态的成员属性,其中separator描述的与操作系统相关的路径分隔符
		File f5 = new File("e:"+File.separator+"java_code"+File.separator+"hello.java");
	}
}

2.3、File类的获取

创建完了File对象之后,那么File类中都有那些方法,可以对File进行操作。

public class FileMethodDemo {
	public static void main(String[] args) {
		//创建文件对象
		File file = new File("Test22.java");
		//获取文件的绝对路径,即全路径
		String absPath = file.getAbsolutePath();
		//File中封装的路径是什么获取到的就是什么。
		String path = file.getPath();
		//获取文件名称
		String fileName = file.getName();
		//获取文件大小
		long size = file.length();
		//获取文件修改时间,获取到的是毫秒值,可以使用日期格式化把毫秒值转成字符串文本格式
		long time = file.lastModified();
		String str_date = DateFormat.getDateTimeInstance(DateFormat.LONG,DateFormat.LONG).format(new Date(time));
	}
}
		

2.4、文件和文件夹的创建删除

public class FileMethodDemo2 {
	public static void main(String[] args) throws IOException {
		// 对文件或者文件加进行操作。
		File file = new File("e:\\file.txt");
		// 创建文件,如果文件不存在,创建 true 如果文件存在,则不创建 false。 如果路径错误,IOException。
		boolean b1 = file.createNewFile();
		//-----------删除文件操作-------注意:不去回收站。慎用------
		boolean b2 = file.delete();
		//-----------需要判断文件是否存在------------
		boolean b3 = file.exists();
		//-----------对目录操作 创建,删除,判断------------
		File dir = new File("D:\\a");
		//mkdir()创建单个目录。//dir.mkdirs();创建多级目录
		boolean b4 = dir.mkdir();
		//删除目录时,如果目录中有内容,无法直接删除。
		boolean b5 = dir.delete();
		//-----------判断文件,目录------------
		File f = new File("e:\\javahaha");
		System.out.println(f.isFile());//判断是否为文件
		System.out.println(f.isDirectory());//判断是否为目录
	}
}

2.5、listFiles()方法介绍

一个目录中可能有多个文件或者文件夹,那么如果File中有功能获取到一个目录中的所有文件和文件夹,那么功能得到的结果要么是数组,要么是集合。

public class FileMethodDemo3 {
	public static void main(String[] args) {
		File dir = new File("e:\\java_code");
		//获取的是目录下的当前的文件以及文件夹的名称。
		String[] names = dir.list();
		for(String name : names){
			System.out.println(name);
		}
		//获取目录下当前文件以及文件对象,只要拿到了文件对象,那么就可以获取其中想要的信息
		File[] files = dir.listFiles();
		for(File file : files){
			System.out.println(file);
		}
	}
}

注意:在获取指定目录下的文件或者文件夹时必须满足下面两个条件

1,指定的目录必须是存在的,

2,指定的必须是目录。否则容易引发返回数组为null,出现NullPointerException

2.6、文件过滤器

我们是可以先把一个目录下的所有文件和文件夹获取到,并遍历当前获取到所有内容,遍历过程中在进行筛选,但是这个动作有点麻烦,Java给我们提供相应的功能来解决这个问题。

public class FileDemo2 {
	public static void main(String[] args) {
		//获取扩展名为.java所有文件
		//创建File对象
		File file = new File("E:\\JavaSE1115\\code\\day11_code");
		//获取指定扩展名的文件,由于要对所有文件进行扩展名筛选,因此调用方法需要传递过滤器
		File[] files = file.listFiles(new FileFilterBySuffix());
		for(File f : files){
			System.out.println(f);
		}
	}
}
class FileFilterBySuffix implements FilenameFileter{
	public boolean accept(File dir,String name){
		return name.endsWith(".txt");
	}
}

创建我们自己定义的过滤器对象时,在明确具体的需要过滤的名称,那就要对我们的过滤器进行改造。

class FileFilterBySuffix implements FilenameFilter{
	private String suffix;
	//在创建过滤器对象时,明确具体需要过滤的文件名称
	public FileFilterBySuffix (String sufffix){
		this.suffix = suffix;
	}
	public boolean accept(File dir, String name) {
		return  name.endsWith(suffix);
	}
}

2.7、文件队列

遍历的目录太多会使得递归时间很长,还有可能造成溢出,因此我们需要另一个思路做法。
思路:

  1. 可以通过对每一个目录进行for循环,但是目录层级很多,for会死掉。
  2. 每遍历到一个目录,不对其进行遍历,而是先临时存储起来。 相当于把所有目录(无论同级不同级)都存储起来。
  3. 遍历容器时取到就是目录,然后对目录遍历即可。
  4. 从容器中取到目录遍历时发现内部还有目录,一样将这些目录存储到容器中。
  5. 只要不断的遍历这个容器就行了。

注意:当目录特别多的时候,虽然避免递归导致栈溢出了,可是容器存放在堆中,不停的给容器中添加目录,容器中的内容会一直增加下去,最后也会导致堆内存溢出。

public class FileDemo{
	public static void main(String[] args) {
		File file = new File("d:\\test");
		getFileAll(file);
	}
	//获取指定目录以及子目录中的所有的文件
	public static void getFileAll(File file) {
		//获取当前目录下的所有文件和文件夹
		File[] files = file.listFiles();
		//创建队列对象,存放目录
		Queue q = new Queue();
		//遍历当前获取到的文件和文件夹对象
		for(File f : files){
			//判断当前遍历到的f对象,是否为目录
			if(f.isDirectory()){
				//是目录,就添加到队列中
				q.add(f);
			}else{
				//不是目录就打印出
				System.out.println(f);
			}
		}
		//循环结束后,就已经把当前目录下的所有子目录存放在队列中,接下来只需要遍历队列中的目录
		while(!q.isNull()){
			//获取队列中的目录
			File subdir = q.get();
			//获取当前目录下所有目录和文件
			File[] subFiles = subdir.listFiles();
			for(File f : subFiles){
				//判断当前遍历到的f对象,是否为目录
				if(f.isDirectory()){
					//是目录,就添加到队列中
					q.add(f);
				}else{
					//不是目录就打印出
					System.out.println(f);
				}
			}
		}
	}
}
	//描述队列
	class Queue{
		private LinkedList link ;
		//对外提供创建队列的对象
		public Queue(){
			this.link = new LinkedList();
		}
		//提供给队列中添加元素的方法
		public void add(T t){
			link.addLast(t);
		}
		//提供从队列中获取元素的方法
		public T get(){
			//这里获取的同时,把队列中最开始的那个元素已经删除
			return link.removeFirst();
		}
		//判断队列中是否还有元素
		public boolean isNull(){
			return link.isEmpty();
	}
}

三、字节流

1、字节输出流

1.1、数据写入文件中

给文件中写数据,或者读取文件中的数据,API中查找的OutPut很多,其中:OutputStream: 输出字节流的超类。

基本特点:

  1. 操作的数据都是字节。
  2. 定义了输出字节流的基本共性功能。
  3. 输出流中定义都是写write()方法。
    操作字节数组write(byte[]),操作单个字节write(byte)。

子类有规律:所有的子类名称后缀是父类名,前缀名是这个流对象功能。

想要操作文件: FileOutputStream

public class FileOutputStreamDemo {
	public static void main(String[] args) throws IOException{
		//需求:将数据写入到文件中。
		//创建临时目录
		File dir = new File("tempfile");
		if(!dir.exists()){
			dir.mkdir();
		}
		//创建存储数据的文件。
		File file = new File(dir,"file.txt");
		//创建一个用于操作文件的字节输出流对象。一创建就必须明确数据存储目的地。
		//输出流目的是文件,会自动创建。如果文件存在,则覆盖。
		FileOutputStream fos = new FileOutputStream(file);
		//调用父类中的write方法
		byte[] date = "abcd".getBytes();
		fos.write(date);
		//关闭流
		fos.close();
	}
}

1.2、数据写入文件中(Plus)

new FileOutputStream(file) 这样创建对象,会覆盖原有的文件,如果想实现续写。

FileOutputStream的构造函数中,可以接受一个boolean类型的值,如果值true,就会在文件末位继续添加。

public class FileOutputStreamDemo2 {
	private static final String LINE_SEPARATOR = System.getProperty("line.separator");
	public static void main(String[] args) throws Exception {
		File file = new File("tempfile\\file.txt");
		FileOutputStream fos = new FileOutputStream(file, true);
		String str = LINE_SEPARATOR+"itcast";
		fos.write(str.getBytes());
		fos.close();
	}
}

1.3、IO异常的处理

上述代码都发生了IO异常。如何处理(main方法不能再抛出):

public class FileOutputStreamDemo3 {
	public static void main(String[] args) {

		File file = new File("k:\\file.txt");
		//定义FileOutputStream的引用
		FileOutputStream fos = null;
		try{
			//创建流
			fos = new FileOutputStresm(file);
			//写数据
			fos.write("abcd".getBytes();)
		}catch(IOException o){
			System.out.println(e.toString() + "----");
		}finally{
			//一定要判断fos是否为null,只有不为null时,才可以关闭资源
			if (fos != null) {
				try {
					fos.close();
				} catch (IOException e) {
					throw new RuntimeException("");
				}
			}
		}
	}
}

2、字节输入流

2.1、读取数据read方法

InputStream:字节输入流的超类。

常见功能

  1. int read():读取一个字节并返回,没有字节返回-1。
  2. int read(byte[]): 读取一定量的字节数,并存储到字节数组中,返回读取到的字节数。

用于读取文件的字节输入流对象FileInputStream
例:

public class FileInputStreamDemo {
	public static void main(String[] args) throws IOException {
		File file = new File("tempfile\\file.txt");
		//创建一个字节输入流对象,必须明确数据源,其实就是创建字节读取流和数据源相关联。
		FileInputStream fis = new FileInputStream(file);
		//读取数据。使用 read();一次读一个字节。
		int ch = 0;
		whlie((ch = fis.read()) != -1){
			System.out.println((char)ch);
		}
		// 关闭资源。
		fis.close();
	}
]

2.2、读取数据

在读取文件中的数据时,调用read方法,每次只能读取一个,太麻烦了,于是我们可以定义数组作为临时的存储容器,这时可以调用重载的read方法,一次可以读取多个字符。

public class FileInputStreamDemo2 {
	public static void main(String[] args) throws IOException {
		File file = new File("tempfile\\file.txt");
		// 创建一个字节输入流对象,必须明确数据源,其实就是创建字节读取流和数据源相关联。
		FileInputStream fis = new FileInputStream(file);
		byte[] buf = new byte[1024];
		int len = 0;
		whlie((len = fis.read(buf)) != -1){
			System.out.println(new string(buf,0,len));
		}
		fis.close();
	}
}	

2.3、字节流练习

public class CopyFileTest {
	public static void main(String[] args) throws IOException {
		//1,明确源和目的。
		File srcFile = new File("E:\\1.mp3");
		File destFile = new File("E:\\copy_2.mp3");

		//2,明确字节流 输入流和源相关联,输出流和目的关联。
		FileInputStream fis = new FileInputStream(srcFile);
		FileOutputStream fos = new FileOutputStream(destFile);
		//3, 使用输入流的读取方法读取字节,并将字节写入到目的中。
		in ch = 0;
		while((ch = fis.read()) != -1){
			fos.write(ch);
		}
		//4.关闭资源
		fos.close();
		fis.close();
	}
}

2.4、自定义缓冲数组

一次把多文件中多数据都读进内容中然后在一次写出去,这样的速度一定会比前面代码速度快。

public class CopyFileByBufferTest {
	public static void main(String[] args) throws IOException {
		File srcFile = new File("E:\\1.mp3");
		File destFile = new File("E:\\copy_1.mp3");
		// 明确字节流 输入流和源相关联,输出流和目的关联。
		FileInputStream fis = new FileInputStream(srcFile);
		FileOutputStream fos = new FileOutputStream(destFile);
		//定义一个缓冲区。
		byte[] buf = new byte[1024];
		int len = 0;
		while((len = fis.read(buf)) != -1){
			fos.write(buf, 0, len);// 将数组中的指定长度的数据写入到输出流中。
		}
			// 4,关闭资源。
		fos.close();
		fis.close();
	}
}

四、字符流 

1、编码表

为了解决乱码问题

编码表:其实就是生活中文件计算机二进制的对应关系表。

  1. ascii: 一个字节中的 7位就可以表示。对应的字节都是正数。0-xxxxxxx
  2. iso8859-1: 拉丁码表 latin,用了一个字节用的8位。1-xxxxxxx 负数。
  3. GB2312: 简体中文码表。6,7仟的中文和符号。用两个字节表示。两个字节都是开头为1 两个字节都是负数。
    • GBK: 目前最常用的中文码表,2万多的中文和符号。用两个字节表示,一部分文字,第一个字节开头是 1,第二字节开头是0。
    • GB18030: 最新的中文码表,目前还没有正式使用。
  4. unicode: 国际标准码表,无论是什么文字,都用两个字节存储。Java中的char类型用的就是这个码表。char c = ‘a’;占两个字节。在Java中,字符串是按照系统默认码表来解析的。简体中文版字符串默认的码表是GBK。
  5. UTF-8: 基于unicode,一个字节就可以存储数据,不要用两个字节存储,而且这个码表更加的标准化,在每一个字节头加入了。编码信息(后期到api中查找)。

能识别中文的码表: GBKUTF-8;正因为识别中文码表不唯一,涉及到了编码解码问题。

 

2、FileReader类

FileReader是读取字符文件的便捷类。此类的构造方法假定默认字符编码和默认字节缓冲区大小都是适当的。

Reader:读取字符流的抽象超类。read():读取单个字符并返回, read(char[]):将数据读取到数组中,并返回读取的个数。

public class CharStreamDemo {
	public static void main(String[] args) throws IOException {
		//给文件中写中文
		writeCNText();
		//读取文件中的中文
		readCNText();
	}	
	//写中文
	public static void writeCNText() throws IOException {
		FileOutputStream fos = new FileOutputStream("D:\\test\\cn.txt");
		fos.write("欢迎你".getBytes());
		fos.close();
	}

	//读取中文
	public static void readCNText() throws IOException {
		FileReader fr = new FileReader("D:\\text.txt");
		int ch = 0;
		while((ch = fr.read())!=-1){
			//输出的字符对应的编码值
			System.out.println(ch);
			//输出字符本身
			System.out.println((char)ch);
		}
}

3、FileWriter类

Writer是写入字符流的抽象类。其中描述了相应的写的动作。

public class FileWriterDemo {
	public static void main(String[] args) throws IOException {
		//演示FileWriter 用于操作文件的便捷类。
		FileWriter fw = new FileWriter("d:\\text\\fw.txt");
		fw.write("你好谢谢再见");//这些文字都要先编码。都写入到了流的缓冲区中。
		fw.flush();
		fw.close();
	}
}

4、flush()和close()的区别

flush():将流中的缓冲区缓冲的数据刷新到目的地中,刷新后,流还可以继续使用。

close():关闭资源,但在关闭前会将缓冲区中的数据先刷新到目的地,否则丢失数据,然后在关闭流。流不可以使用。

注意:如果写入数据多,一定要一边写一边刷新,最后一次可以不刷新,由close完成刷新并关闭。

五、转换流

1、OutputStreamWriter

OutputStreamWriter 是字符流通向字节流的桥梁,可使用指定的 charset 将要写入流中的字符编码成字节。它的作用的就是,将字符串按照指定的编码表转成字节,在使用字节流将这些字节写出去

public static void writeCN() throws Exception {
	//创建与文件关联的字节输出流对象
	FileOutputStream fos = new FileOutputStream("D:\\test\\cn8.txt");
	//创建可以把字符转成字节的转换流对象,并指定编码
	OutputStreamWriter ose = new OutputStreamWriter(fos,"utf-8");
	osw.write("你好");
	osw.close();
}

OutputStreamWriter流对象,维护自己的缓冲区,当我们调用OutputStreamWriter对象的write方法时,会拿着字符到指定的码表中进行查询,把查到的字符编码值转成字节数存放到OutputStreamWriter缓冲区中。然后再调用刷新功能或者关闭流,或者缓冲区存满后会把缓冲区中的字节数据使用字节流写到指定的文件中。

2、InputStreamReader类

InputStreamReader 是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。

public class InputStreamReaderDemo {
	public static void main(String[] args) throws IOException {
			//演示字节转字符流的转换流
			readCN();
		}
	public static void readCN() throw IOException{
		//创建读取文件的字节流对象
			InputStream in = new FileInputStream("D:\\test\\cn8.txt");
			//创建转换流对象 
			InputStreamReader isr = new InputStreamReader(in,"utf-8");
			//使用转换流去读字节流中的字节
			int ch = 0;
			while((ch = isr.read())!=-1){
				System.out.println((char)ch);
			}
			//关闭流
			isr.close();
	}
}

3、转换流和子类区别

OutputStreamWriterInputStreamReader:是字符和字节的桥梁;也可以称之为字符转换流。字符转换流原理:字节流+编码表。

FileWriterFileReader:作为子类,仅作为操作字符文件的便捷类存在。当操作的字符文件,使用的是默认编码表时可以不用父类,而直接用子类就完成操作了,简化了代码。

注意:一旦要指定其他编码时,绝对不能用子类,必须使用字符转换流。

 

你可能感兴趣的:(java,学习,开发语言)