zoukankan      html  css  js  c++  java
  • 流与文件(二)

    文本流

    前面讨论的是二进制的输入输出,如果直接打开文件,会发现里面不是我们能读懂的内容。(用记事本打开里面是些空格)虽然二进制I/O速度快且效率高,但不易于人们阅读。java中的字符串,使用的是Unicode字符,例如字符串"1234"在字符编码中实际上是【00 31 00 32 00 33 00 34】,然而,Java所运行的环境有自己的字符编码,例如Windows用ASCII码,编码为【31 32 33 34】。为了在运行环境的编码和Unicode编码之间转换,Java提供了一套流过滤器。例如InputStreamReader/OutputStreamWriter

    InputStreamReader in = new InputStreamReader(System.in);//从控制台读入,并自动将其转化为Unicode码。

    字符集

    在JDK1.4中引入的java.nio包通过引入Charset类来统一字符集的转换。

    字符集给出了双字节Unicode码序列与在本地字符编码中采用的字节序列间的映射。一旦有了字符集,就可以用它在Unicode字符串和字节序列编码之间进行转换。

    文本输出

    进行文本输出时,应该使用PrintWriter。

    PrintWriter out = new PrintWriter(new FileOutputStream("employee.txt"));

    PrintWriter(OutputStream)构造器自动增加一个OutputStreamWriter来将Unicode字符转换为本地字符。

    PrintWriter中有print方法和println方法,用以写入数据。

    String name = "Harry Hacker";
    double salary = 75000;
    out.print(name);
    out.print(' ');
    out.println(salary);

    这将下列字符

    Harry Hacker 75000

    写入输出流out中。随后字符被转换为字节病最终进入文件employee.txt中。

    PrintWriter总是缓冲的,可以通过PrintWriter(Writer, boolean)构造器中的第二个参数来开启或关闭自动刷新。如果开启,那么println将刷新缓冲区。

    文本输入

    BufferedReader类,readLine方法,以行的方式读取文本。

    BufferedReader in = new BufferedReader(new FileReader("employee.txt"));

    如果没有输入数据,readLine方法返回null。

    FileReader类已经把本地字节转化为Unicode字符。对于其他输入源,需要使用InputStreamReader

    BufferedReader in = BufferedReader(new InputStreamReader(System.in));

    流的使用

    分隔符输出,例如Employee类的如下记录:

    Harry Hacker|35500|1989|10|1

    Carl Cracker|75000|1987|12|15

    Tony Tester|38000|1990|3|15

    每个实例域由分隔符【|】隔开。

    实现的方法是在Employee类中增加一个方法:writeData

    public void writeData(PrintWriter out) throws IOException
    {
        GregorianCalendar calendar = new GregorianCalendar();
        calendar.setTime(hireDay);
        out.println(name + "|"
            + salary + "|"
            + calendar.get(Calendar.YEAR) + "|"
            + (calendar.get(Calendar.MONTH) + 1) + "|"
            + calendar.get(Calendar.DAY_OF_MONTH));
    }

    Java中,处理带分隔符的字符串,使用StringTokenizer类。(C++中使用string类的find和substr方法)

    StringTokenizer tokenizer = new StringTokenizer(line, "|");

    也可以在一个字符串里指定多个分隔符,例如:

    StringTokenizer tokenizer = new StringTokenizer(line, "|,;");

    如果不指定分隔符集合,默认的就是" ",即所有的空白字符(空格、tab、新行,回车)。

    while(tokenizer.hasMoreTokens())
    {
        String token = tokenizer.nextToken();
        process token;
    }

    这与C语言中的strtok函数类似

    char *p = strtok("a.b.c.d.e",".")
    while(p != NULL)
    {
        printf("%s
    ",p);
        p = strtok(NULL, ".");
    }

    下面,在Employee类中实现一个readData的方法,来读取带分隔符的数据。

    public void readData(BufferedReader in) throws IOException
    {
        String s = in.readLine();
        StringTokenizer t = new StringTokenizer(s, "|");
        name = t.nextToken();
        salary = Double.parseDouble(t.nextToken());
        int y = Integer.parseInt(t.nextToken());
        int m = Integer.parseInt(t.nextToken());
        int d = Integer.parseInt(t.nextToken());
        GregorianCalendar calendar = new GregorianCalendar(y,m-1,d);
            //GregorianCalendar uses 0 = January
        hireDay = calendar.getTime();
    }

     随机存取流

     如果每一条记录的长度不相等,就没法用RandomAccessFile类的seek方法来定位第n条记录。

     为了让每一条记录长度相等,我们需要自己定义一个方法。

    static void writeFixedString(String s, int size, DataOutput out) throws IOException
    {
        int i;
        for(int i=0; i<size; i++)
        {
            if(i<s.length())
            {
                out.writeChar(s.charAt(i));
            }
            else
            {
                out.writeChar(0);
            }
        }
    }

    而读取则需要读到一个0值字符为止。

    static String readFixedString(int size, DataInput in)throws IOException
    {
        StringBuilder b = new StringBuilder(size);
        int i=0;
        while(i<size)
        {
            char ch = in.readChar();
            i++;
            if(0 == ch)
            {
                break;
            }
            else
            {
                b.append(ch);
            }
        }
        in.skipBytes(2*(size-i));
        return b.toString();
    }

    这里的StringBuilder类比String类的优势在于,如果不断对String进行【+】拼接,那么字符串的空间要一次一次重新分配。

    java.lang.StringBuilder

    StringBuilder()

    StringBuilder(int length)//初始长度length

    StringBuilder(String str)//初始内容str

    int length()//返回builder的长度

    StringBuilder append(String str/char  c)//追加字符串str或字符c

    void setCharAt(int i, char c)//将第i个代码单元设置成c

    StringBuilder insert(int offset, String str/char c)//在offset位置插入一个字符串str或者字符c

    StringBuilder delete(int startIndex, int endIndex)//删除从startIndex到endIndex-1的内容

    String toString()//返回一个与builder内容相同的字符串

    对象流

    如果存储同类型的数据,使用固定长度的记录格式是一个很好的选择。但当类型不同,例如既有Employee类,又有其子类Manager类时,就不能这么做了。

    存储对象的方法是序列化:序列化就是将一个对象的状态(各个成员变量)保存起来,然后在适当的时候再获得。
    序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。

    序列化的什么特点:
    如果某个类能够被序列化,其子类也可以被序列化。声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态, transient代表对象的临时数据。

    什么时候使用序列化:
    一:对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
    二:java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。

    需要序列化的类必须实现Serializable接口,该接口没有任何方法,所以不需要对类进行任何修改。

    一个类实现了Serializable接口后,就可以通过ObjectOutputStream类的对象进行存储,通过ObjectInputStream类的对象读取:

    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("employee.dat"));
    Employee harry = new Employee("Harry Hacker",50000,1989,10,1);
    Manager boss = new Manager("Carl Cracker",80000,1987,12,25);
    out.writeObject(harry);
    out.writeObject(boss);
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("Employee.dat"));
    Employee e1 = (Employee) in.readObject();
    Employee e2 = (Employee) in.readObject();

    读取的时候需要注意,读的顺序与写入的顺序是一致的,且readObject方法返回的是Object类型的对象,需要进行转化。如果需要动态查询对象的类型,可以使用getClass方法。

    对象流类实现了DataInput/DataOutput接口,对于基本类型(非对象)的值,可以使用writeInt/readInt等方法。

    前面提到过,不想被序列化的成员变量要声明为transient类型,实际上,Java中有些包里的类是不能被序列化的,当这些类的对象是另一个类的数据成员时,在类中就要声明为transient。否则对整个类进行序列化时会抛出NotSerializableException异常。如果想记录这些不能被序列化的对象,可以通过自己定义readObject和writeObject方法,来取代默认的这两种方法:这两种方法定义在被序列化的类中。

    例如:LabeledPoint类中包含不能被序列化的Point2D.Double类的对象,现在要序列化LabeledPoint类的对象,就需要两步:

    1.将Point2D.Double类的对象声明为transient类型;

    2.在LabeledPoint类中定义自己的readObject/writeObject方法。

    public class LabeledPoint implements Serializable
    {
        ...
        
        private void writeObject(ObjectOutputStream out)throws IOException
        {
            out.defaultWriteObject();
            out.writeDouble(point.getX());
            out.writeDouble(point.getY());
        }
        private void readObject(ObjectInputStream out)throws IOException
        {
            in.defaultWriteObject();
            double x = in.readDouble();
            double y = in.readDouble();
            point = new Point2D.Double(x,y);
        }
        private String label;
        private transient Point2D.Double point;
    }

    类可以定义自己的机制,而不需要让序列化机制存储和恢复对象数据。要做到这点,必须实现Externalizable接口。这就需要定义下面两个方法:

    public void readExternal(ObjectInputStream in) throws IOException, ClassNotFoundException;

    public void writeExternal(ObjectOutputStream out) throws IOException;

    不同于上一节介绍的readObject和writeObject方法,这些方法将负责整个对象(包括超类数据)的保存和恢复。下面是Employee类实现的这些方法:

    public void readExternal(ObjectInputStream in) throws IOException
    {
        name = s.readUTF();
        salary = s.rreadDouble();
        hireDay = new Date(s.readLong());
    }
    
    public void writeExternal(ObjectOutputStream out) throws IOException
    {
        s.writeUTF(name);
        s.writeDouble(salary);
        s.writeLong(hireDay.getTime());
    }

    注意:readObject/writeObject方法是私有的,并且只能被序列化机制调用;readExternal/writeExternal方法是共有的。

  • 相关阅读:
    TreeView控件应用(包含递归调用)
    FTP操作(FTPClient)
    利用正则表达式 进行字符的判断
    复合查询
    NPOI操作
    导航特效
    拼音检索
    项目中的注意事项
    计算文件的MD5值上传到服务器 下载验证文件是否被篡改
    OLE Automation (C#读取EXCEL)
  • 原文地址:https://www.cnblogs.com/johnsblog/p/4163121.html
Copyright © 2011-2022 走看看