·IO流用来处理设备之间的数据传输
·java对数据的操作是通过流的方式
·java用于操作流的对象都是在IO包中
·流按操作数据分为两种:字节流和字符流
·流按流向分为:输入流和输出流
字节流的抽象基类:
InputStream,OutputStream
字符流的抽象基类:
Reader,Writer
注意:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream的子类FileInputStream
如:Reader的子类FileReader
导入IO包中的类
进行IO异常处理
在finally中对流进行关闭
思考:
有了垃圾回收机制为什么还要调用close方法进行关闭
为什么IO异常一定要处理
只要与系统相关的设备有关的操作都会抛出IO异常
import java.io.*;
class FileWriterDemo2
{
public static void main(String[] args)
{
//在外边建立引用,在try内进行初始化,作用域为整个函数(关闭流也会使用)
FileWriter fw = null;
try
{
//try内部初始化,且创建和写入都会发生异常,且他们有关联,所以一起处理
fw = new FileWriter("demo.txt");
fw.write("abcdefg");
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally//在此代码块中,必须进行关闭流的操作
{
try//关闭流时会发生IO异常,所以必须try处理
{
//当有多个流时,应当分别关闭和进行非空判断
//此句话可以写在try外面,当创建文件发生异常时,会发生空指针异常
if(fw!=null)
fw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
·创建流对象,建立数据存放文件
FileWriter fw = new FileWriter(“Test.txt”);
·调用流对象的写入方法,将数据写入流
fw.write(“test”);
·关闭流资源,并将流中的数据清空到文件中
fw.close();
找到一个专门用于操作文件的Writer子类对象。FileWriter。
后缀名是父类名。 前缀名是该流对象的功能。
import java.io.*;
class FileWriterDemo
{
publicstatic void main(String[] args) throws IOException
{
//1、创建一个FileWriter对象。该对象一被初始化就必须要明确被操作的文件。而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。其实该步就是在明确数据要存放的目的地。
FileWriterfw = new FileWriter("demo.txt");
//2、调用write方法,将字符串写入到流中。
fw.write("abcde");
//3、刷新流对象中的缓冲中的数据。将数据刷到目的地中。
//fw.flush();//可以多次使用,但是必须最后关闭
//4、关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。将数据刷到目的地中。和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
}
}
import java.io.*;
class FileWriterDemo3
{
public static void main(String[] args) throws IOException
{
//传递一个true参数,若没有文件会创建文件,若有文件则不覆盖已有的文件。并在已有文件的末尾处进行数据续写。
FileWriter fw = new FileWriter("demo.txt",true);
//在window(包括XP和win7)系统中的记事本程序中,换行是识别符是“\r\n”。
fw.write("nihao\r\nxiexie");
fw.close();
}
}
·建立一个流对象,将已存在的一个文件加载进流
FileReader fr =new FileReader(“Test.txt”);
·创建一个临时存放数据的数组
char[] ch = new char[1024];
·调用流对象的读取方法将流中的数据读入到数组中
fr.read(ch);
思考:
在加载文件时候是否将文件全部加载进流
为什么定义数组,要定义多大呢?
定义文件路径时,可以用“/”或者“\\”
在创建一个文件时,如果目录下有同名文件将被覆盖
在读取文件时,必须保证该文件已存在,否则出异常
会有一个默认的编码方式就是系统的默认编码
import java.io.*;
class FileReaderDemo
{
public static void main(String[] args) throws IOException
{
//1、创建一个文件读取流对象和指定名称的文件相关联。要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
FileReader fr = new FileReader("demo.txt");
//2、调用读取流对象的read方法。
//read():一次读一个字符。而且会自动往下读。独到末尾处返回-1
//while循环读取文件内容,书写比较简便
int ch = 0;
while((ch=fr.read())!=-1)
{
System.out.println(
}
/*
while(true)
{
int ch = fr.read();
if(ch==-1)
break;
System.out.println("ch="+(char)ch);
}
*/
fr.close();//关闭该流并释放与之关联的所有资源,不刷新
}
}图示
第二种方式:通过字符数组进行读取。
import java.io.*;
class FileReaderDemo2
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("demo.txt");
//定义一个字符数组。用于存储读到字符。
//该read(char[])返回的是读到的字符个数。
//字符是2个字节,定义1024的整数倍,这里是2K。
char[] buf = new char[1024];
//int num=fr.read(buf);//把流关联的数据读取num个存入数组中。
int num = 0;
while((num=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,num));
}
fr.close();
}
}图示
文本文件使用字符流
最好打印语句不加换行,因为当打印的数据长度大于1024时,会出现一次换行。
import java.io.*;
class FileReaderTest
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("DateDemo.java");
char[] buf = new char[1024];
int num = 0;
while((num=fr.read(buf))!=-1)
{
System.out.print(new String(buf,0,num));
}
fr.close();
}
}
将C盘一个文本文件复制到D盘。
复制的原理:其实就是将C盘下的文件数据存储到D盘的一个文件中。
步骤:
1,在D盘创建一个文件。用于存储C盘文件中的数据。
2,定义读取流和C盘文件关联。
3,通过不断的读写完成数据存储。
4,关闭资源。
import java.io.*;
class CopyText
{
public static void main(String[] args) throws IOException
{
copy_2();
}
public static void copy_2()//建议掌握
{
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("SystemDemo_copy.txt");
fr = new FileReader("SystemDemo.java");
char[] buf = new char[1024];
int len = 0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
}
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
}
}
}
//第一种方式:从C盘读一个字符,就往D盘写一个字符。
public static void copy_1()throws IOException
{
//1、创建目的地。
FileWriter fw = new FileWriter("RuntimeDemo_copy.txt");
//2、与已有文件关联。
FileReader fr = new FileReader("RuntimeDemo.java");
//3、读取一个字符,存一个字符
int ch = 0;
while((ch=fr.read())!=-1)
{
fw.write(ch);
}
fw.close();
fr.close();
}
}复制图例
缓冲区的出现是为了提高流的操作效率而出现的。所以在创建缓冲区之前,必须要先有流对象。
缓冲区的出现提高了对数据的读写效率
对应类
BufferedWriter
BufferedReader
缓冲区要结合流才可以使用
在流的基础上对流的功能进行增强
该缓冲区中提供了一个跨平台的换行符。
newLine();写入一个行分隔符(在window和linux系统中都是换行,只有缓冲区对象具有此方法)
\r\n:window系统中是换行,\n:在linux系统中是换行
import java.io.*;
class BufferedWriterDemo
{
public static void main(String[] args) throws IOException
{
//1、创建一个字符写入流对象。
FileWriter fw = new FileWriter("buf.txt");
//2、为了提高字符写入流效率。加入了缓冲技术。只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter bufw = new BufferedWriter(fw);
for(int x=1; x<5; x++)
{
bufw.write("abcd"+x);
bufw.newLine();//跨平台的通用换行方法
bufw.flush();//刷新缓冲区
}
//记住,只要用到缓冲区,就要记得刷新。
//bufw.flush();
//其实关闭缓冲区,就是在关闭缓冲区中的流对象。
bufw.close();
}
}
字符读取流缓冲区:字符、数组、行
该缓冲区提供了一个一次读一行的方法 readLine,方便于对文本数据的获取。当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容。并不返回回车符。
import java.io.*;
class BufferedReaderDemo
{
public static void main(String[] args) throws IOException
{
//1、创建一个读取流对象和文件相关联。
FileReader fr = new FileReader("buf.txt");
//2、为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲对象的构造函数。
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line);
}
bufr.close();
}
}
import java.io.*;
class CopyTextByBuf
{
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
bufw = new BufferedWriter(new FileWriter("bufWriter_Copy.txt"));
String line = null;//资源的中转站
while((line=bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();//补充readLine方法的缺陷
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}
readLine方法图例
明白了BufferedReader类中特有方法readLine的原理后,
可以自定义一个类中包含一个功能和readLine一致的方法。
来模拟一下BufferedReader
此类是一个使得读取增强的装饰类
import java.io.*;
class MyBufferedReader extends Reader
{
private Reader r;
MyBufferedReader(Reader r)
{
this.r = r;
}
//可以一次读一行数据的方法。
public String myReadLine()throws IOException
{
//定义一个临时容器。原BufferReader封装的是字符数组。
//为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=r.read())!=-1)
{
if(ch=='\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();
}
}
对原有类进行了功能的改变,增强
装饰设计模式的基本格式
它与继承有什么不同?
了解BufferedReader的原理
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
class Person
{
public void chifan()
{
System.out.println("吃饭");
}
}
class SuperPerson //与Person同属于一个接口或者一个类
{
private Person p ;
SuperPerson(Person p)//传递被增强的对象
{
this.p = p;
}
public void superChifan()
{
System.out.println("开胃酒");
p.chifan();
System.out.println("甜点");
System.out.println("来一根");
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();
//p.chifan();
SuperPerson sp = new SuperPerson(p);
sp.superChifan();
}
}
例子:MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
|--MyBufferDataReader
class MyBufferReader
{
MyBufferReader(MyTextReader text)
{}
MyBufferReader(MyMediaReader media)
{}
}
上面这个类扩展性很差。找到其参数的共同类型。通过多态的形式。可以提高扩展性。
//组合结构
class MyBufferReader extends MyReader
{
private MyReader r;
MyBufferReader(MyReader r)
{}
}
MyReader//专门用于读取数据的类。,优化后的体系,扩展性更强
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader//基于前类增加的类
以前是通过继承将每一个子类都具备缓冲功能。
那么继承体系会复杂,并不利于扩展。
现在优化思想。单独描述一下缓冲内容。将需要被缓冲的对象。传递进来。也就是,谁需要被缓冲,谁就作为参数传递给缓冲区。这样继承体系就变得很简单。优化了体系结构。
装饰模式比继承要灵活。避免了继承体系臃肿。而且降低了类于类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常是都属于一个体系中的。
import java.io.*;
class LineNumberReaderDemo
{
public static void main(String[] args)throws IOException
{
FileReader fr = new FileReader("PersonDemo.java");
//获取行号的装饰类LineNumberReader,内部定义了一个私有属性LineNumber
LineNumberReader lnr = new LineNumberReader(fr);
String line = null;
//默认为从0开始,这里设置其从100开始
lnr.setLineNumber(100);
while((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
import java.io.*;
//优化后,继承了MyBufferedReader
class MyLineNumberReader extends MyBufferedReader
{
private int lineNumber;
MyLineNumberReader(Reader r)
{
super(r);
}
public String myReadLine()throws IOException
{
lineNumber++;
return super.myReadLine();
}
public void setLineNumber(int lineNumber)
{
this.lineNumber = lineNumber;
}
public int getLineNumber()
{
return lineNumber;
}
}
/*优化前
class MyLineNumberReader
{
private Reader r;//包装一个Reader
private int lineNumber;
MyLineNumberReader(Reader r)
{
this.r = r;
}
public String myReadLine()throws IOException
{
lineNumber++;
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch=r.read())!=-1)
{
if(ch=='\r')
continue;
if(ch=='\n')
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)
return sb.toString();
return null;
}
public void setLineNumber(int lineNumber)
{
this.lineNumber = lineNumber;
}
public int getLineNumber()
{
return lineNumber;
}
public void myClose()throws IOException
{
r.close();
}
}
*/
class MyLineNumberReaderDemo
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("copyTextByBuf.java");
MyLineNumberReader mylnr = new MyLineNumberReader(fr);
String line = null;
mylnr.setLineNumber(100);
while((line=mylnr.myReadLine())!=null)
{
System.out.println(mylnr.getLineNumber()+"::"+line);
}
mylnr.myClose();
}
}
基本操作与字符流类相同
但它不仅仅操作字符,还可以操作其他媒体文件
字符流:编码使用的是系统默认的编码
字符流的操作类:
FileReader
FileWriter。
BufferedReader
BufferedWriter
字节流:
FileInputStream
FileOutputStream
BufferedInputStream
BufferedOutputStream
字节流的操作类:Stream(都是操作的字节流)
抽象类:InputStream读 OutputStream写(针对电脑而言)
需求,想要操作图片数据。这时就要用到字节流。
代码示例
import java.io.*;
class FileStream
{
public static void main(String[] args) throws IOException
{
readFile_3();
}
//第三种读方式:字节特有的available方法读取
public static void readFile_3()throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
// int num = fis.available();//(字符数)
//定义一个刚刚好的缓冲区。不用在循环了。
//有可能超出虚拟机的最大容量,默认是64M。会发生内存溢出。数据不太大的可以
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_1()throws IOException
{
FileInputStream fis = new FileInputStream("fos.txt");
int ch = 0;
while((ch=fis.read())!=-1)
{
//读过之后是一个数字,需要ASCII码表的转换
System.out.println((char)ch);
}
fis.close();
}
//字节流对文本文件的写入方式
public static void writeFile()throws IOException
{
//写数据的目的
FileOutputStream fos = new FileOutputStream("fos.txt");
//把字符串变成字节数组
fos.write("abcde".getBytes());
//必须关闭资源,但是不具备刷新功能
fos.close();
}
}
复制一个图片(标准写法包含异常处理)
思路:
1,用字节读取流对象和图片关联。
2,用字节写入流对象创建一个图片文件。用于存储获取到的图片数据。
3,通过循环读写,完成数据的存储。
4,关闭资源。
注意:字符流可以复制的,但是打开后可能看不了,或者打不开。因为读取的时候去查表,找不到的编码会找相近的编码处理,所以会有问题。
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{
fos = new FileOutputStream("c:\\2.bmp");//输出文件
fis = new FileInputStream("c:\\1.bmp");//源文件
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException("复制文件失败");
}
finally //分别关闭资源
{
try
{
if(fis!=null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(fos!=null)
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}
演示mp3的复制。通过缓冲区。初始化必须有流关联
BufferedOutputStream
BufferedInputStream
import java.io.*;
class CopyMp3
{
public static void main(String[] args) throws IOException
{
long start = System.currentTimeMillis();//开始时间
copy_2();
long end = System.currentTimeMillis();//结束时间
System.out.println((end-start)+"毫秒");//时间差,为拷贝时间
}
//第二种方式:自定义字节流的缓冲区拷贝mp3文件
public static void copy_2()throws IOException
{
MyBufferedInputStream bufis = new MyBufferedInputStream(new FileInputStream("c:\\9.mp3"));
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\3.mp3"));
int by = 0;
//System.out.println("第一个字节:"+bufis.myRead());
while((by=bufis.myRead())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.myClose();
}
//第一种方式:通过字节流的缓冲区完成复制。
public static void copy_1()throws IOException
{
BufferedInputStream bufis = new BufferedInputStream(new FileInputStream("c:\\0.mp3"));//源文件
BufferedOutputStream bufos = new BufferedOutputStream(new FileOutputStream("c:\\1.mp3"));//输出文件
//双反都有数组,所以就不必再定义数组
int by = 0;
while((by=bufis.read())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.close();
}
}
同样是提高了字节流的读写效率
MP3文件的拷贝比较效率
模拟一个BufferedInputStream
import java.io.*;
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf = new byte[1024*4];//定义字节数组
private int pos = 0,count = 0;//指针和计数器
MyBufferedInputStream(InputStream in)//包装类,装饰类
{
this.in = in;
}
//一次读一个字节,从缓冲区(字节数组)获取。
public int myRead()throws IOException
{
//通过in对象读取硬盘上数据,并存储buf中。
if(count==0)//确保字符数组中的数据都被取完
{
count = in.read(buf);//记录读取字符数组的字节数
if(count<0)
return -1;
pos = 0;//设置指针归零,方便下次继续存储
byte b = buf[pos];//取得字节数组元素
count--;//取过之后的计数器减一
pos++;//指针向后加一,继续取
return b&255;//原因在下面
}
else if(count>0)//取元素
{
byte b = buf[pos];
count--;
pos++;
return b&0xff;//255的八进制表示形式
}
return -1;
}
public void myClose()throws IOException//关流
{
in.close();
}
}
原因:读取数据时有可能读到8个1的字节,结果为-1,程序在未完成时就结束。
读取的字节byte: -1 会被提升为 int : -1;
字节型:1111-1111 int型:高位补1:1111-1111 1111-1111 1111-1111 1111-1111
为了保证数据的原样为:00000000 00000000 00000000 11111111 255
那么我只要在前面补0,即可以保留原字节数据不变,又可以避免-1的出现。
怎么补0呢?
用&运算
11111111 11111111 11111111 11111111 -1
&00000000 00000000 00000000 11111111 255
------------------------------------
00000000 00000000 00000000 11111111 255
求-1的过程:
0000-0001 正1
1111-1110 取反
000000001 加1
1111-1111 得-1
结论:
字节流的读一个字节的read方法为什么返回值类型不是byte,而是int。
因为有可能会读到连续8个二进制1的情况,8个二进制1对应的十进制是-1.
那么就会数据还没有读完,就结束的情况。因为我们判断读取结束是通过结尾标记-1来确定的。
所以,为了避免这种情况将读到的字节进行int类型的提升。
并在保留原字节数据的情况前面了补了24个0,变成了int类型的数值。
而在写入数据时,只写该int类型数据的最低8位。
read方法在读取的时候进行了提升,但是write方法在写的时候进行了强转。
InputStreamReader,OutputStreamWriter
转换流的由来
字符流与字节流之间的桥梁
方便了字符流与字节流之间的操作
转换流的应用
字节流中的数据都是字符,转成字符流操作更高效
字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。
通过下面的键盘录入一行数据并打印其大写,发现其实就是读一行数据的原理。也就是readLine方法。
能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?
readLine方法是字符流BufferedReader类中的方法。
而键盘录入的read方法是字节流InputStream的方法。
那么能不能将字节流转成字符流在使用字符流缓冲去的readLine方法呢?
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//1、创建显示器输出流
OutputStream out = System.out;
//2、将字符流对象转成字节流对象,使用转换流。OutputStreamWriter
OutputStreamWriter osw = new OutputStreamWriter(out);
//3、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedWriter
BufferedWriter bufw = new BufferedWriter(osw);
优化上面的程序代码:将上述的三句话简写为下面的一句话(嵌套进去)
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))//定义键盘录入结束标记
break;
//包装了System.out就可以使用此将对象打印出去。
bufw.write(line.toUpperCase());
bufw.newLine();//newline()是缓冲区的特有方法
bufw.flush();//刷新缓冲区
}
//1、获取键盘录入对象。
InputStream in = System.in;
//2、将字节流对象转成字符流对象,使用转换流。InputStreamReader
InputStreamReader isr = new InputStreamReader(in);
//3、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReader bufr = new BufferedReader(isr);
优化上面的程序代码:将上述的三句话简写为下面的一句话(嵌套进去)
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘录入,一读一行,最常见写法,必须会
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))//定义键盘录入结束标记
break;
bufw.write(line.toUpperCase());
}
bufr.close();//关不关都可以,因为只有键盘录入操作对象
}
}
System类中的字段:in,out
它们各代表了系统标准的输入和输出设备
默认输入设备是键盘,输出设备是显示器
System.in的类型是InputStream
System.out的类型是PrintStream是OutputStream的子类FileOutStream的子类
键盘录入的结束标记:除了Ctrl+C就是定义录入结束标记。
读取键盘录入。
System.out:对应的是标准输出设备,控制台。
System.in:对应的标准输入设备:键盘。
需求:
通过键盘录入数据。
当录入一行数据后,就将该行数据进行打印。
如果录入的数据是over,那么停止录入。
import java.io.*;
class ReadIn
{
public static void main(String[] args) throws IOException
{
InputStream in = System.in;//创建键盘输入流
StringBuilder sb = new StringBuilder();//创建一个临时缓冲区
while(true)
{
int ch = in.read();//没有读到数据是就会等待输入
if(ch=='\r')//ASCII码中的编码是13
continue;
if(ch=='\n') //ASCII码中的编码是10
{
String s = sb.toString();//缓冲区变成字符串
if("over".equals(s))
break;
System.out.println(s.toUpperCase());//编程大写打印
sb.delete(0,sb.length());//清空缓冲区
}
else
sb.append((char)ch);//添加到缓冲区
}
}
}
//优化代码见上面的转换流里的代码示例
·流是用来处理数据的
·处理数据时,一定要先明确数据源,与数据目的地(数据汇)
·数据源可以是文件,可以是键盘
·数据目的地可以是文件、显示器或者其它设备
·而流只是在帮助数据进行传输,并对传输的数据进行处理,比如过滤处理、转换处理等。
数据源:键盘录入。 数据目的:控制台。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘录入,数据的源
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//控制台,数据的目的
数据源:键盘。 数据目的:文件。
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘录入
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(“out.txt”)));//文件输出
数据源:文件。 数据目的:控制台。
BufferedReader bufr = new BufferedReader(new InputStreamReader(new FileInputStream(“copy.txt”)));//文件
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//控制台
病症所在:最痛苦的就是流对象有很多,不知道该用哪一个。
解决办法:通过三个明确来完成。
数据源:输入流。InputStream(字节输入)Reader(字符输入)
数据目的:输出流。OutputStream(字节输出)Writer(字符输入)
是:字符流。
否:字节流。
数据源设备:内存,硬盘。键盘
数据目的设备:内存,硬盘,控制台。
(有时候操作数据,只有数据源或者数据目的,不一定都有)
数据源:因为是源,所以使用读取流。InputStream Reader
是不是操作文本文件。
是!这时就可以选择Reader
这样体系就明确了。
接下来明确要使用该体系中的哪个对象。
明确设备:硬盘上一个文件。
Reader体系中可以操作文件的对象是 FileReader
是否需要提高效率:是!加入Reader体系中缓冲区 BufferedReader.
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
目的:OutputStream和Writer
是否是纯文本。
是!Writer。
设备:硬盘上的一个文件。
Writer体系中可以操作文件的对象FileWriter。
是否需要提高效率:是!。加入Writer体系中缓冲区 BufferedWriter
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
---------------------------------------------------------------------
这个需求中有源和目的都存在。那么分别分析
数据源:InputStream和Reader
是不是纯文本?是!Reader
设备:键盘。对应的对象是System.in.
疑问:不是选择Reader吗?System.in对应的不是字节流吗?
解释:为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转换成Reader。用了Reader体系中转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
需要提高效率吗?需要!BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream和Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
FileWriter fw = new FileWriter("c.txt");
需要提高效率吗?需要。
BufferedWriter bufw = new BufferedWriter(fw);
---------------------------------------------------------------------
数据目的:OutputStream和Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
但是FileWriter是使用的默认编码表(系统默认的编码表)GBK.
但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。而该转换流对象要接收一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要。
BufferedWriter bufw = new BufferedWriter(osw);
字符和字节之间的桥梁,通常,涉及到字符编码转换时,需要用到转换流。
import java.io.*;
class TransStreamDemo2
{
public static void main(String[] args) throws IOException
{
//改变标准输入输出流设备使用setXxx方法
System.setIn(new FileInputStream("PersonDemo.java"));
System.setOut(new PrintStream("zzz.txt"));
//若不改变,则默认的为键盘和控制台,改变之后则使用改变的输入输出流设备
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//数据的源
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));//数据的目的
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
bufr.close();
}
}
import java.io.*;
import java.util.*;
import java.text.*;//日期格式化
class ExceptionInfo
{
public static void main(String[] args)throws IOException
{
try
{
int[] arr = new int[2];//创建一个数组
System.out.println(arr[3]);//使其发生脚标越界异常
}
catch (Exception e)
{
try
{
//创建一个日期
Date d = new Date();
//装换日期的格式,使其模式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//把日期格式化为一个字符串形式
String s = sdf.format(d);
//创建一个新的打印输出流:形成一个日志文件
PrintStream ps = new PrintStream("exeception.log");
//打印异常发生的日期
ps.println(s);
//改变默认输出流,设置为新的打印流
System.setOut(ps);
}
catch (IOException ex)
{
//抛出运行异常,使得程序停止
throw new RuntimeException("日志文件创建失败");
}
//使得异常信息成变成输出流
e.printStackTrace(System.out); }
}
}
//log4j:工具下载下来,存储日志文件
import java.util.*;
import java.io.*;
class SystemInfo
{
public static void main(String[] args) throws IOException
{
Properties prop = System.getProperties();//获取系统信息
//System.out.println(prop);
prop.list(new PrintStream("sysinfo.txt"));
}
}
sysinfo.txt文件内容:
--listing properties --
java.runtime.name=Java(TM)SE Runtime Environment
sun.boot.library.path=C:\ProgramFiles\Java\jre6\bin
java.vm.version=20.6-b01
java.vm.vendor=SunMicrosystems Inc.
java.vendor.url=http://java.sun.com/
path.separator=;
java.vm.name=JavaHotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
user.country=CN
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=ServicePack 1
java.vm.specification.name=JavaVirtual Machine Specification
user.dir=D:\demo//获得用户当前目录
java.runtime.version=1.6.0_31-b05
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=C:\ProgramFiles\Java\jre6\lib\endorsed
os.arch=amd64
java.io.tmpdir=C:\Users\abc\AppData\Local\Temp\
line.separator=
java.vm.specification.vendor=SunMicrosystems Inc.
user.variant=
os.name=Windows7//操作系统名称
sun.jnu.encoding=GBK
java.library.path=C:\WINDOWS\SYSTEM32;C:\windows\Sun\Ja...
java.specification.name=JavaPlatform API Specification
java.class.version=50.0
sun.management.compiler=HotSpot64-Bit Tiered Compilers
os.version=6.1
user.home=C:\Users\abc
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK//系统默认编码表(简体中文版的操作系统)
java.specification.version=1.6
user.name=abc
java.class.path=.;C:\ProgramFiles\Java\jdk1.6.0_31\l...
java.vm.specification.version=1.0
sun.arch.data.model=64
java.home=C:\ProgramFiles\Java\jre6
sun.java.command=demo
java.specification.vendor=SunMicrosystems Inc.
user.language=zh
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=mixedmode
java.version=1.6.0_31//JDK版本
java.ext.dirs=C:\ProgramFiles\Java\jre6\lib\ext;C:...
sun.boot.class.path=C:\ProgramFiles\Java\jre6\lib\resour...
java.vendor=SunMicrosystems Inc.
file.separator=\
java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport..sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
sun.desktop=windows
sun.cpu.isalist=amd64
·用来将文件或者文件夹封装成对象
·方便对文件与文件夹的属性信息进行操作
·File对象可以作为参数传递给流的构造函数
·了解File类中的常用方法
·是文件和目录路径名的抽象表示形式
在操作文件的时候会使用底层资源,所以会发生异常。
boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。创建成功返回true。注意和输出流不一样,输出流对象一建立就会创建文件。而且若文件已经存在,则会覆盖。
可以创建临时文件,在程序运行使其使用,在程序结束后成为垃圾文件,一些程序的配置文件,此临时文件也可以存储在System32系统级目录下面。
|
createTempFile |
|
createTempFile |
boolean mkdir():创建文件夹。只能创建一级目录(”abc\\kkk”)
boolean mkdirs():创建多级文件夹。mkdirs可以创建多级目录。
boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回false。
void deleteOnExit();在程序退出时删除指定文件,直接写在创建文件的后面。
|
canExecute |
|
canRead |
|
canWrite |
boolean exists():文件是否存在.
boolean isFile():是否是文件
boolean isDirectory();是否是目录
boolean isHidden();是否隐藏(java用流对系统盘下的(System开头的系统卷标目录)文件是访问不了的,对隐藏文件尽量不要访问)
boolean isAbsolute();是否是绝对路径,是相对路径返回为false,是绝对路径返回为true,不管文件是否存在
File |
getAbsoluteFile |
String |
getAbsolutePath |
File |
getCanonicalFile |
String |
getCanonicalPath |
|
getFreeSpace |
String |
getName |
String |
getParent |
File |
getParentFile |
String |
getPath |
|
getTotalSpace |
|
getUsableSpace |
|
lastModified |
|
length |
getAbsolutePath()
long lastModified() //操作后的文件是否被修改,返回时间是毫秒值
long length() //数据的大小
import java.io.*;
class FileDemo
{
public static void main(String[] args) throws IOException
{
method_5();
}
public static void method_5()
{
File f1 = new File("c:\\Test.java");
File f2 = new File("d:\\hahah.java");
sop("rename:"+f2.renameTo(f1));//类似于移动,不变内容
}
public static void method_4()
{
File f = new File("file.txt");
sop("path:"+f.getPath());//获取封装的路径
sop("abspath:"+f.getAbsolutePath());//获取绝对路径(虽然不存在)
sop("parent:"+f.getParent());//该方法返回的是绝对路径中的父目录。如果获取的是相对路径,返回null。如果相对路径中有上一层目录那么该目录就是返回结果。
}
public static void method_3()throws IOException
{
File f = new File("d:\\java1223\\day20\\file2.txt");
//f.createNewFile();
//f.mkdir();
//记住在判断文件对象是否是文件或者目录时,必须要先判断该文件对象封装的内容是否存在。通过exists判断。
sop("dir:"+f.isDirectory());
sop("file:"+f.isFile());
sop(f.isAbsolute());//相对路径为false,绝对路径为true,不管文件是否存在
}
public static void method_2()
{
File f = new File("file.txt");
//判断文件是否存在,返回值为boolean型(使用的频率比较高,流操作)
//sop("exists:"+f.exists());
//判断文件是否可执行
//sop("execute:"+f.canExecute());
//创建文件夹,返回值为boolean型,说明目录是否创建成功,mkdir方法只能创建一级目录(”abc\\kkk”),mkdirs可以创建多级目录。
File dir = new File("abc\\kkk\\a\\a\\dd\\ee\\qq\\aaa");
sop("mkdir:"+dir.mkdirs());
}
public static void method_1()throws IOException
{
File f = new File("file.txt");
//创建文件,返回值为boolean型的,说明创建文件是否成功。
sop("create:"+f.createNewFile());
//删除文件,返回值为boolean型的,说明文件是否删除成功
sop("delete:"+f.delete());
}
//创建File对象
public static void consMethod()
{
//File类的构造方法,创建File类
//第一种方式:将a.txt封装成file对象。可以将已有的和没有出现的文件或者文件夹封装成对象。
File f1 = new File("a.txt");
//第二种方式:将文件和目录换成两个变量传递(父目录和文件中的文件名)等同于File f2 = new File("c:\\abc\\b.txt");
File f2 = new File("c:\\abc","b.txt");
//第三种方式:此种方式等同于第二种方式,一个是字符串,另一个是File对象
File d = new File("c:\\abc");
File f3 = new File(d,"c.txt");
//打印结果为路径:封装相对路径打印相对路径(f1),分装绝对路径打印绝对路径(f2,f3)。
sop("f1:"+f1); //结果为:a.txt
sop("f2:"+f2); //结果为:c:\abc\b.txt
sop("f3:"+f3); //结果为:c:\abc\c.txt
//打印路径中会有目录分隔符,反斜杠“\”后的为转义字符,所以会有两个。也可以写作为“/”(在window下)。在Linux下会变化,所以不利于跨平台。解决的办法就是用File的一个属性写法为:File.separator。
File f4 = new File("c:"+File.separator+"abc"+File.separator+"zzz"+File.separator+"a.txt");
}
public static void sop(Object obj)
{
System.out.println(obj);
}
}
列出目录中的内容
import java.io.*;
class FileDemo2
{
public static void main(String[] args)
{
File dir = new File("c:\\");
File[] files = dir.listFiles();
for(File f : files)
{
System.out.println(f.getName()+"::"+f.length());//文件的名字和大小
}
}
public static void listDemo_2()
{
File dir = new File("d:\\java1223\\day18");
String[] arr = dir.list(new FilenameFilter()//匿名内部类
{
public boolean accept(File dir,String name)
{
System.out.println("dir:"+dir+"....name::"+name);
/*优化前
if(name.endsWith(".bmp"))
return true;
else
return false;
*/
return name.endsWith(".bmp");//优化后
}
});
System.out.println("len:"+arr.length);
for(String name : arr)
{
System.out.println(name);
}
}
public static void listDemo()
{
File f = new File("c:\\abc.txt");
String[] names = f.list();//列出当前目录下的所有文件,包含隐藏文件。调用list方法的file对象必须是封装了一个目录。该目录还必须存在。
for(String name : names)
{
System.out.println(name);
}
}
public static void listRootsDemo()
{
File[] files = File.listRoots();//列出机器中有效的盘符
for(File f : files)
{
System.out.println(f);
}
}
}
函数自己调用自己
注意:递归时一定要明确结束条件
应用场景:当某一功能要重复使用时
练习:列出一个文件夹下所有的子文件夹以及子文件
思考:删除一个目录的过程是如何进行的?
/*
列出指定目录下文件或者文件夹,包含子目录中的内容。
也就是列出指定目录下所有内容。
因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身。
这种表现形式,或者编程手法,称为递归。
1,限定条件。
2,要注意递归的次数。尽量避免内存溢出。
import java.io.*;
class FileDemo3
{
public static void main(String[] args)
{
File dir = new File("d:\\testdir");
//showDir(dir,0);
//toBin(6);
//int n = getSum(8000);
//System.out.println("n="+n);
System.out.println(dir.delete());
}
public static String getLevel(int level)
{
StringBuilder sb = new StringBuilder();
sb.append("|--");//调用一次方法加一个标记
for(int x=0; x {//sb.append("|--");sb.insert(0,"| ");//插入}return sb.toString();}public static void showDir(File dir,int level)//调用时传入目录和标记{System.out.println(getLevel(level)+dir.getName());level++;File[] files = dir.listFiles();for(int x=0; x {if(files[x].isDirectory())showDir(files[x],level);elseSystem.out.println(getLevel(level)+files[x]);}}public static int getSum(int n)//演示递归通过加法运算{if(n==1)return 1;return n+getSum(n-1);}public static void toBin(int num)//演示递归通过十进制向二进制转换{if(num>0){toBin(num/2);System.out.println(num%2);}}public static void method()//演示递归,必须有控制条件{method();}}12.16.3递归应用——删除带内容的目录
删除一个带内容的目录。java删除文件时不走回收站的。删除原理:在window中,删除目录从里面往外删除的。既然是从里往外删除。就需要用到递归。import java.io.*;class RemoveDir{public static void main(String[] args){File dir = new File("d:\\testdir");removeDir(dir);}public static void removeDir(File dir){File[] files = dir.listFiles();for(int x=0; x {//最好避免文件为隐藏的,因为隐藏的无法访问files[x].isHidden()if(files[x].isDirectory())removeDir(files[x]);else//删除文件System.out.println(files[x].toString()+":-file-:"+files[x].delete());}System.out.println(dir+"::dir::"+dir.delete());//删除文件夹}}12.16.4递归应用——创建java文件列表
将一个指定目录下的java文件的绝对路径,存储到一个文本文件中。建立一个java文件列表文件。思路:1,对指定的目录进行递归。2,获取递归过程所以的java文件的路径。3,将这些路径存储到集合中。4,将集合中的数据写入到一个文件中。import java.io.*;import java.util.*;集合类class JavaFileList{public static void main(String[] args) throws IOException{File dir = new File("d:\\java1223");Listlist = new ArrayList (); 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,Listlist)//路径和集合 {File[] files = dir.listFiles();for(File file : files){if(file.isDirectory())//是目录就递归fileToList(file,list);else{//获取文件名字,并判断扩展名if(file.getName().endsWith(".java"))list.add(file);}}}public static void writeToFile(Listlist,String javaListFile)throws IOException {BufferedWriter bufw = null;try{bufw = new BufferedWriter(new FileWriter(javaListFile));for(File f : list){String path = f.getAbsolutePath();bufw.write(path);bufw.newLine();bufw.flush();}}catch (IOException e){throw e;}finally{try{if(bufw!=null)bufw.close();}catch (IOException e){throw e;}}}}12.17Properties
12.17.1Properties类声明
java.util 类 Propertiesjava.lang.Objectjava.util.Dictionaryjava.util.Hashtablejava.util.Properties所有已实现的接口:
Serializable, Cloneable, Map
直接已知子类:
Provider
12.17.2Properties类的常用方法
Properties对象是hashtable的子类。也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。是集合中和IO技术相结合的集合容器。Properties对象的特点:可以用于键值对形式的配置文件。那么在加载数据时,需要数据有固定格式:键=值。import java.io.*;import java.util.*;class PropertiesDemo{public static void main(String[] args) throws IOException{//method_1();loadDemo();}public static void loadDemo()throws IOException{Properties prop = new Properties();FileInputStream fis = new FileInputStream("info.txt");//将流中的数据加载进集合。JDK1.6版本以后出现的prop.load(fis);//拥有集合以后就可以操作集合,修改文件,改变的是内存的结果。prop.setProperty("wangwu","39");FileOutputStream fos = new FileOutputStream("info.txt");//store方法是将内存的结果存到流中,并存到一个文件中。prop.store(fos,"haha");//输出流和注释信息(不要写中文),自动回添加一个系统当前时间,注释信息前面带有“#”号,不会被Properties信息所加载,被加载的信息必须是键值对。不是键值对加载的没有意义。// System.out.println(prop);//流加载进集合之后就可以打印prop.list(System.out);//列出集合目录fos.close();fis.close();}演示,如何将流中的数据存储到集合中。想要将info.txt中键值数据存到集合中进行操作。思路:1,用一个流和info.txt文件关联。2,读取一行数据,将该行数据用"="进行切割。3,等号左边作为键,右边作为值。存入到Properties集合中即可。public static void method_1()throws IOException{BufferedReader bufr = new BufferedReader(new FileReader("info.txt"));String line = null;Properties prop = new Properties();while((line=bufr.readLine())!=null){String[] arr = line.split("=");//读到一行进行切///System.out.println(arr[0]+"...."+arr[1]);//读取的键值对prop.setProperty(arr[0],arr[1]);//将键值对放到Properties中}bufr.close();//关流System.out.println(prop);打印Properties对象}// 设置和获取元素。public static void setAndGet(){Properties prop = new Properties();//创建对象prop.setProperty("zhangsan","30");prop.setProperty("lisi","39");// System.out.println(prop);//默认等号连接,大括号内String value = prop.getProperty("lisi");//通过键获取值//System.out.println(value);prop.setProperty("lisi",89+"");//修改,字符串//JDK1.6以后的版本才有的stringPropertyNames()方法,返回的是键集Setnames = prop.stringPropertyNames(); for(String s : names){System.out.println(s+":"+prop.getProperty(s));//返回键和值}}}12.17.3Properties类的应用——记录程序运行的次数
/*用于记录应用程序运行次数。如果使用次数已到,那么给出注册提示。很容易想到的是:计数器。可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。可是随着该应用程序的退出,该计数器也在内存中消失了。下一次再启动该程序,又重新开始从0计数。这样不是我们想要的。程序即使结束,该计数器的值也存在。下次程序启动后会先加载该计数器的值并加1后在重新存储起来。所以要建立一个配置文件。用于记录该软件的使用次数。该配置文件使用键值对的形式。这样便于阅读数据,并操作数据。键值对数据是map集合。数据是以文件形式存储,使用io技术。那么map+io -->properties.12.17.4配置文件可以实现应用程序数据的共享
java中常用的配置文件**.properties扩展名就是这集合名,其中的配置信息就是键值对。常用的配置文件有:键值对和XML文件(这两种最常见),框架也是这样的,配置文件(字符串)可以把配置文件写到系统级目录下(在非常绝密的情况下)import java.io.*;import java.util.*;class RunCount{public static void main(String[] args) throws IOException{Properties prop = new Properties();File file = new File("count.ini");//把文件分装成文件对象//对文件的存在性判断,下面的流文件就不会报异常。否则路径异常。if(!file.exists())file.createNewFile();//在此处可以写一个否则FileInputStream fis = new FileInputStream(file);prop.load(fis);//将流中的数据加载到集合int count = 0;String value = prop.getProperty("time");if(value!=null){count = Integer.parseInt(value);if(count>=5){System.out.println("您好,使用次数已到,拿钱!");return ;}}count++;prop.setProperty("time",count+"");FileOutputStream fos = new FileOutputStream(file);//写回源文件prop.store(fos,"");fos.close();fis.close();}}/*12.17.5配置文件描述
配置文件.ini文件name=zhangsanage=20XML文件配置信息(取此文件的信息的工具在org.w3c.dom包中的Document接口建立对象那这个数据,但是java获取这个数据是非常的麻烦,要用好多对象和方法完成。)但是有更好的办法去获取这个信息就是dom4j:dom for java(four和for谐音)//标记 zhagnsan 30 bj */12.17、IO包中的其他类
12.17.1打印流
PrintWriter与PrintStream
可以直接操作输入流和文件
打印流:该流提供了打印方法,可以将各种数据类型的数据都原样打印。12.17.1.1字节打印流
打印int型的字节打印是最低八位PrintStream构造函数可以接收的参数类型:1,file对象。File2,字符串路径。String3,字节输出流。OutputStream12.17.1.2字符打印流
PrintWriter构造函数可以接收的参数类型:1,file对象。File2,字符串路径。String3,字节输出流。OutputStream4,字符输出流,Writer。12.17.1.3代码示例
import java.io.*;class PrintStreamDemo{public static void main(String[] args) throws IOException{BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘输入,数据源//通用性极强的输出流,控制台,//自动刷新必须有刷新标记,文件对象可以装到缓冲区中,new BufferedWriter(new FileWriter("a.txt"))。PrintWriter out = new PrintWriter(new FileWriter("a.txt"),true);String line = null;while((line=bufr.readLine())!=null){if("over".equals(line))//结束标记break;out.println(line.toUpperCase());//out.flush();//刷新}out.close();//关闭打印流bufr.close();//关闭键盘录入}}12.17.2序列流
SequenceInputStream对多个流进行合并
12.17.2.1SequenceInputStream类声明
java.io (没有对应的OutputStream)
类 SequenceInputStreamjava.lang.Objectjava.io.InputStreamjava.io.SequenceInputStream所有已实现的接口:
Closeable
public class SequenceInputStreamextends InputStream
SequenceInputStream
表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。从以下版本开始:
JDK1.0
12.17.2.2示例代码
import java.io.*;import java.util.*;class SequenceDemo{public static void main(String[] args) throws IOException{//Vector集合中有Enumeration创建集合,存入流对象Vectorv = new Vector (); v.add(new FileInputStream("c:\\1.txt"));v.add(new FileInputStream("c:\\2.txt"));v.add(new FileInputStream("c:\\3.txt"));//集合中的Enumeration对象操作集合Enumerationen = v.elements(); //创建序列流对象,并将集合传入SequenceInputStream sis = new SequenceInputStream(en);//由于不涉及编码可以使用字节流FileOutputStream fos = new FileOutputStream("c:\\4.txt");byte[] buf = new byte[1024];int len =0;while((len=sis.read(buf))!=-1){fos.write(buf,0,len);}fos.close();sis.close();//关闭所有流}}12.17.2.3文件切割合并的程序
此程序切割图片和Mp3没有问题,但是切割电影时可以使用切割也是用1M的空间,然后循环使用当100M时存入一个空间。
import java.io.*;import java.util.*;class SplitFile{public static void main(String[] args) throws IOException{//splitFile();merge();}//合并文件public static void merge()throws IOException{//创建一个集合存储切割后的部分文件ArrayListal = new ArrayList (); //循环将切割的部分文件添加到集合中去for(int x=1; x<=3; x++){//把切割的部分文件分装成读取流,添加到集合中去al.add(new FileInputStream("c:\\splitfiles\\"+x+".part"));}//迭代器中含有Enumeration对象,用迭代器遍历集合中的分割文件final Iteratorit = al.iterator(); //使用的是匿名内部类Enumerationen = new Enumeration () {//覆盖Enumeration中的方法public boolean hasMoreElements(){return it.hasNext();}public FileInputStream nextElement(){return it.next();}};//创建一个序列流,关联Enumeration对象SequenceInputStream sis = new SequenceInputStream(en);//创建一个输出流FileOutputStream fos = new FileOutputStream("c:\\splitfiles\\0.bmp");byte[] buf = new byte[1024];int len = 0;while((len=sis.read(buf))!=-1){fos.write(buf,0,len);}fos.close();sis.close();}//切割文件public static void splitFile()throws IOException{//关联文件FileInputStream fis = new FileInputStream("c:\\1.bmp");FileOutputStream fos = null;byte[] buf = new byte[1024*1024];//创建缓冲区为1M大//此程序切割图片和Mp3没有问题,但是切割电影时可以使用切割也是用1M的空间,然后循环使用当100M时存入一个空间。
int len = 0;int count = 1;//文件分割后的部分为1开始while((len=fis.read(buf))!=-1){//此时的文件路径最好不要写死,扩展名为部分文件fos = new FileOutputStream("c:\\splitfiles\\"+(count++)+".part");fos.write(buf,0,len);//写流fos.close();//关流,循环一次创建一个流,并使用完毕后关闭该流}fis.close();}}12.17.3操作对象
12.17.3.1操作对象的流
ObjectInputStream与ObjectOutputStream被操作的对象需要实现Serializable(标记接口);
对象本身存在于堆内存中,当我们的程序用完之后,内存被释放,堆内存的垃圾被回收,对象就会不存在。使用流将对象存放在硬盘上。
对象的持久化存储(对象的序列化,对象的可串行性),找一个介质能长期的存储保存数据。
12.17.3.2示例代码
import java.io.*;class ObjectStreamDemo{public static void main(String[] args) throws Exception{//writeObj();readObj();}//读取对象,读取第一个后,再次读的时候就是第二个对象(按顺序读就可以)public static void readObj()throws Exception{ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));//读取的是Object对象,然后强转Person p = (Person)ois.readObject();System.out.println(p);ois.close();}//写入对象,可以写多个对象(因为每个对象都有标记)public static void writeObj()throws IOException{//将一个对象写到文件中去,不是纯文本(看懂的字符),是字节文件。一般的对象文件名为:类名.object。如:person.objectObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("person.object "));//写对进硬盘象oos.writeObject(new Person("lisi0",399,"kr"));oos.close();}}12.17.3.3对象序列化和UID标识
import java.io.*;//实现其接口可以启用其序列化功能,导包,没有方法的接口就是标记接口class Person implements Serializable{//有一个UID标识,编译使用,Long型值。UID是根据类中的成员算出来的,所以要一一对应。若自己写个UID,则对应的使用该类,若该类的成员有所改变,但是使用该类的的文件也不会改变//自定义的UID号42L可以变,给类定义个固定标识,方便序列化。public static final long serialVersionUID = 42L;private String name;//对非静态成员不想序列化,可以加上一个关键字transient,保证其值在堆内存中存在,而不在文本文件中存在。transient int age;//静态是不能被序列化的,因为在方法区中,只能序列化堆内存中的对象。读取的时候就是初始值,不可在写入的方法中再次传入静态的变量值static String country = "cn";Person(String name,int age,String country){this.name = name;this.age = age;this.country = country;}public String toString(){return name+":"+age+":"+country;}}12.17.4管道流
12.17.4.1管道流概述
PipedInputStream和PipedOutputStream输入输出可以直接进行连接,通过结合线程使用
12.17.4.2类声明
java.io
类 PipedInputStreamjava.lang.Objectjava.io.InputStreamjava.io.PipedInputStream所有已实现的接口:
Closeable
public class PipedInputStreamextends InputStream管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从
PipedInputStream
对象读取,并由其他线程将其写入到相应的PipedOutputStream
。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。从以下版本开始:
JDK1.0
另请参见:
PipedOutputStream
12.17.4.3示例代码
读和写不一定谁先谁后import java.io.*;//读的多线程class Read implements Runnable{private PipedInputStream in;Read(PipedInputStream in)//读的一个流{this.in = in;}public void run(){try{byte[] buf = new byte[1024];System.out.println("读取前。。没有数据阻塞");int len = in.read(buf);System.out.println("读到数据。。阻塞结束");String s= new String(buf,0,len);System.out.println(s);in.close();}catch (IOException e){throw new RuntimeException("管道读取流失败");}}}//写的多线程class Write implements Runnable{private PipedOutputStream out;Write(PipedOutputStream out)//写个一个流{this.out = out;}public void run(){try{System.out.println("开始写入数据,等待6秒后。");Thread.sleep(6000);out.write("piped lai la".getBytes());//写数据out.close();}catch (Exception e){throw new RuntimeException("管道输出流失败");}}}class PipedStreamDemo{public static void main(String[] args) throws IOException{PipedInputStream in = new PipedInputStream();//管道流PipedOutputStream out = new PipedOutputStream();in.connect(out);//两个流接上Read r = new Read(in);Write w = new Write(out);new Thread(r).start();new Thread(w).start();}}
12.17.5RandomAccessFile
12.17.5.1 RandomAccessFile概述
RandomAccessFile随机访问文件,自身具备读写的方法。 通过skipBytes(intx),seek(int x)来到达随机访问
12.17.5.2RandomAccessFile类声明
java.io
类 RandomAccessFilejava.lang.Objectjava.io.RandomAccessFile所有已实现的接口:
Closeable, DataInput, DataOutput
public class RandomAccessFileextends Objectimplements DataOutput, DataInput, Closeable12.17.5.3RandomAccessFile类说明
此类的实例支持对随机访问文件的读取和写入。随机访问文件的行为类似存储在文件系统中的一个大型 byte 数组。存在指向该隐含数组的光标或索引,称为文件指针;输入操作从文件指针开始读取字节,并随着对字节的读取而前移此文件指针。如果随机访问文件以读取/写入模式创建,则输出操作也可用;输出操作从文件指针开始写入字节,并随着对字节的写入而前移此文件指针。写入隐含数组的当前末尾之后的输出操作导致该数组扩展。该文件指针可以通过
getFilePointer
方法读取,并通过seek
方法设置。通常,如果此类中的所有读取例程在读取所需数量的字节之前已到达文件末尾,则抛出
EOFException
(是一种IOException
)。如果由于某些原因无法读取任何字节,而不是在读取所需数量的字节之前已到达文件末尾,则抛出IOException
,而不是EOFException
。需要特别指出的是,如果流已被关闭,则可能抛出IOException
。从以下版本开始:
JDK1.0
12.17.5.4RandomAccessFile类概述
该类不是算是IO体系中子类。而是直接继承自Object。但是它是IO包中成员。因为它具备读和写功能。内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。其实完成读写的原理就是内部封装了字节输入流和输出流。通过构造函数可以看出,该类只能操作文件。而且操作文件还有模式:只读r,,读写rw等。如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。如果模式rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。12.17.5.5RandomAccessFile功能是实现文件的分段写入
下载软件的原理,多线程的下载12.17.5.6代码示例
import java.io.*;class RandomAccessFileDemo{public static void main(String[] args) throws IOException{//writeFile_2();//readFile();//System.out.println(Integer.toBinaryString(258));}public static void readFile()throws IOException{//读取一个文件RandomAccessFile raf = new RandomAccessFile("ran.txt","r");
//调整对象中指针。可以随机取得任意一个数据,但是保证其规律//raf.seek(8*1);//写姓名是可以按8个字16个字节处理,年龄4个字节int型,则20个字节一个人//跳过指定的字节数,但是这个跳过只能往下跳,不能往回跳raf.skipBytes(8);byte[] buf = new byte[4];//创建一个字符数组raf.read(buf);//用读取文件读字节数组String name = new String(buf);//把数组变成字符串输出int age = raf.readInt();//读一个32位二进制数System.out.println("name="+name);System.out.println("age="+age);raf.close();//关流}public static void writeFile_2()throws IOException{RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");raf.seek(8*0);//随机的添加和修改(若指定位置有数据则修改)raf.write("周期".getBytes());raf.writeInt(103);
raf.close();}public static void writeFile()throws IOException//抛出异常{//创建写入的文件对象RandomAccessFile raf = new RandomAccessFile("ran.txt","rw");raf.write("李四".getBytes());//写入字节数组raf.writeInt(97);//输出为a(字符),因为write方法只写了最低的八位而此字符的二进制位超出了8位。所以要使用writeInt()方法将字符的4个字节写出去。(记事本的一个空格代表一个字节)raf.write("王五".getBytes());raf.writeInt(99);raf.close();}}12.17.6操作基本数据类型
DataInputStream与DataOutputStream
可以用于操作基本数据类型的数据的流对象。
将基本数据类型和流相结合。它的功能里面可以操作基本数据类型。所以初始化之后就有一个字节流
对于“你好”:
GBK编码之后写入的是4个字节,
UTF-8编码之后写入的是6个字节。
UTF-8修改版编码之后写入的是8个字节
12.17.6.1代码示例
import java.io.*;class DataStreamDemo{public static void main(String[] args) throws IOException{//writeData();//readData();//writeUTFDemo();// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");//// osw.write("你好");// osw.close();// readUTFDemo();}public static void readUTFDemo()throws IOException
{
//创建一个流对象DataInputStream dis = new DataInputStream(new FileInputStream("utfdata.txt"));
String s = dis.readUTF();//读成字符串System.out.println(s);
dis.close();
}
public static void writeUTFDemo()throws IOException
{
DataOutputStream dos = new DataOutputStream(new FileOutputStream("utfdata.txt"));//写入的是8个字节,UTF-8是读不出来的,原因是修改版做的,此方式写的只能用此对应的方式读出来dos.writeUTF("你好");//必须使用指定的编码。转换流不可以dos.close();
}public static void readData()throws IOException{DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));//读取的时候是依照写入的顺序int num = dis.readInt();boolean b = dis.readBoolean();double d = dis.readDouble();System.out.println("num="+num);System.out.println("b="+b);System.out.println("d="+d);dis.close();//关流}public static void writeData()throws IOException{DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));//记事本中编码dos.writeInt(234);//写了4个字节dos.writeBoolean(true);//写了1个字节dos.writeDouble(9887.543);//写了8个字节dos.close();ObjectOutputStream oos = null;oos.writeObject(new O());}}12.17.7操作字节数组
ByteArrayInputStream与ByteArrayOutputStream
操作字符数组
CharArrayReader与CharArrayWriter
操作字符串
StringReader与StringWriter
用于操作字节数组中数据的流对象。12.17.7.1 ByteArrayInputStream和ByteArrayOutputStream比较
ByteArrayInputStream :在构造的时候,需要接收数据源。而且数据源是一个字节数组。ByteArrayOutputStream: 在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。因为这两个流对象都操作的数组,并没有使用系统资源。所以,不用进行close关闭。12.17.7.2在流操作规律讲解
数据源设备,键盘 System.in,硬盘 FileStream,内存 ArrayStream。数据目的设备:控制台 System.out,硬盘FileStream,内存 ArrayStream。用流的读写思想来操作数据。12.17.7.3代码示例
import java.io.*;class ByteArrayStream{public static void main(String[] args){//数据源,为字节数组ByteArrayInputStream bis = new ByteArrayInputStream("ABCDEFD".getBytes());//数据源可以改变//数据目的,是一个数组,可以不用指定,可以指定大小ByteArrayOutputStream bos = new ByteArrayOutputStream();int by = 0;while((by=bis.read())!=-1) //读取数据{bos.write(by);//写数据}System.out.println(bos.size());//打印缓冲区大小System.out.println(bos.toString());//转换成字符串// bos.writeTo(new FileOutputStream("a.txt"));只有这个方法涉及到异常。}}12.18、字符编码
12.18.1字符编码的出现
字符流的出现为了方便操作字符,更重要的是加入了编码转换
通过子类转换流来完成
InputStreamReader,OutputStreamWriter在进行构造的时候可以加入字符集。PrintStream,PrintWrite也可以加入字符集(编码表),但是只能去打印,不可以去读取。所以还是以转换流为主。
12.18.2编码表的由来
计算机只能识别二进制数据,早期由来是电信号
为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,使用10的不同组合,并一一对应,形成一张表
12.18.3常见的编码表(不区分大小写)
12.18.3.1 ASCII:美国标准信息交换码
用一个字节的7位可以表示
12.18.3.2 ISO8859-1:拉丁码表(欧洲码表)
用一个字节的8位表示,高位表示1 。不识别中文。
12.18.3.3 GB2312:中国的中文编码表
两个字节的高位都是1,避免和ASCII重复。中国的码表兼容ASCII码表。包括中文的符号六七千个字。
12.18.3.4 GBK:中国的中文编码表升级
融合了更多(两万多,但是还在扩容)的中文字符号。
12.18.3.5 Unicode:国际标准码(融合了多种文字)
所有文字都用两个字节(6535)来表示,java语言使用是就是unicode(char类型的编码)
12.18.3.6 UTF-8:Unicode转换格式以最小8位为单位
最多用三个字节来表示一个字符,在每一个字节的开头都加了一个标识符。但是同一个文字在UTF-8中和GBK中对应的数字不是同一个。那么就涉及到编码转换问题。
12.18.4转换流的编码应用
·可以将字符以指定编码格式存储
·可以对文本数据指定编码格式来解读
·指定编码表的动作有构造函数完成
12.18.4.1字符集转换示例代码(应当编码解码一致)
import java.io.*;class EncodeStream{public static void main(String[] args) throws IOException{//writeText();readText();}public static void readText()throws IOException{InputStreamReader isr = new InputStreamReader(new FileInputStream("utf.txt"),"utf-8");char[] buf = new char[10];int len = isr.read(buf);String str = new String(buf,0,len);System.out.println(str);isr.close();}public static void writeText()throws IOException{//编码,需要转换流OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"));//未指定,默认为GBK编码表。4个字节OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf.txt"),"UTF-8");//UTF-8编码,6个字节osw.write("你好");osw.close();}}12.18.5字符编码
12.18.5.1编码和解码
编码:字符串(人类熟悉的数据)à字节数组(电脑识别的数据)
String-->byte[];语法:str.getBytes(charsetName);指定编码表
解码:字节数组à字符串
byte[] -->String;语法:new String(byte[],charsetName);12.18.5.2乱码问题
当编码错误时,就不要再去解码(使用不识别中文的去编码);当编码正确时,解码错误时,有可能使用其他的字符对应或者使用“?”对应错误编码中的一个字符。产生的是乱码。解决乱码问题办法是:“用(错码)编一次,再用(正码)解一次”
原理如下:客户端(默认GBK)发送由GBK编码而成的数组到Tomcat服务器(默认编码是ISO8859-1)。如果是get提交的话,必须,将Tomcat服务器返回的乱码,进行用Tomcat默认的ISO8859-1编码表进行编码,得到的数组,再用客户端的GBK字符集进行解码。图示如下:如果是post提交的话,可以通过,服务器当中的一个对象的函数叫做set^Encoding,设置字符编码,指定字符集,会自动转换。可以将Tomcat服务器的码表改成GBK,这样做是可以的。但是,一个服务器中可以装好多web应用程序(网站),新浪使用GBK编码,搜狐使用UTF-8,当指定GBK是,搜狐全是乱码。当使用GBK编码正确时,然后使用UTF-8解码,错误后,在使用UTF-8进行编码,最后使用GBK进行解码后,会出现问题。原因是GBK和UTF-8都识别中文造成的。所以以后要注意这个问题。而(GBK和ISO8859-1)或者(UTF-8和ISO8859-1)就不会出现这个问题,因为ISO8859-1不识别中文。12.18.5.3示例代码
import java.util.*;class EncodeDemo{public static void main(String[] args){String s = "哈哈";//编码返回一个字节数组,若指定一个字符集(异常),必须抛出异常。byte[] b1 = s.getBytes("GBK");//数组变成字符串,(GBK,默认编码)结果是四个负数组成的一个数组;(UTF-8)结果是六个数字组成的一个数组。System.out.println(Arrays.toString(b1));//打印数组//解码成字符串,若出现使用错误解码,如iso8859-1String s1 = new String(b1,"iso8859-1");System.out.println("s1="+s1);//解码正确,否则出现乱码//对s1进行iso8859-1编码。返回原数组byte[] b2 = s1.getBytes("iso8859-1");System.out.println(Arrays.toString(b2));//重新对原数组进行解码,使用正确的字符集String s2 = new String(b2,"gbk");System.out.println("s2="+s2);//解码正确}}12.18.5.4“联通”问题
问题描述:新建一个记事本文件,在里面写入“你好”,两个汉字,保存。然后打开,将“你好”改成“联通”然后保存。再次打开,则出现乱码。此时的编码为UTF-8。
字符流和转化字符流,在读取数据的时候是读取的编码数组,一次读取两个字节,然后去查字符集;查表一次返回一个中文,然后再读后两个字节。而字节流读取的方式是一个字节一个字节的读取。可是当编码数组使用的是UTF-8编码时,有的是两个字节表示一个文字,有的是三个字节表示一个文字。这时字符流读取数组的时候不太确定。
所以,UTF-8字符集对所有的字节加了一个标识头信息。当使用这个表示头信息的时候UTF-8就知道一次读取几个字节。
12.18.5.5UTF-8的标识头是UTF-8修改版
DataInput 和 DataOutput 接口的实现表示稍作改版的 UTF-8 格式的 Unicode 字符串。(关于标准 UTF-8 格式的信息,请参阅The Unicode Standard,Version 4.0 的3.9Unicode Encoding Forms 节)。注意,在下表中,最高有效位显示在最左边的列中。
'\u0001'
到'\u007F'
范围内的所有字符都是用单个字节表示的:
位值
字节 1
0
位 6-0
null 字符
'\u0000'
以及从'\u0080'
到'\u07FF'
的范围内的字符用两个字节表示:
位值
字节 1
1
1
0
位 10-6
字节 2
1
0
位 5-0
'\u0800'
到'\uFFFF'
范围内的char
值用三个字节表示:
位值
字节 1
1
1
1
0
位 15-12
字节 2
1
0
位 11-6
字节 3
1
0
位 5-0
这种格式与标准 UTF-8 格式之间的不同如下:
- null 字节
'\u0000'
是用 2-byte 格式而不是 1-byte 格式编码的,因此已编码的字符串中决不会有嵌入的 null。- 仅使用 1-byte、2-byte 和 3-byte 格式。
- 增补字符是以代理项对的形式表示的。
从以下版本开始:
JDK1.0
另请参见:
DataInputStream
,DataOutput
12.18.5.6“联通”问题示例代码
//联通的GBK编码数组的二进制表示形式,记事本软件在解码的时候由于其特殊性已经具备了UTF-8的标识头的特征,所以会使用其UTF-8的字符集进行解码,所以会出现乱码。解决办法前面只要有汉字就可以了。
class EncodeDemo2{public static void main(String[] args) throws Exception{String s = "联通";byte[] by = s.getBytes("gbk");for(byte b : by){System.out.println(Integer.toBinaryString(b&255));}System.out.println("Hello World!");}}12.19章节练习
需求:有五个学生,每个学生有3门课的成绩,从键盘输入以上数据(包括姓名,三门课成绩),输入的格式:如:zhagnsan,30,40,60计算出总成绩,并把学生的信息和计算出的总分数高低顺序存放在磁盘文件"stud.txt"中。1,描述学生对象。2,定义一个可操作学生对象的工具类。思想:1,通过获取键盘录入一行数据,并将该行中的信息取出封装成学生对象。2,因为学生对象有很多,那么就需要存储,使用到集合。因为要对学生对象的总分排序。所以可以使用TreeSet。3,将集合的信息写入到一个文件中。import java.io.*;//流操作import java.util.*;//集合class Student implements Comparable//学生具有比较性 {private String name;//学生姓名private int ma,cn,en;//定义三门成绩private int sum;//总分数Student(String name,int ma,int cn,int en)//构造函数初始化{this.name = name;this.ma = ma;this.cn = cn;this.en = en;sum = ma + cn + en;//算出总成绩}public int compareTo(Student s)//比较方法{int num = new Integer(this.sum).compareTo(new Integer(s.sum));if(num==0)return this.name.compareTo(s.name);return num;}public String getName()//获取姓名{return name;}public int getSum()//获取总分{return sum;}public int hashCode()//复写HashCode方法,{return name.hashCode()+sum*78;}public boolean equals(Object obj)//复写equals方法{if(!(obj instanceof Student))//判断传入的对象throw new ClassCastException("类型不匹配");Student s = (Student)obj;//强制转换return this.name.equals(s.name) && this.sum==s.sum;}public String toString()//转换成字符串输出{return "student["+name+", "+ma+", "+cn+", "+en+"]";}}class StudentInfoTool{public static SetgetStudents()throws IOException {return getStudents(null);//无比较器的比较}public static SetgetStudents(Comparator cmp)throws IOException//带比较器的比较 {BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//键盘输入String line = null;Setstus = null; if(cmp==null)stus = new TreeSet(); elsestus = new TreeSet(cmp); while((line=bufr.readLine())!=null){if("over".equals(line))break;String[] info = line.split(",");Student stu = new Student(info[0],Integer.parseInt(info[1]),Integer.parseInt(info[2]),Integer.parseInt(info[3]));stus.add(stu);}bufr.close();return stus;}public static void write2File(Setstus)throws IOException {BufferedWriter bufw = new BufferedWriter(new FileWriter("stuinfo.txt"));for(Student stu : stus){bufw.write(stu.toString()+"\t");bufw.write(stu.getSum()+"");bufw.newLine();bufw.flush();}bufw.close();}}class StudentInfoTest{public static void main(String[] args) throws IOException{Comparatorcmp = Collections.reverseOrder(); Setstus = StudentInfoTool.getStudents(cmp); StudentInfoTool.write2File(stus);}}