JavaIO流
IO(Input Output)流
流的概念:
流是一个很形象的概念,当程序需要读取数据的时候,就会开启一个通向数据源的流,这个数据源可以是文件,内存,硬盘,网络连接。类似的当程序需要写入数据时,就会开启一个通向目的地的流,这时候你就可以想象数据好像在这其中流动一样。
作用:
IO流用来处理设备之间的数据传输。
Java用于操作流的对象都在IO包中。
流按照数据类型分为:字符流和字节流
字节流:InputStream/OutputStream
字符流:Writer/Reader
按照数据流向分为:输入流和输出流
输入流:InputStream/Reader
输出流:OutputStream/Writer
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀
如:InputStream的子类FileInputStream。
如:Reader的子类FileReader。
字符流
字符流原理:
其实字符流在底层调用的还是字节流,字节流读取指定文字字节数据后,不直接操作,而是先查指定的编码表。获取对应的文字再对文字进行操作。
作用:
它是为了更便于操作文字数据。简单说:字节流+编码表。如果要操作文字数据,优先考虑字符流。
IO程序的书写:
1, 导入IO包中的类
2, 进行IO异常处理
3, 在finally中对流进行关闭
字符流—创建对象
1, 创建流对象,该对象一初始化就要明确被操作的文件
(注意:该文件会被创建到指定目录下,如果该目录下已有同名文件,将会被覆盖。)
FileWriter fw = new FileWriter(“Text.txt”);
2, 调用流对象的写入方法,将数据写入流。
fw.write(“text”);
3, 一刷新流对象中的缓冲数据,将数据刷到目的地中
fw.fiush();
或者关闭流资源,并将流中的数据清空到文件中。
fw.close();
close和flush的区别:flush刷新后 流可以继续使用,close刷新后会将流关闭。
IO异常处理方式完整代码:
import java.io.*;
class FileWriterDemo {
public static void main(String[] args) {
FileWriter fw = null;
//因为要作用于整个方法,所以需要在外边建立引用在try内进行初始化
try{
fw = new FileWriter("File.txt");
fw.write("lianxi java");
}
catch (Exception e){
System.out.println(e.toString());
}
finally{
try{
if(fw!=null)
fw.close();
}
catch (Exception e){
System.out.println(e.toString());
}
}
}
}
对已有文件的数据续写:
步骤传递一个true参数,代表不覆盖已有文件,并在已有文件的末尾处进行数据的续写。
FileWriter fw = new FileWriter(“File.txt”,true);
fw.write(“你好\r\n谢谢”);
fw.close();
文本文件的读取方式一:
1, 创建一个文件读取流对象,和指定名称的文件箱关联,若指定文件不存在会发生FileNotFoundException异常
FileReader fr = new FileReader(“File.txt”);
2, 调用读取流对象的read方法。(read()一次只读一个字符,而且会自动往下读,当读到末尾时没有了会返回-1)
int ch = 0;
while((ch = fr.read())!=-1){
System.out.println((char)ch);
}
文本文件的读取方式二:通过字符数组进行读取
1,与方式一一样,建立一个读取流对象
FileReader fr = new FileReader(“File.txt”);
2,创建一个临时存放数据的数组
char[] ch = new char[1024];
3, 调用流对象的读取方法将流中的数据读入到数组中,再将数组转换成字符串
//fr.read(ch);
int x = 0;
while((x=fr.read(ch))!=-1){
System.out.println(new String(ch,0,x));
}
示例:
练习一:
*
练习:读取一个.JAVA文件并打印在控制台上。
*/
import java.io.*;
class FileReaderDemo{
public static void main(String[] args) {
FileReader fr = null;
try{
fr = new FileReader("FileWriterDemo.java");
char[] ch = new char[1024];
int num = 0;
while((num = fr.read(ch))!=-1){
System.out.println(new String(ch,0,num));
}
}
catch (Exception e){
System.out.println(e.toString());
}
finally{
try{
if(fr!=null)
fr.close();
}
catch (Exception e){
System.out.println(e.toString());
}
}
}
}
练习二:
/*
将c盘的一个文本文件复制到d盘
分析原理步骤:
1,建立读取和存入流对象。
2,读一个数据就往指定文件里存一个
3,将流关闭*/
import java.io.*;
class CopyDemo{
public static void main(String[] args) {
copy();
}
public static void copy(){
FileWriter fw = null;
FileReader fr = null;
try{
fw = new FileWriter("d:\\copy.txt");
fr = new FileReader("c:\\FileWriterDemo.java");
int len = 0;
char[] ch = new char[1024];
while((len=fr.read(ch))!=-1){
fw.write(ch,0,len);
}
}
catch (Exception e){
throw new RuntimeException("文件复制失败");
}
finally{
try{
if(fw!=null)
fw.close();
}
catch (Exception e){
throw new RuntimeException("文件写入关闭失败");
}
try{
if(fr!=null)
fr.close();
}
catch (Exception e) {
throw new RuntimeException("文件读取关闭失败");
}
}
}
}
字符流的缓冲区
缓冲区的出现提高了对数据的读写效率。
对应类:
BufferedWriter
BufferedReader
缓冲区要结合流才可以使用。
缓冲区在流的基础上对流的功能进行了增强。
示例步骤BufferedWriter:
创建一个字符流写入流对象
FileWriter fw = new FileWriter(“java.txt”);
为了提高字符流写入流效率,加入缓冲技术,把需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter buwr = newBufferedWriter(fw);
buwr.write(“java”);
buwr.flush();
其实关闭缓冲区,就是在关闭缓冲区中的流对象,所以流对象不要刻意再关闭
buwr.close();
//fw.close();
因为在不同系统换行的代码可能不一样,在缓冲区里就定义了跨平台换行分隔符方法:
void newline();该方法返回的时候只返回回车符之前的数据内容,并不返回回车符,
示例步骤BufferedReader:
创建一个读取流对象和文件相关类
FileReader fr = new FileReader(“java.txt”);
为了提高效率加入缓冲技术,将字符流对象作为参数传递给缓冲对象的构造函数
BufferedReader bufr = new BufferedR(fr);
该缓冲区提供了一个一次读一行的高效方法:readLine,当读到文件末尾没有数据时返回null
String line = null;
While((line = bufr.readLine())!=null){
System.out.println(line);
关闭流对象
bufr.close();
自定义读取缓冲区
根据BufferedReader类中特有readLine的原理,可以自定义一个类中包含一个功能和readLine一致的方法。来模拟BufferedReader。
分析:
缓冲区中就是封装了一个数组, 并对外提供了更多的方法对数组进行访问。 其实这些方法最终操作的都是数组的指针。
缓冲区原理:
实际上就是把数据源的数据依次封装进一定临时空间里,然后再将临时空间的数据取出。然后依次往复循环
当数据源的数据没有时返回-1,。
当临时空间取出到末尾时,返回null。
/*自定义readLine方法*/
import java.io.*;
class MyBufferedReader{
private FileReader fr;
MyBufferedReader(FileReader fr){
this.fr = fr;
}
public String myReadLine()throws IOException
{//定义临时容器存储 读出来的数据
StringBuilder sb = new StringBuilder();
int ch = 0;
while((ch = fr.read())!=-1)
{//若读到\r继续往下读,若读到\n说明回车符,
//以字符串形式返回临时存储的数据,否则继续忘临时存储器存储
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 myClose()throws IOException{
fr.close();
}
}
装饰设计模式
根据上面示例,自定义缓冲区实际上就是把FileReader里的read方法进行一次读一行的增强,这我们也把他称为装饰设计模式。
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能并提供加强功能。那么自定义的该类称为装饰类。
装饰类通常会通过构造函数接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能
装饰和继承的区别
装饰模式比继承要灵活,避免了继承体系的臃肿。
结构上:
装饰是组合结构,继承的继承结构,降低了类鱼类之间的关系
装饰类因为增强已有对象,它们具备的功能是一样的,只不过提供更强的相同功能,所以装饰类和被装饰类通常是属于一个体系中的。
LineNumberReader类
概述:
跟踪行号的缓冲字符输入流。此类定义了方法 setLineNumber(int) 和 getLineNumber(),它们可分别用于设置和获取当前行号。默认情况下,行编号从 0 开始。该行号随数据读取在每个行结束符处递增,并且可以通过调用 setLineNumber(int) 更改行号。但要注意的是,setLineNumber(int) 不会实际更改流中的当前位置;它只更改将由 getLineNumber() 返回的值。
可认为行在遇到以下符号之一时结束:换行符('\n')、回车符('\r')、回车后紧跟换行符。
示例:
import java.io.*;
class LineNumberReaderDemo {
public static void main(String[] args) throws IOException{
FileReader fr = new FileReader ("MyBufferedReader.java");
LineNumberReader lnr = new LineNumberReader(fr);
String len = null;
lnr.setLineNumber(100);//设置显示行号
while ((len= lnr.readLine())!=null){
System.out.println(lnr.getLineNumber()+""+len); //获取行号
}
lnr.close();//关闭流对象
}
}
练习:自定义带行号的缓冲区
练习*
自定义带行号的缓冲区
*/
import java.io.*;
class MyLineNumberReader extends MyBufferedReader
{//代码优化后
private int count;
MyLineNumberReader(FileReader r){
super(r);
}
public String myReadLine()throws IOException{
count++;//每调用一次行号自增一次
super.myReadLine();
}
public void setLineNumber(int count){
this.count = count;
}
public int getLineNumber(){
return count;
}
//代码优化前
/*private FileReader r;
private int count;
MyLineNumberReader(FileReader r){
this.r = r;
}
public String myReadLine()throws IOException{
count++;//每调用一次行号自增一次
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(ch);
}
if(sb.length()!=0)
sb.toString();
return null;
}
public void setLineNumber(int count){
this.count = count;
}
public int getLineNumber(){
return count;
}
public void myClose()throws IOException{
r.close();
}*/
}
字节流
InputStream OutputStream
概述:
字节流和字符流的异同
首先,基本操作与字符流相同
但是它不仅可以操作字符,还可以操作其他媒体文件如图片,音乐格式,电影格式等。
字符流使用的是字符数组
字节流使用的是字节数组
字符流其实底层走的也是字节流,只是他是把数据临时存储起来去查表,所以有个刷新动作。
而我们之间使用字节流,没有使用具体指导缓冲区,就不用刷新。
字节流的读写操作:
在InputStream的子类FileInputStream中有一个available方法,即:返回下一次读取流估计的剩余数。
available()
好处:可以确定缓冲区的大小使定义大小刚刚好。就不用循环,非常方便
弊端:只能操作较小型数据,若超过如电影文件,会造成内存溢出。
所以根据具体情况来定义,一般使用1024 的整数倍。
示例:
import java.io.*;
class FileStream{
Public static void main(String[] args)throws IOException{
readFile_1();
readFile_2()
}
Public static void readFile_1()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(buy,o,len));
}
Fie.close();
}
Public static void readFile_2()throws IOException{
FileInputStream fis = new FileInputStream(“fos.txt”);
Byte[] buf = new byte[fis.available()];//流对象调用available方法确定数组长度
Fis.read(buy);
System.out.println(new String(buy));
Fie.close();
}
}
练习:拷贝一个图片文件
思路:
1, 用字节读取流对象和图片想关联
2, 用字节写入流对象创建一个图片文件,用于存储获取到的图片数据。
3, 通过循环读写,完成数据的存储。
4, 关闭流对象,关闭资源
import java.io.*;
class CopyDemo{
public static void main(String[] args) {
copy();
}
public static void copy(){
FileOutputStream fos = null;
FileInputStream fis = null;
try{
fos = new FileOutputStream("d:\\复制美女.jpg");
fis = new FileInputStream("c:\\美女.jpg");//关联文件对象
byte[]buf = new byte[1024];
int len = 0;
while ((len = fis.read(buf))!=-1){
fos.write(buf,0,len);
}
}
catch (Exception e){
throw new RuntimeException("图片拷贝异常");
}
finally{
try{
if(fos!=null)
fos.close();
}
catch (Exception e){
throw new RuntimeException("xie ruwen error");
}
try{
if(fis!=null)
fis.close();
}
catch (Exception e){
throw new RuntimeException("du qu error");
}
}
}
}
字节流的缓冲区
BufferedInputStream
BufferedOutputStream
练习:自定义字节流的缓冲区。
/*
自定义字节流读取缓冲区
*/
import java.io.*;
class MyBufferedInputStream{
private InputStream is;
private byte[] buf = new byte[1024];//定义字节数组即缓冲区
private int pos= 0,count = 0;
MyBufferedInputStream(InputStream is){
this.is = is;
}
public int myRead()throws IOException//一次读一个字节,从缓存区(字节数组)获取{
if(count==0){
count = is.read(buf);
//通过is对象读取硬盘上的数据并存储到buf数组里,返回实际字节数
if(count<0)
return-1;
pos = 0;//从新把指针归零
byte b = buf[pos];
count--;//在字节数组取一个就自减一个
pos++;//操作指针移位
return b&255;
//注意:因为字节数据底层二进制,读取时可能读到8个1,
//返回十进制就是-1,提升int类型后返回还是-1,二进制就是32个1
//怎么才能把8个1前面的1去除呢?那么只需要在前面补0即可。直接&255,返回int后就是正数。
//11111111 11111111 11111111 11111111
//00000000 00000000 00000000 11111111
//00000000 00000000 00000000 11111111
}
else if(count>0){
byte b = buf[pos];
count--;
pos++;
return b&255;
}
return -1;
}
public void myClose()throws IOException{
is.close();
}
}
class FileInputStreamDemo{
public static void main(String[] args)throws IOException{
MyBufferedInputStream mbis = //调用自定义的缓冲区
new MyBufferedInputStream(new FileInputStream("c:\\刘柏延-那年夏天.mp3"));
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("d:\\复制那年夏天.mp3"));
int buy = 0;
while ((buy = mbis.myRead())!=-1){
bos.write(buy);
}
mbis.myClose();
bos.close();
}
}
读取键盘录入
System.out:对应的是标准输出设备,控制台
System.in:对应的标准输入设备:键盘
查询API文档,System.in的返回类型是InputStream读取流。
示例:
/*
练习键盘录入,录入一行数据后,就将该行数据进行打印
如果录入的数据是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')
continue;
if(ch=='\n')
{//当读到回车符时就将临时存储里的数据转成字符串
String s = sb.toString();
if ("over".equals(s))//如果读到over程序停止
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length());//将临时存储里的数据清零
}
else//将键盘录入的数据添加进临时存储里。
sb.append((char)ch);
}
}
}
读取转换流
通过上面示例发现其实这就是读一行数据的原理,也就是readeLine方法。
那我们能直接用readLine方法来完成键盘录入的一行数据的读取吗?
分析:(转换流InputStreamReader)
ReadLine方法是字符流BufferedReader类中的方法,二键盘录入飞read方法是字节流InputStream的方法。那能不能将字节流转成字符流再使用字符流缓冲的readline方法呢?
在字符流里就是一个字节流通向字符流的桥梁:InputStreamReader。
在其构造方法摘要里他可以接收字节流对象。,那写代码就简单很多了。
示例:
//获取键盘录入对象
InputStream in= Ststem.in;
将字节流对象转成字符流对象,使用转换流InputStreamReader
InputStreamReader isr = new InputStreamReader(in);
为了提高效率,将字符串进行缓冲区高效操作,BufferedReader
BufferedReader br = new BufferedReader(isr);
String str = null;
While((str.br.readLine())!=null){
If(“over”.equals(str))
Break;
Sop(str);
}
Br.close();
写入转换流
同上对应,写入转换流:OutputStreamWriter
System.out返回类型是PrintStream,也可以让其父类OutputStream接收。
示例:
OutputStream out = System.out();
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);
String line = null;
While((line = bufr.readLine())!=null)
{
If(“over”.equals(line))
Break;
Bufw.write(line.toUpperCase());
Bufw.newLine();//换行
Bufw.flush();//刷新
}
Bufw.close();
流的操作规律
对于流对象有很多,什么时候该用什么呢?
通过三个明确来完成。
1, 明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer
2, 操作的数据是否纯文本
是:字符流。
不是:字节流。
3, 当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台。
示例1:
将一个文本文件中的数据存储到另一个文件中,复制文件
源:InputStream Reader。因为是源所以使用读取流。
是不是文本文件。
是,选择Reader
明确要使用该体系中的哪个对象。
设备:硬盘,上一个文件。
Reader体系中可以操作文件的对象是FileReader
是否需要提高效率?
是,加入Reader体系中缓冲区BufferedReader。
FileReader fr = new FileReader(“a.txt”);
BufferReader bufr= newBufferReader(fr);
目的:OutputStream Writer
是否是纯文本
是,选择Writer
设备:硬盘,一个文件。
Writer体系中可以操作文件的对象FileWriter
FileReader fw = new FileReader(“b.txt”);
是否需要提高效率?
是,加入Writer体系中缓冲区BufferedWriter。
BufferedWriter bufw = new BufferedWriter(fw);
示例2
需求: 将键盘录入的数据保存到一个文件中。
这个需求中有源和目的都存在。
分析:
源:InputStream Reader
是不是纯文本?是,Reader
设备:键盘,对象对象是System.in
为了操作键盘的文本数据方便,转成字符流按照字符串操作是最方便
Reader体系中转换流:InputStreamReader
需要提高效率吗?需要 BufferedReader
即:BufferedReader bufr= newBufferedReader(new InputStreamReader(System.in));
目的:
OutputStream Writer
是否是纯文本?是,Writer
设备:硬盘,一个文件。使用FileWriter。
需要提高效率吗? 需要
BufferedWriter bufw = new BufferedWriter(new FileWriter(“c.txt”));
扩展:
想要把录入的数据按照指定的编码表(utf-8),将数据存储到文件中
目的:
OutputStreamWriter
是否是纯文本?是,Writer
设备:硬盘,一个文件。使用FileWriter。
但是FileWriter是使用的默认编码表:GBK
在存储时需要加入指定编码表utf-8.
指定编码表只有转换流才可以指定
所以要使用对象是:OutputStreamWriter
该转换流对象要接收一个字节输出流,
而且还可以操作的文件的字节输出流:FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(“d.txt”),”UTF-8”);
需要提高效率吗?需要
BufferedWriter bufw newBufferedWriter(osw);
所以,请记住,转换流什么时候使用,字符和自己之间的桥梁通常涉及到字符编码转换时,需要用到转换流。
改变标准输入输出设备
在System类里面有关于改变标准输入输出设备的方法
即:setIn(InputStream in) setOut(PrintStream out)
示例:
System.setIn(new FileInputStream(“Demo.java”));
System.setOut(new PrintStream(“zzz.txt”));
相当于复制文件了
异常日志文件
系统信息打印。
File类
概述:
它是文件和目录路径名的抽象表现形式
作用:
1,用来将文件或者文件夹封装成对象
2,方便对文件与文件夹的属性信息进行操作。
3,File对象可以作为参数传递给流的构造函数
File类常用方法:
创建:
1, boolean createNewFile();在指定位置创建文件,如果该文件已经存在,则不创建返回false,和输出流不一样,输出流对象已建立就创建文件,而且文件已经存在,会覆盖
2, Boolean mkdir();创建一个文件夹
3, Boolean mkdirs();创建一个多级文件夹
删除:
1, boolean delete():删除失败返回false,若文件正被使用,则删除不了返回false
2, void deleteOnExit();在程序退出时删除指定文件。
判断:
1, boolean exists();判断文件是否存在
2, isFile();
3, isDirectory();
4, isHidden();是否是隐藏
5, isAbsolute();
注意:在判断文件对象是否是文件或者目的时,必须要先判断该文件对象封装的内容是否存在。通过exists
获取:
1, String getName();
2, String getPath();
3, String getparent();返回绝对路径中的父目录字符串,若是相对路径返回null
4, String getAbstolutePath();获取绝对路径
5, Long lastModified();最后一次修改时间
6, Long length()
文件列表:
String[] list();返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录.
操作文件列表常用方法:
1,Static File[] listRoots():列出可用文件系统的根。
示例:
File[] files = File.listRoots();
For (File f:files){
sop(f);
}
2, String[] list();返回一个字符串数组,是列出当前所有文件。
示例:
File f = new File(“c:\\”);
String[] names = f.list();//注意调用list方法的file对象必须是封装了一个目录。该目录还必须存在。否则报空指针异常。
For (String name : names){
Sop(name);
}
3, String[] list(FilenameFilter filter)//实现FilenameFilter接口的类可以用于过滤器文件名,里面有一个方法,accept(file dir,String name):即测试指定文件是否一个包含在某一文件列表中。因为是接口类型,里面的方法只有一个,所以使用匿名内部类最为方便。
示例:
File dir = new File(“d:\\java1223\\day18”);
String[] arr = dir.list(new FilenameFilter()
{使用匿名内部类复写其方法,判断是.bmp格式结尾的就输出。
Public Boolean accept(File dir,String name){
return name.endsWith(“.bmp”);
}});
列出目录下所以内容-递归
使用上面方法可以得到指导目录下的文件,但是目录中还有目录,怎么查看呢?
因为目录中还有目录,只要使用同一个处理目录功能的函数即可。
在列出过程中出现的还是目录的话,还可以再次调用本功能。也就是函数自身调用自身。
这种表现形式或者编程手法称为递归。
递归要注意:
1, 限定条件。
2, 注意递归次数,尽量避免内存溢出。
示例:
/*
列出目录下所有内容
*/
import java.io.*;
class FileDemo{
public static void main(String[] args) {
File file = new File("F:\\音乐2");
showDir(file);
}
public static void showDir(File file){
System.out.println(file);//
File[] files = file.listFiles();//返回该目录下的文件对象的数组
for(File name:files)
{//遍历循环取出所有文件对象,判断对象是否是目录,是,就递归,不是就打印
if(name.isDirectory())
showDir(name);
else
System.out.println(name);
}
}
}
递归练习二:
使列出目录所有内容带有层次
/*
列出目录下所以内容
*/
import java.io.*;
class FileDemo{
public static void main(String[] args) {
File file = new File("F:\\音乐2");
showDir(file,0);
}
public static String getLevel(int level){
StringBuilder sb = new StringBuilder();
sb.append("|_");
for (int x = 0;x
删除带内容的目录
原理:
在window中,删除目录从里面往外面删除的,既然是这样,就可以用递归来删除。
示例:
*
删除带内容的目录。
*/
import java.io.*;
class RemoveDir{
public static void main(String[] args) {
File file = new File("d:\\testDir");
removeDir(file);
}
public static void removeDir(File file){
File[] names = file.listFile();
for(File name:names){
if(name.isDirectory())//判断是否是目录
removeDir(name);//是目录就递归
else
System.out.println(name.getName()+"| "+name.delete())
}
//删除完目录里的文件后再删除目录文件
System.out.println(file+"| "+file.delete());
}
}
递归练习四:
将一个目录下的java文件的绝对路径,存储到一个文本文件中。
建立一个java文件列表。
/*
思路:
1,对指定的目录进行递归
2,获取递归过程所有的java文件的路径
3,将这些路径存储到集合中
4,将集合中的数据写入到一个文件中
*/
import java.io.*;
import java.util.*;
class JavaFileList{
public static void main(String[] args) {
File dir = new File("e:\\java02");
List list = new ArrayList();
fileToList(dir,list);//将目录里的数据存储进集合
File file = new File(dir,"javalist.txt");
writeToFile(list,file);//将集合中的数据写入到指定文件
}
public static void fileToList(File dir,List list){
File[] file = dir.listFiles();//把dir目录里的文件存储到数组里
for(File name : file){
if(name.isDirectory())//若是目录就递归
fileToList(name,list);
else{
if(name.getName().endsWith(".java"))//判断文件后缀名是否是.java文件
list.add(name);
}
}
}
public static void writeToFile(List list,File file){
BufferedWriter bufw = null;
try
{//将字符流写到指定文件中,这个文件可以自己指定创建,也可以将参数传入
bufw = new BufferedWriter(new FileWriter(file));
//将传入的集合里的数据遍历
for(File name:list)
{//获取文件的绝对路径,存储进缓冲区,换行,刷新
String path = name.getAbsolutePath();
bufw.write(path);
bufw.newLine();
bufw.flush();
}
}
catch (IOException e){
throw new RuntimeException(" ");
}
finally{
try{
if(bufw!=null)
bufw.close();
}
catch (IOException e){
System.out.println("流对象关闭失败。");
}
}
}
}
Properties
概述:
Properties是hashtable的子类
也就是说他具备map集合的特点,而且他里面存储的键值对都是字符串,不需要泛型。
是集合里面和IO技术相结合的集合容器。
该对象特点:可以用于键值对形式的配置文件
在加载数据前,数据必须有固定的格式即:键=值
常见特有方法:
设置与获取元素
Objetc setProperty(String key,String value);//设置键值对,添加键值对
String getProperty(String key);//指定键在属性列表中收索所对应的属性
String getProperty(String key,String defaultValue);
Set
示例:
Properties pro = new Properties();
Pro.setProperty(“zhangsan”,”22”);//添加键值对
Pro.setProperty(“lisi”,”23”);
Pro.setProperty(“zhangsan”,”33”);//对已添加的进行从新设置。
String value = pro.getProperty(“zhangsan”);//通过键获取其属性。
Set
For(String s : names){
Sop(name);
}
Properties存取配置文件
演示:如何将流中的数据存储到集合中。
如,要将info.txt中的键值数据存储到集合进行操作
步骤:
1, 用一个流和info文件关联
2, 读取一行数据,将该行数据用“=”进行切割返回字符串数组。
3, 因为数组里两个元素,所以将第一个元素作为键,第二个作为值,存入到Properties集合里面即可。
Public static void method_1()throws IOException{
BufferedReader bufr = new BufferedReader(new FileReader(“info.txt”));关联文件对象
String line = null;
Properties pro = new Properties();创建Properties对象
While((line = bufr.readLine())!=null){
String[] str = line.split(“=”);进行切割
pro.setProperty(str[0],str[1]);将切割后的数据以键值对形式存储
}
Bufr.close();
sop(pro);
}
因为上面方法比较麻烦所以java提供了load方法,直接加入字节流字符流都行。
早期都是加入字节流,1.6版本后就可以加入字符流。
void load(InputStream inStream)//从输出流中读取属性列表(键和元素对)
void load(Reader reader)//按简单的面向行的格式从输入字符流中读取属性列表
示例:
Public static void loadDemo()
{
Properties prop = new Properties();
FileInputStream is = new FileInputStream(“info.txt”);
Prop.load(is);
Prop.list(System.out);
}
回顾:
Void list(PrintStream out)将属性列表输出到指定的输出流
Void list(PrintWriter out)将属性列表输出到指定的输出流
void store(OutputStream out,String comments) 将此 Properties 表中的属性列表(键和元素对)写入输出流,
因为若有修改键对应的属性信息,setProperty()方法只是将内存里的修改,在硬盘或者文件中也完成修改就要用到store
如何创建配置文件使用配置文件?
Properties练习
用于记录应用程序运行次数
如果使用次数已到,那么给出注册提示
分析:
定义计数器,可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。
可是随着应用程序的结束,该计数器也在内存中消失。
下一次再启动程序有要从新开始计数。
我们需要即使程序结束,该计数器值也存在,下一次程序启动再会先加载计数器并自增后从新存储。所以需要建立一个配置文件用于记录该软件使用次数。
该配置文件使用的是键值对的形式
这样便于阅读数据并操作数据。
键值对是map集合
数据是以文本形式存储,使用io技术
MAP+io àProperties
配置文件可以实现应用程序数据的共享
/*
Properties练习
用于记录应用程序运行次数
如果使用次数已到,那么给出注册提示
*/
import java.util.*;
import java.io.*;
class PropertiesDemo{
public static void main(String[] args)throws IOException {
getNum();
}
public static void getNum()throws IOException{
//创建一个Properties对象
Properties pro = new Properties();
//创建一个配置文件
File file = new File("count.Properties");
//判断文件是否存在
if(!file.exists())
file.createNewFile();
//创建一个文件读取流对象
FileInputStream fis = new FileInputStream(file);
pro.load(fis);//将流对象添加进配置文件对象
int count = 0;
//根据键取出属性信息
String value = pro.getProperty("time");
if (value!=null){
count = Integer.parseInt(value);
if (count >5) {
System.out.println("您的使用次数已到,请付费");
return;
}
count++;//使用一次计数器自增一次
}//将自增后的数据从新写入
pro.setProperty("time",count+"");
//通过关联写入流对象,将配置文件里的信息长久存储
FileOutputStream fos = new FileOutputStream(file);
pro.store(fos,"有效次数");
fos.close();
fis.close();
}
}
IO包中的其他类
打印流
PrintWriter与PrintStream可以直接操作输入流和文件
序列流
SequenceInputStreamà对多个流进行合并
操作对象
ObiectInputStream与ObjectOutputStreamà被操作的对象需要实现Serializable(标记接口)
具体:
打印流:
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
字节打印流:
PrintStream构造函数可以接收的参数类型:
1, file对象。File
2, 字符串路径,String
3, 字节输出流,OutputStream
PrintWriter构造函数可以接收的参数类型:
1, file对象。File
2, 字符串路径,String
3, 字节输出流,OutputStream
4,字符输出流,Writer
示例:
import java.io.*;
class PrintWriterDemo {
public static void main(String[] args) throws IOException{
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));//键盘录入
//当时传入参数是流对象,就可以用true刷新流
PrintWriter pw =
new PrintWriter(new FileOutputStream("1.txt"),true);
String line = null;
while((line =br.readLine())!=null){
if("over".equals(line))
break;
pw.println(line.toUpperCase());
//pw.flush();
}
pw.close();
br.close();
}
}
序列流-合并流
概述
SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
构造方法
SequenceInputStream(Enumeration e)
通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。
示例:
对音乐文件的切割和再次合并练习
分析:
1, 关联读取文件对象
2, 在临时数组那里每读取一定的数据就封装成文件,并将剩余的依次封装成文件。
3, 将切割的文件关联,存储进集合里面,
4, 通过SequenceInputStream,将数据封装成一个流对象。
5, 将流对象写入到指定文件中。
import java.io.*;
import java.util.*;
class SequenceInputStreamDemo2{
public static void main(String[] args) throws IOException{
//splitFile();
merge();
}
public static void merge()throws IOException//将切割的音乐文件合并
{//获取源文件,将碎片文件添加进集合
ArrayList arr = new ArrayList();
for (int x = 1;x<=3 ;x++ ){
arr.add(new FileInputStream("c:\\"+x+".part"));
}
final Iteratorit = arr.iterator();
//因为SquenceInputStream接收的参数是Enumeration类型,
//但是Vector较低效,所以复写Enumeration里的方法
Enumeration en = new Enumeration(){//匿名内部类
public boolean hasMoreElements(){//复写方法
return it.hasNext();
}
public FileInputStream nextElement(){//复习方法
return it.next();
}
};
//将几个流的数据合并成一个流对象,再将其写入文件即可
SequenceInputStream sis = new SequenceInputStream(en);//en参数是Enumeration类型
FileOutputStream fos = new FileOutputStream("c:\\合并别离开.mp3");
byte[] buy = new byte[1024];
int len = 0;
while ((len = sis.read(buy))!=-1){
fos.write(buy,0,len);
}
fos.close();
sis.close();
}
public static void splitFile()throws IOException//将音乐文件切割{
//关联某个文件
FileInputStream file = new FileInputStream("c:\\别离开.mp3");
FileOutputStream out = null;
int len = 0;
byte[] buy = new byte[1024*1024];//定义1M数据临时存储
int num = 1;
while ((len = file.read(buy))!=-1)
{//每读取音乐文件1M就创建一个文件将这个数据写入
out = new FileOutputStream("c:\\"+(num++)+".part");
out.write(buy,0,len);
out.close();
}
file.close();
}
}结果如图:
操作对象-对象序列化
ObjectInputStream与ObjectOutputStream
被操作的对象需要实现Serializable
概述:
序列化是干什么的?
简单的说就是把堆内存中各种对象和其状态,保存到硬盘上,并且可以使用相关的操作读取保存的数据。把Java对象转换为字节序列的过程称为对象的序列化;
什么时候需要序列化?
1, 当你想把内存中的对象保存到一个文件中或者数据库中时候
2, 当想用套接字在网络上传对象时候
3, 当想通过RMI传输对象时候
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
当一个对象实现序列化时,过程是什么?
在没有序列化前,每个保存在堆内存中的对象都有相应状态,即实例变量
如:
Person p = new Person();
p.setName(“lisi”);
p.setAge(“22”);
将代码序列化后,把p对象中的name和age的值保存到指定文件中,这样以后也可以通过ObjectInputStream将其从文件中读取,重新在堆内存中创建原来的对象。注意,实际上在保存对象实例变量的值时,jvm还保存了一些小量信息,如类的类型一遍恢复原来对象。
如:
常用的方法:
writeObject//负责写入特定类的对象的状态,以便相应的 readObject 方法可以恢复它
readObject//负责从流中读取并恢复类字段
FileOutputStream fos = new FileOutputStream(“person.object”);//将要序列化的对象保存到指定文件中。
ObjetcOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(p);//将Person对象写入保存。
注意:
要序列化的对象必须是实现了Serializable,形象的说就是给该对象打上标签说明是可序列化的。如果没有实现,就会报异常NotSerializableException。
内部原理
就是序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类
示例:
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"));//指定关联的文件
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
public static void writeObj()throws IOException{
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("obj.txt"));//指定要存储的目的源
oos.writeObject(new Person("lisi0",399,"kr"));
oos.close();
}
}
相关注意事项
1, 当一个父类实现序列化,子类自动实现序列化
2, 当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化
管道流
PipedInputStream和PipedOutputStream
概述:
输入和输出可以直接进行,通过结合线程使用。
管道输入流应该连接到管道输出流;
管道输入流提供要写入管道输出流的所有数据字节。
管道输出流是管道的发送端。
通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。 如果向连接管道输出流提供数据字节的线程不再存在,则认为该管道已损坏。
示例:
/*
管道输入流和管道输出流
PipedInputStream和PipedOutputStream
*/
import java.io.*;
import java.util.*;
class Read implements Runnable{
private PipedInputStream pis;
Read(PipedInputStream pis){
this.pis = pis;
}
public void run(){
try {
byte[] buy = new byte[1024];
int len=0;
while ((len =pis.read(buy))!=-1){
System.out.println(new String(buy,0,len));
}
pis.close();
}
catch (IOException e){
throw new RuntimeException("管道读取流异常");
}
}
}
class Write implements Runnable{
private PipedOutputStream pos;
Write(PipedOutputStream pos){
this.pos = pos;
}
public void run(){
try{
pos.write("管道流daying".getBytes());
pos.close();
}
catch (IOException e){
throw new RuntimeException("管道xieru流异常");
}
}
}
class PipedLiuDemo{
public static void main(String[] args) throws IOException{
//创建管道流对象
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
pis.connect(pos);//将管道输入流连接到输出流
new Thread(new Read(pis)).start();//创建线程并开启
new Thread(new Write(pos)).start();
}
}
类RandomAccessFile
概述
该类是继承Object
此类的实例支持对随机访问文件的读取和写入。
该类不算是IO体系中的子类,而是直接继承Object
但是它是IO包中的成员,因为它具备读和写功能。
它内部封装了一个数组,而且通过指针对数组的元素进行操作,可以通过getFilePointer获取指针位置。同时可以通过seek改变的位置。
其实完成读写的原理就是里面封装了字节读输入和输出流。
通过构造函数可以了解只能操作文件。
而且操作文件还有模式:
"r" |
以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。 |
"rw" |
打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。 |
"rws" |
打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。 |
"rwd" |
打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。 |
常见方法的重点注意事项:
1,write(int b)向文件写入最低八位的字节,如果超过八位,读取就会损失精度。
如10000001,编译出来是1,所以不正确
writeInt(int v)想文件写入32的字节。
2,readInt()从文件中读取一个有符号的32位整数。
read()是从文件中读取一个数据的字节。
3,skipbytes(int n)跳过指定的字节数。比如需要从第八个开始读取。注意该方法只能望大数跳,不能返向
4,seek(long pos)调整对象中的指针。从指针的下一个位置开始读取数据。
该方法可以灵活运用:
可以进行分段,
对数据位置进行修改
示例:
//创建对象并关联文件,定义模式rw,
RandomAccessFile raf = new RandomAccessFile(“ran.txt”,”rw”);
//调整指针位置
Raf.seek(8*0)//从8*0指针开始读取或者写入
Raf.write(“王五”.getBytes());//该方法传入参数是字节类型
Raf.writeInt(111);当大于255,即大于二进制8位,容易损失精度,所用该方法是32位的。
//创建对象并关联文件,定义模式r,
RandomAccessFile raf2 = new RandomAccessFile(“ran.txt”,r);
//当我们需要跳过一些数据读后面的数据时,有两种方法
一个是:raf.seek(8*0);
一个是:raf.skipBytes(0);
//根据名字和年龄占用字节可以固定缓冲区来存取;如名字一般两个到四个,最大占用字节8,年龄最大占用6,所以先把8字节的姓名获取,再根据指针或者后面的6字节年龄
byte[] buy = new byte[8];
raf.read(buy);
String name = new String(buy);
Int age = raf.readInt();
Sop(name+””+age);
操作基本数据类型的流对象DataStream
DataInputStream与DataOutpuStream
示例:
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);
dos.writeBoolean(true);
dos.writeDouble(9887.543);
dos.close();
ObjectOutputStream oos = null;
oos.writeObject(new O());
}
}
扩展:
void |
writeUTF(String str) |
该方法与转换流之间的区别
用该方法写入的就必须用相对应的方法读取,不能用转换流里面的读写来操作。因为这个格式与标准的UTD-8格式有所变化。有些占位字节数不同,所以当我们用不同的编码编写,相同数据,但是占字节数不同。
操作字节数组
ByteArrayInputStream和ByteArrayOutputStream
ByteArrayInputStream:在构造的时候需要接受数据源,而且数据源是一个字节数组
ByteArrayOutputStream:在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。
因为这两个流对象都操作的数组,并没有使用系统资源,所以不用进行close关闭。
在流操作规律讲解时:
源设备:
键盘 System.in,硬盘 FileStream,内存 ArrayStream
目的设备:
控制台 System.out,硬盘FileStream,内存ArrayStream
实际上我们可以用流读写的思想来操作数组。
示例:
import java.io.*;
class ByteArrayInputStreamDemo{
public static void main(String[] args) {
//数据源,里面参数是一个字节数组
ByteArrayInputStream bais =
new ByteArrayInputStream("qwertyuio".getBytes());
//数据目的
ByteArrayOutputStream baos =
new ByteArrayOutputStream();
int buy = 0;
while ((buy= bais.read())!=-1){
baos.write(buy);//读一个数据就写入一个
}
System.out.println(baos.size());//获取长度
System.out.println(baos.toString());//返回字符串形式
}
}打印结果如图
操作字符数组
CharArrayReader与CharArrayWrite
操作字符串
StringReader与StringWriter
这两个操作方法和操作字节数组方法是一样类似的,创建一个源,再创建一个目的,将源里面的数据读取再写入到目的里面。
字符编码
作用:
字符流的出现是为了方便操作字符。
跟重要的是加入了编码转换。
通过子类转换流来完成即:
InputStreamReader
OutputStreamWriter
在两个对象进行构造的时候可以加入字符集,即编码表。
编码表的由来:
因为计算机只能识别二进制数据,早期由来是电信号。
为了方便应用计算机,让他可以识别各个国家的文字,就将各个国家的文字用数组来标示,并一一对应形成一张表。
这就是编码表。
常见编码表
ASCII:美国标准信息交换码
用一个字节的7位可以标示。
ISO8859-1:拉丁码表,欧洲码表
用用一个字节的8位标示。
GB2312:中国的中文编码表。
GBK:中国的中文编码表升级,融合了更多的中文文字字符号。
Unicode:国际标准码,融合了多种文字。、
所以文字都用两个字节来标示,Java语言使用的是nuicode
UTF-8:最多用三个字节来标示一个字符。
什么是编码解码?怎么用?
形象的说就像发电报一样,当发报员发出信息就是在编码,接收员在接收信息并对应查找,就是解码。
编码:字符串编程字节数组。
解码:字节数组变成字符串。
String byte[]; str.getBytes()//默认的是GBK编码表。
String byte[]; str.getBytes(charsetName)//指定编码表编码
byte[] String;new String(byte[],charsetName);//
在编码时,你用什么码表编的,就用神码表来解码。
示例:
如果没有用对码表解码。
import java.util.*;
class EncodeDemo{
public static void main(String[] args) throws Exception{
String s = "你好";
byte[] b1 = s.getBytes("GBK");//指定GBK编码表编译
System.out.println(Arrays.toString(b1));
String s1 = new String(b1,"ISO8859-1");//没用想对应的码表解码
System.out.println(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);
}
}过程如图示例:
练习:
/*
有五个学生,每个学生有3门课程的成绩。
从键盘输入以上数据(包括姓名,三门课成绩)
输入格式:张三,30,40,30,总成绩
把学生的信息和计算出的总分数按高低顺序存储到硬盘“stud.txt”
步骤:
描述学生对象。
定义一个可以操作学生对象的工具类
1,通过获取键盘录入一行数据,并将行中的信息取出封装成学生对象。
2,因为学生有很多,那么就需要存储,使用到集合,因为要对学生的总分排序。
3,将集合的信息写入到一个文件中。
*/
import java.util.*;
import java.io.*;
class Students implements Comparable{//实现Comparable接口
private String name;
private int yw,sx,yy;
private int num ;
Students(String name,int yw,int sx,int yy){
this.name = name;
this.yw = yw;
this.sx = sx;
this.yy = yy;
num = yw+sx+yy;
}
public int compareTo(Students s){//复写方法
int n = new Integer(this.num).compareTo(new Integer(s.num));
if(n==0)//先比较总分,若相同再按照姓名的自然顺序排序
return this.name.compareTo(s.name);
return n;
}
public int hasCode(){
return name.hashCode()+num*77;//保证元素的唯一性。
}
public boolean equals(Object obj){
//若传进来的不是指定类型就报异常
if(!(obj instanceof Students))
throw new ClassCastException("输入类型异常");
Students stu = (Students)obj;
return this.name.equals(stu.name)&&this.num==stu.num;
}
public String getName(){
return name;
}
public int getNum(){
return num;
}
public String toString(){
return "姓名:"+name+",成绩排名:【语文 "+yw+"数学 "+sx+"英语 "+yy+"】总分:"+num;
}
}
//定义学生工具类
class StudentsTool{
public static TreeSet getStudent()throws Exception{
return getStudent(null);//默认的排序方式。
}
public static TreeSet getStudent(Comparator cmp)throws Exception{
//获取键盘录入。
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in));
String str = null;
TreeSetts = null;//定义要将数据存储进的容器
if(cmp==null)
ts = new TreeSet();
else
ts = new TreeSet(cmp);
while ((str = br.readLine())!=null){
if("over".equals(str))
break;
String[] s = str.split(",");//将传进来的数据切割变成字符串数组。
Students stu = new Students(s[0],Integer.parseInt(s[1]),Integer.parseInt(s[2]),Integer.parseInt(s[3]));
ts.add(stu);//每从键盘读取一个对象就往集合里面存储一个学生对象
}
br.close();
return ts;
}
public static void writeFile(TreeSet ts)throws Exception{
BufferedWriter bufw =
new BufferedWriter(new FileWriter("Stu.txt"));
int n = 0;
for (Students s:ts){
bufw.write(s.toString());//将集合里的数据写到文本文件中。
bufw.newLine();
bufw.flush();
}
bufw.close();
}
}
class StudentsInFoTest {
public static void main(String[] args) throws Exception{
Comparator cmp = Collections.reverseOrder();//将比较器反转
TreeSet ss = StudentsTool.getStudent(cmp);
StudentsTool.writeFile(ss);
}
}打印结果如图: