IO流字符流的读写
因为不断向上抽取,所以功能变得不具体了,很抽象,形成了抽象类。所以要看父类,一看就知道是做什么用的。用子类,子类有很多具体的功能。
import java.io.FileWriter; import java.io.IOException; public class FileWriterDemo { private static final String LINE_SEPARATOR = System.getProperty("line.separator");//解决不同操作平台写入换行问题 public static void main(String[] args) throws IOException { FileWriter fileWriter = new FileWriter("abc.txt",true);//创建FileWriter对象,构造函数填写目的地,true代表可以续写,不覆盖 fileWriter.write("456" + LINE_SEPARATOR + "789");//写到缓存区中一个字符串 fileWriter.flush();//刷出缓冲区,写到硬盘中 fileWriter.close();//关闭,关闭前会调用一次flush方法。因为写的功能调用的是windows的系统资源,所以用完了要关了,但关了再写就不能写了 //想象一下记事本 //write就像写了后没保存,断电后就没了 //flush就相当于保存操作,把你写到内存的东西保存到了硬盘 //close就相当于关闭了记事本,但是关闭时候提示了你保存(关闭前保存) } }
执行完成后,看一下
写完事了,在玩一下读。读的话用的哪个类,猜也能猜的差不多。FileReader
首先是一个字符一个字符读,这个比较简单,就是读到了就返回,都不到就返回-1
public class FileReaderDemo { public static void main(String[] args) throws IOException { FileReader fileReader = new FileReader("abc.txt");//读这个文件 int c;//用于接收单个字符 while((c = fileReader.read()) != -1)//如果当前读到的字符不是-1,就一直读。-1是规定的没读到的标识 { System.out.print((char)c);//读到后转一下显示 } } }
一个一个读不爽?一堆一堆读!
这是我们要读取的文件
这是读的源码
FileReader fileReader = new FileReader("abc.txt");//读这个文件 char[] c = new char[3]; int len; //一次读满数组,如果读到了,返回读了几个,如果没读到返回-1 while((len = fileReader.read(c)) != -1) { //输出每次读到的数组转换成字符串 System.out.print(new String(c,0,len));//new String(char[] c,start,length)重新构造一个字符串,start作为开始索引,length是要获取几个 }
看一下过程,首先我们声明了一个数组c,利用fileReader.read(c)读取。
第一次就把数组读满了,结果为123,返回的是读到的个数,是3
第二次在读的时候,读到了456,也是读满了数组
第三次读的时候,读到了换行符,可以看到在windows下,换行符是\r\n(这不是重点。。)
以此类推还是依此类推?不管了,反正再往下读,就是89J,最后一次读到QK的时候,发现读不满数组了,读到的长度为2,数组的前两个位置被替换变成了QK,最后一个元素由于没有读到被替换,所以还是上回读到的值
最后一次读不到了,返回读到的长度为-1,不进循环,程序结束!
读写都会了后,来个小practice试一试。拷贝D盘的abc.txt到E盘
public class CopyFileDemo { public static void main(String[] args) throws IOException { //拷贝文件的原理就是先读在写 FileReader fileReader = new FileReader("D:/abc.txt"); FileWriter fileWriter = new FileWriter("E:/abc.txt"); char[] c = new char[3]; int len; while((len = fileReader.read(c)) != -1) { fileWriter.write(c,0,len);//每次读几个就写几个 } fileWriter.flush(); fileWriter.close(); fileReader.close(); } }
--------------------------------------------------------------------------
缓冲区
缓冲区怎么理解呢?就比如你吃瓜子,毛嗑,向日葵,其实是一个东西。你拨一个吃一个肯定不爽,你有空就拨出来,然后放在一个盒子里面,想吃的时候直接拿,就非常海皮。在比如说,你去超市买东西,你推了一个购物车,买一个扔进去一个,到结账的时候,你还得一个一个拿出来,不如在购物车里面加个框,最后把筐给它就完事了(学到没有?)。计算机里面也是,读一次写一次效率肯定不高,可以先读到一个缓冲区里面,然后最后缓冲区装满了,一次性把缓冲区的内容写完,这样效率就得到了提高。缓冲区的出现用于提高流的效率。
BufferedWriter
public static void main(String[] args) throws IOException { FileWriter fileWriter = new FileWriter("abc.txt"); BufferedWriter bw = new BufferedWriter(fileWriter);//缓冲区只是作为缓冲对象,真正的写操作还是FileWriter来操作的 bw.write("123");//写入数据到缓冲区 bw.flush();//刷到硬盘 bw.close();//bw关闭,底层关闭的是fileWriter }
BufferedReader
这个方法有个readLine()方法,可以一次读一行,如果读不到就返回null,你也可以选择一个一个读,也是用read方法,只不过重写了父类的read方法。为什么要重写呢?这就需要分析一下原理了。
public static void main(String[] args) throws IOException { FileReader reader = new FileReader("abc.txt"); BufferedReader br = new BufferedReader(reader); String str = null; while((str = br.readLine()) != null) { System.out.print(str); } }
BufferedReader中read()方法实现原理
public class MyBufferedReader { FileReader fileReader; public MyBufferedReader(FileReader fileReader)////构造函数传递需要被缓冲的对象 { this.fileReader = fileReader; } char[] cArr = new char[1024];//缓冲区 int count;//缓冲区里面元素的数量 int pos;//用于获取缓冲区的角标 //自定义read方法 public int read() throws IOException { //筐里面没有馒头,去厨房取 if(count == 0) { count = fileReader.read(cArr);//如果缓冲区没有了,就去拿 pos = 0;//重新获取之后,要把角标重新设置为0 } //厨房也没有馒头了 //如果没有数据,拿不到了,返回-1 if(count < 0) { return -1; } //在筐里拿馒头吃 char c = cArr[pos++];//每次自增变量获取 count--;//缓冲区数量减少 return c; } //自定义readLine方法 public String readLine() throws IOException { StringBuffer sb = new StringBuffer(); //判断/r/n int c; while((c = this.read()) != -1)//循环读 { if(c == '\r')//如果是/r就跳过去,不追加到字符串中 { continue; } if(c == '\n')//如果是/n,就代表读到了换行符,返回一行的字符串 { return sb.toString(); } sb.append(c);//日常追加字符串 } return sb.toString(); } }
字符流说完了,总结一下把,首先要了解字符流的两个抽象父类,Writer和Reader,下面有一些子类,其中FileReader是读文件用的(输入),常用的方法有int read(),int read(char)。FileWriter是写文件用的(输出),常用的方法有writer(); flush(); close(); 还有给这两位缓冲用的对象,BufferedWriter和BufferedReader,BufferWriter不用多说了,BufferWriter常用的方法有int read();和 String readLine();这两个方法都是重写过的。还有一个LineNumberReader对象没有说,就是读到后加行号,一般用不上,非常简单,自己看下就行了。
-----------------------------------------------------------------------------------------------------
字节流
接下来看字节流,字节流相比字符流就比较万能了,但是对于处理文本来说还是要优先考虑字符流的。可以说字节流能处理任何类型的文件,比如.txt的,jgp的,mp3,mp4,mp5的,mp6有嘛?avi视频文件等等。
InputStream -> FileInputStream,用字节流玩一玩文件,见名知意,写文件用的。
public class FileInputStreamDemo { public static void main(String[] args) throws IOException { FileInputStream fis = new FileInputStream("abc.txt"); //一个一个读 int i; while((i = fis.read()) != -1) { System.out.print((char)i); } //一堆一堆读 byte[] c = new byte[1024]; int len; while((len = fis.read(c)) != -1) { System.out.print(new String(c,0,len)); } //读小文件用,慎用 byte[] c2 = new byte[fis.available()];//fis.available文件字节数 int len2 = fis.read(c2); System.out.print(new String(c2,0,len2)); } }
和字符流都差不多,只不过接受用的是byte字节,还多了一个fis.available()方法,大批量读不建议使用。
读完事了,来一个写。用到了FileOutputStream
public class FileOutputStreamDemo { public static void main(String[] args) throws IOException { FileOutputStream fos = new FileOutputStream("abc.txt"); fos.write("123456".getBytes()); fos.flush();//字节流不需要调用flush方法,不需要先写到缓冲区,直接和文件打交道。可以看到继承了父类的flush,并没有重写, fos.close();//关闭还是要调用的,子类也进行了重写 } }
都是和字符流用法差不多,非常简单。
来一个拷贝mp3的练习,顺便用一下缓冲区,和字符流也差不多,不过多解释。
public class CopyMP3Demo { public static void main(String[] args) throws IOException { FileInputStream is = new FileInputStream("River flows in you.mp3"); BufferedInputStream bis = new BufferedInputStream(is); FileOutputStream os = new FileOutputStream("abc.mp3"); BufferedOutputStream bos = new BufferedOutputStream(os); byte[] c = new byte[1024]; int len; while((len = bis.read(c)) != -1) { bos.write(c,0,len); bos.flush(); } bos.close(); bis.close(); } }
----------------------------------------------------------------------------------------------------------------------------
转换流
需求:让用户输入一段话,按回车结束。如果不是over就输出,是over结束程序。
如果不用转换流,InputStream不存在字符流中的readLine()方法,所以自己写要判断换行符,非常麻烦。这时候就需要将字节流转换成字符流解码
InputStreamReader
InputStream is = System.in; InputStreamReader isr = new InputStreamReader(is);//将字节流转换成字符流,放入一个字节流 BufferedReader br = new BufferedReader(isr);//缓冲区高效读取 String line; while((line = br.readLine()) != null) { if(line.equals("over")) break; System.out.print(line.toString()); }
OutputStreamWriter编码
----------------------------------------------------------------------
学了这么多流对象?到底怎么实战?
1.首先明确源和目的地
源:InputStream Reader
目的地:OutputStream Writer
2.明确数据是否是纯文本
源
|--是,Reader
|--否,InputStream
目的地:
|--是,Writer
|--否,OutputStream
3.明确具体的设备
源设备:硬盘File,键盘System.in,内存Array,网络Socket
目的地设备:硬盘File,控制台System.out,内存Array,网络Socket
4.是否需要额外功能
高效缓冲区,转换流
-----------------------------------------------------------------------------------------------------------------
明确了这四点之后,来看几个需求
1.复制一个文本文件
首先是纯文本,选择Reader和Writer,具体设备是硬盘,选择FileReader和FileWriter,再看是否需要额外功能,需要缓冲区高效读写,那么选择BufferedReader和BufferedWriter
2.读取键盘录入的数据写到硬盘中
首先也是纯文本,选择Reader和Writer,具体设备是控制台System.in和硬盘File,选择InputStream is = System.in;
和FileWriter,需要额外功能吗?考虑读取到的数据需要转换成字符会更好操作,所以选择转换流,字节转换成字符选用
InputStreamReader,需要高效吗?需要!
那就BufferedReader br = new BufferedReader(new InputStreamReader(System));
BufferedWriter bw = new BufferedWriter(new FileWriter("abc.txt"));
3.将一个文本上的数据显示在控制台上
首先是纯文本,选择Reader和Writer,具体设备是硬盘和控制台,选择FileReader和OutputStream os = System.out;
需要额外功能嘛?需要转换流,OutputStreamWriter(System.out),需要高效嘛?需要!
BufferedReader br = new BufferedReader(new FileReader("abc.txt"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
4.将控制台输入的文本,输出在控制台上
纯文本,Reader和Writer,具体设备控制台,选择InputStream is = System.in;和OutputStream os = System.out;
需要额外功能?转换和高效
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
ok,只要明确了对象,使用的话就比较简单了。
---------------------------------------------------------------------------------------------------
到这里,IO的基本对象就学的差不多了
字节流FileInputStream,FileOutputStream,BufferedInputStream,BufferedOutputStream
字符流FileReader,FileWriter,InputStreamReader,OutputStreamWriter,BufferedReader,BufferedWriter
但是这些对象都是操作的数据,操作不了属性。比如获取文件名称,文件大小,操作文件夹等等。
这时候就出现了File类
File类将文件和文件夹封装成了对象,更方便的操作文件和文件夹。没有什么技术含量,就写API的Demo了
//-----------------File构造的几种方式--------------- File f1 = new File("abc.mp3"); File f2 = new File("c:\\","abc.txt"); File f = new File("c:\\"); File f3 = new File(f,"abc.txt"); File f4 = new File("c:" + File.separator + "abc.txt"); //----------------File常用获取------------------ String name = f1.getName();//获取文件名称 String path = f1.getPath();//获取相对路径 String absPath = f1.getAbsolutePath();//获取绝对路径 long len = f1.length();//获取长度 long time = f1.lastModified();//获取最后修改时间 //看上去爽一点的 最后修改时间 Date date = new Date(time); DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG); String timeStr = df.format(date); System.out.println(name); System.out.println(path); System.out.println(absPath); System.out.println(len); System.out.println(time); System.out.println(timeStr); //-----------------------创建与删除-------------------------- //和输出流不同的是,如果没有就创建,有就不做操作 File f5 = new File("abc.txt"); boolean b = f5.createNewFile();//创建一个新的文件 File f6 = new File("abc"); boolean b2 = f6.mkdir();//创建文件夹 File f7 = new File("abc\\def"); boolean b3 = f7.mkdirs();//创建多个文件夹 File f8 = new File("abc\\def"); boolean b4 = f8.delete();//删除,如果文件夹里面有内容,是不可以删除的 f8.deleteOnExit();//在程序退出后删除,比如IO流读取完文件,把文件删除 //----------------------------判断-------------------------------- File f9 = new File("abc.txt"); boolean bb = f9.exists();//判断文件或文件夹之前一定要判断文件是否存在 boolean b5 = f9.isDirectory(); boolean b6 = f9.isFile(); //----------------------------替换--------------------------------- File f10 = new File("123.txt"); f10.renameTo(new File("456.txt")); //---------------------系统根目录获取容量---------------------------- File[] files = File.listRoots();//获取所有盘符 for(File fx : files) { System.out.print(fx); } File fJ = new File("c:\\"); System.out.println(fJ.getFreeSpace());//可用 System.out.println(fJ.getTotalSpace());//总共 System.out.println(fJ.getUsableSpace());//已用 //--------------------------获取目录内容------------------------------------ File fQ = new File("c:\\"); String[] list = fQ.list(); for(String s : list) { System.out.println(s); } //--------------------------------过滤目录内容----------------------------------- //找到所有.java的文件 File fK = new File("c;\\"); fK.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".java"); } });
深度遍历文件夹 -- 递归思想
使用递归时,要注意两点
1.结束条件
2.递归次数,小心内存溢出(光压栈不弹栈)
public static void main(String[] args) { depthDir(new File("d:\\123"),0); } public static void depthDir(File f,int level) { System.out.println(printSpace(level) + f.getName()); level++; File[] files = f.listFiles(); for(int x = 0; x < files.length; x++) { //如果当前是文件夹,就在进去遍历 if(files[x].isDirectory()) { depthDir(files[x],level); } else { System.out.println(printSpace(level)+ files[x].getName()); } } } private static String printSpace(int level) { StringBuffer sb = new StringBuffer(); for(int x = 0; x < level; x++) { sb.append(" "); } return sb.toString(); }
遍历删除文件夹,Windows下删除文件只能从里往外删
public static void main(String[] args) { File f = new File("d:\\123"); deleteDir(f); } private static void deleteDir(File f) { //遍历,如果不是文件夹就删除,是就继续进去,等到进到最后一个了开始删除 File[] files = f.listFiles(); for(int x = 0; x < files.length; x++) { if(files[x].isDirectory()) { deleteDir(files[x]); } else { files[x].delete(); } } f.delete(); }