三十六、Java包装类
Java包装类(Wrapper Classes)是Java提供的一种特殊类,用于将基本数据类型(primitive data types)转换为对象(objects)。
Java提供了与八种基本数据类型对应的包装类:
对于Integer包装类,Java提供了一个特殊的缓存机制:对于值在-128到127之间的整数,Java会缓存这些对象,因此当你创建这些范围内的Integer对象时,实际上会得到缓存中的对象,而不是创建新的对象。
- 提供基本类型与对象之间的桥梁,因为Java中的集合类(如List、Set、Map等)只能存储对象,不能直接存储基本类型的数据,因此需要将基本类型包装为对象才能放入集合中。
- 提供了一些基本类型不具备的方法,例如比较大小、进制转换、字符串转换等。
- 支持自动装箱(Autoboxing)和拆箱(Unboxing)功能。自Java 5.0开始,编译器会自动在基本类型与它们的包装类之间进行转换。
- 包装类内部维护了缓存机制,例如对于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
文章为作者独立观点不代本网立场,未经允许不得转载。