一,概述
IO流(input output):用来处理设备之间的数据。
Java对数据的操作是通过流的对象。
Java用于操作流的对象都在IO包中。
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
二,IO流的分类
根据处理数据类型的不同分为:字符流和字节流
根据数据流向不同分为:输入流和输出流
1.1 字符流和字节流
字符流的由来:在早期IO包中存在的都是字节流,因为无论是内存还是硬盘中的文件,它们都是以字节的形式传输或保存。随着编码技术的不断提高,人们陆续创造出了不同的编码表,【如:ASCII(美国信息交换标准代码),gb2312, GBK, unicode(国际标准码表:无论哪个字符都用两个字节表示),utf-8(国际标准码表的优化表:根据字符使用的字节长度来调整字节位数)】,但是当存储数据的计算机和取出数据的计算机使用的编码表不同时,就会造成取出数据的乱码情况。
为了解决这个乱码问题,Java就在字节流的基础上产生了一个字符流,而这个字符流的好处就是当读取数据时可以自己指定编码表。
字节流和字符流的区别:
(1)读写单位不同:字节流以字节(8bit)为单位。字符流以字符为单位,根据码表映射字符,一次可能读多个字节。
字节流:一次读入或读出是8位二进制,分别操作字节和字节数组。字符流:一次读入或读出是16位二进制,分别操作字符,字符数组或字符串。
(2)处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
(3)字节流在操作的时候本身是不会用到缓冲区的,是文件本身的直接操作的;而字符流在操作的时候是会用到缓冲区的,是通过缓冲区来操作文件。(因为一个字符通常是由两个字节组成的,所以当读取了一个字节时,无法查找正确的字符)
结论:只要是处理纯文本数据,就优先考虑使用字符流, 除此之外建议使用字节流。但字节流是通用的。
字节流的抽象基类:InputStream OutputStream
字符流的抽象基类:Reader Writer
注意:由这四个类派生出来的子类名称都是以其父类作为子类名的后缀。
如:InputStream的子类FileInputStream
Reader的子类FileReader
1.2 输入流和输出流
对输入流只能进行读操作,对输出流只能进行写操作,程序中需要根据待传输数据的不同特性而使用不同的流。
三,Java IO体系
1.1字符流writer
Writer 写入字符流的抽象类。
它的子类有:
BufferedWriter 是一个装饰器为Writer 提供缓冲功能。
CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。
FilterWriter 过滤器
PipedWriter 是向与其它线程共用的管道中写入数据,
PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。
OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。
构造函数:
方法:
【演示案例】:在硬盘上创建一个文件并写入一些文字数据。
分析:因为是写操作,所以找到一个专门用于操作文件的Writer子类对象,FileWriter,(后缀名是父亲名,前缀名是该流对象的功能)
1 import java.io.FileWriter; 2 import java.io.IOException; 3 public class WriterDome { 4 public static void main(String[] args) throws IOException { 5 //创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件。 6 //并且该文件会被创建到指定目录下,如果该目录下已有同名文件,将会被覆盖。 7 FileWriter fw=new FileWriter("C:\\html\\demo.txt"); 8 fw.write("abcde"); 9 //调用write方法,将字符串写入流中 10 fw.flush(); 11 //调用flush方法,刷新流对象的缓冲中的数据,将数据刷到目的地。 12 fw.close(); 13 //调用close方法,关闭流资源,但关闭之前会刷新一次内部缓冲中的数据。 14 } 15 }
字符流的异常处理
上述案例中所有的异常都只是进行了抛出处理,这样是不合理的。所以上述代码并不完善,因为异常没有处理。当我们打开流,读和写,关闭流的时候都会出现异常,异常出现后,后面的代码都不会执行了,因此我们要进行异常处理。
异常处理的方法:使用try{} catch(){}finally{}语句。try中放入可能出现异常的语句,catch是捕获异常对象,fianlly是一定要执行的代码
要注意使用try进行异常处理时,将close()方法放在finally中,假设没有放,那么当打开和操作流出现了异常,显然close方法就不会再执行,而导致关闭流失败。
同时还要注意,关闭多个流时,为了避免一个流关闭失败而导致另一个流也无法关闭的情况,所以要将各个流的关闭分别写在不同的try语句中。
【演示案例】: IO异常处理的方式:
1 import java.io.FileWriter; 2 import java.io.IOException; 3 public class WriterDome { 4 public static void main(String[] args) { 5 FileWriter fw=null; 6 try{ 7 fw=new FileWriter("C:\\html\\demo.txt"); 8 fw.write("abcdeljl"); 9 fw.flush(); 10 11 }catch (IOException e){ 12 System.out.println("写失败"); 13 } 14 finally {//finally中的代码不管前面try中的代码是否执行成功,finally中的代码总会执行。因此将关闭流的代码放在finally中 15 try{ 16 fw.close(); 17 }catch (IOException e){ 18 System.out.println("关闭失败"); 19 } 20 } 21 } 22 }
1.2字符流 Reader
Reader 是所有读取字符流的父类,它是一个抽象类。
它的子类有:
BufferedReader 很明显就是一个装饰器,它和其子类(LineNumberReader)负责装饰其它Reader 对象。
CharReader、StringReader是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据。
FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。
FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。
Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系。
构造函数:
方法:
read方法():一次读取一个字符,读到文件末尾返回-1.
注意:该方法返回的是本次读取到的字符的int形式。所以打印时需有将其转化成char类型。
read(char[] b) 方法:使用缓冲区(关键是缓冲区大小的确定),使用read方法的时候,是将读到的数据装入到字符数组中,然后一次性的操作数组,可以提高效率,流需要读一次就处理一次,因为本次读取的数据会覆盖上次读取的。
注意:该方法返回的是本次读取到的字符的个数。
read(char[] b,int off,int len):查看api文档,b显然是一个char类型数组,当做容器来使用。off,是指定从数组的什么位置开始存字节。len,希望读多少个,其实就是把数组的一部分当做流的容器来使用。告诉容器,从什么地方开始装要装多少。
【演示案例】:从文件中读取内容
1 import java.io.FileReader; 2 import java.io.IOException; 3 4 public class ReaderDemo { 5 public static void main(String[] args) throws IOException { 6 FileReader fr=new FileReader("c:\\html\\demo.txt"); 7 /** 8 * 一次读一个字符。 9 */ 10 int ch1=0;//因为read()方法返回的int型数据,所以定义一个int型数据接收 11 while ((ch1=fr.read())!=-1){ 12 // 调用read()方法,该方法返回的是本次读到的字符的整数型,当本次没有读到字符时,返回的是-1 13 System.out.println((char) ch1); //强转成char类型 14 } 15 16 /** 17 * 一次读多个字符。 18 */ 19 char[] buf=new char[1024];//定义一个字符 20 int num=0; 21 while ((num=fr.read(buf))!=-1){ 22 //调用read(char[] ch)方法,将读到的字符都存到ch数组中,该方法返回的是本次读到的字符的个数 23 //当本次没有读到字符时,返回的是-1 24 System.out.print(new String(buf,0,num));//将数组中的数据输出 25 } 26 fr.close(); 27 28 } 29 }
【演示案例】:将C盘的一个文本文档复制到D盘。
1 import java.io.FileReader; 2 import java.io.FileWriter; 3 import java.io.IOException; 4 5 public class CopyTxt { 6 public static void main(String[] args) throws IOException { 7 FileReader fr=new FileReader("c:\\html\\demo.txt"); 8 FileWriter fw=new FileWriter("D:\\demo\\demo.txt"); 9 char[] buf=new char[1024]; 10 while (fr.read(buf)!=-1){ 11 fw.write(buf);//fw.write(buf,0,num); 12 fw.flush(); 13 } 14 fr.close(); 15 fw.close(); 16 } 17 }
1.3字符缓冲流对象
缓冲区的出现提高了对数据的读写效率,其要结合流才可以使用,所以在创建缓冲流对象之前,必须先有流对象。它是在流的基础上对流的功能进行了增强,或者也可以说是对流对象的装饰。
缓冲流:
上述程序中我们为了提高流的使用效率,自定义了字符数组,作为缓冲区.Java其实提供了专门的字符流缓冲来提高效率.BufferedWriter和BufferedReader
BufferedWriter和BufferedReader类可以通过减少读写次数来提高输入和输出的速度。它们内部有一个缓冲区,用来提高处理效率。查看API文档,发现可以指定缓冲区的大小。其实内部也是封装了字符数组。
显然缓冲区输入流和缓冲区输出流要配合使用。首先缓冲区输入流会将读取到的数据读入缓冲区,当缓冲区满时,缓冲输出流会将数据写出。
注意:当然使用缓冲流来进行提高效率时,对于小文件可能看不到性能in的提升。但是文件稍微大一些的话,就可以看到实质的性能提升了。
缓冲流对象类:BufferedWriter (后缀是父类,前缀是功能)
BufferedReader
缓冲区的原理:1、使用了底层流对象从具体设备上获取数据,并将数据存储到缓冲区的数组内。
2、通过缓冲区的read()方法从缓冲区获取具体的字符数据,这样就提高了效率。
3、如果用read方法读取字符数据,并存储到另一个容器中,直到读取到了换行符时,将另一个容器临时存储的数据转成字符串返回,就形成了readLine()功能。
【演示案例】:模拟BufferedReader的底层代码
1 import java.io.FileReader; 2 import java.io.IOException; 3 import java.io.Reader; 4 5 class MyBufferedReaderDemo { 6 private Reader r=null; 7 public MyBufferedReaderDemo(Reader r){ 8 this.r=r; 9 } 10 public String myreadLine() throws IOException { 11 int str=0; 12 //定义一个临时容器,原BufferedReader中封装的是字符数组。 13 //但为了演示方便,此处定义了一个StringBuilder容器。 14 StringBuilder sb=new StringBuilder(); 15 while ((str=r.read())!=-1){ 16 if (str=='\r') continue; 17 if (str=='\n') return sb.toString(); 18 sb.append((char)str); 19 } 20 if (sb.length()!=0)return sb.toString(); 21 return null; 22 } 23 24 public void myclose() throws IOException { 25 r.close(); 26 } 27 } 28 public class MyBufferedReader{ 29 public static void main(String[] args) throws IOException{ 30 FileReader fr=new FileReader("c:\\html\\demo.txt"); 31 MyBufferedReaderDemo mbur=new MyBufferedReaderDemo(fr); 32 String str=null; 33 while ((str=mbur.myreadLine())!=null){ 34 System.out.println(new String(str)); 35 } 36 mbur.myclose(); 37 } 38 }
上述代码说明,1.BufferedReader中调用的read()方法是来自FileReader的。
2.只要关闭BufferedReader,就关闭了FileReader流对象,是因为在BufferedReader的close方法中,已经调用了FileReader的close方法。
3.BufferedReader就是FileReader流对象的装饰器,其实在BufferedReader中真正操作文件的还是FileReader。
1.3.1 字符流的缓冲区对象BufferedWriter
public class BufferedWriter extends Writer
(1)将文本写入字符输出流(一个数组中),缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
(2)可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
(3)该类在基类的基础上提供了 newLine() 方法,因为并非所有平台都使用换行符 ('\r\n') 来终止各行。因此调用此方法来终止每个输出行,使代码具有跨平台性。
(4)通常 Writer 将其输出立即发送到底层字符或字节流。除非要求提示输出,否则建议用 BufferedWriter 包装所有其 write() 操作可能开销很高的 Writer(如 FileWriters 和 OutputStreamWriters)。例如,
PrintWriter out
= new PrintWriter(new BufferedWriter(new FileWriter("foo.out")));
(5)缓冲 PrintWriter 对文件的输出。如果没有缓冲,则每次调用 print() 方法会导致将字符转换为字节,然后立即写入到文件,而这是极其低效的。
【演示案例】:创建文件并写入内容。
1 import java.io.BufferedWriter; 2 import java.io.FileWriter; 3 import java.io.IOException; 4 5 public class BufferWeiterDemo { 6 public static void main(String[] args) throws IOException { 7 //创建一个字符写入流对象 8 FileWriter fw=new FileWriter("c:\\html\\demo.txt"); 9 //为了提高字符写入流的效率,加入缓冲技术。 10 //将需要提高效率的流对象作为参数提供给缓冲区的构造函数。 11 BufferedWriter buw=new BufferedWriter(fw); 12 13 for (int i = 0; i <5; i++) { 14 buw.write("aaa"+i); 15 buw.newLine();//作用等同于buw.write("\r\n");但是\r\n只是Windows系统的换行符,因此不具有跨平台性 16 buw.flush(); //写一次刷新一次,防止意外 17 } 18 buw.close(); 19 //关闭缓冲区就是在关闭缓冲区中的流对象 20 //因为真正实现写操作的是FileWriter对象,真正与文件相关联的也是FileWriter对象 21 } 22 }
1.3.2 字符流的缓冲区对象BufferedReader
public class BufferedReader extends Reader
(1)从字符输入流(一个作为缓冲区的数组)中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。
(2)可以指定缓冲区的大小,或者可使用默认的大小。大多数情况下,默认值就足够大了。
(3)在基类的基础上提供了readLine()方法(底层调用了read()方法),读取一个文本行,返回该行内容的字符串,不包括任何终止符。如果已到达流末尾,则返回null。
(4)通常,Reader 所作的每个读取请求都会导致对底层字符或字节流进行相应的读取请求。因此,建议用 BufferedReader 包装所有其 read() 操作可能开销很高的 Reader(如 FileReader 和 InputStreamReader)。例如,
BufferedReader in
= new BufferedReader(new FileReader("foo.in"));
(5)将缓冲指定文件的输入。如果没有缓冲,则每次调用 read() 或 readLine() 都会导致从文件中读取字节,并将其转换为字符后返回,而这是极其低效的。
【演示案例】:通过缓冲区从C盘复制文件到D盘。
1 import java.io.*; 2 3 public class BufferReaderDemo { 4 public static void main(String[] args) throws IOException { 5 //创建一个字符读取流对象 6 FileReader fr=new FileReader("c:\\html\\demo.txt"); 7 //创建一个字符写入流对象 8 FileWriter fw=new FileWriter("D:\\Demo\\demo.txt"); 9 //为了提高字符流的效率,加入缓冲技术。 10 //将需要提高效率的流对象作为参数提供给缓冲区的构造函数。 11 BufferedReader bur=new BufferedReader(fr); 12 BufferedWriter buw=new BufferedWriter(fw); 13 String str=null; 14 while ((str=bur.readLine())!=null){ 15 buw.write(str,0,str.length()); 16 buw.newLine();//因为readLine()方法读取的字符不包括换行符。 17 buw.flush(); 18 } 19 bur.close(); 20 buw.close(); 21 22 } 23 }
1.3.3字符流的缓冲区对象BufferedWriter 的子类LineNumberReader
它是对缓冲区对象BufferedWriter的功能进行了增强, 是可以跟踪行号的缓冲字符输入流,此类定义了方法setLineNumber()和getLineNumber(),它们分别用于设置和获取当前行号。
setLineNumber()方法:设置当前行号。默认情况下是从0开始的。
getLineNumber()方法:获取当前行号。
【演示案例】:字符流的缓冲区对象BufferedWriter 的子类LineNumberReader
1 import java.io.FileReader; 2 import java.io.IOException; 3 import java.io.LineNumberReader; 4 5 public class LineNumberReaderDemo { 6 public static void main(String[] args) throws IOException { 7 FileReader fr=new FileReader("c:\\html\\demo.txt"); 8 LineNumberReader lnr=new LineNumberReader(fr); 9 String str=null; 10 lnr.setLineNumber(100); 11 while ((str=lnr.readLine())!=null){ 12 System.out.println(lnr.getLineNumber()+":"+str); 13 } 14 lnr.close(); 15 } 16 }
【演示案例】:模拟LineNumberReader的底层代码
1 import java.io.BufferedReader; 2 import java.io.FileReader; 3 import java.io.IOException; 4 import java.io.Reader; 5 6 class MyLineNumberReader extends BufferedReader{ 7 private int linenumber=0; 8 //private Reader r; 9 public MyLineNumberReader(Reader r){ 10 super(r); 11 } 12 public void setLinenumber(int i){ 13 this.linenumber=i; 14 } 15 public int getLinenumber(){ 16 return linenumber; 17 } 18 /*public void close() throws IOException { 19 super.close(); 20 }*///已被父类实现 21 public String readLine() throws IOException{ 22 linenumber++; 23 return super.readLine(); 24 } 25 } 26 public class MyLineNumberReaderDemo { 27 public static void main(String[] args) throws IOException{ 28 FileReader fr=new FileReader("c:\\html\\demo.txt"); 29 MyLineNumberReader mlnr=new MyLineNumberReader(fr); 30 String str=null; 31 mlnr.setLinenumber(100); 32 while ((str=mlnr.readLine())!=null){ 33 System.out.println(mlnr.getLinenumber()+":"+str); 34 } 35 mlnr.close(); 36 } 37 }
1.4字节流InputStream
- InputStream 是所有的输入字节流的父类,它是一个抽象类。
- ByteArrayInputStream、StringBufferInputStream、FileInputStream 是三种基本的介质流,它们分别从Byte 数组、StringBuffer、和本地文件中读取数据。
- PipedInputStream 是从与其它线程共用的管道中读取数据,与Piped 相关的知识后续单独介绍。
- ObjectInputStream 和所有FilterInputStream 的子类都是装饰流(装饰器模式的主角)。
1.4.1字节流对象FileInputStream
public class FileInputStream extends InputStream
用于从文件系统中的某个文件中读取诸如图片,视频,音频之类数据的原始字节流,若要读取字符流,请考虑使用FileReader。
【演示案例】:使用字节流对象读取文件。
1 import java.io.FileInputStream; 2 import java.io.IOException; 3 4 public class FileInputStreamDemo { 5 public static void main(String[] args) throws IOException { 6 7 /** 8 *一次读取一个字节 9 */ 10 FileInputStream fis=new FileInputStream("c:\\html\\demo.txt"); 11 int num=0; 12 while ((num=fis.read())!=-1){ 13 System.out.print((char)num); 14 } 15 //因为字节流对象是读一个写一个,所以不存在刷新 16 fis.close(); 17 18 /** 19 * 一次读取多个字节:方法一 20 */ 21 FileInputStream fis=new FileInputStream("c:\\html\\demo.txt"); 22 byte[] buf=new byte[1024];//注意:字符流读入的是char[]数组,而字节流读入的是byte[]数组 23 int num=0; 24 while ((num=fis.read(buf))!=-1){ 25 System.out.println(new String(buf,0,num)); 26 } 27 //因为字节流对象是读一个写一个,所以不存在刷新 28 fis.close(); 29 30 /** 31 * 一次读取多个字节:方法一 32 */ 33 FileInputStream fis=new FileInputStream("c:\\html\\demo.txt"); 34 int num=fis.available();//返回字节的个数 35 byte[] buf=new byte[num];//注意:字符流读入的是char[]数组,而字节流读入的是byte[]数组 36 fis.read(buf); 37 System.out.println(new String(buf)); 38 //因为字节流对象是读一个写一个,所以不存在刷新 39 fis.close(); 40 } 41 }
【演示案例】:使用字节流对象复制图片。
1 import java.io.FileInputStream; 2 import java.io.FileOutputStream; 3 import java.io.IOException; 4 5 public class CopyPicture { 6 public static void main(String[] args) throws IOException { 7 FileInputStream fis=new FileInputStream("c:\\html\\picture.png"); 8 FileOutputStream fos=new FileOutputStream("D:\\demo\\picture.png"); 9 byte[] buf=new byte[1024]; 10 int len=0; 11 while ((len=fis.read(buf))!=-1){ 12 fos.write(buf,0,len); 13 } 14 fis.close(); 15 fos.close(); 16 17 } 18 }
1.4.2字节缓冲流BufferedInputStream(装饰)
public class BufferedInputStream extends FilterInputStream
该类为另一个输入流添加一些功能,即缓冲输入以及支持mark和reset方法的能力。在创建BufferedInputStream时会创建一个数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。mark操作记录输入流中的某个点,reset操作使得在从包含的输入流中获取新字节之前,在此读取自最后一次mark操作后读取的所有字节。
【演示案例】:模拟BufferedInputStream底层代码。
1 import java.io.*; 2 class MyBufferedInputStream { 3 private InputStream ins; 4 private int pos=0; 5 private int count=0; 6 private byte[] buf=new byte[1024]; 7 public MyBufferedInputStream(InputStream in){ 8 this.ins=in; 9 } 10 public int read() throws IOException{ 11 if(count==0){ 12 count=ins.read(buf); 13 pos=0; 14 } 15 if(count<0) { 16 return -1; 17 } 18 byte b=buf[pos++]; 19 count--; 20 return b&255; 21 } 22 public void close()throws IOException{ 23 ins.close(); 24 } 25 } 26 27 public class FileOutputStreamDemo { 28 public static void main(String[] args) throws IOException { 29 FileOutputStream fos=new FileOutputStream("D:\\demo\\demo.txt");//写 30 FileInputStream fis=new FileInputStream("c:\\html\\demo.txt");//读 31 BufferedOutputStream bos=new BufferedOutputStream(fos); 32 MyBufferedInputStream bis=new MyBufferedInputStream(fis); 33 int buff=0; 34 while ((buff=bis.read())!=-1){ 35 bos.write(buff); 36 bos.flush(); 37 } 38 bis.close(); 39 bos.close(); 40 } 41 }
我们知道字节流对象一般读取到的都是一个字节,那为什么read()方法返回的为什么不是byte而是int呢?这是因为字节流对象读取的音频或图片文件,其中数据在底层都是以二进制的形式存储的,但是当字节流对象读取到的某个字节是11111111(8个1)时,转化成十进制就是-1,这与我们判断结束的条件是相同的(while ((num=bis.read(buf))!=-1){}),此时系统就会认为该文件内容已经读取完而停止继续读取,因而造成数据丢失。为了解决这种情况,就将需要返回的数据转化(提升)为int型数据(从一个字节提升为四个字节),并与上255,之后在返回。那为什么要与上255?这是因为byte型-1转化为int型后还是-1,但是与上255(或0xff)之后,数据大小没有变,且解决了-1的问题。
-1的问题解决了,那我们又不得不思考另一个问题,我们每次读取时,都读到的是一个字节,但是返回时返回的是四个字节,这样读取到的文件不就成原来文件的四倍了?所以为了解决这个问题,我们在调用写功能时,其底层又进行了强转(向下转型),因此就解决了文件扩大的问题。
【代码演示】:打印文件中出现次数最多的三个数。
1 import java.io.BufferedReader; 2 import java.io.FileReader; 3 import java.io.IOException; 4 import java.util.*; 5 public class FindNumber { 6 public static void main(String[] args) throws IOException { 7 BufferedReader br=new BufferedReader(new FileReader("c:\\html\\test\\1317.txt"));//将文件加载到输入流中 8 String num=null; 9 HashMap<Integer,Integer> hashMap=new HashMap();//定义集合存储键值对,出现的数字作为Key,出现数字的次数作为Value 10 while ((num=br.readLine())!=null){ //一次读取一行数据 11 String[] str= num.split(",");//以逗号将各个数字分隔开,分好后的数据存储到数组中 12 for (String number:str) {//遍历数组 13 Integer key=Integer.valueOf(number);//因为数组是String类型,所以取出数据后将其元素转化为Interger类型 14 if (hashMap.containsKey(key)){//对集合进行判断 15 hashMap.put(key,hashMap.get(key)+1); 16 }else { 17 hashMap.put(key,1); 18 } 19 } 20 } 21 System.out.println(hashMap); 22 //借用PrioritrQueue的特点,将集合中的数组导入PrioritrQueue中 23 //重写compare方法,让PrioritrQueue中的元素按从大到小的顺序排列 24 Comparator<Map.Entry<Integer,Integer>> comparator=new Comparator<Map.Entry<Integer, Integer>>(){ 25 @Override 26 public int compare(Map.Entry<Integer,Integer> o1, Map.Entry<Integer,Integer> o2) { 27 return o2.getValue()-o1.getValue(); 28 } 29 }; 30 PriorityQueue<Map.Entry<Integer,Integer>> priorityQueue=new PriorityQueue(comparator); 31 Iterator<Map.Entry<Integer,Integer>> iterator=hashMap.entrySet().iterator(); 32 while (iterator.hasNext()){ 33 Map.Entry<Integer,Integer> entry=iterator.next(); 34 priorityQueue.add(entry); 35 } 36 System.out.println(priorityQueue); 37 for (int i = 0; i <3; i++) { 38 Map.Entry<Integer,Integer> entry=priorityQueue.poll(); 39 System.out.println("出现的数字:"+entry.getKey()+" 出现次数:"+entry.getValue()); 40 } 41 } 42 }
运行结果:
1.5.输出字节流OutputStream
- OutputStream 是所有的输出字节流的父类,它是一个抽象类。
- ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。PipedOutputStream 是向与其它线程共用的管道中写入数据,
- ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流。
1.5.1字节流对象FileOutputStream
public class FileOutputStream extends OutputStream
用于将某个文件中诸如图片,视频,音频之类数据的原始字节流写入到另一个文件中。
write(int b)方法,一次写出一个字节.
注意:使用write(int b)方法,虽然接收的是int类型参数,但是write 的常规协定是:向输出流写入一个字节。要写入的字节是参数 b 的八个低位。b 的 24 个高位将被忽略。
write(byte[] b),就是使用缓冲.提高效率.查找API文档,将 b.length 个字节从指定的 byte 数组写入此输出流中。
补充:
调用上述方法往文件里写数据,运行多次就会发现,程序每运行一次,老的内容就会被覆盖掉。那么如何不覆盖已有信息,能够往文件里追加信息呢。查看API文档,发现FileOutputStream类中的构造方法中有一个构造可以实现追加的功能FileOutputStream(File file, boolean append) 第二个参数,append - 如果为 true,则将字节写入文件末尾处,而不是写入文件开始处。
问题1: 使用缓冲(字节数组)拷贝数据,拷贝后的文件大于源文件的问题.
测试该方法,拷贝文本文件,仔细观察发现和源文件不太一致。
打开文件发现拷贝后的文件和拷贝前的源文件不同,拷贝后的文件要比源文件多一些内容问题就在于我们使用的容器,这个容器我们是重复使用的,新的数据会覆盖掉老的数据,显然最后一次读文件的时候,容器并没有装满,出现了新老数据并存的情况。所以最后一次把容器中数据写入到文件中就出现了问题。
那么如何避免?使用FileOutputStream 的write(byte[] b, int off, int len)
b 是容器,off是从数组的什么位置开始,len是获取的个数,容器用了多少就写出多少。
1.5.2字节缓冲流 BufferedOutputStream (装饰)
public class BufferedOutputStream extends FilterOutputStream
该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
【演示案例】:使用字节流缓冲对象复制文件。
1 import java.io.*; 2 3 public class FileOutputStreamDemo { 4 public static void main(String[] args) throws IOException { 5 FileOutputStream fos=new FileOutputStream("D:\\demo\\demo.txt");//写 6 FileInputStream fis=new FileInputStream("c:\\html\\demo.txt");//读 7 BufferedOutputStream bos=new BufferedOutputStream(fos); 8 BufferedInputStream bis=new BufferedInputStream(fis); 9 byte[] buf=new byte[1024]; 10 int num=0; 11 while ((num=bis.read(buf))!=-1){ 12 bos.write(buf,0,num); 13 } 14 bis.close(); 15 bos.close(); 16 } 17 }