14_IO_其他流

文章目录

  • 数据流
    • DataOutputStream数据输出流
    • DataInputStream数据输入流
  • 打印流
    • PrintStream字节打印流
    • PrintWriter字符打印流
  • 标准输入输出流
    • 标准输入流
    • 标准输出流
  • 对象流(序列化与反序列化流)
    • ObjectOutputStream序列化流
    • ObjectInputStream反序列化流
  • RandomAccessFile随机访问文件流
  • NIO
    • NIO核心
    • Buffer
      • Buffer的实例化
      • Buffer的存取数据
      • Buffer的常用方法
    • Channel
      • FileChannel
      • Scatter/Gather
      • 文件复制
  • 总结


数据流

DataOutputStream数据输出流

数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中
然后,应用程序可以使用数据输入流将数据读入。

构造方法

DataOutputStream(OutputStream out)        
// 创建一个新的数据输出流,将数据写入指定基础输出流。

成员方法

14_IO_其他流_第1张图片

  • 每种数据类型都有1个对应的write方法

DataInputStream数据输入流

数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型

构造方法

DataInputStream(InputStream in)       
// 使用指定的底层 InputStream 创建一个 DataInputStream。

成员方法

14_IO_其他流_第2张图片

  • 每种数据类型都有1个对应的read方法

eg:

/*
通过数据流写数据
 */
 
// 1. 创建数据输出流对象
DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("a.txt"));

// 2. writeInt
dataOutputStream.writeInt(1000);

// 3. writeDouble
dataOutputStream.writeDouble(3.14);

// 4. close
dataOutputStream.close();

/*
通过数据流读取数据
 */

// 1. 创建数据输入流对象
DataInputStream dataInputStream = new DataInputStream(new FileInputStream("a.txt"));

// 2. readInt
int readint = dataInputStream.readInt();
System.out.println(readint);

// 3. readDouble
double readdouble = dataInputStream.readDouble();
System.out.println(readdouble);

// 4. close
dataInputStream.close();

注意事项

  • 读取的顺序要跟写的顺序保持一致

打印流

核心思想: 把不同的数据类型转换成String

需求

  • 定义一个类Printer
  • 定义成员变量OutputStream
  • 定义5个方法
    • int的方法 void printInt(int a)
    • int并且换行的方法 void printIntLn(int a)
    • double的方法 void printDouble(double a)
    • double并且换行的方法 void printDoubleLn(double a)
    • 写一个close方法 void close()

eg:


class Printer{
    // 定义成员变量OutputStream
    OutputStream out;

    public Printer(OutputStream out){
        this.out = out;
    }

    // 写int的方法  void printInt(int a)
    public void printInt(int a) throws IOException {
        // int 转换成 String
        String s = String.valueOf(a);
        out.write(s.getBytes());
    }

    // 写int并且换行的方法 void printIntLn(int a)
    public void printIntLn(int a) throws IOException{
        // int 转换成 String
        String s = String.valueOf(a);
        out.write(s.getBytes());
        out.write(System.lineSeparator().getBytes());
    }

    // 写double的方法  void printDouble(double a)
    public void printDouble(double a) throws IOException{
        // int 转换成 String
        String s = String.valueOf(a);
        out.write(s.getBytes());
    }

    // 写double并且换行的方法 void printDoubleLn(double a)
    public void printDoubleLn(double a) throws IOException{
        // int 转换成 String
        String s = String.valueOf(a);
        out.write(s.getBytes());
        out.write(System.lineSeparator().getBytes());
    }

    // 写一个close方法  void  close()
    public void close() throws IOException{
        out.close();
    }

}

PrintStream字节打印流

PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式

继承关系
14_IO_其他流_第3张图片

构造方法


PrintStream(File file)        // 创建具有指定文件且不带自动行刷新的新打印流。

PrintStream(OutputStream out)       // 创建新的打印流。

PrintStream(OutputStream out, boolean autoFlush)      // 创建新的打印流。

PrintStream(String fileName)       // 创建具有指定文件名称且不带自动行刷新的新打印流。

成员方法

14_IO_其他流_第4张图片

  • 每个数据类型有一个相对应的print方法

/*
使用字节打印流打印数据
 */

// 1. 创建字节打印流对象
PrintStream printStream = new PrintStream("a.txt");

// 2. 写 1000
printStream.print(1000);

// 3. 写 true
printStream.print(true);

// 4. close
printStream.close();

PrintWriter字符打印流

向文本输出流打印对象的格式化表示形式

构造方法


PrintWriter(File file)       // 使用指定文件创建不具有自动行刷新的新 PrintWriter。

PrintWriter(OutputStream out)  //  根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。

PrintWriter(OutputStream out,  boolean autoFlush)    
// 通过现有的 OutputStream 创建新的  PrintWriter。

PrintWriter(String fileName)    //  创建具有指定文件名称且不带自动行刷新的新 PrintWriter。

PrintWriter(Writer out)    // 创建不带自动行刷新的新 PrintWriter。

PrintWriter(Writer out,  boolean autoFlush)     // 创建新 PrintWriter。

成员方法

14_IO_其他流_第5张图片

  • 每个数据类型有一个相对应的print方法

eg:

// 1.
PrintWriter printWriter = new PrintWriter("a.txt");

// 2.
printWriter.println(100);
printWriter.print("a");

// 3.
printWriter.flush();

// 4.
printWriter.close();
    

打印流特点

  • 只能操作目的地,不能操作数据来源。
    • 没有与之相对应的输入流
  • 可以操作任意类型的数据
    • 任意类型的数据—>String(String.valueOf(不同类型的数据))
  • 如果启动了自动刷新,能够自动刷新。
    • autoFlush如果为 true,则 printlnprintfformat 方法将刷新输出缓冲区
    • 下面是println源码举例:
    public void write(String s) {
        write(s, 0, s.length());
    }
    
    private void newLine() {
        try {
            synchronized (lock) {
                ensureOpen();
                out.write(System.lineSeparator());
                if (autoFlush)
                    out.flush();
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }
    
  • 可以操作文件的流
    • 构造方法里可以传File对象或者String fileName

标准输入输出流

标准输入流

  • System.in
  • 默认输入设备是键盘
  • 本质: InputStream 普通的字节输入流

标准输出流

  • System.out
  • 默认输出设备是显示器
  • 本质: PrintStream 字节打印流

注意事项

  • read方法是一个阻塞方法

eg:

需求:利用System.in 完成Scanner的nextLine()的功能


BufferedReader bufferedReader = new BufferedReader(
        new InputStreamReader(System.in));
// InputStreamReader是字节流和字符流的转换桥梁

String s = bufferedReader.readLine();
System.out.println(s);

bufferedReader.close();


对象流(序列化与反序列化流)

  • 序列化: 把对象数据转为二进制数据, 存到文件的过程
  • 反序列:(序列化的逆过程) :把二进制数据还原回对象数据的过程

ObjectOutputStream序列化流

  • ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。
    可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储
    如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
  • 只能将支持 java.io.Serializable 接口的对象写入流中,Serializable接口是一个空接口(跟cloneable接口是一样的), 起到标记的作用
  • writeObject 方法用于将对象写入流中

继承关系

14_IO_其他流_第6张图片

构造方法

ObjectOutputStream(OutputStream out)  // 创建写入指定 OutputStream 的 ObjectOutputStream。

成员方法

writeObject(Object obj)      // 将指定的对象写入 ObjectOutputStream。

ObjectInputStream反序列化流

ObjectInputStream以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化

构造方法

ObjectInputStream(InputStream in)   //  创建从指定 InputStream 读取的 ObjectInputStream。

成员方法

readObject()     //  从 ObjectInputStream 读取对象。

eg:


public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        /*
        使用对象流(序列化和反序列化)
        写入对象
        读取对象
         */

        // 序列化流操作

        // 1. 创建学生对象
        Student stu = new Student("zs",10);

        // 2. 创建序列化流的对象
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));

        // writeObject(Object obj)
        out.writeObject(stu);

        // close
        out.close();

        // 反序列化流操作

        // 1. 创建反序列化流对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("a.txt"));

        // 2. readObject()
        Object o = in.readObject();
        System.out.println(o);

        // 3. close
        in.close();

    }
}

/*
java.io.NotSerializableException 没有实现Serializable接口
 */

class Student implements Serializable {
    String name;
    int age;

    // transient修饰不想被序列化的成员变量
    transient int score;
    static final long serialVersionUID = -7889256375299507710L;

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

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

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

注意事项

  • java.io.NotSerializableException
    • 没有实现Serializable接口
  • java.io.InvalidClassException: _18io03.com.cskaoyan._04serialize.Student; local class incompatible: stream classdesc serialVersionUID = -7889256375299507710, local class serialVersionUID = 7388140007375758175
    • 设置为:static final long serialVersionUID = -7889256375299507710L;
    • SerialVersionUID不匹配
  • transient修饰不想被序列化的成员变量,就是反序列化后看不到具体的值

RandomAccessFile随机访问文件流

  • RandomAccessFile 声明在java.io包下,但直接继承于java.lang.Object类,这个类既可以读也 可以写
  • 支持 “随机访问” 的方式,程序可以直接跳到文件的任意地方来读、写文件
    • 支持只访问文件的部分内容
    • 可以向已存在的文件后追加内容
  • RandomAccessFile 对象包含一个记录指针,用以标示当前读写处的位置。

构造方法

RandomAccessFile(File file, String mode)   
// 创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。

RandomAccessFile(String name, String mode)   
// 创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。

注意
创建 RandomAccessFile 类实例需要指定一个 mode 参数,该参数指 定 RandomAccessFile 的访问模式(介绍2种常用的):

  • r: 以只读方式打开(不会创建文件,读取已经存在的文件)
  • rw:可读可写(文件不存在会创建,存在不会创建)

成员方法

  • 常规的readwrite方法
  • RandomAccessFile 类对象可以自由移动记录指针
    • long getFilePointer():获取文件记录指针的当前位置
    • void seek(long pos):将文件记录指针定位到 pos 位置

eg:

@Test
public void Test() throws IOException {
    File file = new File("D:\\java_test\\a.txt");

    RandomAccessFile randomAccessFile = 
    new RandomAccessFile("D:\\java_test\\a.txt", "rw");

    // 获取当前文件的指针
    long filePointer = randomAccessFile.getFilePointer();

    // write写数据
    randomAccessFile.write("abjdefg".getBytes());

    // 从文件的某个位置进行写数据
    // 移动指针
    randomAccessFile.seek(4);
    randomAccessFile.write("999".getBytes());

    // 从文件的末尾开始写
    randomAccessFile.seek(file.length());
    randomAccessFile.write("1998".getBytes());

    // 从文件中读取数据
    randomAccessFile.seek(0);
    byte[] bytes = new byte[1024];
    int readCount = randomAccessFile.read(bytes);
    System.out.println(new String(bytes,0,readCount));

    randomAccessFile.close();
}

NIO

  • Java NIO(New IO, Non Blocking IO),可以替代标准的Java IO API。
  • NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同
  • NIO支持面向缓冲区buffer的、基于通道channel的IO操作
  • NIO将以更加高效的方式进行文件的读写操作。
IO NIO
面向流(Stream Oriented) 面向缓冲区(块)(Buffer Oriented)
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)

NIO核心

主要用在Netty框架

  • Buffer
  • Channel

Channel负责传输,Buffer负责存储

Buffer

除了boolean之外的基本数据类型都有1个对应的Buffer
比如 IntBuffer 放int数据(除了boolean)

  • ByteBuffer 最常用

  • IntBuffer

  • ShortBuffer

  • LongBuffer

  • DoubleBuffer

  • FloatBuffer

  • CharBuffer
    14_IO_其他流_第7张图片

Buffer的实例化

ByteBuffer为例:

public static ByteBuffer allocate(int capacity)

public static ByteBuffer allocateDirect(int capacity)

public static ByteBuffer wrap(byte[] array)

public static ByteBuffer wrap(byte[] array, int offset, int length)

eg:

    @Test
    public void Test2(){
        ByteBuffer allocate = ByteBuffer.allocate(10);
        System.out.println(allocate);

		
    }

在这里插入图片描述
其中

  • capacity

    • 表示Buffer最大数据容量,创建后不能更改.一旦Buffer满了,需要将其清空(通过读数据或者清除数据, 才能继续往里写数据)
  • limit

    • 是第一个不应该读取或写入的元素的索引。缓冲区的限制不能为负,并且不能大于其容量
    • 写数据到Buffer时,limit表示可对Buffer最多写入多少个数据(写模式下 limit = capacity)
    • 读数据时,limit表示Buffer里有多少可读数据,可以读取到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
  • position

    • 写数据时,position表示写入数据的当前位置,初始值为0.当数据写入到Buffer中后,position会向后移动到1个可插入数据的Buffer单元.position最大值可为capacity
    • 读数据时,position表示读入数据的当前位置,如position=2时,表示已经读入了2个值,或从第2个位置开始读取.通过flip()切换到读模式时position会被重置为0,当Buffer从position读入数据后,position会移动到下一个可读入的数据Buffer单元
  • mark

    • 跟reset()结合进行标记,通过Buffer中的mark()方法,指定一个Buffer的特定的position,之后可以通过reset()方法恢复到这个position.
  • 这4个属性遵循如下关系:

    • 0<=mark<=position<=limit<=capacity

Buffer的存取数据

put操作(存)

put(byte b)   // 将给定的字节写入此缓冲区的当前位置,然后该位置递增 。  position+1

put(int index,  byte b) // 将给定字节写入此缓冲区的给定索引处。position不变

put(byte[] src,  int offset, int length)   //  此方法将把给定源数组中的字节字传输到此缓冲区中。

put(byte[] src)   //  此方法将给定的源 byte 数组的所有内容传输到此缓冲区中。

get操作(取)

get()   // 读取此缓冲区当前位置的字节,然后该位置递增。

get(int index) // 读取指定索引处的字节。(position的值不变)

get(byte[] dst,  int offset, int length) // 此方法将此缓冲区的字节传输到给定的目标数组中

get(byte[] dst)   //  此方法将此缓冲区的字节传输到给定的目标数组中

Buffer的常用方法

Buffer flip() 
// 反转此缓冲区。首先将limit设置为当前位置,然后将position设置为 0。
// 上面的意思就是把limit的值设置为position,然后position的值设置为0。
// 如果已定义了mark,则丢弃该mark。( 修改 position为0   limit为之前position的位置 mark = -1 )

Buffer rewind() // 重绕此缓冲区。将position设置为 0 并丢弃mark。

Buffer clear()  // 清除此缓冲区,将position设置为 0,将limit设置为capacity,并丢弃mark。 mark=-1

Buffer reset() // 将位置 position 转到以前设置的 mark 所在的位置

flip() VS rewind() VS clear()

  • 相同:设置position为0,丢弃mark
  • 不同:
    • flip方法会修改limit值为position
    • rewind不会修改limit值
    • clear修改limit值为capacity

eg:


// 1. 创建缓冲区对象
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer);

// 2. put(byte[] src)
buffer.put("abcd".getBytes());
System.out.println(buffer);

// 3. 读写模式的切换flip
buffer.flip();
System.out.println(buffer);

// 4. 取数据get方法
byte b = buffer.get();
System.out.println(b);
System.out.println(buffer);

Channel

java.nio.channels 包定义 的。Channel 表示IO 源与目标打开的连接。
Channel 类似于传统的“流”
只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

FileChannel

用于读取、写入、映射和操作文件的通道

文件通道在其文件中有一个当前 position,可对其进行查询和修改。

实例化FileChannel(无法new)

  • RandomAccessFile (可读可写) 双工
  • FileInputStream (可读 单向) 单工
  • FileOutputStream (可写 单向)

eg:

FileChannel inChannel = new FileInputStream("a.mp4").getChannel();

FileChannel outChannel = new FileOutputStream("a.mp4").getChannel();

FileChannel rafIn = new RandomAccessFile("aa.mp4", "rw").getChannel();

FileChannel中的map()方法将文件的部分或全部映射到内存

映射文件的三种模式(MapMode)

  • READ_ONLY

    • 产生只读缓冲区,对缓冲区的写入操作将导致ReadOnlyBufferException
  • READ_WRITE

    • 产生可写缓冲区,写或修改文件内容的话是会立刻反映到磁盘文件中去的别的进程如果共享了同一个映射文件,那么也会立即看到变化
  • PRIVATE

    • 此模式下,对内存映射的修改,不会反应到磁盘文件中,而是创建修改后缓冲区的私有副本。

请求的映射模式将受到被调用map方法FileChannel对象的访问权限所限制

eg:


// 1. 创建文件映射对象
FileChannel channel =
        new RandomAccessFile("D:\\Java_test\\a.txt", "rw").getChannel();

// 2. map方法进行映射
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, channel.size());

// 3. 获取内存中的数据
byte[] bytes = new byte[4];
buffer.get(bytes);
System.out.println(new String(bytes));

// 4. 修改内存中的数据
buffer.put(0,((byte) 98));

数据传输

eg:


// 创建通道
FileChannel inChannel = new FileInputStream("a.txt").getChannel();

// 创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(64);

// 向缓冲区写数据
buffer.put("hello".getBytes());
buffer.flip();

// 向通道中写数据
inChannel.write(buffer);

// 将数据从通道读入到缓冲区
inChannel.read(buffer);
buffer.flip();

// 把数据从缓冲区取出来
byte[] bytes = new byte[1024];
buffer.get(bytes,0,buffer.limit());
System.out.println(new String(bytes,0,buffer.limit()));

Scatter/Gather

  • 分散scatter)从 Channel 中读取是指在读操作时将读取的数据写入多个 buffer 中。 因此,Channel 将从 Channel 中读取的数据“分散(scatter)”到多个 Buffer 中
  • 聚集gather)写入 Channel 是指在写操作时将多个 buffer 的数据写入同一个 Channel,因此,Channel 将多个 Buffer 中的数据“聚集(gather)”后发送到 Channel

eg:


/*
分散(Scatter)
 */
// 1. 创建一个通道
FileChannel inChannel = new FileInputStream("D:\\Java_test\\a.txt").getChannel();

// 2. 创建两个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(64);
ByteBuffer buffer2 = ByteBuffer.allocate(64);
ByteBuffer[] byteBuffers = {buffer1,buffer2};

// 3. 将数据读入到缓冲区
long readCount = inChannel.read(byteBuffers);
System.out.println("readCount = " + readCount);
System.out.println("buffer1.capacity() = " + buffer1.capacity());
System.out.println("buffer1.position() = " + buffer1.position());
System.out.println("buffer2.capacity() = " + buffer2.capacity());
System.out.println("buffer2.position() = " + buffer2.position());


/*
聚集(Gather)
 */

// 创建1个通道
FileChannel channel = new FileOutputStream("D:\\Java_test\\a.txt").getChannel();

// 创建2个缓冲区
ByteBuffer buffer1 = ByteBuffer.allocate(64);
ByteBuffer buffer2 = ByteBuffer.allocate(64);

// 写入数据
buffer1.put("nihao".getBytes());
buffer1.flip();
buffer2.put("woshi".getBytes());
buffer2.flip();
ByteBuffer[] buffers = {buffer1, buffer2};

// 将多个Buffer写入Channel
channel.write(buffers);

文件复制

通道之间进行传输(零拷贝:无需内核态与用户态切换)

  • transferForm(srcChannle,position,channelSize)
  • transferTo(position,channelSize,destChannel)

eg:


// transferForm(srcChannle,position,channelSize)
FileChannel inChannel = new FileInputStream("D:\\Java_test\\a.txt").getChannel();
FileChannel outChannel = new FileOutputStream("D:\\Java_test\\a_Copy.txt").getChannel();
outChannel.transferFrom(inChannel,0,inChannel.size());


// transferTo(position,channelSize,destChannel)
inChannel.transferTo(0,inChannel.size(),outChannel);


总结

类型 字节输出流 字节输入流 字符输出流 字符输入流
抽象基类 OutputStream InputStream Writer Reader
文件相关 FileOutputStream FileInputStream FileWriter FileReader
缓冲相关 BufferedOutputStream BufferedInputStream BufferedWriter BufferedReader
转换相关 OutputStreamWriter InputStreamReader
数据相关 DataOutputStream DataInputStream
打印相关 PrintStream PrintWriter
对象相关 ObjectOutpuStream ObjectInputStream

你可能感兴趣的:(JavaSE,java)