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方法是共有的。

  • 相关阅读:
    轻量级数据库sqlite的使用
    Integer引发的思考
    css限制显示行数
    数据库 chapter 17 数据仓库与联机分析处理技术
    数据库 chapter 15 对象关系数据库系统
    数据库 chapter 16 XML数据库
    数据库 chapter 14 分布式数据库系统
    数据库 chapter 11 并发控制
    数据库 chapter 12 数据库管理系统
    数据库 chapter 13 数据库技术新发展
  • 原文地址:https://www.cnblogs.com/johnsblog/p/4163121.html
Copyright © 2011-2022 走看看