三十六、Java包装类

Java包装类(Wrapper Classes)是Java提供的一种特殊类,用于将基本数据类型(primitive data types)转换为对象(objects)。

Java提供了与八种基本数据类型对应的包装类:

对于Integer包装类,Java提供了一个特殊的缓存机制:对于值在-128到127之间的整数,Java会缓存这些对象,因此当你创建这些范围内的Integer对象时,实际上会得到缓存中的对象,而不是创建新的对象。

  1. 提供基本类型对象之间的桥梁,因为Java中的集合类(如List、Set、Map等)只能存储对象,不能直接存储基本类型的数据,因此需要将基本类型包装为对象才能放入集合中。
  2. 提供了一些基本类型不具备的方法,例如比较大小进制转换字符串转换等。
  3. 支持自动装箱(Autoboxing)和拆箱(Unboxing)功能。自Java 5.0开始,编译器会自动在基本类型与它们的包装类之间进行转换。
  4. 包装类内部维护了缓存机制,例如对于Integer,在-128至127之间的小整数值,会共享相同的对象实例,以提高性能和减少内存消耗。

Java包装类为基本数据类型提供了对象包装,使得基本数据类型可以在需要对象的场合(如集合)中使用,并提供了额外的功能。同时,自动装箱拆箱机制简化了基本数据类型和包装类之间的转换过程。

5分钟课堂:Java集合框架

集合框架(Collections Frameworks)是Java 平台提F供的表示和操作集合的统一架构。

集合框架包含以下3 个部分。

(1)集合接口用于表示集合的抽象数据类型,允许集合操作独立于实现细节。接口多用于方法的参数类型。

(2)集合实现实现了对应集合接口的具体类,本质上是可重用的数据结构。 实现类可以创建具体的对象。

(3)集合算法算法是对实现了集合接口的对象执行具体计算(如查找、排序等)的方法。同一个方法可以用于相关集合接口的不同实现。这些方法可以看成是工具方法,一般都是static 方法,可以用类名直接调用。本质上,算法是可重用的功能。

Java中集合的接口和类都是泛型类型(参考 ),而算法大多是泛型方法(参考 )。

Java 集合框架中的核心集合接口中封装了不同的集合类型,允许集合的操作独立于集合的具体实现。

核心接口是Java 集合框架的基础,Java 集合框架中的核心接口主要包括:Collection、Set、List、Queue、Deque、SortedSet 和Map、SortedMap 等接口。

核心集合接口包含两个独立的继承树,集合(Collection)子树和映射(Map)子树。集(Set)是一种不包含重复元素的集合(Collection),而有序集(SortedSet)则是包含有序元素的集(Set)。

所有的核心集合接口都是泛型接口。在创建集合实例时,必须指定具体元素类型。

Java集合框架种主要的集合核心接口及部分实现类如图所示。

Collection接口继承与实现

Map接口继承与实现

集合实现是用于存储集合的数据对象,其类定义实现了对应的集合接口。Java 集合框架提供的通用功能实现类实现了Set,List 和Map 等接口,这些具体实现类可以满足大多数应用的需要。

集合框架通用功能实现类

Java 集合框架提供了集合的排序、置换、常规数据操作、查找、组合等算法。这些算法都是多态算法,提供可重用的功能,并且,全部使用静态方法,方法的第一个参数为操作执行的集合。大多数算法都是针对List 对象,也有部分是操作Collection 对象。

例如,在工具类Collections中提供的算法有:

(1)根据元素的自然顺序对指定列表元素按升序进行排序public static <T extends Comparable<? super T>> void sort(List<T> list);

(2)使用默认随机源对指定列表打乱元素的位置public static void shuffle(List<?> list);

(3)使用二分搜索法搜索列表中的指定对象public static <T> int binarySearch(List<? extends Comparable<? super T>> list,T key);

下面例子以List的元素操作为例,实现了ArrayList<String>对象的创建、加入元素、排序和查找等操作。其中,排序还使用了自定义的逆序比较器进行了逆序的排序,并进行了对应的查找操作。

运行结果如下:

3 JDK 常见的包和BIO,NIO,AIO

JDK常见的包

java.lang:系统基础类

java.io:文件操作相关类,比如文件操作

java.nio:为了完善io包中的功能,提高io性能而写的一个新包

java.net:网络相关的包

java.util:java辅助类,特别是集合类

java.sql:数据库操作类

IO流

按照流的流向分可分为输入流和输出流

按照操作单元分可分为字节流和字符流

按照流的角色分可分为节点流和处理流

java io 流共涉及40多个类,看上去很乱,其实有自己的规则,而且都是相关联的。java io都是从下面的四个基类中派生出来的。

InputStream/Reader:所以输入流的基类。前面是字节输入流,后面是字符输入流

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

按照操作方式给相关的类分类,如下图:

BIO,NIO,AIO有什么区别

BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

NIO (New I/O):NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发

AIO (Asynchronous I/O):AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty之前也尝试使用过 AIO,不过又放弃了。

上面是一些书面的表达,读起来感觉就是一些冰冷的文字。

我们先描述下BIO,当我们开启一个线程执行IO操作的时候,要等这个IO流在对应的通道中读写完成之后,线程才能接着往下执行。我们写一个简单的网络编程的代码如下:

import java.io.*;

import java.net.*;

public class BIOServer {

public static void main(String[] args) throws IOException {

ServerSocket serverSocket = new ServerSocket(8080);

while (true) {

Socket clientSocket = serverSocket.accept();

handleClient(clientSocket);

}

}

private static void handleClient(Socket client) throws IOException {

BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));

PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true);

String request;

while ((request = in.readLine()) != null) {

out.println(\”Request received: \” + request);

if (request.equals(\”END\”)) {

break;

}

}

in.close();

out.close();

client.close();

}

}

我们发现这段代码中,一直在等待client发送消息,如果没有发送消息就会一直等待并阻塞下去

我们看下下面基于NIO的代码实现

package com.study.test;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.ServerSocketChannel;

import java.nio.channels.SocketChannel;

import java.util.Iterator;

public class Server {

public static void main(String[] args) throws IOException {

//1. 获取通道

ServerSocketChannel ssChannel = ServerSocketChannel.open();

//2. 切换非阻塞模式

ssChannel.configureBlocking(false);

//3. 绑定连接

ssChannel.bind(new InetSocketAddress(9999));

//4. 获取选择器

Selector selector = Selector.open();

//5. 将通道注册到选择器上, 并且指定“监听接收事件”

ssChannel.register(selector, SelectionKey.OP_ACCEPT);

//6. 轮询式的获取选择器上已经“准备就绪”的事件

// selector.select() 方法会阻塞,直到有客户端连接进来,或者客户端SocketChannel发送数据过来

while (selector.select() > 0) {

System.out.println(\”轮一轮\”);

//7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”

Iterator<SelectionKey> it = selector.selectedKeys().iterator();

while (it.hasNext()) {

//8. 获取准备“就绪”的是事件

SelectionKey sk = it.next();

//9. 判断具体是什么事件准备就绪

if (sk.isAcceptable()) {

//10. 若“接收就绪”,获取客户端连接

SocketChannel sChannel = ssChannel.accept();

//11. 切换非阻塞模式

sChannel.configureBlocking(false);

//12. 将该通道注册到选择器上

sChannel.register(selector, SelectionKey.OP_READ);

} else if (sk.isReadable()) {

//13. 获取当前选择器上“读就绪”状态的通道

SocketChannel sChannel = (SocketChannel) sk.channel();

//14. 读取数据

ByteBuffer buf = ByteBuffer.allocate(1024);//[pos=0 lim=1024 cap=1024]

int len;//数据长度

while ((len = sChannel.read(buf)) > 0) {//[pos=数据长度 lim=1024 cap=1024]

buf.flip();//[pos=0 lim=数据长度 cap=1024]

System.out.println(new String(buf.array(), 0, len));

buf.clear();

}

}

//15. 取消选择键 SelectionKey

it.remove();

}

}

}

}

##客户端的代码

package com.study.test;

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.SocketChannel;

import java.text.SimpleDateFormat;

import java.util.Scanner;

public class Client {

public static void main(String[] args) throws Exception {

//1. 获取通道

SocketChannel sChannel = SocketChannel.open(new InetSocketAddress(\”127.0.0.1\”, 9999));

//2. 切换非阻塞模式

sChannel.configureBlocking(false);

//3. 分配指定大小的缓冲区

ByteBuffer buf = ByteBuffer.allocate(1024);

//4. 发送数据给服务端

Scanner scan = new Scanner(System.in);

while (scan.hasNext()) {

String str = scan.nextLine();

buf.put((new SimpleDateFormat(\”yyyy/MM/dd HH:mm:ss\”).format(System.currentTimeMillis())

+ \”\\n\” + str).getBytes());

buf.flip();

sChannel.write(buf);

buf.clear();

}

//5. 关闭通道

sChannel.close();

}

}

这个案例启动了一个 Selector 选择器,将服务端通道 ServerSocketChannel 注册到 Selector 选择器上,并绑定一个接收 OP_ACCEPT 事件

然后调用 Selector 选择器select()方法,这个方法是阻塞,直到 有 OP_ACCEPT事件(有客户端SocketChannel 连接请求),select()方法不再阻塞,继续往下执行,

接收到客户端SocketChannel 连接请求后,将这个SocketChannel 再注册到 Selector 选择器上,并绑定一个读 OP_READ 事件,以后 选择器 就可以监听到 客户端发送过来的数据

每次 Selector 选择器 接收到 事件驱动后 要记得移除 这个驱动事件

这就是我们NIO的处理方式。如下图:

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。