Java NIO 详解

news2024/11/17 5:44:40

Java 从1.4开始引入NIO(New IO),是一种基于块(Block)的IO机制,也称为非阻塞IO。相比于传统的Java IO(IO流)方式,Java NIO提供了更快速、高效、灵活的IO操作。

Java NIO的核心组件包括以下几个部分:

  1. Channel(通道):Channel是Java NIO的基础,代表了一个与IO设备(如文件、套接字)交互的双向通信通道。它可以读取和写入数据。Channel可以通过Selector来实现非阻塞IO操作。

  2. Buffer(缓冲区):Buffer是一个用来存储数据的对象,NIO的读写操作都是基于缓冲区的。它提供一组方法来读写数据,并且在读写过程中维护缓冲区的状态信息。

  3. Selector多路复用选择器):Selector是一个用于多路复用的对象,它可以同时监控多个Channel的状态,以便在有IO事件到来时通知程序进行处理。通过Selector,可以使单个线程就可以处理多个Channel的IO操作。

  4. Non-blocking IO(非阻塞IO):Java NIO提供了非阻塞IO的特性,即在等待IO操作完成时,线程不会被阻塞,可以继续执行其他任务。这样,一个线程可以同时处理多个Channel的IO操作,提高了系统的吞吐量和响应性能。

Java NIO相对于传统的Java IO方式,具有如下优势:

  1. 更快速的IO操作:通过使用缓冲区和非阻塞IO,Java NIO能够更高效地进行数据读写操作。

  2. 处理多个连接:使用Selector可以单线程处理多个连接,提高系统的并发能力和资源利用率。

  3. 异步IO:Java NIO还提供了一些异步IO的方式,通过回调或者Future来实现。

Channel : 

java.nio.channels.Channel 是Java NIO的基础,代表了一个与IO设备(如文件、Socket)交互的通道。常用的实现类包括:

  1. FileChannel : 用于读写文件中的数据,可以从文件中读取字节数据到Buffer,或将Buffer中的数据写入文件。

  2. SocketChannel:用于通过 TCP 协议进行网络数据的读写操作,可以与远程Socket建立连接,并进行读写操作。

  3. ServerSocketChannel:用于监听并接受客户端连接请求。

  4. DatagramChannel:用于通过 UDP 协议进行网络数据的读写操作。

FileChannel 的使用:        

用于读写文件中的数据,可以从文件中读取字节数据到Buffer,或将Buffer中的数据写入文件。

1、打开FileChannel 

1.1 通过FileInputStream或FileOutputStream获取FileChannel

FileInputStream fis = new FileInputStream("path/to/file");
FileChannel channel = fis.getChannel();

FileOutputStream fos = new FileOutputStream("path/to/file");
FileChannel channel = fos.getChannel();

1.2  通过RandomAccessFile获取FileChannel: 

RandomAccessFile file = new RandomAccessFile("path/to/file", "rw");
FileChannel channel = file.getChannel();

1.3  使用java.nio.file.Files工具类获取FileChannel:open(Path path, OpenOption... options)

Path path = Paths.get("path/to/file");
FileChannel channel = FileChannel.open(path, StandardOpenOption.READ);

Java中的Files类是java.nio.file包提供的一个实用工具类,用于进行文件和目录的各种操作。它包含了大量的静态方法,用于操作文件、目录、路径等。Files类的一些常用方法:

Path path = Paths.get("path/to/file");

// 判断文件或目录是否存在:exists(Path path)
boolean exists = Files.exists(path);

// 创建文件:createFile(Path path, FileAttribute<?>... attrs) 
Files.createFile(path);

// 删除文件或目录:delete(Path path) 
Files.delete(path);

// 创建目录:createDirectory(Path dir, FileAttribute<?>... attrs)
Path dir = Paths.get("path/to/directory");
Files.createDirectory(dir);

Path source = Paths.get("path/to/source");
Path target = Paths.get("path/to/target");
// 复制文件或目录:copy(Path source, Path target, CopyOption... options)
Files.copy(source, target);

// 移动/重命名文件或目录:move(Path source, Path target, CopyOption... options)
Files.move(source, target);

// 写入内容到文件:writeString(Path path, CharSequence csq, Charset charset, OpenOption... options) 
String content = "Hello, World!";
Files.writeString(path, content, StandardCharsets.UTF_8);

// 获取文件属性:readAttributes(Path path, Class<A> type, LinkOption... options)
BasicFileAttributes attributes = Files.readAttributes(path, BasicFileAttributes.class);

1.4 通过java.nio.file.FileSystems工具类获取FileChannel

// 返回代表默认文件系统的FileSystem对象
FileSystem fs = FileSystems.getDefault();
Path path = fs.getPath("path/to/file");
FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE);

FileSystems类提供了一些方便的方法,用于获取默认文件系统、根据URI或Path构建文件系统以及获取文件系统提供程序。它们可以用于实现更灵活多样的文件系统操作。需要注意的是,使用FileSystems类时,需要考虑安全性、权限和适用性等因素,以确保操作正确可靠。如: 

// 根据URI获取文件系统 newFileSystem(URI uri, Map<String, ?> env)
URI uri = new URI("file:/path/to/directory/");
FileSystem fs = FileSystems.newFileSystem(uri, null);

//根据Path获取文件系统 newFileSystem(Path path, ClassLoader loader)
Path path = Paths.get("/path/to/jarfile.jar");
FileSystem fs2 = FileSystems.newFileSystem(path, null);

// 获取支持的文件系统提供程序:newFileSystemProvider(Class<? extends FileSystem> type)
FileSystemProvider provider = FileSystems.newFileSystemProvider(FTPFileSystem.class);

1.5 通过FileDescriptor获取FileChannel

FileInputStream fis = new FileInputStream("path/to/file"); 
FileDescriptor fd = fis.getFD();
FileChannel channel = new FileInputStream(fd).getChannel();

在Java中,FileDescriptor类表示一个文件描述符,它是与底层操作系统文件句柄相关联的标识符。FileDescriptor主要用于在Java程序中直接操作底层文件句柄,例如创建FileInputStream、FileOutputStream等。

2、从FileChannel读取数据到缓冲区

// 创建一个大小为1024 bit 的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
// FileChannel 数据读到缓冲区
int bytesRead = channel.read(buffer);

3、将数据从缓冲区写入到FileChannel

// flip() 将缓冲区 读/写 模式切换
buffer.flip();
while (buffer.hasRemaining()) {
    channel.write(buffer);
}
buffer.clear();

4、关闭FileChannel

channel.close();

5、其它操作

  1. 移动FileChannel的位置:
    // 移动文件指针位置到当前位置后的50个字节
    long newPosition = channel.position() + 50; 
    channel.position(newPosition);
  2. 截断文件(截短或扩展文件长度)
    channel.truncate(1024); // 将文件截断为1024个字节
  3. 强制将FileChannel的内容刷新到磁盘:
    channel.force(true); // 强制刷新文件数据到磁盘

​​​​​​​​2、SocketChannel 使用

        SocketChannel是用于进行套接字通信的重要组件,它提供了非阻塞的、基于缓冲区的I/O操作。你可以使用SocketChannel来建立连接、发送和接收数据,以及关闭连接等操作。

// 打开一个SocketChannel实例
SocketChannel socketChannel = SocketChannel.open();
// 切换为非阻塞模式
channel.configureBlocking(false);

// 连接远程服务器:使用connect()方法连接到远程服务器
InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 9001);
socketChannel.connect(remoteAddress);

while (!socketChannel.finishConnect()) { // 等待连接完成
    // 写入数据到SocketChannel
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.put("要发送的数据".getBytes());
    buffer.flip();  // 将缓冲区切换为读模式
    socketChannel.write(buffer);

    // 从SocketChannel中读取数据到ByteBuffer
    ByteBuffer buffer2 = ByteBuffer.allocate(1024);
    int bytesRead = channel.read(buffer2);
}

channel.close();

3、ServerSocketChannel 使用

        ServerSocketChannel是一种用于监听传入连接的通道。它作为服务器端通道,用于接受客户端的连接请求。

// 打开ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 切换为非阻塞模式 [根据情况选择是否要这么处理]
serverChannel.configureBlocking(false);

//绑定端口和地址
serverChannel.bind(new InetSocketAddress("localhost", 8080));

// 接受客户端的连接请求。该方法默认会阻塞,直到有客户端连接到达,返回一个SocketChannel对象
// 如果切换到了非阻塞, 这里就不会阻塞
SocketChannel clientChannel = serverChannel.accept();

serverChannel.close();

4、DatagramChannel 使用

DatagramChannel是一种用于进行UDP协议通信的通道。它可以发送和接收UDP数据报

// 打开DatagramChannel
DatagramChannel channel = DatagramChannel.open();

// 绑定IP和端口
channel.bind(new InetSocketAddress("localhost", 8080));

// 该方法会阻塞,直到接收到数据报,返回一个SocketAddress和接收到的数据报的数量
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketAddress address = channel.receive(buffer);

//发送数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello, Server!".getBytes());
buffer.flip();
int bytesSent = channel.send(buffer, new InetSocketAddress("example.com", 8080));

// 配置阻塞模式, 默认情况下是阻塞模式
channel.configureBlocking(true);

channel.close();

Buffer : 

Buffer介绍:

        java.nio.Buffer 用于读写数据,是Java NIO读写操作的中间容器。​​​​​​​数据从通道读入缓冲区,从缓冲区写入通道。

        缓冲区本质上是一块可以写入和读取数据的内存,这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便该块的内存访问​​​​​​​。缓冲区实际上就是一个容器对象/数组。 

  1. 常用的Buffer子类包括:

    1. ByteBuffer:ByteBuffer是最常用的Buffer实现类,用于读写字节数据。

    2. CharBuffer:用于读写字符数据

    3. ShortBuffer/ IntBuffer/ LongBuffer/ FloatBuffer/ DoubleBuffer:用于读写特定类型数据

  2. Buffer的主要特性:

    1. 容量(Capacity):缓冲区的容量表示它可以存储的最大数据量,一旦缓冲区被创建,其容量不能更改。
    2. 位置(Position):读写操作的位置,表示下一个要读取或写入的元素的索引
    3. 上界(Limit):缓冲区当前有效数据的边界,即下一个要读取或写入的元素的索引。

    4. 标记(Mark):记住某个特定位置的索引,可以通过 mark()和 reset()方法来返回该位置。

    5. 读写模式切换:缓冲区可以处于读模式(读取数据到缓冲区)或写模式(将数据从缓冲区写出)。

Buffer使用: 

        1、创建Buffer,分配空间:通过调用Buffer的静态方法allocate()来分配指定容量的缓冲区。

// 分配一个容量为1024字节的ByteBuffer实例
ByteBuffer buffer = ByteBuffer.allocate(1024); 

        2、写入数据到缓冲区:使用Buffer的put()方法将数据写入缓冲区

// 写入一个字节数据到缓冲区
buffer.put((byte) 65); 

        3、转换为读模式:通过调用Buffer的flip()方法,将Buffer切换为读取模式。在读模式下,可以读取缓冲区中的数据。

// 切换为读取模式
buffer.flip(); 

        4、从缓冲区读取数据:使用Buffer的get()方法读取缓冲区中的数据。

// 从缓冲区读取一个字节数据
byte data = buffer.get(); 

        5、重复读取:Buffer的rewind()方法将缓冲区切换为读模式,但保留之前读取到的数据。然后再次使用get()方法读取数据。

// 重复读取前需要调用rewind()方法
buffer.rewind(); 

// 重新读取一个字节数据
byte data = buffer.get(); 

        6、清空缓冲区:

  • clear()方法,将Buffer切换为写入模式,并清空缓冲区的数据。在写入模式下,可以写入新的数据; 
  • compact()方法:相比clear()方法,compact()方法会清除已经读取的数据,但是会保留未读取的数据。未读取的数据会被移动到缓冲区的开头,可以继续写入新的数据。

Buffer的其他API: 

  1. int capacity() : 获取缓冲区的大小
  2. int limit() :获取缓冲区的限制位置,即缓冲区中可读写的数据范围。写入模式下,默认等于缓冲区的容量;读取模式下,默认等于写入模式下的位置
  3. Buffer limit( int newLimit):设置缓冲区的限制位置
  4. int position() : 获取缓冲区的当前位置,即读取或写入数据的位置
  5. Buffer position( int newPosition):设置当前位置的绝对位置
  6. boolean hasRemaining() : 判断缓冲区是否还有未读取的数据
  7. void mark(): 将当前位置设置为标记位置
  8. void reset() : 重置位置为标记位置
  9. void flip() : 切换为读取模式 
  10. void clear() :清空缓冲区,切换为写入模式
  11. void compact() : 压缩缓冲区,将未读取的数据移到开头
  12. void rewind() : 重绕缓冲区,切换为读取模式

Selector:

        java.nio.channels.Selector 是一种用于多路复用的对象,用于管理多个通道的I/O事件。它允许单个线程同时监视多个通道,以及在有准备就绪的通道上进行读写操作,从而提高系统的性能和可伸缩性。

 

Selector的工作原理如下:

  1. 首先,通过Selector.open()创建一个Selector实例。

  2. 然后,将需要进行IO操作的通道注册到Selector上,通过调用通道的register()方法完成注册,此时Selector上生成一个SelectionKey。通道是抽象类SelectableChannel,其子类有: SocketChannel、ServerSocketChannel、DatagramChannel 等。

  3. Selector调用select()方法进行阻塞,等待注册的通道中有事件发生。当有一个或多个通道有事件发生时,select()方法返回,并返回发生事件的通道的数量。

  4. 根据返回的数量,可以通过selectedKeys()方法获取发生事件的通道的集合,然后进行相应的IO操作。

  5. 建议在处理完一个通道的事件后,调用selectedKeys()的remove()方法将该通道从集合中移除,以避免重复处理。

Selector使用示例: 

Selector selector = Selector.open();
SelectableChannel channel = SocketChannel.open();
channel.configureBlocking(false); // 非阻塞模式
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

while (true) {
    int readyChannels = selector.select();
    if (readyChannels == 0) {
        continue;
    }

    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
    while (keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if (key.isReadable()) {
            // 处理可读事件
            // ...
        }
        keyIterator.remove();
    }
}

Selector API 

  1. static Selector open(): 创建一个新的Selector对象。
  2. int select(): 阻塞等待就绪的通道,返回已就绪通道的数量
  3. int select(long timeout): 最多阻塞timeout毫秒,等待就绪的通道
  4. int selectNow(): 非阻塞立即返回就绪的通道数量
  5. Set<SelectionKey> selectedKeys(): 获取就绪通道的SelectionKey集合
  6. Set<SelectionKey> keys(): 获取所有注册通道的SelectionKey集合
  7. Selector wakeup() :唤醒其他阻塞线程的 select 方法立刻执行并返回
  8. void close() :关闭Selector 

其它与Selector相关的API 

Selector 的使用过程中, SelectableChannel 和 SelectionKey 一直贯穿着Selector的始终,SelectableChannel 是注册进Selector的通道, SelectionKey 是通道注册进Selector后生成对象,里面包含了通道注册信息和进程状态信息。

  1. SelectableChannel  的 API : 
    1. SelectionKey register(Selector sel, int ops):将通道注册到Selector上,同时指定关注的事件类型,常用的时间类型有:
      1. SelectionKey.OP_READ = 1 << 0:读事件
      2. SelectionKey.OP_WRITE = 1 << 2:写事件
      3. SelectionKey.OP_CONNECT = 1 << 3:连接事件
      4. SelectionKey.OP_ACCEPT = 1 << 4 :接收事件
    2. SelectableChannel configureBlocking(boolean block) : 设置通道为【非】阻塞模
    3. boolean isRegistered(): 判断通道是否已经注册到Selector上
    4. boolean isBlocking() : 通道是否阻塞
    5. SelectionKey keyFor(Selector sel) :返回通道在Selector中最后一次注册的信息
    6. Object blockingLock() :返回的是通道对象关联的锁对象,用于同步操作阻塞模式切换。它并不是用于直接控制通道的阻塞/非阻塞状态。
  2. SelectionKey 的API :SelectionKey类表示一个通道在选择器上的注册信息。 
    1. SelectableChannel channel() : 获取关联的通道
    2. Selector selector() :获取关联的选择器
    3. boolean isReadable() | isWritable() | isConnectable() | isAcceptable() : 判断通道是否可读、写、连接、接受连接
    4. int readyOps() : 获取准备就绪的操作集合
    5. int interestOps() : 获取所有注册的操作集合
    6. SelectionKey interestOps(int ops) : 修改操作集合
    7. void cancel() : 取消注册
    8. Object attach(Object ob) : 这是关联的附加对象
    9. Object attachment() : 获取关联的附加对象

Scatter/Gather

Java NIO(New I/O)中的Scatter/Gather是一种用于处理I/O操作的重要模式,通过将数据分散读取(Scatter Read)或聚集写入(Gather Write),可以有效地处理复杂的数据结构。在Scatter/Gather模式下,可以一次性读取多个数据块到多个缓冲区,或将多个缓冲区中的数据一次性写入到目标通道。

1. Scatter Read(分散读取)

  • 使用多个缓冲区准备好接收数据。
  • 调用通道的read()方法,将数据按照缓冲区的顺序依次读取到各个缓冲区中。
ByteBuffer buffer1 = ByteBuffer.allocate(64);
ByteBuffer buffer2 = ByteBuffer.allocate(128);
ByteBuffer[] buffers = { buffer1, buffer2 };

channel.read(buffers);

2. Gather Write(聚集写入)

  • 使用多个缓冲区存储要写入的数据。
  • 调用通道的write()方法,将多个缓冲区中的数据一起写入到目标通道。
ByteBuffer buffer1 = ByteBuffer.allocate(64);
ByteBuffer buffer2 = ByteBuffer.allocate(128);
ByteBuffer[] buffers = { buffer1, buffer2 };

channel.write(buffers);

Scatter/Gather模式可以用于处理复杂的数据结构,例如由多个部分组成的消息。通过Scatter将数据分散到多个缓冲区,可以对消息的各个部分进行独立处理。而通过Gather将多个缓冲区中的数据聚集写入到目标通道,可以将多个部分组装成完整的消息并进行发送。

需要注意的是,Scatter/Gather模式依赖于底层通道的能力支持。并非所有的通道都支持Scatter/Gather操作,只有实现了`ReadableByteChannel`(可读通道)或`WritableByteChannel`(可写通道)接口的通道才能进行Scatter/Gather操作。

总结: 

 Java NIO还提供了其他一些类和接口,如FileLock、MappedByteBuffer、Pipe等,用于实现更复杂的IO操作和功能。

需要注意的是,Java NIO的使用与传统的Java IO方式有很大的区别,它对于开发者而言更加复杂。但在高并发、大规模等场景下,Java NIO可以提供更好的性能和可扩展性。

IO 与 NIO对比
IONIO
面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO非阻塞IO
(无)选择器(Selectors)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/803609.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文让你彻底搞懂Mybatis之缓存机制

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 Mybatis版本&#xff1a;3.5.6 文章目录 一. 缓存是什么&#xff1f;二. 为什么要使用缓存&#xff1f;三. Mybatis中的缓存分哪几种&#…

自己实现MyBatis 底层机制--抽丝剥茧(上)

&#x1f600;前言 本篇博文是学习过程中的笔记和对于MyBatis底层机制的分析思路&#xff0c;希望能够给您带来帮助&#x1f60a; &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到…

Base64之间的相互转化

使用org.apache.ommons.codec.binary.Base64实现字符串和Base64之间的相互转化 字符串转化为Base64之间的相互转化一 //转化为Base64字符串 String strOld "Welcome to the new world"; base64EncodeStr Base64.encodeBase64String(strOld.getBytes()); System.o…

黑马点评项目学习笔记(15w字详解,堪称史上最详细,欢迎收藏)

黑马点评项目学习笔记 文章目录 黑马点评项目学习笔记前言项目搭建导入数据库初始化项目启动项目启动前端项目启动后端项目 基于Session实现短信验证码登录短信验证码登录配置登录拦截器数据脱敏 Session集群共享问题基于Redis实现短信验证码登录短信验证登录配置登录拦截器 店…

漏洞分析|Metabase 代码执行漏洞(CVE-2023-38646):H2 JDBC 深入利用

0x01 概述 最近 Metabase 出了一个远程代码执行漏洞&#xff08;CVE-2023-38646&#xff09;&#xff0c;我们通过研究分析发现该漏洞是通过 JDBC 来利用的。在 Metabase 中兼容了多种数据库&#xff0c;本次漏洞中主要通过 H2 JDBC 连接信息触发漏洞。目前公开针对 H2 数据库…

国产内存强势崛起,光威神条有神价,无套路闭眼可入

今年的DIY电脑市场终于摆脱了前两年的阴霾&#xff0c;从CPU到内存都有着充足的货源&#xff0c;而且价格靠谱&#xff0c;特别是国产存储品牌超级厚道&#xff0c;内存、硬盘等配件的价格基本都是大跳水&#xff0c;相比于去年&#xff0c;同样的价格能够买到容量和性能翻倍的…

ERROR 1064 - You have an error in your SQL syntax;

ERROR 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near (/, 少个逗号吧&#xff0c;以前开始写SQL&#xff0c;特别是修改SQL的时候容易出现这样错误。 而且自己也知道在附近…

SAP财务系统中的“复式记账法”

1. 前言 “复式记账法”是财务的基础知识&#xff0c;对于财务出身的小伙伴是so easy&#xff0c;但对于技术出身的同学&#xff0c;通常会被“借贷”关系弄的晕头转向。 本文会简明扼要的总结“复式记账法”的基本原理&#xff0c;并以采购和销售流程为例来介绍如何进行复式…

Java - 注解开发

注解开发定义bean Component的衍生注解 Service&#xff1a; 服务层的注解 Repository&#xff1a; 数据层的注解 Controller&#xff1a; 控制层的注解 纯注解开发 bean管理 bean作用范围 在类上面添加Scope(“singleton”) // prototype: 非单例 bean生命周期 PostCon…

PyTorch BatchNorm2d详解

通常和卷积层&#xff0c;激活函数一起使用

基于51单片机和proteus的加热洗手器系统设计

此系统是基于51单片机和proteus的仿真设计&#xff0c;功能如下&#xff1a; 1. 检测到人手后开启出水及加热。 2. LED指示加热出水及系统运行状态。 功能框图如下&#xff1a; Proteus仿真界面如下&#xff1a; 下面就各个模块逐一介绍&#xff0c; 模拟人手检测模块 通过…

redis 第三章

目录 1.主从复制 2.哨兵 3.集群 4.总结 1.主从复制 结果&#xff1a; 2.哨兵 3.集群 4.总结 通过集群&#xff0c;redis 解决了写操作无法负载均衡&#xff0c;以及存储能力受到单机限制的问题&#xff0c;实现了较为完善的高可用方案。

【设计模式】详解单例设计模式(包含并发、JVM)

文章目录 1、背景2、单例模式3、代码实现1、第一种实现&#xff08;饿汉式&#xff09;为什么属性都是static的&#xff1f;2、第二种实现&#xff08;懒汉式&#xff0c;线程不安全&#xff09;3、第三种实现&#xff08;懒汉式&#xff0c;线程安全&#xff09;4、第四种实现…

Android kotlin系列讲解之最佳的UI体验 - Material Design 实战

目录 一、什么是Material Design二、Toolbar三、滑动菜单1、DrawerLayout2、NavigationView 四、悬浮按钮和可交互提示1、FloatingActionButton2、Snackbar3、CoordinatorLayout 五、卡片式布局1、MaterialCardView2、AppBarLayout 六、可折叠式标题栏1、CollapsingToolbarLayo…

Python MySQL

pymysql 除了使用图形化工具以外&#xff0c;我们也可以使用编程语言来执行SQL从而操作数据库。 在Python中&#xff0c;使用第三方库&#xff1a;pymysql 来完成对MySQL数据库的操作。 安装&#xff1a; pip install pymysql 或在pycharm中搜索pymysql插件安装 创建到MySQ…

蓝桥杯单片机第十届国赛 真题+代码

iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码调试和修改。 */ #include <STC1…

如何快速用Go获取短信验证码

要用Go获取短信验证码&#xff0c;通常需要连接到一个短信服务提供商的API&#xff0c;并通过该API发送请求来获取验证码。由于不同的短信服务提供商可能具有不同的API和授权方式&#xff0c;我将以一个简单的示例介绍如何使用Go语言来获取短信验证码。 在这个示例中&#xff0…

【Ansible】Ansible自动化运维工具之playbook剧本

playbook 一、playbook 的概述1. playbook 的概念2. playbook 的构成 二、playbook 的应用1. 安装 httpd 并启动2. 定义、引用变量3. 指定远程主机 sudo 切换用户4. when条件判断5. 迭代6. Templates 模块6.1 添加模板文件6.2 修改主机清单文件6.3 编写 playbook 7. tags 模块 …

338. 比特位计数

题目 题解一 动态规划——最低设置位 public static int[] countBits(int n) {int [] nums new int[n1];//存放1的个数nums[0]0;for (int i 1; i <n ; i) {nums[i] nums[i & (i-1)]1;}return nums;}题解二 分奇数和偶数&#xff1a; public static int[] countBits…

【MySQL主从复制】

目录 一、MySQL Replication 1.概述 2.优点 二、MySQL复制类型 1.异步复制&#xff08;Asynchronous repication&#xff09; 2.全同步复制&#xff08;Fully synchronous replication&#xff09; 3.半同步复制&#xff08;Semisynchronous replication&#xff09; 三…