Netty Review - Netty与Protostuff:打造高效的网络通信
文章目录
- 概念
- Pre
- Pom
- Server & Client
- ProtostuffUtil 解读
- 测试
- 小结
概念

Pre
每日一博 - Protobuf vs. Protostuff:性能、易用性和适用场景分析
Pom
<dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-api</artifactId><version>1.2.2</version></dependency><dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.2.2</version></dependency><dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.2.2</version></dependency>
Server & Client
package com.artisan.codec.protostuff;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty服务器类
public class NettyServer {// main方法为程序入口点public static void main(String[] args) throws Exception {// 创建主从线程组,用于处理Netty的事件循环EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {// 创建ServerBootstrap实例,用于设置服务器参数ServerBootstrap serverBootstrap = new ServerBootstrap();// 配置服务器线程组serverBootstrap.group(bossGroup, workerGroup)// 设置使用的Channel类型.channel(NioServerSocketChannel.class)// 设置Channel初始化处理器.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 获取Channel的PipelineChannelPipeline pipeline = ch.pipeline();// 添加自定义的处理器pipeline.addLast(new NettyServerHandler());}});// 打印启动信息System.out.println("netty server start。。");// 绑定端口并启动服务器ChannelFuture channelFuture = serverBootstrap.bind(9876).sync();// 等待服务器Channel关闭channelFuture.channel().closeFuture().sync();} finally {// 优雅地关闭主从线程组bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
这段代码首先设置了两个EventLoopGroup,一个用于接受连接(boss),另一个用于处理已接受连接的IO操作(worker)。然后创建了一个ServerBootstrap实例来配置和启动服务器。在ServerBootstrap中指定了使用的EventLoopGroup、Channel类型以及子Channel的初始化处理器。初始化处理器中添加了一个自定义的NettyServerHandler,这应该是处理网络事件和业务逻辑的地方。
服务器启动后,会绑定到本地端口9876,并等待连接。程序最后会优雅地关闭线程组,释放资源。
需要注意的是,这段代码缺少了NettyServerHandler类的定义,这应该是处理网络事件和业务逻辑的具体实现。同时,这段代码没有异常处理和资源管理的健壮性考虑,例如可能需要捕获并处理Exception等。
NettyServerHandler的类,该类继承了ChannelInboundHandlerAdapter,表示一个自定义的Netty通道入站处理器。处理器中重写了channelRead和exceptionCaught方法,分别用于处理通道读取事件和异常事件。

package com.artisan.codec.protostuff;
// 引入Netty相关类
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty服务器处理器类
public class NettyServerHandler extends ChannelInboundHandlerAdapter {// 重写channelRead方法,处理通道读取事件@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 获取客户端发送的ByteBuf数据ByteBuf buf = (ByteBuf) msg;// 将ByteBuf数据转换为字节数组byte[] bytes = new byte[buf.readableBytes()];buf.readBytes(bytes);// 使用ProtostuffUtil工具类对字节数组进行反序列化操作System.out.println("从客户端读取到Object:" + ProtostuffUtil.deserializer(bytes, Artisan.class));}// 重写exceptionCaught方法,处理异常事件@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {// 打印异常堆栈信息cause.printStackTrace();// 关闭通道ctx.close();}
}
在channelRead方法中,当通道读取到数据时,会将接收到的ByteBuf对象转换为字节数组,并使用ProtostuffUtil工具类的deserializer方法进行反序列化操作,将字节数组还原为Artisan对象。Artisan类是客户端发送的数据对应的Java对象。
在exceptionCaught方法中,当发生异常时,会打印异常堆栈信息,并关闭通道。这有助于及时发现并处理异常,避免程序出现异常无法处理的情况。
这段代码是一个使用Netty框架的简单客户端程序。客户端程序的主要作用是连接到服务器,并发送或接收数据。下面是对这段代码的解读和增加的中文注释:
package com.artisan.codec.protostuff;
// 引入Netty相关类
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty客户端类
public class NettyClient {// main方法为程序入口点public static void main(String[] args) throws Exception {// 创建事件循环组EventLoopGroup group = new NioEventLoopGroup();try {// 创建Bootstrap实例,用于设置客户端参数Bootstrap bootstrap = new Bootstrap();// 配置客户端事件循环组bootstrap.group(group).channel(NioSocketChannel.class)// 设置客户端Channel初始化处理器.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 获取Channel的PipelineChannelPipeline pipeline = ch.pipeline();// 添加自定义的处理器pipeline.addLast(new NettyClientHandler());}});// 打印启动信息System.out.println("netty client start。。");// 连接到服务器ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9876).sync();// 等待客户端Channel关闭channelFuture.channel().closeFuture().sync();} finally {// 优雅地关闭事件循环组group.shutdownGracefully();}}
}
这段代码首先创建了一个NioEventLoopGroup,用于处理Netty的事件循环。然后创建了一个Bootstrap实例来配置和启动客户端。在Bootstrap中指定了使用的事件循环组、Channel类型以及子Channel的初始化处理器。初始化处理器中添加了一个自定义的NettyClientHandler,这是处理网络事件和业务逻辑的地方。
客户端启动后,会连接到服务器127.0.0.1的端口9876。程序最后会优雅地关闭事件循环组,释放资源。
NettyClientHandler的类,该类继承了ChannelInboundHandlerAdapter,表示一个自定义的Netty通道入站处理器。处理器中重写了channelRead和channelActive方法,分别用于处理通道读取事件和通道激活事件。

package com.artisan.codec.protostuff;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Arrays;
/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
// 定义Netty客户端处理器类
public class NettyClientHandler extends ChannelInboundHandlerAdapter {// 重写channelRead方法,处理通道读取事件@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 打印服务器发送的消息System.out.println("收到服务器消息:" + msg);}// 重写channelActive方法,处理通道激活事件@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 打印客户端处理器发送数据信息System.out.println("NettyClientHandler发送数据");// 使用ProtostuffUtil工具类对对象进行序列化操作Artisan artisan = new Artisan(1, "artisan", new Address("xx", Arrays.asList("code", "run")));ByteBuf buf = Unpooled.copiedBuffer(ProtostuffUtil.serializer(artisan));// 向服务器发送序列化后的数据ctx.writeAndFlush(buf);}
}
这段代码定义了一个名为NettyClientHandler的Netty通道入站处理器,主要用于处理通道读取事件和通道激活事件。具体功能如下:
- 重写
channelRead方法:当通道读取到数据时,该方法会被调用,并打印服务器发送的消息。 - 重写
channelActive方法:当通道激活时(即成功连接到服务器),该方法会被调用。在该方法中,使用ProtostuffUtil工具类对Artisan对象进行序列化操作,并将序列化后的数据发送给服务器。
注意:在实际使用中,建议在channelActive方法最后添加buf.release();来释放ByteBuf对象,避免内存泄漏。
package com.artisan.codec.protostuff;import java.io.Serializable;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class Artisan implements Serializable {private int id;private String name;private Address address;public Artisan() {}public Artisan(int id, String name) {super();this.id = id;this.name = name;}public Artisan(int id, String name, Address address) {this.id = id;this.name = name;this.address = address;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Address getAddress() {return address;}public void setAddress(Address address) {this.address = address;}@Overridepublic String toString() {return "Artisan{" +"id=" + id +", name='" + name + '\'' +", address=" + address +'}';}
}
package com.artisan.codec.protostuff;import java.util.List;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class Address {private String location;private List<String> hobbies;public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}public List<String> getHobbiies() {return hobbies;}public void setHobbies(List<String> hobbies) {this.hobbies = hobbies;}public Address() {}public Address(String location) {this.location = location;}public Address(String location, List<String> hobbies) {this.location = location;this.hobbies = hobbies;}@Overridepublic String toString() {return "Address{" +"location='" + location + '\'' +", hobbies=" + hobbies +'}';}
}
ProtostuffUtil 解读
package com.artisan.codec.protostuff;import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.runtime.RuntimeSchema;import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world* @desc: protostuff 序列化工具类,基于protobuf封装*/
public class ProtostuffUtil {// 使用ConcurrentHashMap缓存Schema,提高性能private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>(); // 定义一个ConcurrentHashMap,用于缓存Schema// 获取类的Schema,如果缓存中没有则创建一个新的Schema并缓存起来private static <T> Schema<T> getSchema(Class<T> clazz) { // 定义一个泛型方法,用于获取类的SchemaSchema<T> schema = (Schema<T>) cachedSchema.get(clazz); // 从缓存中获取Schemaif (schema == null) { // 如果Schema为空schema = RuntimeSchema.getSchema(clazz); // 创建一个新的Schemaif (schema != null) { // 如果新的Schema不为空cachedSchema.put(clazz, schema); // 将新的Schema添加到缓存中}}return schema; // 返回Schema}/*** 序列化** @param obj 要序列化的对象* @return 序列化后的字节流*/public static <T> byte[] serializer(T obj) { // 定义一个泛型方法,用于序列化对象@SuppressWarnings("unchecked")Class<T> clazz = (Class<T>) obj.getClass(); // 获取对象的类类型LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE); // 分配一个LinkedBuffer缓冲区try {Schema<T> schema = getSchema(clazz); // 获取对象的Schemareturn ProtostuffIOUtil.toByteArray(obj, schema, buffer); // 将对象序列化为字节流} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e); // 如果出现异常,则抛出自定义异常} finally {buffer.clear(); // 清理LinkedBuffer缓冲区}}/*** 反序列化** @param data 序列化的字节流* @param clazz 对象的类类型* @return 反序列化后的对象*/public static <T> T deserializer(byte[] data, Class<T> clazz) { // 定义一个泛型方法,用于反序列化字节流try {T obj = clazz.newInstance(); // 创建一个新的对象实例Schema<T> schema = getSchema(clazz); // 获取对象的SchemaProtostuffIOUtil.mergeFrom(data, obj, schema); // 将字节流反序列化为对象return obj; // 返回反序列化后的对象} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e); // 如果出现异常,则抛出自定义异常}}public static void main(String[] args) {byte[] userBytes = ProtostuffUtil.serializer(new Artisan(1, "artisan", new Address("ssss", Arrays.asList("code", "run"))));Artisan artisan = ProtostuffUtil.deserializer(userBytes, Artisan.class);System.out.println(artisan);}
}
ProtostuffUtil提供了对Google的Protocol Buffers(protobuf)序列化格式的封装。该工具类使用com.dyuproject.protostuff库,这是一个Google protobuf的Java扩展库,提供了更简单、更灵活的API。
-
缓存Schema:
ProtostuffUtil使用一个ConcurrentHashMap来缓存不同类的Schema。这样做可以避免在每次序列化或反序列化时都创建新的Schema,从而提高性能。private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<>(); -
获取Schema: 工具类提供了一个泛型方法
getSchema(Class<T> clazz)来获取某个类的Schema。如果Schema已经在缓存中,就直接返回;否则,创建一个新的Schema并将其添加到缓存中。private static <T> Schema<T> getSchema(Class<T> clazz) {Schema<T> schema = cachedSchema.get(clazz);if (schema == null) {schema = RuntimeSchema.getSchema(clazz);if (schema != null) {cachedSchema.put(clazz, schema);}}return schema; } -
序列化:
serializer(T obj)方法用于将对象序列化为字节流。这个方法使用ProtostuffIOUtil.toByteArray方法完成序列化,并返回序列化后的字节流。public static <T> byte[] serializer(T obj) {@SuppressWarnings("unchecked")Class<T> clazz = (Class<T>) obj.getClass();LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);try {Schema<T> schema = getSchema(clazz);return ProtostuffIOUtil.toByteArray(obj, schema, buffer);} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);} finally {buffer.clear();} } -
反序列化:
deserializer(byte[] data, Class<T> clazz)方法用于将字节流反序列化为对象。这个方法使用ProtostuffIOUtil.mergeFrom方法完成反序列化,并返回反序列化后的对象。public static <T> T deserializer(byte[] data, Class<T> clazz) {try {T obj = clazz.newInstance();Schema<T> schema = getSchema(clazz);ProtostuffIOUtil.mergeFrom(data, obj, schema);return obj;} catch (Exception e) {throw new IllegalStateException(e.getMessage(), e);} }
总体而言,该工具类简化了protobuf的序列化和反序列化操作,并提供了Schema的缓存机制以提高性能。
测试
启动Server和Client后

小结
Protostuff是一个用于Java对象的序列化和反序列化的库,它的主要特点和功能如下:
- 高性能:Protostuff的序列化和反序列化操作非常快,对于大量数据的处理具有较高的性能优势。
- 简单易用:Protostuff的API设计简单明了,使用起来非常方便,可以快速实现对象的序列化和反序列化操作。
- 可扩展性:Protostuff允许用户自定义序列化和反序列化的逻辑,提供了丰富的扩展点,满足不同场景的需求。
总的来说,Protostuff是一个高效、简单、可扩展的Java序列化库,适用于多种场景,特别是对于大量数据和高性能要求的应用场景有较好的表现。

相关文章:
Netty Review - Netty与Protostuff:打造高效的网络通信
文章目录 概念PrePomServer & ClientProtostuffUtil 解读测试小结 概念 Pre 每日一博 - Protobuf vs. Protostuff:性能、易用性和适用场景分析 Pom <dependency><groupId>com.dyuproject.protostuff</groupId><artifactId>protostuff-…...
在ClickHouse数据库中启用预测功能
在这篇博文中,我们将介绍如何将机器学习支持的预测功能与 ClickHouse 数据库集成。ClickHouse 是一个快速、开源、面向列的 SQL 数据库,对于数据分析和实时分析非常有用。该项目由 ClickHouse, Inc. 维护和支持。我们将探索它在需要数据准备以…...
目标检测YOLO实战应用案例100讲-树上果实识别与跟踪计数(续)
目录 3.2 损失函数优化 3.3 实验过程 3.3.1 果实图像采集 3.3.2 数据扩增...
Docker 文件和卷 权限拒绝
一 创作背景 再复制Docker影像文件或访问Docker容器内已安装卷上的文件时我们常常会遇到:“权限被拒绝”的错误,在此,您将了解到为什么会出现“权限被拒绝”的错误以及如何解决这个问题。 二 目的 在深入探讨 Docker 容器中的 Permission De…...
Appium Server 启动失败常见原因及解决办法
Error: listen EADDRINUSE: address already in use 0.0.0.0:4723 如下图: 错误原因:Appium 默认的4723端口被占用 解决办法: 出现该提示,有可能是 Appium Server 已启动,关闭已经启动的 Appium Server 即可。472…...
将Abp默认事件总线改造为分布式事件总线
文章目录 原理创建分布式事件总线实现自动订阅和事件转发 使用启动Redis服务配置传递Abp默认事件传递自定义事件 项目地址 原理 本地事件总线是通过Ioc容器来实现的。 IEventBus接口定义了事件总线的基本功能,如注册事件、取消注册事件、触发事件等。 Abp.Events…...
Jupyter Notebook修改默认工作目录
1、参考修改Jupyter Notebook的默认工作目录_jupyter文件路径-CSDN博客修改配置文件 2.在上述博客内容的基础上,这里不是删除【%USERPROFILE%】而是把这个地方替换为所要设置的工作目录路径, 3.【起始位置】也可以更改为所要设置的工作目录路径&#x…...
高校/企业如何去做数据挖掘呢?
随着近年来人工智能及大数据、云计算进入爆发时期,依托三者进行的数据分析、数据挖掘服务已逐渐成为各行业进行产业升级的载体,缓慢渗透进我们的工作和生活,成为新时代升级版的智能“大案牍术”。 那么对于多数企业来说,如何做数据…...
数据仓库-数据治理小厂实践
一、简介 数据治理贯穿数仓中数据的整个生命周期,从数据的产生、加载、清洗、计算,再到数据展示、应用,每个阶段都需要对数据进行治理,像有些比较大的企业都是有自己的数据治理平台或者会开发一些便捷的平台,对于没有平…...
【C++多线程编程】(五)之 线程生命周期管理join() 与 detach()
在C中,std::thread 类用于创建和管理线程。std::thread 提供了两种主要的方法来控制线程的生命周期:join 和 detach。 detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。join方式&…...
金融信贷场景的风险“要素”与主要“风险点”
目录 要素一:贷款对象 风险点1:为不具备主体资格或主体资格有瑕疵的借款人发放贷款 风险表现: 防控措施: 风险点2:向国家限控行业发放贷款 风险表现: 防控措施: 风险点3:受理不符合准入条件的客户申请 风险表现: 防控措施: 要素二:金额 风险点4:过渡授…...
ubuntu下docker安装,配置python运行环境
参考自: 1.最详细ubuntu安装docker教程 2.使用docker搭建python环境 首先假设已经安装了docker,卸载原来的docker 在命令行中运行: sudo apt-get updatesudo apt-get remove docker docker-engine docker.io containerd runc 安装docker依赖 apt-get…...
在Docker中安装kafka遇到问题记录
命令含义解答: 在docker安装kafka的时候,启动kafka的时候会执行下面语句: docker run -d --log-driver json-file --log-opt max-size100m --log-opt max-file2 --name kafka -p 9092:9092 -e KAFKA_BROKER_ID0 -e KAFKA_ZOOKEEPER_CONNEC…...
aws-waf-cdn 基于规则组的永黑解决方案
1. 新建waf 规则组 2. 为规则组添加规则 根据需求创建不同的规则 3. waf中附加规则组 (此时规则组所有规则都会附加到waf中,但是不会永黑) 此刻,可以选择测试下规则是否生效,测试前确认保护资源绑定无误 4. 创建堆…...
如何实现免费无限流量云同步笔记软件Obsidian?
目录 前言 如何实现免费无限流量云同步笔记软件Obsidian? 一、简介 软件特色演示: 二、使用免费群晖虚拟机搭建群晖Synology Drive服务,实现局域网同步 1 安装并设置Synology Drive套件 2 局域网内同步文件测试 三、内网穿透群晖Synol…...
GPTs | Actions应用案例
上篇文章说道,如何使用创建的GPTs通过API接口去获取外部的一些信息,然后把获取的外部信息返回给ChatGPT让它加工出来,回答你的问题,今天我们就来做一个通俗易懂的小案例,让大家来初步了解一下它的使用法! …...
Python Opencv实践 - 手势音量控制
本文基于前面的手部跟踪功能做一个手势音量控制功能,代码用到了前面手部跟踪封装的HandDetector.这篇文章在这里: Python Opencv实践 - 手部跟踪-CSDN博客文章浏览阅读626次,点赞11次,收藏7次。使用mediapipe库做手部的实时跟踪&…...
关于Selenium的网页对象单元测试的设计模式
写在前面:经过了实践总结一下经验,心得进行一个分享。 首先driver是可以单独抽出来的,变成一个driver函数放在driver.py。 from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver…...
基于多反应堆的高并发服务器【C/C++/Reactor】(上)
(一)初始化服务器端用于监听的套接字 Server.h #pragma once // 初始化监听的套接字 int initListenFd(unsigned short port); Server.c int initListenFd(unsigned short port) {// 1.创建监听的fdint lfd socket(AF_INET, SOCK_STREAM, 0);if(lf…...
腾讯云debian服务器的连接与初始化
目录 1. 远程连接2. 软件下载3. 设置开机自启动 1. 远程连接 腾讯云给的服务器在安装好系统之后,只需要在防火墙里面添加一个白名单(ip 或者域名)就能访问了。 浏览器打开https://www.ipip.net/,在左下角找到自己所用的WIFI的公…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
