File类:
java要对文件(这里文件,是指文件名与路径)进行处理,首先需要做的是如何找到该对应的文件,java通过File类来创建需要对应文件的实例。
File类的构造方法:
File(Stinr path):一般我们创建一个File,是根据路径字符串来创建。该路径可以指向文件或者是目录,也可以是不存在的路径。当然还有其他的构造方法,不过至今我都没有用到。
File类有许多的方法,最常用的方法如下:
boolean File.exists():判断文件是否存在,存在返回true,不存在访问false;
File.createNewFile(): 创建一个与path指向文件的文件名
File.mkdir()以及File.mkdirs():创建一个与path指向目录的目录
Stirng File.list(): 列出File对象的所有子文件名和路径。
文件过滤器FilenameFilter:
在上述File类方法中有个list方法,该方法可以接收一个FilenameFilter参数,通过该参数可以只列出符合条件的文件。
代码如下:作用是返回当前程序目录下,子目录中有包含后缀名为".java"文件的名字
public class IO { //文件过滤器 public void fileNameFilter(){ File file = new File("."); System.out.println(file.getAbsolutePath()); // FilenameFilter filenameFileter = new FilenameFilter() { // // @Override // public boolean accept(File dir, String name) { // // TODO Auto-generated method stub // System.out.println("accept方法中的name="+name); // return name.endsWith(".java") || new File(name).isDirectory(); // } // }; // String[] nameList = file.list(filenameFileter); //使用Lambda表达式,相当于上述的代码 String[] nameList = file.list((dir,name) -> name.endsWith(".java") || new File(name).isDirectory()); for(String name : nameList){ System.out.println(name); } } public static void main(String[] args){ IO io = new IO(); io.fileNameFilter(); } }
运行效果如下:
D:\programme\javaEE\project\fzkjshop_project\fzkjshop_test\.
.settings
src
target
File类只能找到文件或者目录,也可以创建删除文件与目录,但是不能对文件进行读与写。为了实现文件的读与写
IO流:
IO流是输入/输出的基础,它可以方便的实现数据的输入/输出操作,不仅可以实现文件的读与写,还可以现实网络传输数据的输入与输出等等。在Java中把不同的输入/输出源(键盘,文件,网络连接等)抽象表述为“流”(stream),通过流的方式,允许Java程序使用相同的方式来访问不同的输入/输出源。stream是从起源(source)到接收(sink)的有序数据。
流可以根据方向分成:输入流与输出流,一个只能输入,不能输出;另一个只能输出,不能输入
可以根据传输类型分成: 字节流与字符流,一个使用字节传输,另外一个使用字符传输;PS,字符流是字节流的一个分类
可以根据角色分成:节点流与处理流,从/向一个特定的IO设备(如硬盘,网络)读/写数据的流,称为节点流;将节点流进行封装或者连接的称之为处理流。处理流比节点流在编写操作上更简单,同时执行的效率更高。
根据输入/输出流以及字符/字节流,IO流提供4个抽象基类,即是字符输入流(Reader),字符输出流(Writer),字节输入流(InputStream),字节输出流(OutputStream),IO流提供的40多个类都是由4个抽象基类派生而来了,如下图:
4个抽象基类的方法如下:
InputStream(字节输入流)与Reader(字节输入流)的方法:
1. int read():从输入流中读取当个字符/字节,返回所读取的字符/字节数据(字符/字节数据可以直接转化为int类型)
2. int read(byte[]/char[] b): 从输入流中最多读取b.length个字节/字符的数据,并将其存入byte[]/char[]数组中,返回实际读取的字节/字符数
3. int read(byte[]/char[] b,int off ,int len): 从输入流中最多读取len个字节/字符的数据,并将存入byte[]/char[]数组中,从off位置开始,返回实际读取的字节/字符数
OutputStream(字节输出流)与Writer(字符输入流)的方法:
1. void write(int c) :将指定的字节/字符数据写入到输出流中,参数为int类型,可以代表字节/字符
2. void write(btye[]/char[] b): 将字节/字符数组中的数据写入输出流中
3. void write(byte[]/char[] b ,int off , int len):将字节/字符数组中从off位置开始,长度为len的字节/字符写入到输出流中
基本上所有的输入/输出流都会重载/重写以上的方法,因此我们只要熟练的使用上述方法,便可以在任意的输入/输出流中实现输入与输出操作。
根据上图,我们得知了用于访问文件的流,现通过该流编写一个拷贝文件的程序:
public class IO { //拷贝文件 public void copyFile(String path,String copyPath,int fileType){ File file_1 = new File(path); File file_2 = new File(copyPath); try{ if(!file_2.exists()){ file_2.createNewFile(); } if(!file_1.exists()){ System.out.println("要拷贝文件的路径不存在"); } if(file_1.isFile() && file_2.isFile()){ byte[] readByte = new byte[1024]; char[] chars = new char[512]; int hasRead,account = 0; if(fileType == 0){ //当文件时字符类型时 Reader read = new FileReader(file_1); Writer write = new FileWriter(file_2); while((hasRead = read.read(chars))>0){ write.write(chars,0,hasRead); } read.close(); write.close(); } if(fileType == 1){ //当文件时字符类型时 InputStream in = new FileInputStream(file_1); OutputStream out = new FileOutputStream(file_2); while((hasRead = in.read(readByte)) >0){ out.write(readByte,0,hasRead); } in.close(); out.close(); } } }catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); // io.fileNameFilter(); String bytePath = "D:\\data\\file\\img.jpg"; String byteCopyPath ="D:\\data\\file\\img_1.jpg"; String charPath = "D:\\data\\file\\book.txt"; String charCopyPath = "D:\\data\\file\\book_1.txt"; io.copyFile(charPath,charCopyPath,0); io.copyFile(bytePath, byteCopyPath,1); } }
在上述代码中,我们实现了拷贝文件的方法,传入的参数分别是对要拷贝文件的路径,拷贝文件后放置的路径根据以及文件类型,根据文件类型分成了字节拷贝与字符拷贝,各自的流都不一样。
在上述代码中,是根据节点流实现文件拷贝的,现在通过处理流实现文件的拷贝
public class IO { public void copyFileByBuffered(String path,String copyPath,int fileType){ File file_1 = new File(path); File file_2 = new File(copyPath); try{ if(!file_1.exists()){ System.out.println("要拷贝文件的路径不存在"); } if(!file_2.exists()){ file_2.createNewFile(); } if(file_1.isFile() && file_2.isFile()){ if(fileType == 0){ BufferedReader br = new BufferedReader(new FileReader(file_1)); BufferedWriter bw = new BufferedWriter(new FileWriter(file_2)); String line = null; while((line=br.readLine()) !=null){ System.out.println(line); bw.write(line); } bw.close(); br.close(); }if(fileType == 1){ BufferedInputStream in = new BufferedInputStream(new FileInputStream(file_1)); BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file_2)); byte[] b = new byte[1024]; int hasNext = 0; while((hasNext = in.read(b)) > 0 ){ out.write(b,0,hasNext); } in.close(); out.close(); } } }catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); String bytePath = "D:\\data\\file\\img.jpg"; String byteCopyPath ="D:\\data\\file\\img_1.jpg"; String charPath = "D:\\data\\file\\book.txt"; String charCopyPath = "D:\\data\\file\\book_1.txt"; io.copyFileByBuffered(charPath,charCopyPath,0); io.copyFileByBuffered(bytePath, byteCopyPath,1); } }
上述代码中,我们将字节流都换成了处理流,实现的功能都是一样的。
转换流:
转换流是字符与字节的转换,严格来说,是字节流转换成字符流,而字符流是不可以转成字节流的,原因是字符流是字节流的一个分支。我们都知道计算机存储文件都是以二进制进行存储的,对二进制的读与写就需要用到字节流。而什么时候用到字符流呢,当对操作的文件存储的都是字符的时候,便是用字符流,比如说TXT文件,而字符也是通过二进制而形成的,因此文件全都可以使用字节流操作读与写,极少文件时可以通过字符流操作读与写的。
转换流一般用在网络传输上,由于网络传输都是以字节流的形式进行传输,当我们知道网络传给我们的资源是可以通过字符流的形式读取时,我们便可以将对应字节流转成字符流,方便我们操作。例如以下代码,实现了读取网页并在控制台打印了该网页的内容:
public class IO { public void readHTML(URL url){ try{ URLConnection urlConnection = url.openConnection(); urlConnection.connect(); InputStream in = urlConnection.getInputStream(); InputStreamReader reader = new InputStreamReader(in); BufferedReader br = new BufferedReader(reader); while(br.readLine() != null){ System.out.println(br.readLine()); } br.close(); }catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); try{ URL url = new URL("https://www.cnblogs.com/hjlin/"); io.readHTML(url); }catch(Exception e){} } }
我们可以看到上述代码中,先将对应字节输入流转换成了字符输入流,并将其转成了处理流进行读取。
推回输入流:
在Java中IO流,读取输入流中的内容,是从头到尾顺序读取的,前面读取过的就不会在读取了,所以如果还想读取前面的数据的话,就要用到推回输入流了。推回输入流提供以下方法:
1. unread(byte[]/char[] buf):将一个字节/字符数组内容推回到推回缓冲区里。允许重复读取
2. unread(byte[]/char[] b.int off,int len): 将一个字节/字符数组内容从off开始,长度为len字节/字符的内容推回缓冲区里
3. unread(int b): 将一个字节/字符推回到推回缓冲区里。
推回输入流中带有一个推回缓存区,当程序调用这两个推回输入流的unread()方法时,系统将会把指定数组的内容推回到推回缓冲区里,然后推回输入流调用read方法时,总是从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满read()所需的数组才会从原输入流中读取。如下图:
下面程序使用推回输入流,并试图找出文件中的“new PushbackReader”字符串,找到后,打印出目标字符之前的内容:
public class IO { public void pushbackTest(){ try{ File file = new File("./src/main/java/File/IO.java"); //创建一个PushBackReader对象,指定推回缓存区的长度为64 PushbackReader pr = new PushbackReader(new FileReader(file),64); char[] chars = new char[32]; //用于保存上次读取的字符串内容 String lastContent = ""; int hasRead = 0; while((hasRead = pr.read(chars)) > 0){ //将读取的内容转换成字符串 String content = new String(chars,0,hasRead); int targetIndex = 0; if((targetIndex = (lastContent+content).indexOf("new PushbackReader")) > 0){ //将本次内容和上一次的内容一起推回缓存区 pr.unread((lastContent+content).toCharArray()); //重新定义一个长度为targetIndex的char数组 if(targetIndex > 32){ chars = new char[targetIndex]; } //再次读取指定长度的内容(就是目标字符串之前的内容) pr.read(chars,0,targetIndex); //打印读取的内容 System.out.println(new String(chars,0,targetIndex)); }else{ //打印上次读取的内容 System.out.println(lastContent); //将本次内容设置成上次内容 lastContent = content; } } }catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); io.pushbackTest(); } }
上述粗体字代码实现了将指定内容推回到推回缓冲区,让程序在调用read方法读取推回缓冲区的内容,从而现实了只打印目标字符串前面的内容功能
重定向标准输入/输出:
Java的标准输入/输出分别时通过System.in/System.out来代表。在默认情况下,它们分别代表键盘与显示器。当程序从Sytem.in输入是,实际上是从键盘读取输入,当程序将通过System.out执行输出时,该输出数据传到显示器里。
在System类中,提供以下方法,可以将System.in与System.out的输入/输出对象发现改变。
1. static void setErr(PrintStream err):重定向"标准"错误输出流
2. static void setIn(InputStream in): 重定向“标准”输入流
3. static void setOut(PrintStream out): 重定向“标准”输出流
下面将System.out的输出重定向到文件
public class IO { public void rediectSystemOut(){ try{ //一次性创建PrintStream输出流 PrintStream ps = new PrintStream(new FileOutputStream("out.txt")); System.setOut(ps); //将输出的对象改编成了ps对象 System.out.println("普通字符串"); System.out.println(new IO());
}catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); io.rediectSystemOut(); } }
运行后,发现当前控制台没有打印数据,并且在程序文件当前目录下多了一个out.txt文件,文件上的内容与程序运行到控制台的内容一致。
设置System.in输入流对象:
public class IO { public void rediectSystemOut(){ try{ //一次性创建PrintStream输出流 PrintStream ps = new PrintStream(new FileOutputStream("out.txt")); System.setOut(ps); //将输出的对象改编成了ps对象 System.out.println("普通字符串"); System.out.println(new IO()); ps.close(); }catch(Exception e){} } public void rediectSystemIn(){ try{ File file = new File("./src/main/java/File/IO.java"); FileInputStream fis = new FileInputStream(file); System.setIn(fis); //将System.in输出流对象改成fis对象 Scanner sc = new Scanner(System.in); sc.useDelimiter("\n"); //判断是否还有下一个输入项 while(sc.hasNext()){ //输出输入项 System.out.println("内容是:"+sc.next()); } }catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); io.rediectSystemIn(); } }
运行后,发现将IO.java文件的内容输出到了控制台:
Java虚拟机读写其他进程的数据:
Java是通过runtime对象的exec()方法可以运行平台上的其他程序,该方法产生一个Process对象(进程对象),Process对象代表有Java程序启动的子进程。Process对象提供以下方法,用于让程序与子进程进行通信:
1. InputStream getErrorStream():获取子进程的错误流
2. InputStream getInputStream(): 获取子进程的输入流
3. OutputStream getOutputStream(): 获取子进程的输出流
下面程序示范了读取其他进程的输出信息:
public class IO { public void readProcess(){ try{ Process p = Runtime.getRuntime().exec("javac"); BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream())); String buff = null; while((buff = br.readLine()) != null){ System.out.println(buff); } }catch(Exception e){} } public static void main(String[] args){ IO io = new IO(); io.readProcess(); } }
其运行结果如下:由于个人电脑系统编码的原因,导致了乱码,但也不碍事
RandomAccessFile
RandomAccessFile是Java输入/输入流体系中功能最丰富的文件内容访问类,它提供了众多来访问文件的内容,既可以读文件,也可以写文件,最重要的是,它支持“随机访问”,程序可以跳到文件的任意一处地方进行读写操作,因此用它来做只访问文件部分内容或者进行文件断点下载操作时最好不过的了。
RandomAccess之所以能“随机访问”,是因为该类中有一个记录指针存在。当创建该类实例时,记录指针的值为0,也就是文件的开头,当读/写了n个字节时,该记录指针也会向后移动n个字节。因此我们只需在读/写操作之前,设置文件的记录指针到相应的位置,那么便可以在文件的指定位置上进行读/写操作.RandomAccess提供两个方法来操作记录指针:
1. long getFilePointer() : 得到当前记录指针值
2. void seek(int pos) : 设置当前记录指针值为pos,即文件从pos位置开始读/写操作
RandomAccess提供两个构造方法:
1. RandowAccess(String filename,Stirng mode): 以String的形式指向文件,并以mode参数决定RandowAccess对该文件的访问模式
2. RandowAccess(File file ,String mode ):以File的形式指向文件,并以mode参数决定RandowAccess对该文件的访问模式
mode的值有如下:
“r”:RandowAccess以只读模式来访问该文件,若RandowAccess对该文件进行写的操作会报错
“rw”:RandowAccess以只读模式来访问该文件,若文件不存在的时候,会尝试创建该文件
“rws”:相当于“rw”,还要求对文件的内容与元数据的每个更新都同步写入底层存储设备里。
“rwd”:相当于“rw”,还要求对文件内容的每个更新都同步写入底层存储设备里。
下面代码实现了从文件中间读取指定长度的数据,并将该数据写入该文件的最后位置。
public class IO { public void randomAccessTest(String path){ try{ File file = new File(path); if(!file.exists()){ System.out.println("文件不存在"); }else{ RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); int fileLen=0,hasNext= 0; byte[] b = new byte[1024]; while((hasNext=randomAccessFile.read(b))>0){ fileLen += b.length; } System.out.println("该文件的总字节数:"+fileLen); randomAccessFile.seek(300); randomAccessFile.read(b); System.out.println(new String(b,0,b.length)); randomAccessFile.seek(fileLen); randomAccessFile.write(b); randomAccessFile.close(); } }catch(Exception e){} } public static void main(String[] args){ String path = "D:\\data\\file\\book.txt"; io.randomAccessTest(path); } }
总结:IO流中,存在4个抽象基类,这4个抽象基类分别对应了字符输入流(reader),字符输出流(Writer),字节输入流(InputStream),字节输出流(OutputStream)。根据这4个抽象基类而延申了40多种类。这些类中,有的是字节流,有的是处理流,还有转换流,用于处理字节流转换成字符流推回输入流;推回输入流,用于多次读取内容;RandomAccessFile,用于随机访问文件等等。