Java Review(三十六、IO

ps.println(“普通字符串”);

// 直接使用PrintStream输出对象

ps.println(new PrintStreamTest());

}

catch (IOException ioe)

{

ioe.printStackTrace();

}

}

}

上面程序中先定义了一个节点输出流 FileOutputStream, 然 后程序使用PrintStream 包装了该节点输出流, 最后使用 PrintStream 输出字符串、 输出对象……

PrintStream 的输出功能非常强大, 前面程序中一直使用的标准输出 System.out 的类型就是 PrintStream。

程序使用处理流, 通常只需要在创建处理流时传入一个节点流作为构造器参数即可, 这样创建的处理流就是包装了该节点流的处理流。

API:java.io.PrintStream

转换流


输入/输出流体系中还提供了两个转换流, 这两个转换流用于实现将字节流转换成字符流, 其中InputStreamReader 将字节输入流转换成字符输入流, OutputStreamWriter 将字节输出流转换成字符输出流。

下面以获取键盘输入为例来介绍转换流的用法。 Java 使用 System.in 代表标准输入, 即键盘输入,但这个标准输入流是 InputStream 类的实例, 使用不太方便, 而且键盘输入内容都是文本内容, 所以可以使用 InputStreamReader 将其转换成字符输入流, 普通的 Reader 读取输入内容时依然不太方便, 可以将普通的 Reader 再次包装成 BufferedReader, 利用 BufferedReader 的 readLine()方法可以一次读取一行内容。

如下程序所示:

import java.io.*;

public class KeyinTest

{

public static void main(String[] args)

{

try(

// 将Sytem.in对象转换成Reader对象

InputStreamReader reader = new InputStreamReader(System.in);

// 将普通Reader包装成BufferedReader

BufferedReader br = new BufferedReader(reader))

{

String line = null;

// 采用循环方式来一行一行的读取

while ((line = br.readLine()) != null)

{

// 如果读取的字符串为"exit",程序退出

if (line.equals(“exit”))

{

System.exit(1);

}

// 打印读取的内容

System.out.println(“输入内容为:” + line);

}

}

catch (IOException ioe)

{

ioe.printStackTrace();

}

}

}

API:java.io.InputStreamReader

API:java.io.InputStreamWriter

对象序列化

=======================================================================

对象序列化的目标是将对象保存到磁盘中, 或允许在网络中直接传输对象。 对象序列化机制允许把内存中的 Java 对象转换成平台无关的二进制流, 从而允许把这种二进制流持久地保存在磁盘上, 通过网络将这种二进制流传输到另一个网络节点。 其他程序一旦获得了这种二进制流( 无论是从磁盘中获取的, 还是通过网络获取的), 都可以将这种二进制流恢复成原来的 Java 对象。

序列化的含义和意义


序列化机制允许将实现序列化的 Java 对象转换成字节序列, 这些字节序列可以保存在磁盘上, 或通过网络传输, 以备以后重新恢复成原来的对象。 序列化机制使得对象可以脱离程序的运行而独立存在。

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

如果需要让某个对象支持序列化机制, 则必须让它的类是可序列化的 (serializable )o 为了让某个

类是可序列化的, 该类必须实现如下两个接口之一。

  • Serializable

  • Extemalizable

Java 的很多类己经实现了 Serializable, 该接口是一个标记接口, 实现该接口无须实现任何方法, 它只是表明该类的实例是可序列化的。

所有可能在网络上传输的对象的类都应该是可序列化的, 否则程序将会出现异常, 比如 RMI( Remote Method Invoke, 即远程方法调用, 是 Java EE 的基础) 过程中的参数和返回值; 所有需要保存到磁盘里的对象的类都必须可序列化, 比如 Web 应用中需要保存到 HttpSession 或 ServletContext 属性的 Java 对象。

因为序列化是 RMI 过程的参数和返回值都必须实现的机制, 而 RMI 又是 Java EE 技术的基础——所有的分布式应用常常需要跨平台、 跨网络, 所以要求所有传递的参数、 返回值必须实现序列化。 因此序列化机制是 Java EE 平台的基础。 通常建议: 程序创建的每个 JavaBean 类都实现 Serializable。

使用对象流实现序列化


使用 Serializable 来实现序列化, 只需要让目标类实现 Serializable 标记接口即可, 无须实现任何方法。

一旦某个类实现了 Serializable 接口, 该类的对象就是可序列化的, 程序可以通过如下两个步骤来序列化该对象。

  • 创建一个 ObjectOutputStream, 这个输出流是一个处理流, 所以必须建立在其他节点流的基础之上。 如下代码所示:

// 创建一个 ObjectlnputStream 输入流

ObjectlnputStream ois =new ObjectlnputStream(

new FilelnputStream(“object.txt”));

  • 调用 ObjectInputStream 对象的 readObject()方法读取流中的对象, 该方法返回一个 Object 类型的 Java 对象, 如果程序知道该 Java 对象的类型, 则可以将该对象强制类型转换成其真实的类型。 如下代码所示:

// 从输入流中读取一个 Java 对象, 并将其强制类型转换为 Person 类

Person p (Person)ois.readObject();

下面程序定义了一个 Person 类, 这个 Person 类就是一个普通的 Java 类, 只是实现了 Serializable接口, 该接口标识该类的对象是可序列化的。

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方法

// name的setter和getter方法

public void setName(String name)

{

this.name = name;

}

public String getName()

{

return this.name;

}

// age的setter和getter方法

public void setAge(int age)

{

this.age = age;

}

public int getAge()

{

return this.age;

}

}

下面程序使用 ObjectOutputStream 将一个 Person 对象写入磁盘文件:

import java.io.*;

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 对象。

如果希望从二进制流中恢复 Java 对象, 则需要使用反序列化。 反序列化的步骤如下:

  • 创建一个 ObjectlnputStream 输入流, 这个输入流是一个处理流, 所以必须建立在其他节点流的基础之上。 如下代码所示:

/ / 创建一个 ObjectlnputStream 输入流

ObjectlnputStream ois =new ObjectlnputStream(

new FilelnputStream(“object.txt”));

  • 调用 ObjectInputStream 对象的 readObject()方法读取流中的对象, 该方法返回一个 Object 类型的 Java 对象, 如果程序知道该 Java 对象的类型, 则可以将该对象强制类型转换成其真实的类型。 如下代码所示:

// 从输入流中读取一个 Java 对象, 并将其强制类型转换为 Person 类

Person p (Person)ois.readObject();

下面程序从刚刚生成的 object.txt 文件中读取 Person 对象:

import java.io.*;

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();

}

}

}

反序列化读取的仅仅是 Java 对象的数据, 而不是 Java 类, 因此采用反序列化恢复Java 对象时, 必须提供该 Java 对象所属类的 class 文件, 否则将会引发 ClassNotFoundException 异常。

对象引用的序列化


Person 类的两个成员变量分别是 String 类型和 int 类型, 如果某个类的成员变量的类型不是基本类型或 String 类型, 而是另一个引用类型, 那么这个引用类必须是可序列化的, 否则拥有该类型成员变量的类也是不可序列化的。

如下 Teacher 类持有一个 Person 类的引用, 只有 Person 类是可序列化的,Teacher 类才是可序列化的。 如果 Person 类不可序列化, 则无论 Teacher 类是否实现 Serilizable、 Extemalizable 接口, 则 Teacher类都是不可序列化的。

public class Teacher

implements java.io.Serializable

{

private String name;

private Person student;

public Teacher(String name , Person student)

{

this.name = name;

this.student = student;

}

// 此处省略了name和student的setter和getter方法

……

}

Java 序列化机制采用了一种特殊的序列化算法, 其算法内容如下:

  • 所有保存到磁盘中的对象都有一个序列化编号。

  • 当程序试图序列化一个对象时, 程序将先检查该对象是否己经被序列化过, 只有该对象从未(在本次虚拟机中) 被序列化过, 系统才会将该对象转换成字节序列并输出。

  • 如果某个对象已经序列化过, 程序将只是直接输出一个序列化编号, 而不是再次重新序列化该对象。

下面程序序列化了两个 Teacher 对象, 两个 Teacher对象都持有一个引用到同一个 Person 对象的引用, 而且程序两次调用 writeObject()方法输出同一Teacher 对象。

import java.io.*;

public class WriteTeacher

{

public static void main(String[] args)

{

try(

// 创建一个ObjectOutputStream输出流

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream(“teacher.txt”)))

{

Person per = new Person(“孙悟空”, 500);

Teacher t1 = new Teacher(“唐僧” , per);

Teacher t2 = new Teacher(“菩提祖师” , per);

// 依次将四个对象写入输出流

oos.writeObject(t1);

oos.writeObject(t2);

oos.writeObject(per);

oos.writeObject(t2);

}

catch (IOException ex)

{

ex.printStackTrace();

}

}

}

上面程序中的粗体字代码 4 次调用了 writeObject()方法来输出对象, 实际上只序列化了三个对象,而且序列的两个 Teacher 对象的 student 引用实际是同一个 Person 对象。 下面程序读取序列化文件中的对象即可证明这一点:

import java.io.*;

public class SerializeMutable

{

public static void main(String[] args)

{

try(

// 创建一个ObjectOutputStream输入流

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream(“mutable.txt”));

// 创建一个ObjectInputStream输入流

ObjectInputStream ois = new ObjectInputStream(

new FileInputStream(“mutable.txt”)))

{

Person per = new Person(“孙悟空”, 500);

// 系统会per对象转换字节序列并输出

oos.writeObject(per);

// 改变per对象的name实例变量

per.setName(“猪八戒”);

// 系统只是输出序列化编号,所以改变后的name不会被序列化

oos.writeObject(per);

Person p1 = (Person)ois.readObject(); //①

Person p2 = (Person)ois.readObject(); //②

// 下面输出true,即反序列化后p1等于p2

System.out.println(p1 == p2);

// 下面依然看到输出"孙悟空",即改变后的实例变量没有被序列化

System.out.println(p2.getName());

}

catch (Exception ex)

{

ex.printStackTrace();

}

}

}

自定义序列化


在一些特殊的场景下, 如果一个类里包含的某些实例变量是敏感信息, 例如银行账户信息等, 这时不希望系统将该实例变量值进行序列化; 或者某个实例变量的类型是不可序列化的, 因此不希望对该实例变量进行递归序列化, 以避免引发 java.io.NotSerializableException 异常。

通过在实例变量前面使用 transient 关键字修饰, 可以指定 Java 序列化时无须理会该实例变量。 如下 Person 类与前面的 Person 类几乎完全一样, 只是它的 age 使用了 transient 关键字修饰。

public class Person

implements java.io.Serializable

{

private String name;

private transient int age;

// 注意此处没有提供无参数的构造器!

public Person(String name , int age)

{

System.out.println(“有参数的构造器”);

this.name = name;

this.age = age;

}

// 省略name与age的setter和getter方法

……

}

下面程序先序列化一个 Person 对象, 然后再反序列化该 Person 对象, 得到反序列化的 Person 对象后程序输出该对象的 age 实例变量值:

import java.io.*;

public class TransientTest

{

public static void main(String[] args)

{

try(

// 创建一个ObjectOutputStream输出流

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream(“transient.txt”));

// 创建一个ObjectInputStream输入流

ObjectInputStream ois = new ObjectInputStream(

new FileInputStream(“transient.txt”)))

{

Person per = new Person(“孙悟空”, 500);

// 系统会per对象转换字节序列并输出

oos.writeObject(per);

Person p = (Person)ois.readObject();

//age实例变量使用 transient 关键字修饰, 所以输出 0

System.out.println(p.getAge());

}

catch (Exception ex)

{

ex.printStackTrace();

}

}

}

使用 transient 关键字修饰实例变量虽然简单、 方便, 但被 transient 修饰的实例变量将被完全隔离在序列化机制之外, 这样导致在反序列化恢复 Java 对象时无法取得该实例变量值。 Java 还提供了一种自定义序列化机制, 通过这种自定义序列化机制可以让程序控制如何序列化各实例变量, 甚至完全不序列化某些实例变量( 与使用 transient 关键字的效果相同)。

在序列化和反序列化过程中需要特殊处理的类应该提供如下特殊签名的方法, 这些特殊的方法用以实现自定义序列化。

  • private void writeObject(java.io.ObjectOutputStream out)throws IOException

  • private void readObject(java.io.ObjectInputStream in)throws IOException, ClassNotFoundExccption;

  • private void readObjectNoData()throws ObjectStreamException;


  • writeObject()方法负责写入特定类的实例状态, 以便相应的 readObject()方法可以恢复它。 通过重写该方法, 程序员可以完全获得对序列化机制的控制, 可以自主决定哪些实例变量需要序列化, 需要怎样序列化。 在默认情况下, 该方法会调用 out.defaultWriteObject 来保存 Java 对象的各实例变量, 从而可以实现序列化 Java 对象状态的目的。

  • readObject()方法负责从流中读取并恢复对象实例变量, 通过重写该方法, 程序员可以完全获得对反序列化机制的控制, 可以自主决定需要反序列化哪些实例变量, 以及如何进行反序列化。 在默认情况下, 该方法会调用in.defaultReadObject 来恢复 Java 对象的非瞬态实例变量。 在通常情况下, readObject()方法与 writeObject()方法对应, 如果 writeObject()方法中对 Java 对象的实例变量进行了一些处理, 则应该在 readObjectO方法中对其实例变量进行相应的反处理, 以便正确恢复该对象。

  • 当序列化流不完整时, readObjectNoData()方法可以用来正确地初始化反序列化的对象。 例如, 接收方使用的反序列化类的版本不同于发送方, 或者接收方版本扩展的类不是发送方版本扩展的类, 或者序列化流被篡改时, 系统都会调用 readObjectNoData()方法来初始化反序列化的对象。

下面的 Person 类提供了 writeObject()和 readObject()两个方法, 其中 writeObject()方法在保存 Person对象时将其name 实例变量包装成 StringBuffer, 并将其字符序列反转后写入;在 readObjectO方法中处理 name 的策略与此对应 先将读取的数据强制类型转换成 StringBuffer, 再将其反转后赋给例变量。

import java.io.*;

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方法

……

private void writeObject(java.io.ObjectOutputStream out)

throws IOException

{

// 将name实例变量的值反转后写入二进制流

out.writeObject(new StringBuffer(name).reverse());

out.writeInt(age);

}

private void readObject(java.io.ObjectInputStream in)

throws IOException, ClassNotFoundException

{

// 将读取的字符串反转后赋给name实例变量

this.name = ((StringBuffer)in.readObject()).reverse()

.toString();

this.age = in.readInt();

}

}

对于这个 Person 类而言, 序列化、 反序列化 Person 实例并没有任何区别—区别在于序列化后的对象流, 即使有 Cracker 截获到 Person 对象流,他看到的 name 也是加密后的 name 值, 这样就提高序列化的安全性。

还有一种更彻底的自定义机制,它甚至可以在序列化对象时将该对象替换成其他对象。如果需要实 。现序列化某个对象时替换该对象, 则应为序列化类提供如下特殊方法:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

此 writeReplaceO方法将由序列化机制调用, 只要该方法存在。 因为该方法可以拥有私有( private )、受保护的 ( protected) 和 包 私 有 ( package-private) 等访问权限, 所以其子类有可能获得该方法。 例如,下面的 Person 类提供了 writeReplace()方法, 这样可以在写入 Person 对象时将该对象替换成 ArrayList。

import java.util.*;

import java.io.*;

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方法

……

// 重写writeReplace方法,程序在序列化该对象之前,先调用该方法

private Object writeReplace()throws ObjectStreamException

{

ArrayList list = new ArrayList<>();

list.add(name);

list.add(age);

return list;

}

}

Java 的序列化机制保证在序列化某个对象之前, 先调用该对象的writeReplaceO方法, 如果该方法返回另一个 Java 对象, 则系统转为序列化另一个对象。 如下程序表面上是序列化 Person 对象, 但实际上序列化的是 ArrayList:

import java.io.*;

import java.util.*;

public class ReplaceTest

{

public static void main(String[] args)

{

try(

// 创建一个ObjectOutputStream输出流

ObjectOutputStream oos = new ObjectOutputStream(

new FileOutputStream(“replace.txt”));

// 创建一个ObjectInputStream输入流

ObjectInputStream ois = new ObjectInputStream(

new FileInputStream(“replace.txt”)))

{

Person per = new Person(“孙悟空”, 500);

// 系统将per对象转换字节序列并输出

oos.writeObject(per);

// 反序列化读取得到的是ArrayList

ArrayList list = (ArrayList)ois.readObject();

System.out.println(list);

}

catch (Exception ex)

{

ex.printStackTrace();

}

}

}

与 writeReplace()方法相对的是, 序列化机制里还有一个特殊的方法, 它可以实现保护性复制整个对象。 这个方法就是:

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

这个方法会紧接着 readObject()之后被调用, 该方法的返回值将会代替原来反序列化的对象, 而原来 readObject()反序列化的对象将会被立即丢弃。

NIO

=====================================================================

从 JDK 1.4 开始, Java 提供了一系列改进的输入/输出处理的新功能, 这些功能被统称为新 IO ( New IO, 简称 NIO), 新增了许多用于处理输入/输出的类, 这些类都被放在 java.nio 包以及子包下, 并且对原 java.io 包中的很多类都以 NI0 为基础进行了改写, 新增了满足 NI0 的功能。

新 IO 和传统的IO有相同的目的, 都是用于进行输入/输出, 但新 IO 使用了不同的方式来处理输入/输出, 新 IO 采用内存映射文件的方式来处理输入/输出, 新 IO 将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了( 这种方式模拟了操作系统上的虚拟内存的概念), 通过这种方式来进行输入/输出比传统的输入/输出要快得多。

图9:NIO新特性

在这里插入图片描述

Java 中与新 IO 相关的包如下:

  • java.nio 包: 主要包含各种与 Buffer 相关的类。

  • java.nio.channels 包: 主要包含与 Channel 和 Selector 相关的类。

  • java.nio.charset 包: 主要包含与字符集相关的类。

  • java.nio.channels.spi 包: 主要包含与 Channel 相关的服务提供者编程接口。

  • java.nio.charset.spi 包: 包含与字符集相关的服务提供者编程接口。

图10:NIO核心组件

在这里插入图片描述

使用 Buffer


从内部结构上来看, Buffer 就像一个数组, 它可以保存多个类型相同的数据。 Buffer是一个抽象类,其最常用的子类是 ByteBuffer, 它可以在底层字节数组上进行 get/set 操作。 除 ByteBuffer 之外, 对应于其他基本数据类型( boolean除外) 都有相应的 Buffer 类: CharBuffer、 ShortBuffer、 IntBuffer、LongBuffer、FloatBuffer、 DoubleBuffer。

上面这些 Buffer类, 除 ByteBuffer 之外, 它们都采用相同或相似的方法来管理数据, 只是各自管理的数据类型不同而己。 这些 Buffer 类都没有提供构造器, 通过使用如下方法来得到一个 Buffer 对象。

  • static XxxBufFer allocate(int capacity): 创建一个容量为 capacity 的 XxxBuffer 对象。

但实际使用较多的是 ByteBuffer 和 CharBuffer, 其他 Buffer 子类则较少用到。 其中 ByteBuffer 类还有一个子类: MappedByteBuffer, 它用于表示 Channel 将磁盘文件的部分或全部内容映射到内存中后得到的结果, 通常MappedByteBuffer 对象由 Channel 的 map()方法返回。

在 Buffer 中有三个重要的概念: 容量( capacity)、 界限 ( limit ) 和 位 置( position )。

  • 容 量 (capacity): 缓冲区的容量 (capacity) 表 示 该 Buffer 的最大数据容量, 即最多可以存储多少数据。 缓冲区的容量不可能为负值, 创建后不能改变。

  • 界限 ( limit ): 第一个不应该被读出或者写入的缓冲区位置索引。 也就是说, 位于 limit 后的数据既不可被读, 也不可被写。

  • 位置 ( position ): 用于指明下一个可以被读出的或者写入的缓冲区位置索引( 类似于IO流中的记录指针)。 当使用 Buffer 从 Channel 中读取数据时,position 的值恰好等于己经读到了多少数据。 当刚刚新建一个 Buffer 对象时, 其position 为 0; 如 果 从 Channel 中读取了 2 个 数 据 到该 Buffer 中, 则 position 为 2, 指向 Buffer 中 第 3 个( 第1个位置的索引为0) 位 置。

Buffer 里还支持一个可选的标记 (mark, 类 似于传统 IO流中的mark ), Buffer 允许直接将 position 定位到该 mark 处。 这些值满足如下关系:

mark<position<limit<capacity

图11:Buffer 读入数据后的示意图

在这里插入图片描述

Buffer 的主要作用就是装入数据,然后输出数据( 其作用类似于前面介绍的取水的“ 竹筒”), 开始时 Buffer的position 为 0, limit 为 capacity, 程序可通过 put()方法向 Buffer 中放入一些数据 ( 或者从 Channel 中获取一些数据), 每放入一些数据, Buffer 的 position 相应地向后移动一些位置。

当 Buffer 装入数据结束后, 调用 Buffer 的 flip()方法, 该方法将 limit 设置为 position 所在位置, 并将 position 设为 0, 这就使得 Buffer 的读写指针又移到了开始位置。 也就是说, Buffer 调用 flip()方法之后, Buffer 为输出数据做好准备; 当 Buffer 输出数据结束后, Buffer 调用 clear()方法, clear()方法不是清空 Buffer 的数据, 它仅仅将 position 置 为 0, 将 limit 置 为 capacity, 这 样 为 再 次 向 Buffer 中装入数据做好准备。

除此之外, Buffer 还包含如下一些常用的方法。

  • int capacity(): 返回 Buffer 的 capacity 大小。

  • boolean hasRemaining(): 判断当前位置 ( position ) 和界限 ( limit ) 之间是否还有元素可供处理。

  • int limit(): 返回 Buffer 的界限 ( limit ) 的位置。

  • Buffer limit(int newLt): 重新设置界限 ( limit ) 的值, 并返回一个具有新的 limit 的缓冲区对象。

  • Buffer mark(): 设置 Buffer 的 mark 位置, 它只能在 0 和位置 ( position ) 之间做 mark。

  • int position(): 返回 Buffer 中的 position 值。

  • Buffer position(int newPs): 设置 Buffer 的 position, 并 返 回 position 被修改后的 Buffer 对象。

  • int remaining(): 返回当前位置和界限 ( limit ) 之间的元素个数。

  • Buffer reset(): 将位置( position ) 转到 mark 所在的位置。

  • Buffer rewind(): 将位置( position ) 设置成 0, 取消设置的 mark。

除这些移动 position、 limit、 mark 的方法之外, Buffer 的所有子类还提供了两个重要的方法: put()和 get()方法, 用于向 Buffer 中放入数据和从 Buffer 中取出数据。 当使用 put()和 get()方法放入、 取出数据时, Buffer 既支持对单个数据的访问, 也支持对批量数据的访问( 以数组作为参数)。

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

  • 相对( Relative ): 从 Buffer 的当前 position 处开始读取或写入数据, 然后将位置( position ) 的值按处理元素的个数增加。

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

下面程序为 Buffer 的一些常规操作实例:

import java.nio.*;

public class BufferTest

{

public static void main(String[] args)

{

// 创建Buffer

CharBuffer buff = CharBuffer.allocate(8); // ①

System.out.println("capacity: " + buff.capacity());

System.out.println("limit: " + buff.limit());

System.out.println("position: " + buff.position());

// 放入元素

buff.put(‘a’);

buff.put(‘b’);

buff.put(‘c’); // ②

System.out.println("加入三个元素后,position = "

  • buff.position());

// 调用flip()方法

buff.flip(); // ③

System.out.println("执行flip()后,limit = " + buff.limit());

System.out.println("position = " + buff.position());

// 取出第一个元素

System.out.println(“第一个元素(position=0):” + buff.get()); // ④

System.out.println("取出一个元素后,position = "

  • buff.position());

// 调用clear方法

buff.clear(); // ⑤

System.out.println("执行clear()后,limit = " + buff.limit());

System.out.println("执行clear()后,position = "

  • buff.position());

System.out.println(“执行clear()后,缓冲区内容并没有被清除:”

  • “第三个元素为:” + buff.get(2)); // ⑥

System.out.println("执行绝对读取后,position = "

  • buff.position());

}

}

API:java.nio.Buffer
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

Spring全套教学资料

Spring是Java程序员的《葵花宝典》,其中提供的各种大招,能简化我们的开发,大大提升开发效率!目前99%的公司使用了Spring,大家可以去各大招聘网站看一下,Spring算是必备技能,所以一定要掌握。

目录:

部分内容:

Spring源码

  • 第一部分 Spring 概述
  • 第二部分 核心思想
  • 第三部分 手写实现 IoC 和 AOP(自定义Spring框架)
  • 第四部分 Spring IOC 高级应用
    基础特性
    高级特性
  • 第五部分 Spring IOC源码深度剖析
    设计优雅
    设计模式
    注意:原则、方法和技巧
  • 第六部分 Spring AOP 应用
    声明事务控制
  • 第七部分 Spring AOP源码深度剖析
    必要的笔记、必要的图、通俗易懂的语言化解知识难点

脚手框架:SpringBoot技术

它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

  • SpringBoot入门
  • 配置文件
  • 日志
  • Web开发
  • Docker
  • SpringBoot与数据访问
  • 启动配置原理
  • 自定义starter

微服务架构:Spring Cloud Alibaba

同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

Spring MVC

目录:

部分内容:

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
架)

  • 第四部分 Spring IOC 高级应用
    基础特性
    高级特性
  • 第五部分 Spring IOC源码深度剖析
    设计优雅
    设计模式
    注意:原则、方法和技巧
  • 第六部分 Spring AOP 应用
    声明事务控制
  • 第七部分 Spring AOP源码深度剖析
    必要的笔记、必要的图、通俗易懂的语言化解知识难点

[外链图片转存中…(img-53dvjLBz-1712036930917)]

[外链图片转存中…(img-8NGJxMuW-1712036930918)]

脚手框架:SpringBoot技术

它的目标是简化Spring应用和服务的创建、开发与部署,简化了配置文件,使用嵌入式web服务器,含有诸多开箱即用的微服务功能,可以和spring cloud联合部署。

Spring Boot的核心思想是约定大于配置,应用只需要很少的配置即可,简化了应用开发模式。

  • SpringBoot入门
  • 配置文件
  • 日志
  • Web开发
  • Docker
  • SpringBoot与数据访问
  • 启动配置原理
  • 自定义starter

[外链图片转存中…(img-lKCoJoy2-1712036930918)]

[外链图片转存中…(img-mWk0xBLW-1712036930918)]

微服务架构:Spring Cloud Alibaba

同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案,包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  • 微服务架构介绍
  • Spring Cloud Alibaba介绍
  • 微服务环境搭建
  • 服务治理
  • 服务容错
  • 服务网关
  • 链路追踪
  • ZipKin集成及数据持久化
  • 消息驱动
  • 短信服务
  • Nacos Confifig—服务配置
  • Seata—分布式事务
  • Dubbo—rpc通信

[外链图片转存中…(img-pqPiVL0d-1712036930918)]

[外链图片转存中…(img-jjNSQ6yS-1712036930918)]

Spring MVC

目录:

[外链图片转存中…(img-K0ekmlgF-1712036930918)]

[外链图片转存中…(img-AnhIVgho-1712036930919)]

[外链图片转存中…(img-LmyabcqO-1712036930919)]

部分内容:

[外链图片转存中…(img-Fse31l6N-1712036930919)]

[外链图片转存中…(img-USLTmllH-1712036930919)]

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值