您的位置:首页 > 职场人生

黑马程序员——自学总结(五)Java IO技术之File类

2015-08-01 23:57 761 查看
<a href="http://www.itheima.com"
target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!

一、File类概述

        File类用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作,File对象可以作为参数传递给流的构造函数。File对象构造方法:File(文件名),File(路径,文件名),File(文件对象即路径,文件名),构造File对象时文件可以不存在,后两种构造方法可用变量来改变路径, 打印File对象只打印构造时传递的参数。

        File类中字段seperator表示默认文件名称分隔符,为了跨平台识别,可以将程序代码中的“\\”换成File.seperator。

        File类常见方法:

       1、创建

       boolean createNewFile() 在指定位置创建文件,如果该文件已经存在,则不创建,返回false,和文件输出流不一样,文件输出流对象一建立就会创建文件,而且文件已经存在会覆盖。mkdir()创建目录,只能创建一级目录 mkdirs()可以创建多级目录。 

       2、删除

       boolean delete() 删除失败时返回false,boolean deleteOnExit() 在程序退出时删除文件,如果程序运行时抛出异常,则必须在finally块内删除文件,但可能某个流正在操作文件,使用该方法系统提示删不掉,只能用deleteOnExit()删除。  

      3、判断

      canExecute()返回是否能执行,compareTo(File) 比较文件路径,exists()返回文件是否存在。

      isDirectory(),isFile() 判断文件对象是否是文件或者目录,判断时必须先判断该文件对象封装的内容是否存在。

      isHidden()返回是否隐藏,可以避免访问系统文件,isAbsolute() 判断构建时文件路径是否为绝对路径。

      4、获取信息

       getName()返回文件名

       getPath()返回封装即构造的路径

       getParent()返回绝对路径中的文件父目录,否则若获取的是相对路径则返回空,如果相对目录中有上一层目录,那么返回上一层目录。

       getAbsolutePath()返回绝对路径

       getAbsoluteFile()返回文件对象,该文件对象用绝对路径构造。

       lastModefied()返回时间,可判断文件是否被修改过。

       length()返回文件大小

       renameTo()类似剪切

       static list[ ] listRoots()返回list[ ],列出系统可用的根目录

       list()返回String数组,包含文件路径下的文件名字符串,包含隐藏文件,调用list()方法的file对象必须是封装了一个目录,该目录还必须存在,否则遍历时会发生空指针异常。String[] names=file.list();file目录若不存在,则数组names为空,遍历会抛出异常,file目录存在,但没内容,数组names长度为0。

       list[]  list(FilenameFilter) 按照文件名过滤器返回路径下的文件名字符串。接口FilenameFilter有方法accept(File,String) 。类似的方法有listFiles(),listFiles(FileFilter)

      需求:列出指定目录下文件或者文件夹,包含子目录中的内容,也就是列出指定目录下所有内容。

      因为目录中还有目录,只要使用同一个列出目录功能的函数完成就行。在列出过程中出现的还是目录的话,还可以再次调用本功能,也就是函数自身调用自身,这种表现形式或编程手法称为递归。

       代码如下:

import java.io.*;
class getFiles{
static void show(File file){
File[] files=file.listFiles();
for(File f:files){
if(f.isDirectory()){
System.out.println(f);
show(f);
}
else
System.out.println(f);
}
}
public static void main(String[] args){
show(new File("F:\\第二阶段:Java基础\\Java高级视频_IO输入与输出"));
}
}


      输出结果如下:



        递归举例:求二进制数:

        void toBin(int num){

                 if(num>0){

                       toBin(num);

                      打印num%2;

                }

        } 

         求1到n的和:

         int getSum(int n){

                  if(n=1)

                     return 1;

                 return n+getSum(n-1);

         }

       递归层数注意不能太多,否则会内存溢出,抛出异常。递归要注意:(1)限定条件;(2)注意递归的次数,尽量避免内存溢出。

       需求:带层次的打印目录下文件名

       代码如下:

import java.io.*;
class getFiles{
static void show(File file,int level){
level++;
File[] files=file.listFiles();
for(File f:files){
if(f.isDirectory()){
for(int i=0;i<=level;i++)
System.out.print("—");//按层次打印前导符
System.out.println(f.getName());
show(f,level);
}
else{
for(int i=0;i<=level;i++)
System.out.print("—");//按层次打印前导符
System.out.println(f.getName());
}
}
}
public static void main(String[] args){
show(new File("F:\\第二阶段:Java基础\\Java高级视频_IO输入与输出"),0);
}
}


运行结果如下:



        需求:删除一个带内容的目录。删除原理,在windows中,删除目录从里面往外删除的,既然是从里往外删除,就需要用到递归。

         代码如下:

import java.io.*;
class my
4000
delete{
static void mydelete(File file){
File[] files=file.listFiles();
for(File f:files){
if(f.isDirectory())
mydelete(f);
else
f.delete();
}
file.delete();

}
public static void main(String[] args){
mydelete(new File("F:\\第二阶段:Java基础\\Java高级视频_IO输入与输出(备份)"));
}
}


        java删除的文件不保存在回收站里。有些系统隐藏文件Java不能访问,删除时会返回空文件数组,接下来在高级for循环里会抛出空指针异常,所以遍历时应避开隐藏文件,可加判断语句if(!file.isHidden()),防止抛出空指针异常。

       练习:创建Java文件列表,将一个指定目录下的java文件的绝对路径,存储到一个文本文件中,建立一个java文件列表文件。

       思路:(1)对指定的目录进行递归;(2)获取递归过程中所有的java文件的路径;(3)将这些路径存储到集合中;(4)将集合中的数据写入到一个文件中。

       代码如下:

 

import java.io.*;
import java.util.*;
class getJavaList{
static List<String> con=new ArrayList<String>();
static void getJavaList(File file,File to,FileFilter ff){
search(file,ff);
Writer w=null;
try{
w=new FileWriter(to.getAbsolutePath());
for(int i=0;i<con.size();i++){
w.write(con.get(i));
w.write("\r\n");
}
}catch(IOException e){
throw new RuntimeException(e);
}finally{
if(w!=null)
try{
w.close();
}catch(IOException e){
throw new RuntimeException(e);
}
}

}
static void search(File file,FileFilter ff){
File[] files=file.listFiles(ff);
for(File f:files){
if(f.isDirectory())
search(f,ff);
else
con.add(f.getAbsolutePath());
}

}
public static void main(String[] args){
getJavaList(new File("C:\\Documents and Settings\\TEMP"),
new File("D:\\javalist.txt"),
new FileFilter(){
public boolean accept(File file){
return file.getAbsolutePath().matches(".+\\.java");
}
}
);
}
}


二、Properties

        Properties对象是Hashtable子类,具备了Map集合的特点,而且它里面存储的键值对都是字符串,不需要泛型,是集合中和IO技术相结合的集合容器,该对象的特点是可以用于键值对形式的配置文件。因为配置文件中数据都是键值对形式,那么在加载数据时,需要数据有固定格式,通常为键=值。Properties对象常用方法:设置和获取元素 
setProperty(String,String),getProperty(String key),Set<String> stringPropertyNames()返回键集合。

       演示:如何将流中的数据存储到集合中,例如想要将info.txt中键值对数据存储到集合中进行操作

       步骤: 1、用一个流和info.txt关联;2、读取一行数据,将该行数据用“=”进行切割;3、=左边作为键,右边作为值存到Properties集合中即可。

       Properties对象提供了更简易的方法,将流中数据加载进集合的方法:Properties.load(读取流),文件中凡是加#表示注释信息,不会被load加载,显示配置信息方法:Properties.list(PrintStream),store(OutputStream,字符串形式的注释信息)将内存结果存入流当中,文件自动添加时间。

        练习:用于记录应用程序运行的次数,如果使用次数已到,那么给出注册提示。很容易想到计数器,可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行了自增,可是随着该应用程序的退出,该计数器也在内存中消失,下一次再启动该程序又重新开始从0计数,这样不是我们想要的。程序即使结束,该计数器的值依旧存在,下次程序启动会先加载该计数器的值,并+1后重新存储起来,所以要建立一个配置文件,用于记录该软件的使用次数。该配置文件使用键值对的形式。这样便于阅读数据并操作数据。键值对数据是Map集合,数据是以文件形式存储,使用io技术,那么综合集合和IO就使用Properties,配置文件可以实现应用程序数据的共享。

       代码如下:

import java.io.*;
import java.util.*;
class MySoft{
public static void main(String[] args){
Properties prop=new Properties();
File file=new File("peizhi.txt");
try{
if(!file.exists())
file.createNewFile();
}catch(IOException e){
throw new RuntimeException(e);
}
FileInputStream fis=null;
FileOutputStream fos=null;
try{
fis=new FileInputStream(file);
prop.load(fis);
int count=0;
String value=prop.getProperty("time");
if(value!=null){
count=Integer.parseInt(value);
System.out.println("第"+(++count)+"次免费使用");
if(count>=5){
System.out.println("已达到免费使用次数");
return;
}
}else{
count++;
System.out.println("第"+count+"次免费使用");
}
prop.setProperty("time",count+"");
fos=new FileOutputStream(file);
prop.store(fos,"");
}catch(IOException e){
throw new RuntimeException(e);
}finally{
if(fis!=null)
try{
fis.close();
}catch(IOException e){
throw new RuntimeException(e);
}finally{
if(fis!=null)
try{
fis.close();
}catch(IOException e){
throw new RuntimeException(e);
}
}
}
}
}


运行结果如下:



      我在调试这个程序时,发现了一个问题,如果在prop.load(fis)之前将fos关联到配置文件,那么执行prop.load(fis)会将文件内容清空,而且prop内部也没有获得数据。如果在prop.load(fis)之后将fos关联到配置文件,则不会出现这种问题。

      程序先创建File,如果不存在,则创建。如果直接建立读取流,那么第一次运行会抛出异常。

三、IO包中的其他流

       打印流PrintWriter与PrintStream可以对数据进行格式化打印输出,PrintStream构造函数可以接受的参数类型有:file对象,字符串路径,字节输出流OutputStream,PrintWriter造函数可以接受的参数类型有:file对象,字符串路径,字节输出流OutputStream,字符输出流Writer,PrintWrite在创建时候指定true,输出时将自动刷新缓冲区。

       序列流SequenceInputStream,可以通过构造函数SequenceInputStream(Enumeration<? extends InputStream>)对多个输入流进行合并,可以通过Vector.elements()获得Enumeration对象,SequenceInputStream
没有对应的OutputStream。

       需求:切割文件并恢复,恢复文件的过程:用ArrayList存储InputStream(不用Vector,因为ArrayList效率较高),创建一个Enumeration(因为序列流构造函数需要Enumeration对象),覆盖Enumeration对象中的迭代器操作方法,将Enumeration对象中的迭代器操作方法方法基于ArrayList中迭代器操作来完成。

      代码如下:

import java.io.*;
import java.util.*;
class mysplit{
static List<File> mysplit(File file,List<File> li){//对file进行切割,切割后的文件存储在li中
int i=0;
FileInputStream in=null;
try{
in=new FileInputStream(file);
FileOutputStream out=null;
byte[] buff=new byte[1024*20];//每个切割文件大小不超过20KB
File f=null;
int len=0;
while((len=in.read(buff))!=-1){
f=new File((++i)+".part");//每次切割都要创建一个新文件
out=new FileOutputStream(f);
out.write(buff,0,len);
li.add(f);//将本次切割后得到的文件存入li中
}
}catch(IOException e){
throw new RuntimeException(e);
}finally{
if(in!=null)
try{
in.close();
}catch(IOException e){
throw new RuntimeException(e);
}
}
return li;
}
}
class mymerge{
static void mymerge(List<File> li){
File result=new File("merge.jpg");
ArrayList<FileInputStream> al=new ArrayList<FileInputStream>();
//将li中存储的切割文件对象一一取出,并分别用输入流关联
for(int i=0;i<li.size();i++){
try{
al.add(new FileInputStream(li.get(i)));
}catch(IOException e){
throw new RuntimeException(e);
}
}

Iterator<FileInputStream> it=al.iterator();
SequenceInputStream si=null;
FileOutputStream fos=null;
try{    //定义一个序列流,用ArrayList替代Enumeration做参数,效率更高,内部调用ArrayList中迭代器的操作完成
si=new SequenceInputStream(new Enumeration<FileInputStream>(){
public boolean hasMoreElements(){
return it.hasNext();
}
public FileInputStream nextElement(){
return it.next();
}
});
fos=new FileOutputStream(result);
byte[] buff=new byte[1024];
while(si.read(buff)!=-1){
fos.write(buff);
}
}catch(IOException e){
throw new RuntimeException(e);
}finally{
if(si!=null)
try{
si.close();
}catch(IOException e){
throw new RuntimeException(e);
}finally{
if(fos!=null)
try{
si.close();
}catch(IOException e){
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args){
List<File> li=new ArrayList<File>();
mysplit.mysplit(new File("test.jpg"),li);
mymerge(li);
}
}

 

       
ObjectInputStream与ObjectOutputStream直接操作对象的流,可以将对象持久化(序列化),被操作的对象需要实现Serializable接口,该接口没有抽象方法,是一种标记接口。假设定义了一个Person类,有如下代码:

        Person p=new Person(......);

           ObjectInputStream ois=new
ObjectInputStream(new FileOutputStream("obj.txt"));

        ObjectOutputStream  oos=new
ObjectOutputStream (new FileInputStream("obj.txt"));

        oos.writeObject(p);ois.readObject(p);

        以上代码分别写入和读取一个Person对象,前提是Person必须实现Serializable接口。凡是实现了Serializable接口的类在内存中都有一个唯一的序列号,序列号由代表各个成员的数字标签生成,writeObject(p)和readObject(p)都是根据类Person类的序列号来确定要读取的内容,如果Person类的某个成员改变,比如由public变成private,那么该类的序列号就会改变,readObject()按照新的序列号去读取原有的内容,将出现错误,提高了安全性。如果想避免因修改类,造成再次读取原有对象失败,可以自己手动修改类的序列号,确保其始终唯一,这样新类仍然可以访问曾经被序列化的旧类。

          静态数据不能被序列化,也就是说如果从文件中读取一对象,该对象中的静态成员将始终保持第一次写入的值,如果某非静态成员不想序列化用关键字transient ,保证该成员在堆内存中存在,而不存在于文件中,如果读取返回的是默认初始化值,比如int型返回0。一般编程时真正存储Object用文件名:类名.object。

        ObjectOutputStream对象的write(int)读取数据的低8位,write(Int)读取数据的全部32位。

        管道流PipedInputStream和PipedOutputStream,输入流和输出流可以直接进行连接,通过结合多线程使用,可以在构造函数中就直接和对方流对象连接,也可以用connect(对方流对象)方法连接。

        操作基本数据类型的流DataInputStream与DataOutputStream,可以用于操作基本数据类型的数据的流对象。构造方法中要传递流对象。

        DataOutputStream对象的writeUTF()写入的数据只能用对应的DataInputStream对象的.readUTF()方法读取,到结尾没读完UTF规则指定的字节,则抛出EOF异常。

        ByteArrayInputStream和ByteArrayOutputStream操作字节数组的流对象,该类流对象内部没有调用底层资源,关闭无效,不会产生任何IO异常。ByteArrayOutputStream的缓冲区自动增长,可以通过toByteArray(),toString()获得内部字节数组,数据目的封装在内部,即字节数组。  ByteArrayInputStream在构造的时候需要接收数据源,数据源是一个字节数组,输出的流在构造时不用定义数据目的,因为该对象中已经内部封装了一个可变长度的字节数组,这就是数据目的地。因为这两个流对象都操作的是字节数组,并没有使用系统资源,所以不用进行close()关闭,流对象可继续使用。

       ByteArrayOutputStream对象的size()方法返回缓冲区大小。在流操作时,数据源设备包含键盘,硬盘,内存,目的设备包括控制台,硬盘,内存。内存就是数组流,用流的读写思想来操作数组就用到了ByteArrayInputStream和ByteArrayOutputStream。

       ByteArrayOutputStream对象的writeTo(OutputStream),可以将内部字节数组输出到其他字节输出流中。

        CharArray
a85a
Reader和CharArrayWriter 操作字符数组,,StringReader和StringWriter操作字符串数组。

四、RandomAccessFile

        RandomAccessFile随机访问文件,自身具备读写的方法,通过skipBytes(int x),seek(int x)调整文件指针来达到随机访问,skipBytes(int x)跳过指定的字节数,只能往文件尾移动,seek(int x)可以前后调整,按指定位置书写,且覆盖原有数据。getFilePointer()读取文件指针。该类不是IO体系中子类,而是直接继承自Object,但是它是IO包中成员,因为它具备读和写功能。RandomAccessFile类内部封装了字节输入流、字节输出流和一个数组,通过指针对数组中的元素进行操作来达到随即访问的效果,

        RandomAccessFile类只能操作文件,在构造函数中可以指定操作文件的模式:只读r,读写rw等。如果模式为r,不会创建文件,会去读取一个已存在的文件,如果该文件不存在,则会出现异常。如果模式为rw,要操作的文件不存在则自动创建。readXXX()读某个基本数据类型,readLine()读取一行数据,writeXXX()写入某个基本数据类型 。

       该类可以实现分段读写,通过调整文件指针,使多个线程负责每一段,互相不冲突,这就是下载软件的工作原理。

五、字符编码 

       字符流出现后,为了方便操作字符,加入了编码转换,通过子类转换流来完成:InputStreamReader和OutputStreamWriter,这两类对象进行构造的时候可以加入字符集,这样就将字节数据按照指定的编码转换成文本数据。

       编码表的由来:由于计算机只能识别二进制数据,为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。常见的编码表有ASCII,美国标准信息交换码,用一个字节的7位表示表中字符,ISO8859-1,拉丁码表(欧洲码表),用一个字节的8位表示表中字符,GB2312,中国的中文编码表,GBK:中国的中文编码表升级版,融合了更多的中文文字符号,Unicode,国际标准码,融合了多种文字,所有文字都用两个字节来表示,java使用的就是Unicode,UTF-8编码最多用三个字节来表示一个字符。

       编码:将字符串变成字节数组getBytes(),该方法可以指定字符编码集

       解码:将字节数组变成字符串new String(byte[ ]),或者Arrays.toString(byte[ ]) 

       如果A用户按GBK方式编码成字节数组发给B用户,而B用户却错用ISO8859-1方式解码,显然得不到正确数据,这时可将错误转换得到的字符串再按照ISO8859-1方式编码成字节数组,重新将新字节数组按GBK方式解码即可。但是用这种方式有个特例:UTF-8和GBK都能识别中文,如果A用户使用GBK,B用户用UTF—8解码,因为UTF—8也能识别中文字符,遇到不能识别的中文字符将转换成?进行解码,改变了原来的字节数组中数据,不能还原数据,ISO8859-1不能识别中文,可以通过编码解码还原数据。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: