务实java基础之IO

“对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。”由于存在大量不同的设计方案,所以该任务的困难性是很容易证明的。其中最大的挑战似乎是如何覆盖所有可能的因素。不仅有三种不同的种类的 IO 需要考虑(文件、控制台、网络连接),而且需要通过大量不同的方式与它们通信(顺序、随机访问、二进制、字符、按行、按字等等)。
Java 库的设计者通过创建大量类来攻克这个难题。事实上, Java 的 IO 系统采用了如此多的类,以致刚开始会产生不知从何处入手的感觉,本篇博客我们就来详细学习Java的IO 系统。

先上一张图
这里写图片描述

IO中的输入字节流类型

InputStream 的作用是标志那些从不同起源地产生输入的类。这些起源地包括(每个都有一个相关的InputStream 子类):

(1)字节数组
(2) String 对象
(3) 文件
(4) “管道”,它的工作原理与现实生活中的管道类似:将一些东西置入一端,它们在另一端出来。
(5)一系列其他流,以便我们将其统一收集到单独一个流内。
(6) 其他起源地,如 Internet 连接等

除此以外, FilterInputStream 也属于 InputStream 的一种类型,用它可将属性或者有用的接口同输入流连接到一起。

1.InputStream是所有的输入字节流的父类,它是一个抽象类。
2. ByteArrayInputStream、StringBufferInputStream、FileInputStream是三种基本的介质流,它们分别将Byte数组、StringBuffer、和本地文件中读取数据。PipedInputStream是从与其它线程共用的管道中读取数据,与Piped相关的知识会用专门的一小节讲解。
3. ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。

基本输入字节流:

功能如何构造怎样使用
ByteArrayInputStream将内存中的Byte数组适配为一个InputStream。从内存中的Byte数组创建该对象(2种方法)一般作为数据源,会使用其它装饰流提供额外的功能,一般都建议加个缓冲功能。
StringBufferInputStream将内存中的字符串适配为一个InputStream。从一个String对象创建该对象。底层的实现使用StringBuffer。该类被Deprecated。主要原因是StringBuffer不应该属于字节流,所以推荐使用StringReader。一般作为数据源,同样会使用其它装饰器提供额外的功能。
FileInputStream最基本的文件输入流。主要用于从文件中读取信息。通过一个代表文件路径的 String、File对象或者 FileDescriptor对象创建。一般作为数据源,同样会使用其它装饰器提供额外的功能。
PipedInputStream读取从对应PipedOutputStream写入的数据。在流中实现了管道的概念。利用对应的PipedOutputStream创建。在多线程程序中作为数据源,同样会使用其它装饰器提供额外的功能。
SequenceInputStream将2个或者多个InputStream 对象转变为一个InputStream.使用两个InputStream 或者内部对象为InputStream 的Enumeration对象创建该对象。一般作为数据源,同样会使用其它装饰器提供额外的功能。
FilterInputStream给其它被装饰对象提供额外功能的抽象类主要子类见下表

装饰、输入字节流:

功能如何构造怎样使用
DataInputStream一般和DataOutputStream配对使用,完成基本数据类型的读写。利用一个InputStream构造。提供了大量的读取基本数据类新的读取方法。
BufferedInputStream使用该对象阻止每次读取一个字节都会频繁操作IO。将字节读取一个缓存区,从缓存区读取。利用一个InputStream、或者带上一个自定义的缓存区的大小构造。使用InputStream的方法读取,只是背后多一个缓存的功能。设计模式中透明装饰器的应用。
LineNumberInputStream跟踪输入流中的行号。可以调用getLineNumber( )和 setLineNumber(int)方法得到和设置行号。利用一个InputStream构造。紧紧增加一个行号。可以象使用其它InputStream一样使用。
PushbackInputStream可以在读取最后一个byte 后将其放回到缓存中。利用一个InputStream构造。一般仅仅会在设计compiler的scanner 时会用到这个类。在我们的java语言的编译器中使用它。很多程序员可能一辈子都不需要。

IO中的输出字节流类型

1.OutputStream是所有的输出字节流的父类,它是一个抽象类。
2. ByteArrayOutputStream、FileOutputStream是两种基本的介质流,它们分别向Byte数组、和本地文件中写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据
3. ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。下表列出了输出字节流的功能及如何使用它们。

功能如何构造怎样使用
ByteArrayOutputStream在内存中创建一个buffer。所有写入此流中的数据都被放入到此buffer中。无参或者使用一个可选的初始化buffer的大小的参数构造。一般将其和FilterOutputStream套接得到额外的功能。建议首先和BufferedOutputStream套接实现缓冲功能。通过toByteArray方法可以得到流中的数据。(不通明装饰器的用法)
FileOutputStream将信息写入文件中。使用代表文件路径的String、File对象或者 FileDescriptor对象创建。还可以加一个代表写入的方式是否为append的标记。一般将其和FilterOutputStream套接得到额外的功能。
PipedOutputStream任何写入此对象的信息都被放入对应PipedInputStream 对象的缓存中,从而完成线程的通信,实现了“管道”的概念。具体在后面详细讲解。利用PipedInputStream构造在多线程程序中数据的目的地的。一般将其和FilterOutputStream套接得到额外的功能。
FilterOutputStream实现装饰器功能的抽象类。为其它OutputStream对象增加额外的功能。见下表见下表

装饰输出字节流:

功能如何构造怎样使用
DataOutputStream通常和DataInputStream配合使用,使用它可以写入基本数据类新。使用OutputStream构造包含大量的写入基本数据类型的方法。
PrintStream产生具有格式的输出信息。(一般地在java程序中DataOutputStream用于数据的存储,即J2EE中持久层完成的功能,PrintStream完成显示的功能,类似于J2EE中表现层的功能)使用OutputStream和一个可选的表示缓存是否在每次换行时是否flush的标记构造。还提供很多和文件相关的构造方法。一般是一个终极(“final”)的包装器,很多时候我们都使用它!
BufferedOutputStream使用它可以避免频繁地向IO写入数据,数据一般都写入一个缓存区,在调用flush方法后会清空缓存、一次完成数据的写入。从一个OutputStream或者和一个代表缓存区大小的可选参数构造。提供和其它OutputStream一致的接口,只是内部提供一个缓存的功能。

IO中的字节流使用

1.FileInputStream使用

 public static void main(String[] args) {  

      int count=0;  //统计文件字节长度  
      InputStreamstreamReader = null;   //文件输入流  
      try{  
          streamReader=newFileInputStream(new File("D:/files/test.jpg"));  

          while(streamReader.read()!=-1) {  //读取文件字节,并递增指针到下一个字节  
             count++;  
          }  
          System.out.println("---长度是: "+count+" 字节");  
      }catch (final IOException e) {  

          e.printStackTrace();  
      }finally{  
          try{  
             streamReader.close();  
          }catch (IOException e) {  

             e.printStackTrace();  
          }  
      }  
   }  
}  

上面的程序存在问题是,每读取一个自己我都要去用到FileInputStream,我输出的结果是“—长度是: 64982 字节”,那么进行了64982次操作!可能想象如果文件十分庞大,这样的操作肯定会出大问题,所以引出了缓冲区的概念。可以将streamReader.read()改成streamReader.read(byte[]b)此方法读取的字节数目等于字节数组的长度,读取的数据被存储在字节数组中,返回读取的字节数,InputStream还有其他方法mark,reset,markSupported方法,例如:

markSupported 判断该输入流能支持mark 和 reset 方法。
mark用于标记当前位置;在读取一定数量的数据(小于readlimit的数据)后使用reset可以回到mark标记的位置。
FileInputStream不支持mark/reset操作;BufferedInputStream支持此操作;
mark(readlimit)的含义是在当前位置作一个标记,制定可以重新读取的最大字节数,也就是说你如果标记后读取的字节数大于readlimit,你就再也回不到回来的位置了。
通常InputStream的read()返回-1后,说明到达文件尾,不能再读取。除非使用了mark/reset。

2.FileOutputStream 使用

public class FileCopy {  

  public static void main(String[] args) {  

     byte[] buffer=new byte[512];   //一次取出的字节数大小,缓冲区大小  
     int numberRead=0;  
     FileInputStream input=null;  
     FileOutputStream out =null;  
     try {  
        input=new FileInputStream("D:/files/test.jpg");  
        out=new FileOutputStream("D:/files/test2.jpg"); //如果文件不存在会自动创建  

        while ((numberRead=input.read(buffer))!=-1) {  //numberRead的目的在于防止最后一次读取的字节小于buffer长度,  
           out.write(buffer, 0, numberRead);       //否则会自动被填充0  
        }  
     } catch (final IOException e) {  

        e.printStackTrace();  
     }finally{  
        try {  
           input.close();  
           out.close();  
        } catch (IOException e) {  

           e.printStackTrace();  
        }  

     }  
  }  

}  

3.ObjectInputStream 和ObjectOutputStream使用
该流允许读取或写入用户自定义的类,但是要实现这种功能,被读取和写入的类必须实现Serializable接口,其实该接口并没有什么方法,可能相当于一个标记而已,但是确实不合缺少的。

public class ObjetStream {  

   /** 
    * @param args 
    */  
   public static void main(String[] args) {  

      ObjectOutputStream objectwriter=null;  
      ObjectInputStream objectreader=null;  

      try {  
         objectwriter=new ObjectOutputStream(new FileOutputStream("D:/files/student.txt"));  
         objectwriter.writeObject(new Student("gg", 22));  
         objectwriter.writeObject(new Student("tt", 18));  
         objectwriter.writeObject(new Student("rr", 17));  
         objectreader=new ObjectInputStream(new FileInputStream("D:/files/student.txt"));  
         for (int i = 0; i < 3; i++) {  
            System.out.println(objectreader.readObject());  
         }  
      } catch (IOException | ClassNotFoundException e) {  

         e.printStackTrace();  
      }finally{  
         try {  
            objectreader.close();  
            objectwriter.close();  
         } catch (IOException e) {  

            e.printStackTrace();  
         }  

      }  

   }  

}  
class Student implements Serializable{  
   private String name;  
   private int age;  

   public Student(String name, int age) {  
      super();  
      this.name = name;  
      this.age = age;  
   }  

   @Override  
   public String toString() {  
      return "Student [name=" + name + ", age=" + age + "]";  
   }  


}  

运行后系统输出:
Student [name=gg, age=22]
Student [name=tt, age=18]
Student [name=rr, age=17]

4.DataInputStream与DataOutputStream使用
有时没有必要存储整个对象的信息,而只是要存储一个对象的成员数据,成员数据的类型假设都是Java的基本数据类型,这样的需求不必使用到与Object输入、输出相关的流对象,可以使用DataInputStream、DataOutputStream来写入或读出数据。

public class Member {  
    private String name;  
    private int age;  
    public Member() {  
    }  
   public Member(String name, int age) {  
        this.name = name;  
        this.age = age;  
    }  
    public void setName(String name){  
        this.name = name;  
    }  
    public void setAge(int age) {  
        this.age = age;  
    }  
    public String getName() {  
        return name;  
    }  
    public int getAge() {  
        return age;  
    }  
}  

打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。

public class DataStreamDemo  
{  
  public static void main(String[]args)  
  {  
     Member[] members = {newMember("Justin",90),  
                        newMember("momor",95),  
                        newMember("Bush",88)};  
        try  
     {  
        DataOutputStreamdataOutputStream = new DataOutputStream(new FileOutputStream(args[0]));  

        for(Member member:members)  
        {  
            //写入UTF字符串  
           dataOutputStream.writeUTF(member.getName());  
           //写入int数据  
           dataOutputStream.writeInt(member.getAge());  
        }  

        //所有数据至目的地  
        dataOutputStream.flush();  
        //关闭流  
        dataOutputStream.close();  

            DataInputStreamdataInputStream = new DataInputStream(new FileInputStream(args[0]));  

        //读出数据并还原为对象  
        for(inti=0;i<members.length;i++)  
        {  
           //读出UTF字符串  
           String name =dataInputStream.readUTF();  
           //读出int数据  
           int score =dataInputStream.readInt();  
           members[i] = newMember(name,score);  
        }  

        //关闭流  
        dataInputStream.close();  

        //显示还原后的数据  
        for(Member member : members)  
        {  
           System.out.printf("%s\t%d%n",member.getName(),member.getAge());  
        }  
     }  
     catch(IOException e)  
     {  
            e.printStackTrace();  
     }  
  }  
}  

5.PushbackInputStream
PushbackInputStream类继承了FilterInputStream类是iputStream类的修饰者。提供可以将数据插入到输入流前端的能力(当然也可以做其他操作)。简而言之PushbackInputStream类的作用就是能够在读取缓冲区的时候提前知道下一个字节是什么,其实质是读取到下一个字符后回退的做法,这之间可以进行很多操作,这有点向你把读取缓冲区的过程当成一个数组的遍历,遍历到某个字符的时候可以进行的操作,当然,如果要插入,能够插入的最大字节数是与推回缓冲区的大小相关的,插入字符肯定不能大于缓冲区吧!

/** 
 * 回退流操作 
 * */  
public class PushBackInputStreamDemo {  
public static void main(String[] args) throws IOException {  
    String str = "hello,rollenholt";  
    PushbackInputStream push = null; // 声明回退流对象  
    ByteArrayInputStream bat = null; // 声明字节数组流对象  
    bat = new ByteArrayInputStream(str.getBytes());  
    push = new PushbackInputStream(bat); // 创建回退流对象,将拆解的字节数组流传入  
    int temp = 0;  
    while ((temp = push.read()) != -1) { // push.read()逐字节读取存放在temp中,如果读取完成返回-1  
       if (temp == ',') { // 判断读取的是否是逗号  
          push.unread(temp); //回到temp的位置  
          temp = push.read(); //接着读取字节  
          System.out.print("(回退" + (char) temp + ") "); // 输出回退的字符  
       } else {  
          System.out.print((char) temp); // 否则输出字符  
       }  
    }  
}  
}  

6.SequenceInputStream使用
当我们需要从多个输入流中向程序读入数据。此时,可以使用合并流,将多个输入流合并成一个SequenceInputStream流对象。SequenceInputStream会将与之相连接的流集组合成一个输入流并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 合并流的作用是将多个源合并合一个源。其可接收枚举类所封闭的多个字节流对象。

public class SequenceInputStreamTest {  
  /** 
   * @param args 
   *            SequenceInputStream合并流,将与之相连接的流集组合成一个输入流并从第一个输入流开始读取, 
   *            直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 
   *            合并流的作用是将多个源合并合一个源。可接收枚举类所封闭的多个字节流对象。 
   */  
  public static void main(String[] args) {  
     doSequence();  
  }  

  private static void doSequence() {  
     // 创建一个合并流的对象  
     SequenceInputStream sis = null;  
     // 创建输出流。  
     BufferedOutputStream bos = null;  
     try {  
        // 构建流集合。  
        Vector<InputStream> vector = new Vector<InputStream>();  
        vector.addElement(new FileInputStream("D:\text1.txt"));  
        vector.addElement(new FileInputStream("D:\text2.txt"));  
        vector.addElement(new FileInputStream("D:\text3.txt"));  
        Enumeration<InputStream> e = vector.elements();  

        sis = new SequenceInputStream(e);  

        bos = new BufferedOutputStream(new FileOutputStream("D:\text4.txt"));  
        // 读写数据  
        byte[] buf = new byte[1024];  
        int len = 0;  
        while ((len = sis.read(buf)) != -1) {  
           bos.write(buf, 0, len);  
           bos.flush();  
        }  
     } catch (FileNotFoundException e1) {  
        e1.printStackTrace();  
     } catch (IOException e1) {  
        e1.printStackTrace();  
     } finally {  
        try {  
           if (sis != null)  
              sis.close();  
        } catch (IOException e) {  
           e.printStackTrace();  
        }  
        try {  
           if (bos != null)  
              bos.close();  
        } catch (IOException e) {  
           e.printStackTrace();  
        }  
     }  
  }  
}  

7.PrintStream使用
System.out这个对象就是PrintStream

File类

File 类有一个欺骗性的名字—— 通常会认为它对付的是一个文件,但实情并非如此。它既代表一个特定文件的名字,也代表目录内一系列文件的名字。若代表一个文件集,便可用list()方法查询这个集,返回的是一个字串数组。之所以要返回一个数组,而非某个灵活的集合类,是因为元素的数量是固定的。而且若想得到一个不同的目录列表,只需创建一个不同的 File 对象即可。事实上,“ FilePath”(文件路径)似乎是一个更好的名字。

目录列表器
可用两种方式列出 File 对象。若在不含自变量(参数)的情况下调用list(),会获得 File 对象包含的一个完整列表。然而,若想对这个列表进行某些限制,就需要使用一个“目录过滤器”,该类的作用是指出应如何选择 File 对象来完成显示。

//: DirList.java
// Displays directory listing
package c10;
import java.io.*;
public class DirList {
public static void main(String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new DirFilter(args[0])); 

for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
}
class DirFilter implements FilenameFilter {
String afn;
DirFilter(String afn) { this.afn = afn; }
public boolean accept(File dir, String name) {
// Strip path information:
String f = new File(name).getName();
return f.indexOf(afn) != -1;
}
} /// 

DirFilter 类“实现”了 interface FilenameFilter(关于接口的问题,已在第 7 章进行了详述)。下面让我们看看 FilenameFilter 接口有多么简单:
public interface FilenameFilter {
boolean accept(文件目录, 字串名);
}

之所以要创建这样的一个类,背后的全部原因就是把 accept()方法提供给 list()方法,使 list()能够“回调” accept() ,从而判断应将哪些文件名包括到列表中。因此,通常将这种技术称为“回调”,有时也称为“算子”(也就是说, DirFilter 是一个算子,因为它唯一的作用就是容纳一个方法)。

通过 DirFilter,我们看出尽管一个“接口”只包含了一系列方法,但并不局限于只能写那些方法(但是,至少必须提供一个接口内所有方法的定义。在这种情况下, DirFilter 构建器也会创建)。

accept()方法必须接纳一个 File 对象,用它指示用于寻找一个特定文件的目录;并接纳一个 String,其中包含了要寻找之文件的名字。可决定使用或忽略这两个参数之一,但有时至少要使用文件名。记住list()方法准备为目录对象中的每个文件名调用 accept(),核实哪个应包含在内—— 具体由 accept()返回的“布尔”结果决定。

1.匿名内部类
下例用一个匿名内部类来重写显得非常理想。首先创建了一个filter()方法,它返回指向 FilenameFilter 的一个句柄:

//: DirList2.java
// Uses Java 1.1 anonymous inner classes
import java.io.*; 
public class DirList2 {
public static FilenameFilter
filter(final String afn) {
// Creation of anonymous inner class:
return new FilenameFilter() {
String fn = afn;
public boolean accept(File dir, String n) {
// Strip path information:
String f = new File(n).getName();
return f.indexOf(fn) != -1;
}
}; // End of anonymous inner class
}
public static void main(String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(filter(args[0]));
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///: 

注意 filter()的自变量必须是 final。这一点是匿名内部类要求的,使其能使用来自本身作用域以外的一个对象。

之所以认为这样做更好,是由于 FilenameFilter 类现在同 DirList2 紧密地结合在一起。然而,我们可采取进一步的操作,将匿名内部类定义成 list()的一个参数,使其显得更加精简。如下所示:


//: DirList3.java
// Building the anonymous inner class "in-place"
import java.io.*;
public class DirList3 {
public static void main(final String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(
new FilenameFilter() {
public boolean
accept(File dir, String n) {
String f = new File(n).getName();
291
return f.indexOf(args[0]) != -1;
}
});
for(int i = 0; i < list.length; i++)
System.out.println(list[i]); 

} catch(Exception e) {
e.printStackTrace();
}
}
} ///: 

main()现在的自变量是 final,因为匿名内部类直接使用 args[0]。
这展示了如何利用匿名内部类快速创建精简的类,以便解决一些复杂的问题。由于Java 中的所有东西都与类有关,所以它无疑是一种相当有用的编码技术。它的一个好处是将特定的问题隔离在一个地方统一解决。但在另一方面,这样生成的代码不是十分容易阅读,所以使用时必须慎重。

2.顺序目录列表
经常都需要文件名以排好序的方式提供。由于 Java 1.0 和 Java 1.1 都没有提供对排序的支持(从 Java 1.2开始提供),所以必须用 SortVector 将这一能力直接加入自己的程序。就象下面这样:

//: SortedDirList.java
// Displays sorted directory listing
import java.io.*;
import c08.*; 
public class SortedDirList {
private File path;
private String[] list;
public SortedDirList(final String afn) {
path = new File(".");
if(afn == null)
list = path.list();
else
list = path.list(
new FilenameFilter() {
public boolean
accept(File dir, String n) {
String f = new File(n).getName();
return f.indexOf(afn) != -1;
}
});
sort();
}
void print() {
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
}
private void sort() {
StrSortVector sv = new StrSortVector();
for(int i = 0; i < list.length; i++)
sv.addElement(list[i]);
// The first time an element is pulled from 
// the StrSortVector the list is sorted:
for(int i = 0; i < list.length; i++)
list[i] = sv.elementAt(i);
}
// Test it:
public static void main(String[] args) {
SortedDirList sd;
if(args.length == 0)
sd = new SortedDirList(null);
else
sd = new SortedDirList(args[0]);
sd.print();
}
} ///:~ 

这里进行了另外少许改进。不再是将 path(路径)和 list(列表)创建为 main()的本地变量,它们变成了类的成员,使它们的值能在对象“生存”期间方便地访问。

这种排序不要求区分大小写,所以最终不会得到一组全部单词都以大写字母开头的列表,跟着是全部以小写字母开头的列表。然而,我们注意到在以相同字母开头的一组文件名中,大写字母是排在前面的—— 这对标准的排序来说仍是一种不合格的行为。 Java 1.2 已成功解决了这个问题。

检查与创建目录
File 类并不仅仅是对现有目录路径、文件或者文件组的一个表示。亦可用一个 File 对象新建一个目录,甚至创建一个完整的目录路径—— 假如它尚不存在的话。亦可用它了解文件的属性(长度、上一次修改日期、读/写属性等),检查一个 File 对象到底代表一个文件还是一个目录,以及删除一个文件等等。下列程序完整展示了如何运用 File 类剩下的这些方法:

//: MakeDirectories.java
// Demonstrates the use of the File class to
// create directories and manipulate files.
import java.io.*; 
public class MakeDirectories {
private final static String usage =
"Usage:MakeDirectories path1 ...\n" +
"Creates each path\n" +
"Usage:MakeDirectories -d path1 ...\n" +
"Deletes each path\n" +
"Usage:MakeDirectories -r path1 path2\n" +
"Renames from path1 to path2\n";
private static void usage() {
System.err.println(usage);
System.exit(1);
}
private static void fileData(File f) {
System.out.println(
"Absolute path: " + f.getAbsolutePath() +
"\n Can read: " + f.canRead() +
"\n Can write: " + f.canWrite() +
"\n getName: " + f.getName() +
"\n getParent: " + f.getParent() + 
"\n getPath: " + f.getPath() +
"\n length: " + f.length() +
"\n lastModified: " + f.lastModified());
if(f.isFile())
System.out.println("it's a file");
else if(f.isDirectory())
System.out.println("it's a directory");
}
public static void main(String[] args) {
if(args.length < 1) usage();
if(args[0].equals("-r")) {
if(args.length != 3) usage();
File
old = new File(args[1]),
rname = new File(args[2]);
old.renameTo(rname);
fileData(old);
fileData(rname);
return; // Exit main
}
int count = 0;
boolean del = false;
if(args[0].equals("-d")) {
count++;
del = true;
}
for( ; count < args.length; count++) {
File f = new File(args[count]);
if(f.exists()) {
System.out.println(f + " exists");
if(del) {
System.out.println("deleting..." + f);
f.delete();
}
}
else { // Doesn't exist
if(!del) {
f.mkdirs();
System.out.println("created " + f);
}
}
fileData(f);
}
}
} ///:~ 

在 fileData()中,可看到应用了各种文件调查方法来显示与文件或目录路径有关的信息。
main()应用的第一个方法是 renameTo(),利用它可以重命名(或移动)一个文件至一个全新的路径(该路径由参数决定),它属于另一个 File 对象。这也适用于任何长度的目录。
若试验上述程序,就可发现自己能制作任意复杂程度的一个目录路径,因为mkdirs() 会帮我们完成所有工作。在 Java 1.0 中, -d 标志报告目录虽然已被删除,但它依然存在;但在 Java 1.1 中,目录会被实际删除。

IO中的输入字符流

1.Reader是所有的输入字符流的父类,它是一个抽象类。2.CharReader、StringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。PipedReader是从与其它线程共用的管道中读取数据。
3. BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。
4. FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。
5. InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。我们可以从这个类中得到一定的技巧。

IO中的输出字符流

1.Writer是所有的输出字符流的父类,它是一个抽象类。
2. CharArrayWriter、StringWriter是两种基本的介质流,它们分别向Char数组、String中写入数据。PipedWriter是向与其它线程共用的管道中写入数据
3. BufferedWriter是一个装饰器为Writer提供缓冲功能。
4. PrintWriter和PrintStream极其类似,功能和使用也非常相似。
5. OutputStreamWriter是OutputStream到Writer转换的桥梁,它的子类FileWriter其实就是一个实现此功能的具体类(具体可以研究一下Source Code)。功能和使用和OutputStream极其类似,后面会有它们的对应图。

IO字符流的使用

1.FileReader与PrintWriter

public class Print {  

/** 
 * @param args 
 */  
public static void main(String[] args) {  
    // TODO自动生成的方法存根  
    char[] buffer=new char[512];   //一次取出的字节数大小,缓冲区大小  
    int numberRead=0;  
    FileReader reader=null;        //读取字符文件的流  
    PrintWriter writer=null;    //写字符到控制台的流  

    try {  
       reader=new FileReader("D:/files/copy1.txt");  
       writer=new PrintWriter(System.out);  //PrintWriter可以输出字符到文件,也可以输出到控制台  
       while ((numberRead=reader.read(buffer))!=-1) {  
          writer.write(buffer, 0, numberRead);  
       }  
    } catch (IOException e) {  

       e.printStackTrace();  
    }finally{  
       try {  
          reader.close();  
       } catch (IOException e) {  

          e.printStackTrace();  
       }  
       writer.close();       //这个不用抛异常  
    }  

}  

}  

2.BufferedWriter与BufferedReader

public class FileConcatenate {  

  /** 
   * 包装类进行文件级联操作 
   */  
  public static void main(String[] args) {  

     try {  
        concennateFile(args);  
     } catch (IOException e) {  

        e.printStackTrace();  
     }  
  }  
  public static voidconcennateFile(String...fileName) throws IOException{  
     String str;  
     //构建对该文件您的输入流  
     BufferedWriter writer=new BufferedWriter(new FileWriter("D:/files/copy2.txt"));  
     for(String name: fileName){  
        BufferedReader reader=new BufferedReader(new FileReader(name));  

        while ((str=reader.readLine())!=null) {  
           writer.write(str);  
           writer.newLine();  
        }  
     }  
  }  

}  

4.Console
该类提供了用于读取密码的方法,可以禁止控制台回显并返回char数组,对两个特性对保证安全有作用,平时用的不多,了解就行。

5.StreamTokenizer
它可以把输入流解析为标记(token), StreamTokenizer 并非派生自InputStream或者OutputStream,而是归类于io库中,因为StreamTokenizer只处理InputStream对象。

/** 
 * 使用StreamTokenizer来统计文件中的字符数 
 * StreamTokenizer 类获取输入流并将其分析为“标记”,允许一次读取一个标记。 
 * 分析过程由一个表和许多可以设置为各种状态的标志控制。 
 * 该流的标记生成器可以识别标识符、数字、引用的字符串和各种注释样式。 
 * 
 *  默认情况下,StreamTokenizer认为下列内容是Token: 字母、数字、除C和C++注释符号以外的其他符号。 
 *  如符号"/"不是Token,注释后的内容也不是,而"\"是Token。单引号和双引号以及其中的内容,只能算是一个Token。 
 *  统计文章字符数的程序,不是简单的统计Token数就万事大吉,因为字符数不等于Token。按照Token的规定, 
 *  引号中的内容就算是10页也算一个Token。如果希望引号和引号中的内容都算作Token,应该调用下面的代码: 
 *    st.ordinaryChar('\''); 
 * st.ordinaryChar('\"'); 
 */  
public class StreamTokenizerExample {  

    /** 
     * 统计字符数 
     * @param fileName 文件名 
     * @return    字符数 
     */  
public static void main(String[] args) {  
        String fileName = "D:/files/copy1.txt";  
        StreamTokenizerExample.statis(fileName);  
    }  
    public static long statis(String fileName) {  

        FileReader fileReader = null;  
        try {  
            fileReader = new FileReader(fileName);  
            //创建分析给定字符流的标记生成器  
            StreamTokenizer st = new StreamTokenizer(new BufferedReader(  
                    fileReader));  

            //ordinaryChar方法指定字符参数在此标记生成器中是“普通”字符。  
            //下面指定单引号、双引号和注释符号是普通字符  
            st.ordinaryChar('\'');  
            st.ordinaryChar('\"');  
            st.ordinaryChar('/');  

            String s;  
            int numberSum = 0;  
            int wordSum = 0;  
            int symbolSum = 0;  
            int total = 0;  
            //nextToken方法读取下一个Token.  
            //TT_EOF指示已读到流末尾的常量。  
            while (st.nextToken() !=StreamTokenizer.TT_EOF) {  
                //在调用 nextToken 方法之后,ttype字段将包含刚读取的标记的类型  
                switch (st.ttype) {  
                //TT_EOL指示已读到行末尾的常量。  
                case StreamTokenizer.TT_EOL:  
                    break;  
                //TT_NUMBER指示已读到一个数字标记的常量  
                case StreamTokenizer.TT_NUMBER:  
                    //如果当前标记是一个数字,nval字段将包含该数字的值  
                    s = String.valueOf((st.nval));  
                    System.out.println("数字有:"+s);  
                    numberSum ++;  
                    break;  
                //TT_WORD指示已读到一个文字标记的常量  
                case StreamTokenizer.TT_WORD:  
                    //如果当前标记是一个文字标记,sval字段包含一个给出该文字标记的字符的字符串  
                    s = st.sval;  
                    System.out.println("单词有: "+s);  
                    wordSum ++;  
                    break;  
                default:  
                    //如果以上3中类型都不是,则为英文的标点符号  
                    s = String.valueOf((char) st.ttype);  
                    System.out.println("标点有: "+s);  
                    symbolSum ++;  
                }  
            }  
            System.out.println("数字有 " + numberSum+"个");  
            System.out.println("单词有 " + wordSum+"个");  
            System.out.println("标点符号有: " + symbolSum+"个");  
            total = symbolSum + numberSum +wordSum;  
            System.out.println("Total = " + total);  
            return total;  
        } catch (Exception e) {  
            e.printStackTrace();  
            return -1;  
        } finally {  
            if (fileReader != null) {  
                try {  
                    fileReader.close();  
                } catch (IOException e1) {  
                }  
            }  
        }  
    }  


}  
  • 10
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
Java IO(Input/Output)是Java编程语言中用于处理输入和输出的基础库。它提供了一种方便的方式来读取和写入数据,从而与外部世界进行交互。 Java IO库包含多个类和接口,用于在不同的场景下处理输入和输出。这些类和接口可以分为字节流(Byte Stream)和字符流(Character Stream)两种类型。 字节流主要用于处理二进制数据,而字符流则用于处理文本数据。 常用的字节流类有: - InputStream和OutputStream:用于读取和写入字节数据。 - FileInputStream和FileOutputStream:用于读取和写入文件。 - BufferedInputStream和BufferedOutputStream:提供了缓冲功能,以提高读写的效率。 常用的字符流类有: - Reader和Writer:用于读取和写入字符数据。 - FileReader和FileWriter:用于读取和写入文本文件。 - BufferedReader和BufferedWriter:提供了缓冲功能,以提高读写的效率。 除了字节流和字符流之外,Java IO还提供了一些其他的类和接口,用于处理特定类型的输入和输出。例如: - DataInputStream和DataOutputStream:用于读写基本数据类型及字符串。 - ObjectInputStream和ObjectOutputStream:用于读写Java对象。 - PrintWriter:用于格式化输出。 在使用Java IO时,通常需要使用try-catch语句来捕获可能抛出的异常,例如IOException。 总结起来,Java IOJava编程语言中用于处理输入和输出的基础库,提供了字节流和字符流两种类型的处理方式,并且还包含其他一些类和接口,用于处理特定类型的输入和输出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的代码家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值