第15章 输入与输出

1.使用File来访问本地文件系统

File类可以使用文件路径字符串来创建File实例,字符串既可以是绝对路径也可以是相对路径。Windows的路径分隔符使用反斜线(\),Java里应使用\\(单个\表示转义字符)

 

2.IO流的三种分类

(1)输入流和输出流(从内存角度)

输入流:只读取数据

输出流:只写入数据

Java的输入流主要由InputStream和Reader作为基类,而输出流主要由OutputStream和Writer作为基类,均为抽象类,无法直接创建实例。

(2)字节流和字符流

字节流由InputStream和OutputStream作为基类,而字符流由Reader和Writer作为基类。

(3)节点流和处理流

从/向一个特定的IO设备读/写数据的流,称为节点流。

处理流对一个已经存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。

IO流的40多个类都是从以下4个抽象类派生的:

InputStream/Reader:所有输入流的基类,字节输入流/字符输入流

OutputStream/Writer:所有输出流的基类,字节输出流/字符输出流

对于Windows平台,字符串内容的换行符是\r\n。

 

3.输入/输出流体系

(1)处理流的用法

使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的I/O设备、文件交互。如:

try(

    FileOutputStream fos = new FileOutputStream(“test.txt”);
    //创建一个处理流,即传入一个节点流作为构造器参数即可
    PrintStream ps = new PrintStream(fos);
    {
    //执行输出
    }
)

上面程序先定义了一个节点输出流,然后程序使用PrintStream包装了该节点输出流,最后使用PrintStream输出。

计算机文件分为文本文件和二进制文件,所有能用记事本打开的都是文本文件,其他的都是二进制文件,但实质上,文本文件只是二进制文件的一种特例,也是二进制文件。

(2)转换流

InputStreamReader将字节输入流转换为字符输入流,OutputStreamWriter将字节输出流转换为字符输出流。(Java没有提供字符流转换成字节流)

 

4.重定向标准输入/输出

在System类里提供三个重定向标准方法:

Static void setErr(PrintStream err):重定向“标准”错误输出流

Static void setIn(PrintStream in):重定向“标准”输入流

Static void setOut(PrintStream out):重定向“标准”输出流

举例见书P691

衡量输入输出时总是站在运行本程序所在内存的角度。

 

5.RandomAccessFile类用法

RandomAccessFile是Java输入/输出流体系中功能最丰富的文件内容访问类,支持“随机访问”方式,自由访问文件的任意位置。但只能读写文件,不能读写其他IO节点。

RandomAccessFile提供了两个方法操作文件指针:

Long getFilePointer():返回文件记录指针的当前位置

Void seek(long pos):将文件记录指针定位到pos位置

创建RandomAccessFile还需要指定一个mode参数,即访问模式(4种):

“r”:只读

“rw”:以读、写打开指定文件,若文件不存在,则新建

“rws”:以读、写打开指定文件,相对于“rw”模式,还要求对文件内容或元素据的每个更新都同步到底层存储设备。

“rwd”:以读写打开指定文件,相对于“rw”模式,还要求对文件内容的每个更新都同步写入底层存储设备。

如:

try(
    RandomAccessFile raf = new RandomAccessFile(“out.txt”,“rw”);
    {
      raf.seek(raf.length());
      raf.write(“追加的内容!\r\n”.getbytes());
    }
)

RandomAccessFile不能向文件的指定位置插入内容,否则将会覆盖原有内容。如果需要在指定位置插入,则先把插入点之后的内容读入缓冲区,插入数据后,追加在文件后面。举例见P696

 

6.对象序列化

(1)对象的序列化(Serialize)指将一个Java对象写入IO流,对象的反序列化(Deserialize)指从IO流中恢复该对象。

如果需要让某个对象支持序列化机制,则必须它的类是可序列化的,必须实现下面两个接口之一:Serializable 或 Externalizable

举例:

public class Person implements java.io.Serializable{
    private String name;
    private int age;
    public Person(String name,int age){
        System.out.println("有参数的构造器");
        this.name = name;
        this.age = age;
    }
    //省略name与age的setter和getter方法
    ...
}

public class WriteObject{
    public static void main(String[] args)
    {
        try(
                //创建一个ObjectOutputStream输出流
                ObjectOutputStream oos = new ObjectOutputStream((new FileOutputStream("object.txt"))) {
                    Person per = new Person("孙悟空", 500);
                    //将per对象写入输出流
                    oos.writeObject(per);
                }
        catch(IOException ex){
        ex.printStackTrace();
        }
    }
}

运行上面程序,将会看到生成一个object.txt文件,文件内容是Person对象。

反序列化的代码如下:

public class ReadObject{
    public static void main(String[] args)
    {
        try(
                //创建一个ObjectInputStream输入流
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.txt")))
                {
                    //从输入流中读取一个java对象,并将其强制类型转换为Person类
                    Person p = (Person)ois.readObject();
                    System.out.println("名字为:"+p.getName()+"\n年龄为:"+p.getAge());
                }
        catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

(2)对象引用的序列化

一个类引用另一个类(如某类的一个方法:public Teacher (String name , Person student))则另一个类也必须是可序列化的。

如果多次序列化同一个Java对象时,只有第一次序列化时才会把该Java对象转换成字节序列输出,再次调用时,只是输出前面的序列化编号。

(3)自定义序列化

当对某个对象进行序列化时,系统会自动对该对象的所有实例变量依次进行序列化,如果引用到另一个对象,则被引用对象也会被序列化(递归序列化)。

在一些特殊情景下,如银行帐户信息,不应该进行序列化,则应该在实例变量前使用transient关键字修饰,被transiant修饰的实例变量将被完全隔离在序列化机制之外。如:private transient int age;

 

7.NIO(新IO支持)

新IO是面向块的处理,除了Buffer和Channel之外,还提供了逆映射操作的Charset类和支持非阻塞式输入/输出的Selector类。

(1)使用Buffer

Buffer本质是一个数组,可以保存多个类型相同的数据。Buffer是一个抽象类,最常用的子类是ByteBuffer,可以在底层字节数组上进行set/get操作。

Buffer中三个重要概念:

容量(capacity):缓冲区的容量表示该buffer的最大数据容量,不可为负值,创建后不可改变。

界限(limit):位于limit之后的数据既不可读也不可写。

位置(position):记录指针。

除此之外,buffer里还有一个可选标记mark,允许直接将position定位到mairk处。

Put()方法向Buffer中放入一些数据,position相应向后移动一些位置。

Flip()方法将limit设置为position位置,并将pisition设为0.

Clear()方法不是清空buffer数据,仅将position位置为0,将limit置为capacity,再次向Buffer中装入数据做好准备。

当使用put()和get()来访问Buffer中的数据时,分为相对和绝对两种:

相对(Relative):从当前position位置开始读取或写入数据,position值顺后增加。

绝对(Absolute)直接根据索引向Buffer中读取或写入数据,使用绝对方式访问buffer里的数据时,不会影响position的值。

(2)使用channel

 Channel可以直接将指定文件的部分或全部直接映射成Buffer。程序不能直接访问channel中的数据,包括读取写入都不行,只能与Buffer交互。Channel不是通过构造器来创建,而是通过getChannel()方法。

Channel中最常用的三个方法是map(),read(),write()。

Map方法的方法签名为:MappedByteBuffer map(FileChannel.MapMode , long position , long size),第一个参数执行映射时的模式,分为只读和读写。

举例如:

public class RandomFileChannelTest
{
    public static void main(String[] args) throw IOException
    {
         File f = new File("a.txt");
         try(
             //创建一个RandomAccessFile对象
             RandomAccessFile raf = new RandomAccessFile(f,"rw");
             //获取RandomAccessFile对应的Channel
             FileChannel randomChannel = raf.getChannel()
         )
         {
         //将Channel中的所有数据映射成ByteBuffer
         ByteBuffer buffer = randomChannel.map(FileChannel.MapMode.READ_ONLY , 0 
         ,f.length());
         //把Channel的记录指针移动到最后
         randomChannel.position(f.length());
         //将buffer中的所有数据输出
         randomChannel.write(buffer);
         }
    }
}

(3)字符集和CharSet

Charset用来处理字节序列和字符序列(字符串)之间的转换关系,Charset类是不可变的。

    1)获取Charset对象

调用Charset的forName()方法创建Charset对象,如:

Charset csCn = Charset.foeName(“GBK”);

    2)通过该对象的newDecoder()或newEncoder()方法反别返回CharsetDecoder和CharsetEncoder对象,代表解码器和编码器。调用CharsetDecoder的decoder()方法就可以将ByteBuffer(字节序列)转换成CharBuffer(字符序列)。调用CharsetEncoder的encoder()方法就可以将CharBuffer或String(字符序列)转换成ByteBuffer(字节序列)。

(4)文件锁

直接使用lock()或trylock()方法获取的文件锁是排他锁。

区别是:当lock()试图锁定某个文件时,如果无法得到文件锁,将一直阻塞。

而trylock()是尝试锁定文件,如果获得文件锁,则返回该文件锁,否则返回null。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值