黑马程序员 Java基础之IO流

本文详细阐述了Java中文件操作的基本概念,包括流的分类、使用方法及异常处理,着重介绍了字符流的读写过程和缓冲区技术的应用,通过实例展示了如何高效地进行文件复制和文件读取,最后探讨了自定义缓冲区类的设计方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

IO流简述

IO是(Input,Output)的缩写,也简称数据流,主要是用于设备之间的数据传输,Java对数据的操作主要是通过流的形式。而Java中用于操作流的对象都在IO包中。

IO的分类:

流按操作的数据称:字节流(二进制数据,可以处理任意类型的数据)和字符流(为了方便,单独封装了一种文本流,可以在内部融合了所有的编码表,由用户指定的编码表,从而防止乱码)。

按流的流向分为:输入流和输出流。

字节流的两个抽象基类:InputStream(读)和OutStream(写);

字符流的两个抽象基类:Reader和Writer.

注:由这四个类派生出来的子类都是以其基类的名称作为后缀名来进行命名的,前缀名主要是子类的主要功能。如InputStream的子类FileInputStream,如Reader的子类FileReader();

字符流

字符流的两个基类Reader和Writer,既然IO是用来操作数据的,而操作数据的主要的就是操作文件。

FileWriter对象用于创建文件,并向文件中写入数据

FileWriter对象对文件操作的三个步骤:

1 首先创建一个流对象,这个对象一旦被创建,就必须制定被操作得文件(没有空类型的构造函数);

2向流对象中写入字符或者字符串

3刷新流对象缓冲中的数据到目的地文件

4关闭流对象。

IO实例一:

在硬盘上创建一个文件,向文件里写数据

/*通过查阅API帮助文档,我们发现有FileWriter这个类来操作文件*/
import java.io.*;
class JavaCollection1_52 
{
	public static void main(String[] args)throws IOException 
	{
		//1首先要创建一个FileWriter对象,这个对象一旦创建就必须明确要被操作的文件。而且该文件会被创建到指定的目录下。
		//如果该目录下有同名的文件,那么就会覆盖旧的文件。
		//其实这步就是要明确数据存放的位置
		FileWriter fw=new FileWriter("demo.txt");
		//2调用write()方法,将要写的数据放入到流FileWriter中
		fw.write("fileWriterStream");
		//3刷新流对象缓冲的数据(也就是将流刷入到目的地中);
		fw.flush();
		//4close()方法会关闭流资源,关闭之前会刷新流对象中的数据,将其刷新到目的地中,
		fw.write("aaaa");
		fw.close();
		//close()和flush的方法区别是:flush刷新后,流还可以继续使用,但是close()方法关闭流后,流就不能再进行操作了。
	}
}
运行结果如下


IO异常的合理的处理

在对文件的处理中,往往会碰到各种的异常,因此我们需要提供一些异常的的专业处理,来增强程序的健壮性。

代码如下:

import java.io.*;
class JavaCollection1_53 
{
	public static void main(String[] args) 
	{
		//先创建一个对象的引用
		FileWriter fw=null;
		try
		{
			//创建流对象的实例(指定要被流操作的对象)
			fw=new FileWriter("demo1.txt");
			//向流对象中写入数据
			fw.write("demo1.txt");
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			//注意关闭流资源的动作也会抛出异常,所以要使用try
			try
			{
				//关闭流资源
				fw.close();
			}
			catch (IOException e)
			{
				System.out.println(e.getMessage());
			}

		}
	}
}
文件的续写

在对文件的操作中,我们经常会需要对文件进行续写,因此我们可以使用FileWriter(需要续写的文件名,boolean append)的重载构造函数来对文件进行复写

代码如下:

import java.io.*;
class JavaCollection1_54 
{
	public static void main(String[] args) 
	{
		FileWriter fw=null;
		try
		{
			//提示要操作的文件,并指定是否要进行续写,如果续写,那么续写的数据要加到文件的末尾.
			fw=new FileWriter("demo1.txt",true);
			fw.write("xuxieData");
			//对写入的文件进行换行处理
			fw.write("\r\n结果是你的");
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				fw.close();
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
		}
	}
}
上面我们介绍了文件的写入,下面我们要介绍文件的读取方法了

文件的读取方式一:

使用FileReader()类来创建需要读取文件的流对象,然后在调用读取流对象的read()方法来读取文件中的数据。

具体操作代码如下:

import java.io.*;
class JavaCollection1_55 
{
	public static void main(String[] args) 
	{
		//1创建我们需要读取指定文件的读取流,
			FileReader fr=null;
			try
			{
				fr=new FileReader("demo1.txt");
				//调用fr读取流对象的read()方法来读取文件
				//通过演示我们发现read()方法每次只读一个字符,并且自动向后面读。每次读取就返回一个整型数,如果已经读到文件末尾,那么read()方法就返回-1,
				int a=fr.read();
				System.out.println((char)a);
				int a1=fr.read();
				System.out.println((char)a1);
				while(true)
				{
					int val=fr.read();
					if(val==-1)
					{
						break;
					}
					System.out.print((char)val);
				}
			}
			catch(FileNotFoundException e)
			{
				System.out.println("文件名错误");
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
			finally
			{
				    try
				    {
						fr.close();
				    }
				    catch (IOException e)
				    {
						System.out.println(e.toString());
				    }
			}
	}
}
文件的读取方式二:

使用FileReader(char [] arr)方法读取文件,并将数据存储到一个字符数组中,然后在打印字符数组。

代码如下:

/*
读取文件的第二种方式
*/
import java.io.*;
class JavaCollection1_56
{
	public static void main(String[] args) 
	{
		FileReader fr=null;
		char[] arr=new char[3];
		try
		{
			//demo1文件中的字符串是demo1dg,由于我们定义的字符数组是3个,每次读取流每次调用read()方法,那么就将对应的字符存储到字符数组中,如果字符数组的满了,那么又会将数组的指针又返回到零角标,重新又存到数组中,
			fr=new FileReader("demo1.txt");
			//read()方法返回的是读取每次读取并存入到字符数组中的个数,第一次返回的是3
			/*sop(fr.read(arr));//第一次返回的长度是三,存入到数组中的字符是dem
			sop(new String(arr));


			sop(fr.read(arr));//第二次返回的长度也是三,存入的是old
			sop(new String(arr));

			int num=fr.read(arr);
			sop(num);//第三次返回的长度是一,这个字符是g,所以这个g就会覆盖上数组中的o字符,也就是零角标,但是1角标,2角标还是l,和d,不会发生变化,所以这样读取字符就不正确,打印多了
			//所以使用String的构造函数String(char[] value, int offset, int count) 来读取正确的字符串。
			sop(new String(arr,0,num));
			//使用循环读取文件中的数据
			*/
			//一般定义字符的大小为1024的长度,为2k的大小
			char[] arr1=new char[1024];
			int temp=0;
			while( (temp=fr.read(arr1))!=-1)
			{
				sop(new String(arr1,0,temp));
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				fr.close();
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
		}
	}
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}
运行结果如下:


文件读取实例练习:

需求:读取java文件,并显示到控制台。

代码如下:

/*读取java文件,因为java文件也是文件文件,所以我们是可所以使用字符流来处理,所以我么就可以使用FilReader来处理*/
import java.io.*;
class JavaCollection1_57 
{
	public static void main(String[] args) 
	{
		FileReader fr=null;
		try
		{
			fr=new FileReader("JavaCollection1_57.java");
			int num=0;
			char[] arr=new char[1024];
			//如果我们定义的字符数组长度足够大,那么该循环只执行一次,如果字符数组长度小,那么这个循环就会重复多次。
			while((num=fr.read(arr))!=-1)
			{
				System.out.print(new String(arr,0,num));
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				fr.close();
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
		}
	}
}
运行结果如下:


在文件的操作我们,我们可能会对文件进行复制,而我们文件的操作可以总结为下面的步骤:

1先在目的文件夹创建文件

2定义一个读取流和源文件进行关联

3然后将文件不管的存储到目的文件中

4关闭流资源

实例练习:

/*文件的复制
将文件JavaCollection1_57.java 复制文件名称为JavaCollection1_57_copy.java
*/
import java.io.*;
class JavaCollection1_58 
{
	public static void main(String[] args) 
	{
		copyMethod_1();
		copyMethod_2();
	}

	//一个一个字符的读取
	public static void copyMethod_1()
	{
		//申明写入流和读入流的两个引用。
		FileWriter fw=null;
		FileReader fr=null;
		try
		{
			fw=new FileWriter("JavaCollection1_57_copy.java");//目的地
			fr=new FileReader("JavaCollection1_57.java");//源文件
			int num=0;
			while((num=fr.read())!=-1)
			{
				fw.write(num);
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			if(fw!=null)
				try
				{
					fw.close();
				}
				catch (IOException e)
				{
					System.out.println(e.toString());
				}
			if(fr!=null)
				try
				{
					fr.close();
				}
				catch (IOException e)
				{
					System.out.println(e.toString());
				}
		}
	}
	//先读取完源文件,然后将数据保存到流的缓冲区中,然后在将数据存储到目的文件中
	public static void copyMethod_2()
	{
		FileWriter fw=null;
		FileReader fr=null;
		try
		{
			fw=new FileWriter("JavaCollection1_56_copy.java");
			fr=new FileReader("JavaCollection1_56.java");
			//定义缓冲区
			char[] arr =new char[1024];
			int len=0;
			while((len=fr.read(arr))!=-1)
			{
				fw.write(arr,0,len);
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				if(fw!=null)
					fw.close();
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
			try
			{
				if(fr!=null)
					fr.close();
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
		}
	}
}
运行结果如下:

根据上述生成两种方法都可以完成文件的复制,但是第二种方法明显比第一种方法更高效,所以我们选择第二种方法来实现文件的复制。

而复制的流程我们可以用一下图示来表示:

在生活中,我们在喝水的时候,如果水龙头的水是一滴滴流,我们去喝,这样就会特别的郁闷,但是如果我们那一个杯子,然后用杯子接一杯水,然后再喝,我们感觉就很爽,而在文件的操作中就提供了类似杯子的池,我们简称为字符缓冲区。它可以提高文件操作写入和读取的效率。

注:字符区提供了对应提高字符读写的缓冲区技术,而在缓冲区中必须先创建流,再创建缓冲区BufferedWriter对象。

BufferedWriter实例代码:

/*
BufferedWriter提高了流的读写性能。
另外缓冲区也给我们提供了一个可以跨平台的换行方法newLine();
*/
import java.io.*;
class JavaCollection1_59 
{
	public static void main(String[] args) 
	{
		FileWriter fw=null;
		BufferedWriter bw=null;
		try
		{
				//1首先创建一个文件写入流对象
			fw=new FileWriter("bufferedWriter.txt");
			//创建一个缓冲区,并将写入流对象作为参数传递给缓冲区
			bw=new BufferedWriter(fw);
			//将数据写入到缓冲区。
			for(int i=0;i<5;i++)
			{
				bw.write("abcd"+i);
				bw.newLine();
				bw.flush();//注意没写一次就刷新一次,防止缓冲区的数据在断电的情况下没有保存的这种状况。
			}
		}
		catch (IOException e)
		{
			System.out.println(e.toString());
		}
		finally
		{
			try
			{
				if(bw!=null)
				bw.close();//注意其实bw底层操作的还是写入流对象,所以这里就不用再次关闭文件写入流对象了
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
		}
	}
}
运行结果如下:

既然有字符写入流缓冲区,那么也会存在字符读取流缓冲区,字符读取流缓冲区提供了readLine()方法提供了对文件一行一行的读,当方法返回的结果是null,表示读到文件末尾。

使用实例如下:

import java.io.*;
class JavaCollection1_60 
{
	public static void main(String[] args) 
	{
		//1创建字符读取流对象
		FileReader fr=null;
		BufferedReader bfr=null;
		try
		{
			fr=new FileReader("JavaCollection1_56.Java");
			//2创建字符读取流缓冲区
			bfr=new BufferedReader(fr);
			//使用bfr的readLine()方法一行一行的读取文件,该方法返回的是字符串
			//当返回值是null,表示已经读到该文件的末尾。
			String line=null;
			while((line=bfr.readLine())!=null)
			{
				System.out.println(line);
			}
		}
		catch (IOException e)
		{
				System.out.println(e.toString());
		}
		finally
		{
			try
			{
				if(bfr!=null)
				bfr.close();
			}
			catch (IOException e)
			{
				System.out.println(e.toString());
			}
		}
	}
}
运行结果如下:



下面的实例是我们使用字符读取流缓冲区和字符写入流缓冲区来实现文件的复制

代码如下:

/*
注意readLine()只返回换行符前的数据,并不返回换行符,所以需要我们手动的加入换行符。
*/
import java.io.*;
class Java1_51 
{
	public static void main(String[] args) 
	{
		BufferedWriter bfw=null;
		BufferedReader bfr=null;
		try
		{
			bfw=new BufferedWriter(new FileWriter("Demo_Copy.java"));
			bfr=new BufferedReader(new FileReader("Demo.java"));
			//这里的Line就是连接字符写入流缓冲区和字符读取流缓冲区的中转站。
			String line=null;
			while((line=bfr.readLine())!=null)
			{
				bfw.write(line);
				//注意这里复制的文件没有换行
				//需要手动的添加换行符
				bfw.newLine();
				bfw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("文件操作失败");
		}
		finally
		{
		    try
		    {
				if(bfw!=null)
				 bfw.close();
		    }
		    catch (IOException e)
		    {
				throw new RuntimeException("文件写入失败");
		    }
			try
			{
				if(bfr!=null)
					bfr.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("文件读取失败");
			}
		}
	}
}
运行结果如下:

通过查阅JavaAPI文档后我们发现了BufferedReader是调用FileReader的reade()方法,然后在每次判断是否读到了换行符,如果不是换行符,那么就将读到的数据先存到数组中,等到读到换行符时再将全部的换行符前的数据全部返回,当FileReaderl对象的read()方法返回一个-1时,表示文件已经读到了末尾,了解BufferedWriter读取数据和存储数据的原理后我们也可以定义自己的字符缓冲区类,代码如下:

/*自定义MyBufferedReader来读取文件中的数据*/
import java.io.*;
class Java1_52 
{
	public static void main(String[] args)  
	{
		FileReader f=null;
		MyBufferedReader mbr=null;
		try
		{
			f=new FileReader("text.txt");
			mbr =new MyBufferedReader(f);
			String line=null;
			while((line=mbr.myReadLine())!=null)
			{
				System.out.println(line);
			}
		}
		catch(IOException e)
		{
			throw new RuntimeException("文件操作失败");
		}
		finally
		{
			try
			{
				mbr.myColse();
			}
			catch (IOException e)
			{
				throw new RuntimeException("文件资源关闭失败");
			}
		}
	}
}
class MyBufferedReader
{
	private FileReader fr=null;
	public MyBufferedReader(FileReader r)
	{
		this.fr=r;
	}
	public String myReadLine() throws IOException
	{
		//将读取的文件存储在StringBuilder中是最佳的。
		StringBuilder sb=new StringBuilder();
		int num=0;
		//当没有读到文件末尾时。
		while((num=fr.read())!=-1)
		{
			//判断当前是否读到了换行符
			if(num=='\r')
			 continue;
			//如果是换行符,那么就返回换行符前的数据
			if(num=='\n')
				return sb.toString();
			else
				sb.append((char)num);
		}
		//当读到文件末尾时,最后一次只存入到StringBuilder中,但是没有返回,所以这里我们要将最后一行的数据返回回来。
		if(sb.length()!=0)
		{
			return sb.toString();
		}
		return null;
	}
	public void myColse()throws IOException
	{
		//因为字符读取流缓冲区底层调用的还是字符读取流的close()方法,所以我们只需关闭FileReader()方法即可。
		fr.close();
	}
}
当想要对已有的功能进行增强时,这时我们可以定义一个类,将已有对象传入,基于已有功能,并提供加强功能。那么自定义的该类,我们就称为装饰类,通常装饰类会通过构造方法来接收被装饰的对象。通过下面的实例我们来演示装饰设计模式的用法:

/*使用装饰设计模式类DircateClass来扩展人的吃饭的功能*/
import java.io.*;
class Java1_53 
{
	public static void main(String[] args) 
	{
		Person p=new Person();
		DircateClass dc=new DircateClass(p);
		dc.ExetendFunction();
	}
}
class DircateClass
{
	private Person p;
	DircateClass(Person p)
	{
		this.p=p;
	}
	public void ExetendFunction()
	{
		System.out.println("洗手");
		System.out.println("喝酒");
		p.eatDinner();
		System.out.println("吃饭");
		System.out.println("吃甜点");
	}
}
class Person
{
   public void eatDinner()
	{

	   System.out.println("吃晚餐");
   }
}
运行结果如下:


通过结果我们发现:我们使用装饰设计模式不但实现了人已有的功能,而且还扩展了人的吃饭功能。

继承和装饰设计模式的区别就是:装饰设计模式比继承更加的简单,而继承也会显得程序非常的臃肿,而且也降低了程序的联系,灵活性很强,更易于程序的扩展。而一般装饰类可以对多个类型进行功能,所以在装饰类的构造函数中我们就要利用多态的原理来接受多种子类对象,并用子类对象的方法来覆盖基类的方法。

实例:

需求:自定义一个myBufferedReader类来模拟BufferedReader的ReadLine()方法。

/*装饰设计模式的实例演示
自定义一个个MyBufferedReader来模拟BufferedReader类扩展FileReader()类
*/
import java.io.*;
class Java1_54 
{
	public static void main(String[] args) 
	{
		FileReader fr=null;
		MyBufferedReader mfr=null;
		BufferedWriter bfw=null;
		FileWriter fw=null;
		try
		{
			 fr=new FileReader("Java1_53.java");
			 mfr=new MyBufferedReader(fr);
			 fw=new FileWriter("java1_53_copy.java");
			 bfw=new BufferedWriter(fw);
			 String line=null;
			 while((line=mfr.myReadeLine())!=null)
			{
				 bfw.write(line);
				 bfw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException(e.toString());
		}
		finally
		{
			try
			{
				if(mfr!=null)
				mfr.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException(e.toString());
			}
			try
			{
				if(fw!=null)
				fw.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException(e.toString());
			}

		}
	}
}
//通过发现这里的装饰类通常情况下也要继承Reader类,
class MyBufferedReader extends Reader
{
	//注意所有的字符读取流的基类都是Reader,所以这里我们要使用多态来完成
	private Reader r;
	MyBufferedReader(Reader r)
	{
		this.r=r;
	}
	//因为Reader中是抽象基类,并且里面还有两个抽象方法,所以这里我们要覆写基类Reader类的close和read()方法。
	public void close()throws IOException
	{
		r.close();
	}
	public int  read(char[] arr,int off,int length)throws IOException
	{
		return r.read(arr,off,length);
	}
	public String myReadeLine()throws IOException
	{
		StringBuilder sb=new StringBuilder();
		int num=0;
		while((num=r.read())!=-1)
		{
			if((char)num=='\r')
				continue;
			if((char)num=='\n')
				{
				    sb.append("\r\n");
					return sb.toString();
				}
			else
				sb.append((char)num);
		}
		if(sb.length()!=0)
			return sb.toString();
		return null;
	}
}

在文件中,我们往往需要显示代码的行数,或者设置文件的行数。这时Java为我们提供了LineNumberReader类的getLineNumber()和setLineNumber()设置当前行号,readLine()也是一行一行的读取文件。

使用实例如下:

/*LineNumberReader的使用方法*/

import java.io.*;
class  Java1_63
{
	public static void main(String[] args) 
	{
		FileReader fr=null;
		LineNumberReader lnr=null;
		FileWriter fw=null;
		BufferedWriter bw=null;
		try
		{
			fr=new FileReader("JavaCollection1_59.java");
			lnr=new LineNumberReader(fr);
			fw=new FileWriter("JavaCollection1_59_copy.java");
			bw=new BufferedWriter(fw);
			String line=null;
			while((line=lnr.readLine())!=null)
			{
				bw.write(lnr.getLineNumber()+line);
				bw.newLine();
				bw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException(e.toString());
		}
		finally
		{
			try
			{
				if(lnr!=null)
					lnr.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException(e.toString());
			}
			try
			{
				if(bw!=null)
					bw.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException(e.toString());
			}
		}
	}
}
知道了LineNumberReader类读取行号和设置行号的原理后,我们自己来设计我们的自己的MyLineNumberReader类

代码如下:

/**/
import java.io.*;
class Java1_64 
{
	public static void main(String[] args)throws IOException 
	{
		FileReader fr=new FileReader("Java1_63.java");
		MyLineNumberReader mnr=new MyLineNumberReader(fr);
		String line=null;
		//设置行号
		mnr.setLineNumber(100);
		while((line=mnr.myReadLine())!=null)
		{
			//每一行读取行号
			System.out.println(mnr.getLineNumber()+line);
		}
	}
}
class MyLineNumberReader
{
	//行号默认的值为零
	private int numline;
	private FileReader fr=null;
	MyLineNumberReader(FileReader fr)
	{
		this.fr=fr;
	}
	public void setLineNumber(int numline)
	{
		this.numline=numline;
	}
	public int getLineNumber()
	{
		return numline;
	}
	public String myReadLine()throws IOException
	{
		//每次读取一行的时候,就让行号自增。
		numline++;
		StringBuilder sb=new StringBuilder();
		int val;
		while((val=fr.read())!=-1)
		{
			if((char)val=='\r')
				continue;
			if((char)val=='\n')
			{
				return sb.toString();
			}
			else
				sb.append((char)val);
		}
		if(sb.length()!=0)
		{
			return sb.toString();
		}
		return null;
	}
}
运行结果如下:











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值