Java基础——IO流(二)

字符流的缓冲区

缓冲区的出现是为了提高流的操作效率而出现的。

所以在创建缓冲区之前,必须要先有流对象。

BufferedWriter

该缓冲区中提供了一个跨平台的换行符。newLine


//字符写入流缓冲区的基本方法演示
import java.io.*;
public class BufferedWriterDemo {
	//为了演示方便,这里先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		//先创建一个写入流对象
		FileWriter fw = new FileWriter("bufw.txt");
		//创建一个写入流缓冲区对象,只要将流对象传进来即可
		BufferedWriter bufw = new BufferedWriter(fw);

		//那么开始在缓冲区中写入数据:
		for (int x = 0;x<5 ;x++ )
		{
			bufw.write("abcde"+x);
			bufw.flush();//记住:只要用到缓冲区就记得刷新
			bufw.newLine();//该方法是缓冲区中特有的换行的方法,适用于各种平台(系统)
		}
		
		bufw.close();//这里缓冲区对象其实引用的是流对象,所以这里只要关闭了缓冲区对象的资源就可以了
	}
}

运行结果:

缓冲区对象写入数据

BufferedReader

该缓冲区提供了一个可以读取整行的方法readLine,方便与对文本数据的获取。
当返回 为空时,表示已经读到流的末尾。
返回的是回车符之前的数据,不包含回车符。

//读取流缓冲区的基本演示
import java.io.*;
public class BufferedReaderDemo {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		//先创建一个读取流对象,和文件关联
		FileReader fr = new FileReader("bufw.txt");

		//创建一个字符串缓冲区对象,将流对象作为参数传递给缓冲区对象的构造函数
		BufferedReader bufr = new BufferedReader(fr);

		//开始读取文件中的数据
		//缓冲区对象的readLine方法可以直接获取整行的数据,
		//返回包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null 
		//这就限定了可以用while循环的条件
		String line=null;

		while ((line = (bufr.readLine()))!=null)
		{
			System.out.println(line);
		}

	}
}

运行结果为:

缓冲区的newLine方法使用方法

练习1:用缓冲区对象拷贝一个文本文件。

//用缓冲区对象拷贝一个文本文件
import java.io.*;
public class CopyTextByBuf {
	public static void main(String[] args) 
	{
		//先创建一个流对象的引用
		BufferedWriter bufw = null;
		BufferedReader bufr = null;
		try
		{
			bufw = new BufferedWriter(new FileWriter("textByCopy.txt"));
			bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
			//用readLine方法读取数据
			String line = null;
			while ((line = bufr.readLine())!=null)
			{
				bufw.write(line);
				bufw.newLine();//readLine方法只获取数据,没有换行符,所以要自己添加换行符
				bufw.flush();//每次存完记得刷新,以免出现意外情况倒置数据的丢失
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取流对象创建失败");
		}
		//关闭资源
		finally
		{
			if (bufw!=null)
			{
				try
				{
					bufw.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流对象关闭失败");
				}
			}
			if (bufr!=null)
			{
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流对象关闭失败");
				}
			}

		}
	}
}

运行结果为:

用缓冲区对象拷贝一个文件

readLine方法的原理

无论是读一行,获取多个字符,其实最终都是在硬盘上一个一个的读取,所以最终使用的还是read方法一次读一个的方法。

所以根据readLine方法的原理,我们来写一个自己的BufferedReader,实现和BufferedReader同样的方法


//写一个自己的BufferedReader,要求和BufferedReader的方法功能一样
import java.io.*;
class MyBufferedReader
{
	//因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来
	private FileReader r;
	MyBufferedReader(FileReader r)
	{
		this.r = r;
	}
	public String myReadLine() throws IOException
	{
		//因为BufferedReader底层用的是数组来存储读到的字符,当读到回车符的时候,将数组中的字符转成字符串返回去
		//这里为了演示方便就用StringBuilder来代替,效果一样的,
		//原理就是,read方法读一个字符,将该字符存储到StringBuilder中,当读到回车符的时候,就将StringBuilder中的字符串返回去。
		
		StringBuilder sb = new StringBuilder();
		int ch = 0;
		while ((ch = r.read())!=-1)
		{
			char c = (char)ch;		//将返回的数字转换成字符
			if(c=='\r')				//判断是\r的话,继续循环
				continue;
			if(c=='\n')				//判断是\n的话,遇到了回车符,该行结束,将sb中的字符串返回去
				return sb.toString();
			else					//如果没有遇到回车符号,就把读到的字符存储到字符串缓冲区中,
				sb.append(c);
		}
		if(sb.length()!=0)
			return sb.toString();   //当某一行没有回车符的时候,这一行会读到,并且存进sb中,但是读不到回车符,
									//不能将sb中剩下的东西返回去,所以这里要进行一次判断,
									//如果read方法运行完,sb中还有东西,就返回来。
		return null;				//如果read方法返回-1,说明read方法读不到数据了,退出while循环,按照
									//BufferedReader中的规则,这里应该返回null,
		
	}
	//同样的BufferedReader中还有关闭资源的方法
	public void myClose() throws IOException
	{
		r.close();
	}
}
public class MyBufferedReaderDemo {
	//这里为了演示方便先不对异常进行处理
	public static void main(String[] args) throws IOException 
	{
		//创建流对象
		FileReader fr = new FileReader("bufw.txt");
		//创建缓冲区对象
		MyBufferedReader mybuf = new MyBufferedReader(fr);
		//开始读取数据
		String line = null;
		while ((line = mybuf.myReadLine())!=null)
		{
			System.out.println(line);
		}
		//关闭资源
		mybuf.myClose();
	}
}

运行结果为:

自定义BufferedReader进行整行数据的读取

我们把这种像BufferedReader中readLine方法这样的基于read方法的加强功能的方法的设计模式称为装饰设计模式。

装饰设计模式:
当想要对已有的对象进行功能增强时,
可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能
那么自定义的该类称为装饰类。

装饰类通常会通过构造方法接收被装饰类的对象
并基于被装饰的对象的功能,提供更强的功能。

写一个装饰类的例子:

//装饰类的演示
//古时候的人们生活条件差,吃饭仅仅是吃饭而已
class Person
{
	public void chifan()
	{
		System.out.println("吃饭");
	}
}
//改革开放以来,人们的生活水平好了,吃饭功能也增强了
class SuperPerson
{
	private Person p;
	SuperPerson(Person p)
	{
		this.p = p;
	}
	public void superChifan()
	{
		System.out.println("开胃酒");
		p.chifan();
		System.out.println("甜点");
		System.out.println("来一根");
	}

}
public class SuperPersonDemo {
	public static void main(String[] args) 
	{
		Person p = new Person();
		SuperPerson sp = new SuperPerson(p);
		sp.superChifan();

	}
}

运行结果:

装饰设计模式

在以上的示例中,SuperPerson类对Person类的chifan方法的功能增强就是装饰设计模式了。

可是在以上程序中,使用装饰设计模式实现的功能用子类继承父类,然后复写父类的方法也可以实现相同的功能的,那么,装饰设计模式和继承的区别在哪里呢?

举个例子来说

我有自己的读取的一个类MyReader
同时有具体到读取文件类型的类比如:MyTextReader,MyMediaReader
而为了提高读取文件的功能,提供了提高效率的类,MyBufferedTextReder,MyBufferedMediaReader
那么类与类继承的体系应该是这样的

  • MyReader
    • MyTextReader
      • MyBufferedTextReader
    • MyMediaReader
      • MyBufferedMediaReader
    • 如果来了新的读取文件类型,这里还要改代码,不提倡使用这种方法,
      • 而且每一个类都有自己的增强类,每次都这样写很麻烦,而且不利于程序的扩展性。

于是,我们想到了定义一个类,直接将对象传进来,在类中定义增强读取的方法。

class MyBufferedReader
{
	MyBufferedReader(MyTextReader mtr)
	{
	}
	MyBufferedReader(MyMediaReader mdr)
	{
	}
	.....
}

还是不爽,为什么呢?因为每有一个对象创建,都要有一个新的构造函数建立,如果来了新的数据类型,还是要修改代码,于是我们想到了多态,提高其扩展性,只要在构造函数中传入父类对象。

class MyBufferedReader
{
    MyBufferedReader(MyReader mr)
    {
    }
}

这样,不用每一个数据类型,都写一个构造函数,而且新来的数据类型,只要是MyReader的子类就可以直接传进来对象使用增强方法。

于是体系结构就变成了这样子:

  • MyReader
    • MyTextReader
    • MyMediaReader
    • MyBufferedReader

装饰类避免了继承体系的臃肿模式,降低了类与类之间的关系。

装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。
所以装饰类和被装饰类通常都是 属于一个体系的。

于是我们就可以自己定义包装类了。
还是上边自定义readLine方法的例子


//自定义的装饰类,要求在Read的体系中
import java.io.*;
class MyBufferedReader extends Reader//在人家的体系中当然是先继承人家了
{
	//因为BufferedReader的构造方法有流对象作为参数,这里也可以将参数传进来
	private Reader r;
	MyBufferedReader(Reader r)
	{
		this.r = r;
	}
	public String myReadLine() throws IOException
	{
		StringBuilder sb = new StringBuilder();
		int ch = 0;
		while ((ch = r.read())!=-1)
		{
			char c = (char)ch;		
			if(c=='\r')				
				continue;
			if(c=='\n')				
				return sb.toString();
			else					
				sb.append(c);
		}
		if(sb.length()!=0)
			return sb.toString();   
									
		return null;				
	}
	//因为Read类中有抽象方法read和close,所以在这里要进行覆盖
	public void close() throws IOException
	{
		r.close();
	}
	public int read(char[] cbuf, int off, int len) throws IOException
	{
		return r.read(cbuf,off,len);
	}


	public void myClose() throws IOException
	{
		r.close();
	}
	
}
public class MyBufferedReaderDemo {
	
	public static void main(String[] args) throws IOException 
	{
		//创建流对象
		FileReader fr = new FileReader("bufw.txt");
		//创建缓冲区对象
		MyBufferedReader mybuf = new MyBufferedReader(fr);
		//开始读取数据
		String line = null;
		while ((line = mybuf.myReadLine())!=null)
		{
			System.out.println(line);
		}
		//关闭资源
		mybuf.myClose();
	}
}

LineNumberReader
获取行号的缓冲区,继承自BufferedReader

这里写图片描述

获取行号的方法

那么就来演示一下

//获取行号的演示
import java.io.*;
public class LineNumberReaderDemo {
	public static void main(String[] args) 
	{
		FileReader fr =null;
		try
		{
			fr = new FileReader("SuperPersonDemo.java");
			//创建一个读取行号的对象,
			LineNumberReader lnr = new LineNumberReader(fr);
			//也有整行读取的方法
			String line =null;
			while ((line = lnr.readLine())!=null)
			{
				//先写行号再写内容
				System.out.println(lnr.getLineNumber()+" "+line);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		
		//关闭资源
		finally
		{
			try
			{
				if(fr!=null)
				fr.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("关闭资源失败");
			}
			
		}
	}
}

运行结果:
获取行号的对象使用实例

练习:定义一个自己的获取行号的方法。

//写一个自己的获取行号的对象
import java.io.*;
class MyLineNumberReader
{
	private FileReader fr;
	MyLineNumberReader(FileReader fr)
	{
		this.fr = fr;
	}
	private int count = 0;//这里定义一个行号,起始值为0

	public String myReadLine() throws IOException
	{
		StringBuilder sb = new StringBuilder();
		int num = 0;
		while ((num = fr.read())!=-1)
		{
			char ch = (char)num;
			if(ch=='\r')
				continue;
			if(ch=='\n')
			{
				//当读到回车符的时候,count自增一次
				count++;
				return sb.toString();
			}
			else
				sb.append(ch);
		}
		if((sb.length())!=0)
			return sb.toString();
		return null;

	}
	public void mySetLineNumber(int count)//设置count的值
	{
		this.count  = count;
	}
	public int myGetLineNumber()//获取count值
	{
		return count;
	}
	
	
}


public class MyLineNumberReaderDemo {
	public static void main(String[] args) 
	{
		FileReader fr =null;
		try
		{
			fr = new FileReader("SuperPersonDemo.java");
			//创建一个读取行号的对象,
			MyLineNumberReader mylnr = new MyLineNumberReader(fr);
			//也有整行读取的方法
			String line =null;
			while ((line = mylnr.myReadLine())!=null)
			{
				//先写行号再写内容
				System.out.println(mylnr.myGetLineNumber()+" "+line);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		
		//关闭资源
		finally
		{
			try
			{
				if(fr!=null)
				fr.close();
			}
			catch (IOException e)
			{
				throw new RuntimeException("关闭资源失败");
			}
			
		}
	}
}

运行结果:

自定义获取行号的类

发现,这里的代码和MyBufferedReader里边的代码只有几点不一样的地方,于是,我们可以让该获取行号的类继承MyBufferedReader类,将获取行号的类的代码进行优化。

class MyLineNumberReader extends MyBufferedReader
{
	MyLineNumberReader(FileReader fr)
	{
		super(fr);
	}
	private int count = 0;//这里定义一个行号,起始值为0

	public String myReadLine() throws IOException
	{
		count++;
		return super.myReadLine();
	}
	public void mySetLineNumber(int count)//设置count的值
	{
		this.count  = count;
	}
	public int myGetLineNumber()//获取count值
	{
		return count;
	}
	
	
}

其主要原理也就是在某些地方调用了父类(MyBufferedReader)的方法

字节流

InputStream读取字节流
OutputStream写入字节流
API中发现,和字符流中的方法差不多。。

直接演示

//字节流写入方法演示
import java.io.*;
public class FileStream {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) 2 IOException
	{
		writeFile();
	}
	public static void writeFile() throws IOException
	{
		//创建一个字节流对象,并且和文件关联
		FileOutputStream fos = new FileOutputStream("fos.txt");
		//字节流,当然是要写入字节了。
		fos.write("abcdef".getBytes());

	}
}

运行结果:
字节流写文件

发现没有刷新动作,也没有关流的动作,但是文件确确实实写进去了。这是为什么呢?

这是因为,字符流底层用的也是字节的缓冲区,
因为编码表中一个字符对应的两个字节,所以在字符流中,当读取到一个字节的时候,会将字节都放在一个缓冲区中,
然后再去查表,找到字节相应的字符进行操作,

而在字节流中,底层数据类型是字节,不涉及到查表,所以可以实现读一个写一个的动作,

那么写完文件了,,我们试试读取文件

//字节流写入方法演示
import java.io.*;
public class FileStream {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		readText_1();
	}
	//读取文件
	public static void readText_1()throws IOException
	{
		//创建字节流读取对象,并与文件关关联
		FileInputStream fis = new FileInputStream("fos.txt");
		//read方法一样会返回int类型的值
		int num = 0;
		while((num = fis.read())!=-1)
		{
			System.out.println((char)num);
		}
		fis.close();
	}
}

运行结果为:

字节流读取数据

但是这种方法一个字节一个字节的读取,效率超慢的,所以我们用数组缓冲区来提高效率。

//字节流写入方法演示
import java.io.*;
public class FileStream {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		readText_2();
	}
	//读取文件
	public static void readText_2()throws IOException
	{
		//创建字节流读取对象,并与文件关关联
		FileInputStream fis = new FileInputStream("fos.txt");
		int len = 0;
		//和字符流一样的方法过程,只是注意数据类型变成了字节
		byte[] buf = new byte[1024];
		while ((len = fis.read(buf))!=-1)
		{
			System.out.println(new String(buf,0,len));
		}
		fis.close();
	}
}

运行结果:
字节流读取数据2

这种方法效率稍高,
发现InputStream类中有一个available方法,返回int类型的值,
是流对象获取到的所有的字节数(包括\r\n这样的字符)
这样的话,我们就可以直接定义数组缓冲区的大小了。就不用再while循环了。

//字节流写入方法演示
import java.io.*;
public class FileStream {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		readText_3();
	}
	//读取文件
	public static void readText_3()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();
	}
}

运行结果:

用一个刚好的字节数组来存放读取到的数据

这种方法只适用于操作比较小的数据,因为jvm分配的内存一共64M,万一文件过大了,容易造成内存溢出。所以在操作比较大的数据的时候还是以创建一个1024字节的数组来循环比较靠谱。

练习:拷贝一个图片:

//用字节流复制一张图片
import java.io.*;
public class CopyPic {
	public static void main(String[] args) 
	{
		//在最外边创建两个引用
		FileInputStream fis = null;
		FileOutputStream fos = null;
		try
		{
			//开始创建读取流和写入流,并和文件关联
			fis = new FileInputStream("11.jpg");
			fos = new FileOutputStream("22.jpg");
			//开始读写操作
			int len = 0;
			byte[] buf = new byte[1024];
			while ((len = fis.read(buf))!=-1)
			{
				fos.write(buf,0,len);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		//关闭资源
		finally
		{
			if (fis!=null)
			{
				try
				{
					fis.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流关闭失败");
				}
			}
			if (fos!=null)
			{
				try
				{
					fos.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流关闭失败");
				}
			}
		}
	}
}

运行结果:

拷贝图片

练习二:拷贝一个MP3文件,用缓冲区

//拷贝MP3用流缓冲区
import java.io.*;
public class CopyMp3 {
	public static void main(String[] args) 
	{
		//在最外边建立一个引用
		BufferedInputStream bufis = null;
		BufferedOutputStream bufos = null;
		try
		{
			//创建流对象
			FileInputStream fis = new FileInputStream("1.mp3");
			FileOutputStream fos = new FileOutputStream("2.mp3");
			bufis = new BufferedInputStream(fis);
			bufos = new BufferedOutputStream(fos);
			//读取返回的字节
			int by = 0;
			while ((by = bufis.read())!=-1)
			{
				bufos.write(by);//写入字节
			}

		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		//关闭资源
		finally
		{
			if(bufis!=null)
			{
				try
				{
					bufis.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流关闭失败");
				}
			}
			if(bufos!=null)
			{
				try
				{
					bufos.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流关闭失败");
				}
			}
		}
		 
	}
}

实验结果:

拷贝MP3

其实字节流缓冲区中也是有一个数组的,加上缓冲区是为了增强read方法功能。
缓冲区读取数据时过程是这样的。
调用FileInputStream的read方法,从硬盘上读取1024个字节的数据,放在定义的长度为1024的数组中
然后再用BufferedInputStream中的read方法从缓冲区中获取数组中的元素。然后写入到硬盘上来。

具体过程图例:
这里写图片描述

那么了解了字节流缓冲区的原理呢,我们就自己定义一个字节流缓冲区。

//子定义一个字节流缓冲区
import java.io.*;
class MyBufferedInputStream
{
	private InputStream is;
	//因为底层是用的数组,所以这里需要定义指针,和计数器
	//思路是:
	//先拿一堆数据过来,myRead方法通过指针获取数组中的值,然后count用来计算数组中的数据是否全部取出
	//如果count为0了,再去取数据,如果不为0,再从缓冲区中取数据

	private int pos = 0;
	private int count = 0;
	MyBufferedInputStream(InputStream is)
	{
		this.is = is;
	}
	//read方法一次只取一个字节
	public int myRead() throws IOException
	{
		byte[] buf = new byte[1024];
		//如果count为0,抓一把数据进来,读第一位数,返回去
		if (count==0)
		{
			count = is.read(buf);//这个时候count的值就不是0了,如果读到末尾,发现count的值返回为-1 了,
			pos = 0;				//说明数据已经读取完毕了,直接返回-1,读到末尾
			if (count==-1)
			{
				return -1;
			}
			byte b = buf[pos];
			pos++;
			count--;
			return b & 255;//把数组中的元素返回去,注意:这里返回去的是byte类型的,但是上边函数上声明的是int类型的
						//所以会进行一次自动提升类型操作
						//read方法一次读取一个字节,如果MP3文件的前8位都是1呢?那么myRead方法返回的数直接就是-1了
						//这样的话在下边调用myRead方法的时候,遇到这样的情况,就会造成一种数据已经读完的假象
						//造成数据复制的损失,怎么解决呢?
						//假如遇到了全是1的情况,b的值为-1,二进制类型为1111-1111
						//这里return b会进行一次,提升,提升为int类型的,完了之后b的值还是-1,其二进制形式为
						//11111111-11111111-11111111-11111111
						//但是我们可以在int类型的前24位补0,只留下最后的8位,这样就可以解决这种情况了
						//只获取最后的八位,只要用这个数和255进行与运算就可以了。原理是:
						// 11111111-11111111-11111111-11111111    -1
						//&00000000-00000000-00000000-11111111    255
						//=00000000-00000000-00000000-11111111    255
						//所以这里进行的一次之获取后八位的运算方法就是&255
		}
		else if(count>0)//第二次进来的时候count就不为0了,所以不用抓数据,直接取就可以了
		{
			byte b = buf[pos];
			pos++;
			count--;
			return b &255;//把数组中的元素返回去,
		}
		return -1;
	}
	public void myClose()throws IOException
	{
		is.close();
	}


}

class MyBufferedInputStreamDemo
{
	public static void main(String[] args)
	{
		//在最外边建立一个引用
		MyBufferedInputStream bufis = null;
		BufferedOutputStream bufos = null;
		try
		{
			//创建流对象
			FileInputStream fis = new FileInputStream("1.mp3");
			FileOutputStream fos = new FileOutputStream("2.mp3");
			bufis = new MyBufferedInputStream(fis);
			bufos = new BufferedOutputStream(fos);
			//读取返回的字节
			int by = 0;
			while ((by = bufis.myRead())!=-1)
			{
				bufos.write(by);//写入字节
			}

		}
		catch (IOException e)
		{
			throw new RuntimeException("读取失败");
		}
		//关闭资源
		finally
		{
			if(bufis!=null)
			{
				try
				{
					bufis.myClose();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流关闭失败");
				}
			}
			if(bufos!=null)
			{
				try
				{
					bufos.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流关闭失败");
				}
			}
		}
	}
}

运行结果:

自定义字节流缓冲区拷贝MP3文件

获取键盘录入

读取键盘上录入的数据实际上也是读取流在操作:

简单演示一下获取键盘录入的例子

//读取键盘录入
import java.io.*;

public class SystemInDemo {
	public static void main(String[] args) throws IOException
	{
		//获取一个读取流对象,与键盘录入相关联
		InputStream is = System.in;
		int ch = 0;
		while((ch = is.read())!=-1)
		{
			System.out.println(ch);//打印所有读到的内容
		}
	}
}

运行结果为:

键盘录入简单演示

发现,我只输入了一个a,却打印了三个值,
这是因为我还敲了一次回车,虚拟机把回车键的ASCII值也打印出来了。

那么需求变了,我要让键盘上敲的字母打印出来全部大写的,而且一直敲到over,程序就结束。

//读取键盘录入
import java.io.*;

public class SystemInDemo {
	public static void main(String[] args) 
	{
		//获取一个读取流对象,与键盘录入相关联
		InputStream is = null;
		try
		{
			is = System.in;
			int num = 0;
			//穿件一个字符串缓冲区,将读取到的字符先存到缓冲区中,
			StringBuilder sb = new StringBuilder();
			while((num = is.read())!=-1)
			{
				char ch = (char)num;
				if(ch=='\r')
					continue;
				if(ch=='\n')
				{
					//碰到回车符,先进行判断是否为over,如果不是的话打印字符串,如果是的话,跳出循环
					String s = sb.toString();
					if(s.equals("over"))
						break;
					System.out.println(s.toUpperCase());
					//每打印完一次,sb就要清空一次,要不然前边的东西会一直占着
					sb.delete(0,sb.length());
				}
				else
					sb.append(ch);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("流创建失败");
		}
		
	}
}

运行结果:

键盘录入字符,

转换流对象

发现上边的有一部分代码和在自定义readLine方法的时候写的代码有点相似。
那么就想到,用键盘录入的时候,能不能直接用readLine方法,直接读取一整行呢?
可是,readLine方法是在BufferedReader中的,属于字符流对象的,
而键盘录入对象是属于字节流的,在InputStream中的。

InputStreamReader:将字节流转换为字符流对象,可以使用readLine方法。

代码实例:

//转换流对象演示
import java.io.*;
public class TransStreamDemo {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		//获取一个键盘录入对象
		InputStream in = System.in;
		//将字节流装换为字符流对象
		InputStreamReader isr = new InputStreamReader(in);
		//为了提高效率,用到字节流缓冲区对象
		BufferedReader bufr = new BufferedReader(isr);
		String line = null;
		while ((line = bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			System.out.println(line.toUpperCase());
		}
		bufr.close();
	}
}

运行结果为:

用readLine方法获取键盘录入

OutputStreamWriter

将字符流转换为字节流

比如,在上边的例子中,键盘录入的是字符,但是存在硬盘的时候用的就是字节,所以需要将字符流转换为字节流。

示例:

//转换流对象演示
import java.io.*;
public class TransStreamDemo {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		//获取一个键盘录入对象
		InputStream in = System.in;
		//将字节流装换为字符流对象
		InputStreamReader isr = new InputStreamReader(in);
		//为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法
		BufferedReader bufr = new BufferedReader(isr);


		OutputStream out = System.out;
		OutputStreamWriter osw = new OutputStreamWriter(out);
		BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法

		String line = null;
		while ((line = bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			bufw.write(line);
			bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加
			bufw.flush();//每次写完要记得刷新一下,要不然出不来数据
		}
		bufr.close();
		bufw.close();
	}
}

运行结果

字符流转换为字节流

流操作的基本规律

通过两个明确来完成

  1. 明确源和目的
    1. 源:输入流。InputStream ;Reader
    2. 目的:输出流:OutputStream Writer
  2. 操作的数据是否是存文本
    1. 是:字符流
    2. 不是:字节流
  3. 当体系明确后,再明确要使用哪个具体的对象
    1. 通过设备来进行区分
      1. 源设备:内存,硬盘,键盘
      2. 目的设备:内存,硬盘,控制台。

练习:将一个图片文件存储到另一个文件中,复制文件,要求按照以上格式完成三个明确

//复制图片,要求完成三个明确
import java.io.*;
public class CopyPicTest {
	public static void main(String[] args) 
	{
		/*
		源:输入流:Reader,InputStream
		目的:输出流:Writer,OutputStream

		操作的数据是否是纯文本?
		不是:InputStream,和OutputStream

		设备:
		源:硬盘中的文件   FileInputStream
		母的,硬盘中的文件 FileOutputStream

		需要提高效率吗?是的,BufferedInputStream,BufferedOutputStream
		*/
		//开始按照分析思路创建对象
		BufferedInputStream bufis =null; 
		BufferedOutputStream bufos =null; 
		try
		{
			//开始读写操作
			bufis = new BufferedInputStream(new FileInputStream("11.jpg"));
			bufos = new BufferedOutputStream(new FileOutputStream("22.jpg"));
			int by = 0;
			while ((by = bufis.read())!=-1)
			{
				bufos.write(by);
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读取写入失败");
		}
		//关闭资源
		finally
		{
			if(bufos!=null)
			{
				try
				{
					bufos.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流关闭失败");
				}
			}
			if(bufis!=null)
			{
				try
				{
					bufis.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流关闭失败");
				}
			}
		}
	}
}

练习二:将键盘录入的数据保存到一个文件中

//将键盘录入的数据保存到一个文件中

/*
源:InputStream   Reader
是不是纯文本?是!Reader

设备:键盘。对应的对象是System.in
不是选择Reader吗?System.in对应的不是字节流吗?
为了操作键盘的文字数据方便,转换成字符流按照字符串操作是最方便的。
所以既然明确了Reader那么就将System.in转换为Reader
用了Reader中的转换流,InputStreamReader

需要提高效率吗?需要,BufferedReader
目的:OutputStream   Writer
是否是存文本。是!Writer
设备:硬盘,一个文件,使用FileWriter
需要提高效率吗?需要
BufferedWriter bufw = new BufferedWriter()
*/
import java.io.*;
public class Test2 {
	public static void main(String[] args) 
	{
		BufferedReader bufr = null; 
		BufferedWriter bufw = null; 
		try
		{
			bufr = new BufferedReader(new InputStreamReader(System.in));
			bufw = new BufferedWriter(new FileWriter("systemin.txt"));
			String line = null;
			while ((line = bufr.readLine())!=null)
			{
				if("over".equals(line))
					break;
				bufw.write(line);
				bufw.newLine();
				bufw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读写发生异常啦");
		}
		finally
		{
			if (bufr!=null)
			{
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流发生异常");
				}
			}
			if (bufw!=null)
			{
				try
				{
					bufw.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流发生异常");
				}
			}
		}
		
	}
}

写数据

运行结果

扩展一下:
想要把录入的数据按照指定的编码表(UTF-8),将数据存到文件中。

目的:OutputStream,Writer
是否是存文本?是,Writer
设备,硬盘,一个文件,使用FileWriter
但是FileWriter是使用默认的编码表GBK

但是存储时,需要加入指定 编码表utf-8,而指定的编码表只有转换流可以指定
所以要使用的对象是OutputStreamWriter
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流FileOutputStream

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“d.txt”),“utf-8”);

需要高效吗?需要
BufferedWriter bufw = nwe BufferedWriter(osw);

所以记住,转换流什么时候使用,字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流

指定编码表的键盘录入写入文件的程序:

//将键盘录入的数据保存到一个文件中,指定utf-8码表

import java.io.*;
public class Test2 {
	public static void main(String[] args) 
	{
		BufferedReader bufr = null; 
		BufferedWriter bufw = null; 
		try
		{
			bufr = new BufferedReader(new InputStreamReader(System.in));
			//指定编码表,只能用转换流指定。
			bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d2.txt"),"utf-8"));
			String line = null;
			while ((line = bufr.readLine())!=null)
			{
				if("over".equals(line))
					break;
				bufw.write(line);
				bufw.newLine();
				bufw.flush();
			}
		}
		catch (IOException e)
		{
			throw new RuntimeException("读写发生异常啦");
		}
		finally
		{
			if (bufr!=null)
			{
				try
				{
					bufr.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("读取流发生异常");
				}
			}
			if (bufw!=null)
			{
				try
				{
					bufw.close();
				}
				catch (IOException e)
				{
					throw new RuntimeException("写入流发生异常");
				}
			}
		}
		
	}
}

运行结果,指定GBK和utf-8的写入同样的字,你好,两个文件的占内存大小不一样。

改变标准输入输出设备

System.setIn(InputStream is)//将标准输入流从键盘录入改为一个字节读取流
System.setOut(PrintStream ps);//将标准的输出流从控制台改为一个打印流(参数为一个文件名称)

演示一下:

//转换流对象演示
import java.io.*;
public class TransStreamDemo2 {
	//为了演示方便,先不进行异常的处理
	public static void main(String[] args) throws IOException
	{
		//把默认的输入流改变为文件读取流
		System.setIn(new FileInputStream("CopyPic.java"));
		//获取一个标准录入对象
		InputStream in = System.in;
		//将字节流装换为字符流对象
		InputStreamReader isr = new InputStreamReader(in);
		//为了提高效率,用到字节流缓冲区对象,这里边才有readLine方法
		BufferedReader bufr = new BufferedReader(isr);

		//改变标准输出流改变为一个文件
		System.setOut(new PrintStream("zz.txt"));
		OutputStream out = System.out;
		OutputStreamWriter osw = new OutputStreamWriter(out);
		BufferedWriter bufw = new BufferedWriter(osw);//一样的,这里边才有通用的换行newLine方法

		String line = null;
		while ((line = bufr.readLine())!=null)
		{
			if("over".equals(line))
				break;
			bufw.write(line);
			bufw.newLine();//在录入数据的时候不带的回车符,所以需要自己添加
			bufw.flush();//每次写完要记得刷新一下,要不然出不来数据
		}
		bufr.close();
		bufw.close();
	}
}

运行结果为:

修改标准的输入输出流

将异常的信息打印到异常日志文件中,带上时间。

//将异常信息导入到异常日志文件中,要带上时间
import java.io.*;
import java.util.*;
import java.text.*;

public class ExceptionLogDemo {
	public static void main(String[] args) 
	{
		//发现异常
		try
		{
			int[] arr = new int[2];
			System.out.println(arr[4]);//创建一个数据角标越界的异常
		}
		//进行处理

		catch (Exception e)
		{
			//加上时间,获取能够看得懂的时间信息
			Date d = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 kk:mm:ss");
			String time = sdf.format(d);
			

			//将标准输出流换成文件打印流,并且与日志文件相关联
			try
			{
				PrintStream ps = new PrintStream("exception.log");
				System.setOut(ps);
			}
			catch (IOException ex)
			{
				throw new RuntimeException("异常日志文件创建失败");
			}
			System.out.println(time);
			e.printStackTrace(System.out);//将异常打印在指定的输出流中
		}
		
		
	}
}

运行结果:

将异常信息打印在异常日志文件中

想要将系统信息,输出到指定的文件夹

发现系统信息对象Properties有自己的指定输出流的方法list
这里写图片描述

于是,我们就可以直接进行调用:

//将系统信息输出到指定的文件中
import java.io.*;
import java.util.*;
public class SystemInfo {
	public static void main(String[] args) 
	{
		//获取系统信息
		Properties prop = System.getProperties();

		//Properties中的list方法,可以将系统信息输出到指定的输出流
		try
		{
			//输出流和文件相关联
			prop.list(new PrintStream("systeminfo.txt"));
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}
}

运行结果:

将系统信息打印在文件中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java程序设计》课程的题库资料,由贺州学院整理,可供学生期末课程复习使用,也可以供相关任课教师出卷使用。 内容示例为: 40. __________包包含了Collection的接口的类的API。 答案:Java.util 41. Math.round(11.5)等于__________,Math.round(-11.5)等于__________。 答案:12; -11 [考点范围] 常用的系统类 42. ________对象可以使用read方法标准输入设备(通常键盘)读取数据;__________对象可以使用print方法标准输出设备(屏幕)输出显示。 答案:System.in ;System.out [考点范围] JAVA输入输出系统 43. 框架(JFrame)和面板(JPanel)的默认布局管理器分别是______和_______。 答案:BorderLayout FlowLayout [考点范围] 图形用户界面 44. Swing的布局管理器主要包括_______。 答案:FlowLayout、BorderLayout、CardLayout、GridLayout、GridBogLayout、BoxLayout [考点范围] 图形用户界面 45. Java事件处理包括建立事件源、________和将事件源注册到监听器 。 答案:声明监听器 [考点范围] 图形用户界面 46. AWT的事件处理机制包括_______、事件和事件监听者。 答案:事件源 [考点范围] 图形用户界面 47. Swing的顶层容器有________、JApplet、JWwindow和JDialog。 答案:JFrame [考点范围] 图形用户界面 48. 线程的启动是通过调用其______________方法而实现的。 答案:start() [考点范围] 线程 49. Java虚拟机(JVM)中的线程调度器负责管理线程,调度器把线程的优先级分为10个级别,分别用Thread类中的类常量表示,每个Java线程的优先级都在常数________和_______之间,即Thread.MIN_PRIORIY和Thread.MAX_PRIORIY之间。 答案:1;10 [考点范围] 线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值