Netty Review - ObjectEncoder对象和ObjectDecoder对象解码器的使用与源码解读

news2024/9/22 19:41:36

文章目录

  • 概述
    • ObjectEncoder
    • ObjectDecoder
  • Code
  • 源码分析
    • ObjectEncoder
    • ObjectDecoder
  • 小结

在这里插入图片描述


概述

在这里插入图片描述

Netty是一个高性能、异步的网络应用程序框架,它提供了对TCP、UDP和文件传输的支持。在Netty中,数据的发送和接收都是以字节流的形式进行的,因此需要将对象转换为字节流(编码)以及将字节流转换回对象(解码)。

ObjectEncoder

ObjectEncoder 是 Netty 中用于将对象编码为字节流的一种组件。在 Netty 的 pipeline 中,当你需要将某个对象发送到网络时,你可以使用 ObjectEncoder 来实现。它会将对象序列化为字节流,以便可以在网络中传输。
例如,当你使用 Netty 的 Bootstrap 类来配置你的客户端时,你可以为你的 channel pipeline 添加一个 ObjectEncoder

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
       .channel(NioSocketChannel.class)
       .handler(new ChannelInitializer<SocketChannel>() {
           @Override
           public void initChannel(SocketChannel ch) throws Exception {
               ch.pipeline().addLast(new ObjectEncoder());
               // 添加其他 handlers...
           }
       });

在这个例子中,ObjectEncoder 被添加到了 channel 的 pipeline 中,这样在数据传输过程中,发送的对象就会被自动编码为字节流。


ObjectDecoder

ObjectEncoder 相对应,ObjectDecoder 是用于将接收到的字节流解码为对象的组件。当你在 Netty 的 pipeline 中接收到字节流时,你可以使用 ObjectDecoder 来自动将字节流反序列化为对象。

继续上面的例子,如果你想在 pipeline 中添加 ObjectDecoder,你可以这样做:

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
       .channel(NioSocketChannel.class)
       .handler(new ChannelInitializer<SocketChannel>() {
           @Override
           public void initChannel(SocketChannel ch) throws Exception {
               ch.pipeline().addLast(new ObjectDecoder());
               // 添加其他 handlers...
           }
       });

在这个例子中,ObjectDecoder 被添加到了 channel 的 pipeline 中,这样在数据接收过程中,接收到的字节流就会被自动解码为对象。
总的来说,ObjectEncoderObjectDecoder 是 Netty 中用于对象序列化和反序列化的工具,它们让开发者可以更方便地在网络中传输对象。


Code

在这里插入图片描述


这段代码是一个简单的Netty服务器启动类

package com.artisan.codec.objectencoder;
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;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class NettyServer {
    public static void main(String[] args) throws Exception {
        // 创建事件循环组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // 创建ServerBootstrap
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 配置ServerBootstrap
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 初始化通道
                            ChannelPipeline pipeline = ch.pipeline();
                            // 添加ObjectDecoder
                            pipeline.addLast(new ObjectDecoder(10240, ClassResolvers.cacheDisabled(this.getClass().getClassLoader())));
                            // 添加自定义的处理器
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });
            // 打印日志
            System.out.println("netty server start。。");
            // 绑定端口并启动服务器
            ChannelFuture channelFuture = serverBootstrap.bind(4567).sync();
            // 等待服务器通道关闭
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 优雅地关闭事件循环组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

在上述代码中,NettyServer类通过ServerBootstrap配置并启动了一个Netty服务器。服务器使用了两个事件循环组:一个用于处理连接(bossGroup),另一个用于处理已连接的通道(workerGroup)。
initChannel方法中,初始化了SocketChannel的通道 pipeline,并添加了ObjectDecoder和自定义的处理器NettyServerHandlerObjectDecoder用于反序列化接收到的字节流为Java对象,NettyServerHandler用于处理业务逻辑。
服务器启动后,会绑定到指定端口(本例中为4567),并等待服务器通道关闭。在关闭服务器之前,通过调用shutdownGracefully方法优雅地关闭事件循环组。
请注意,此代码片段仅作为Netty服务器启动的示例,实际应用中需要根据具体业务需求调整NettyServerHandler以实现相应的功能。


package com.artisan.codec.objectencoder;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 当接收到客户端发送的消息时,执行该方法
        System.out.println("从客户端读取到Object:" + ((ArtisanSimple) msg).toString());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 当发生异常时,执行该方法
        cause.printStackTrace();
        ctx.close();
    }
}


package com.artisan.codec.objectencoder;
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;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class NettyClient {
    public static void main(String[] args) throws Exception {
        // 创建事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创建Bootstrap
            Bootstrap bootstrap = new Bootstrap();
            // 配置Bootstrap
            bootstrap.group(group).channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 初始化通道
                            ChannelPipeline pipeline = ch.pipeline();
                            // 添加ObjectEncoder
                            pipeline.addLast(new ObjectEncoder());
                            // 添加自定义的处理器
                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            // 打印日志
            System.out.println("netty client start。。");
            // 连接到服务器
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 4567).sync();
            // 等待客户端通道关闭
            channelFuture.channel().closeFuture().sync();
        } finally {
            // 优雅地关闭事件循环组
            group.shutdownGracefully();
        }
    }
}

在上述代码中,NettyClient类通过Bootstrap配置并启动了一个Netty客户端。客户端使用了一个事件循环组(group)来处理通道的连接和接收到的消息。

initChannel方法中,初始化了SocketChannel的通道 pipeline,并添加了ObjectEncoder和自定义的处理器NettyClientHandlerObjectEncoder用于将Java对象序列化为字节流,NettyClientHandler用于处理业务逻辑。

客户端启动后,会连接到指定IP地址(本例中为127.0.0.1)和端口(本例中为4567)的服务器,并等待客户端通道关闭。在关闭客户端之前,通过调用shutdownGracefully方法优雅地关闭事件循环组。

请注意,此代码片段仅作为Netty客户端启动的示例,实际应用中需要根据具体业务需求调整NettyClientHandler以实现相应的功能。


这段代码是一个自定义的Netty处理器,名为NettyClientHandler。它继承自ChannelInboundHandlerAdapter,用于处理客户端接收到的消息和通道激活事件。

package com.artisan.codec.objectencoder;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 当接收到服务器发送的消息时,执行该方法
        System.out.println("收到服务器消息:" + msg);
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 当通道激活时,执行该方法
        System.out.println("NettyClientHandler发送数据");
        // 测试对象编解码
        ArtisanSimple artisanSimple = new ArtisanSimple(1, "xxxx");
        ctx.writeAndFlush(artisanSimple);
    }
}

在上述代码中,NettyClientHandler类重写了channelReadchannelActive方法。
channelRead方法用于处理客户端接收到的服务器消息。在这个例子中,它将打印出接收到的消息。在实际应用中,你可以根据业务需求修改此方法以处理不同的消息类型和逻辑。

channelActive方法用于处理通道激活事件。在这个例子中,它将打印一条日志,并测试对象编解码功能。具体来说,它创建了一个ArtisanSimple对象,并通过ctx.writeAndFlush()方法将其发送到服务器。

在实际应用中,你可以根据需求修改此方法以实现不同的业务逻辑。

NettyClientHandler处理器需要与ObjectEncoderObjectDecoder配合使用,以确保发送和接收到的字节流能够正确地反序列化为Java对象。在客户端启动类NettyClient中,NettyClientHandler已经添加到了通道的pipeline中,因此可以处理发送和接收到的消息。


package com.artisan.codec.objectencoder;

import java.io.Serializable;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class ArtisanSimple implements Serializable {

    private int id;
    private String name;

    public ArtisanSimple() {
    }

    public ArtisanSimple(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }


    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;
    }

    @Override
    public String toString() {
        return "ArtisanSimple{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }



}

package com.artisan.codec.objectencoder;

/**
 * @author 小工匠
 * @version 1.0
 * @mark: show me the code , change the world
 */
public class AddressSimple {

    private String location;


    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }


    public AddressSimple() {
    }

    public AddressSimple(String location) {
        this.location = location;
    }


    @Override
    public String toString() {
        return "AddressSimple{" +
                "location='" + location + '\'' +
                '}';
    }
}
    

【测试】

在这里插入图片描述


源码分析

在这里插入图片描述

ObjectEncoder

这段代码定义了一个名为ObjectEncoder的类,它属于Netty网络通信框架的一部分,用于将Java对象序列化为字节流。

在这里插入图片描述

下面是对代码的详细分析以及增加的中文注释:

package io.netty.handler.codec.serialization; 

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; 
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
 * An encoder which serializes a Java object into a {@link ByteBuf}.
 * <p>
 * 请注意,此编码器产生的序列化形式与标准的{@link ObjectInputStream}不兼容。
 * 请使用{@link ObjectDecoder}或{@link ObjectDecoderInputStream}以确保与该编码器的互操作性。
 */
@Sharable
public class ObjectEncoder extends MessageToByteEncoder<Serializable> {
    // 定义一个占位符,用于标记ByteBuf中对象序列化数据长度的位置
    private static final byte[] LENGTH_PLACEHOLDER = new byte[4];
    // 覆写encode方法,实现序列化逻辑
    @Override
    protected void encode(ChannelHandlerContext ctx, Serializable msg, ByteBuf out) throws Exception {
        int startIdx = out.writerIndex(); // 记录开始编码的位置
        // 创建一个ByteBufOutputStream包装器,用于向ByteBuf中写入数据
        ByteBufOutputStream bout = new ByteBufOutputStream(out);
        ObjectOutputStream oout = null;
        try {
            bout.write(LENGTH_PLACEHOLDER); // 先写入长度占位符
            // 创建一个紧凑型ObjectOutputStream,用于序列化对象
            oout = new CompactObjectOutputStream(bout);
            oout.writeObject(msg); // 将要序列化的对象写入流中
            oout.flush(); // 刷新输出流,确保所有数据都被写出
        } finally {
            // 关闭ObjectOutputStream和ByteBufOutputStream
            if (oout != null) {
                oout.close();
            } else {
                bout.close();
            }
        }
        int endIdx = out.writerIndex(); // 记录编码结束的位置
        // 设置占位符的长度,即实际序列化数据长度
        out.setInt(startIdx, endIdx - startIdx - 4);
    }
}

在上述代码中,ObjectEncoder类继承自MessageToByteEncoder,这意味着它是一个用于将某种类型消息编码成字节流的编码器。encode方法被重写以实现序列化过程。在这个方法中,首先通过ByteBufOutputStreamByteBuf写入了一个长度占位符,然后通过CompactObjectOutputStream将传入的Serializable对象序列化成字节流,并写入到ByteBuf中。最后,修改了长度占位符,将其设置为实际序列化数据的长度。

此代码片段使用@Sharable注解标记,表明这个ChannelHandler是可以共享给多个ChannelPipeline的。
序列化完成后,通过ObjectOutputStreamflush方法刷新流,确保所有数据都被写出。最后,在finally块中关闭输出流,确保资源被正确释放。


ObjectDecoder

这段代码定义了一个名为ObjectDecoder的类,它也是Netty网络通信框架的一部分,用于将接收到的字节流反序列化为Java对象。

在这里插入图片描述

下面是对代码的详细分析以及增加的中文注释:

package io.netty.handler.codec.serialization;
// 引入Netty相关类库
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
// 引入Java序列化相关类库
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
/**
 * A decoder which deserializes the received {@link ByteBuf}s into Java
 * objects.
 * <p>
 * 请注意,此解码器期望的序列化形式与标准的{@link ObjectOutputStream}不兼容。
 * 请使用{@link ObjectEncoder}或{@link ObjectEncoderOutputStream}以确保与该解码器的互操作性。
 */
public class ObjectDecoder extends LengthFieldBasedFrameDecoder {
    // ClassResolver用于加载序列化对象的类
    private final ClassResolver classResolver;
    /**
     * 创建一个新的解码器,其最大对象大小为1048576字节。
     * 如果接收到的对象大小大于1048576字节,将抛出StreamCorruptedException异常。
     *
     * @param classResolver  用于此解码器的ClassResolver
     */
    public ObjectDecoder(ClassResolver classResolver) {
        this(1048576, classResolver);
    }
    /**
     * 创建一个新的解码器,其最大对象大小为指定的值。
     *
     * @param maxObjectSize  序列化对象的最大字节长度。
     *                     如果接收到的对象的长度大于此值,将抛出StreamCorruptedException异常。
     * @param classResolver  用于加载序列化对象类的ClassResolver
     */
    public ObjectDecoder(int maxObjectSize, ClassResolver classResolver) {
        super(maxObjectSize, 0, 4, 0, 4);
        this.classResolver = classResolver;
    }
    // 覆写decode方法,实现反序列化逻辑
    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        ByteBuf frame = (ByteBuf) super.decode(ctx, in);
        if (frame == null) {
            return null;
        }
        // 创建一个紧凑型ObjectInputStream,用于反序列化对象
        ObjectInputStream ois = new CompactObjectInputStream(new ByteBufInputStream(frame, true), classResolver);
        try {
            return ois.readObject(); // 读取并返回反序列化的对象
        } finally {
            ois.close(); // 关闭输入流
        }
    }
}

在上述代码中,ObjectDecoder类继承自LengthFieldBasedFrameDecoder,这意味着它是一个用于解码具有长度字段帧的解码器。decode方法被重写以实现反序列化过程。在这个方法中,首先通过LengthFieldBasedFrameDecoder的解码方法获取到包含序列化数据的ByteBuf帧,然后通过CompactObjectInputStream将字节流反序列化为Java对象。

此代码片段使用了一个ClassResolver,它负责加载序列化对象的类,从而允许在反序列化过程中创建对象。反序列化完成后,通过ObjectInputStreamclose方法关闭输入流,确保资源被正确释放。


小结

ObjectEncoder和ObjectDecoder是Netty框架中的两个重要组件,它们分别负责将Java对象编码为字节流以及将字节流解码为Java对象。

ObjectEncoder是一个ChannelOutboundHandler,它主要负责将Java对象转换为字节流。当发送一个对象时,ObjectEncoder会根据对象的类型将其序列化为字节流,以便在网络上进行传输。ObjectEncoder通常与ObjectDecoder配合使用,以确保编码和解码过程能够正确地进行。

ObjectDecoder是一个ChannelInboundHandler,它主要负责将接收到的字节流解码为Java对象。当接收到字节流时,ObjectDecoder会根据字节流的类型进行反序列化,将字节流转换回原始的Java对象。ObjectDecoder通常与ObjectEncoder配合使用,以确保编码和解码过程能够正确地进行。

在实际应用中,ObjectEncoderObjectDecoder需要根据业务需求进行定制,以便正确地处理各种不同类型的对象。通过使用这两个组件,Netty框架可以在发送和接收消息时自动进行对象的编码和解码,简化了网络编程的复杂度。

在这里插入图片描述

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

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

相关文章

从实践角度优化数据库设计:深入解析三范式的应用

总述 第一范式(1NF):要求关系模式中的每个属性都是不可分的数据项,即属性具有原子性。第二范式(2NF):在满足1NF的基础上,要求关系模式中的所有非主属性都完全函数依赖于整个候选键(或主键)。第三范式(3NF):在满足2NF的基础上,要求关系模式中的每个非主属性都不传…

LVS最终奥义之DR直接路由模式

1 LVS-DR(直接路由模式) 1.1 LVS-DR模式工作过程 1.客户端通过VIP将访问请求报文&#xff08;源IP为客户端IP&#xff0c;目标IP为VIP&#xff09;发送到调度器 2.调度器通过调度算法选择最适合的节点服务器并重新封装数据报文&#xff08;将源mac地址改为调度器的mac地址&am…

centos(linux)安装jenkins

官网&#xff1a;https://pkg.jenkins.io/redhat/ 安装官网进行操作&#xff1a; sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat/jenkins.reposudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io-2023.key若出现如下错误&#xff1a; …

JavaScript基础函数+对象+继承

目录 1.创建函数 2.函数分类 2.1带参数函数 2.2匿名函数 2.3嵌套函数 2.4立即执行函数 ES6特有的箭头函数 2.5对象中的函数 3.this对象 4.有参构造函数创建对象 5.原型 prototype 6.函数应用&#xff08;继承&#xff09; 6.1原型链继承 6.2构造继承 6.3组合继承&…

Observability:捕获 Elastic Agent 和 Elasticsearch 之间的延迟

在现代 IT 基础设施的动态环境中&#xff0c;高效的数据收集和分析至关重要。 Elastic Agent 是 Elastic Stack 的关键组件&#xff0c;通过促进将数据无缝摄取到 Elasticsearch 中&#xff0c;在此过程中发挥着至关重要的作用。 然而&#xff0c;显着影响此过程整体有效性的关…

vue3使用mock模拟后端接口

安装mock axios yarn add mock yarn add axios 新建在src/mockdata/automenu.js 模拟后端的json数据格式 import Mock from mockjs Mock.mock(/menu,get,{status: 200,menuList: [{id : 1,iconCls: "fa fa-window",name: 系统管理,url: /},{id: 2,icon: icon-j…

智能优化算法应用:基于龙格-库塔算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于龙格-库塔算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于龙格-库塔算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.龙格-库塔算法4.实验参数设定5.算法结果…

CSS操纵元素的禁用和启用

通常表单控件都会有属性readonly、disabled对元素进行只读、禁用等操作。 而有时候我们想要div也达到类似效果&#xff0c;可以用CSS样式pointer-events: none进行控制。 科普知识 CSS样式的pointer-events: none用于控制一个元素能否响应鼠标操作。当该属性设置为none时&am…

【Docker-4】Docker 命令

1、镜像管理命令 docker images #查看本机镜像 [rootdocker-0001 ~]# docker imagesdocker search 镜像名称 #从官方仓库查找镜像 [rootdocker-0001 ~]# docker search busybox #需要联网&#xff0c;本次不用操作docker pull 镜像名称:标签 #下载镜像 [rootdocke…

MySQL基本操作 DDL DML DQL三大操作介绍

DDL 数据(结构)定义 创建表DML 数据操作 增删改DQL 查询语句 DDL 数据(结构)定义 创建表 创建 删除数据 注释 --空格内容 创建数据库 CREATE DATABASE [if not exists] 数据库名 [ CHARSET utf8]eg:CREATE DATABASE IF NOT EXISTS school CHARSET utf8如果对应school不存在,…

【GitHub精选项目】IP 地址转地理位置:ip2region 完全指南

前言 本文为大家带来的是 lionsoul2014 开发的 ip2region 项目&#xff0c;一种高效的离线 IP 地址定位库。ip2region 提供了10微秒级别的查询效率&#xff0c;支持多种主流编程语言&#xff0c;是一种理想的 IP 定位解决方案。 这个开源项目可以实现 IP 地址到地理位置的精确映…

2017年第六届数学建模国际赛小美赛A题飓风与全球变暖解题全过程文档及程序

2017年第六届数学建模国际赛小美赛 A题 飓风与全球变暖 原题再现&#xff1a; 飓风&#xff08;也包括在西北太平洋被称为“台风”的风暴以及在印度洋和西南太平洋被称为“严重热带气旋”&#xff09;具有极大的破坏性&#xff0c;往往造成数百人甚至数千人死亡。   许多气…

SVN搭建指导

环境 centos 7.9 SVN安装方式一&#xff1a;yum 1.1 http服务 至今还没有搞定网页版&#xff0c;网页版需要搭建apache http服务。遇到如下问题&#xff1a; centos - svn: Could not open the requested SVN filesystem - Stack Overflow 在试了加777权限&#xff0c;加a…

校园圈子交友系统,APP小程序H5,三端源码交付,支持二开!实名认证,大V认证,地图找伴,二手平台!

校园圈子交友系统&#xff0c;是属于自主定义开发的系统&#xff0c;内容有很多&#xff0c;先截取一些给大家看看&#xff0c;让大家更多的了解本系统&#xff0c;然后再做评价&#xff01; 校园后端下载地址&#xff1a;校园圈子系统小程序&#xff0c;校园拼车&#xff0c;校…

Pycharm 关闭控制台多余窗口详解(console)

文章目录 1 问题描述2 解决办法2.1 步骤1&#xff1a;编辑配置2.2 步骤2&#xff1a;使用 Python 控制台运行&#xff08;取消勾选&#xff09;2.3 验证&#xff1a;再次运行&#xff0c;多余窗口消失 1 问题描述 2 解决办法 2.1 步骤1&#xff1a;编辑配置 菜单路径&#xf…

【bug日记】如何切换jdk版本,如何解决java和javac版本不一致

背景 今天在安装jenkins后&#xff0c;使用java运行war包的时候&#xff0c;提示jdk1.8版本太低&#xff0c;需要提高版本&#xff0c;所以就需要切换jdk版本 解决 在用户变量中&#xff0c;首先更改了JAVA_HOME的地址为17的目录&#xff0c;发现javac的版本改为17了&#x…

云原生扫盲篇

What 云原生加速了应用系统与基础设施资源之间的解耦,向下封装资源以便将复杂性下沉到基础设施层;向上支撑应用,让开发者更关注业务价值 云原生是一种构建和运行应用程序的方法,也是一套技术体系和方法论. Cloud 表示应用程序位于云中而不是传统的数据中心Native表示应用程序从…

函数帧栈的创建和销毁(一)

目录 什么是函数栈帧 理解函数栈帧能解决什么问题 函数栈帧的创建和销毁 什么是栈 认识相关寄存器和汇编指令 相关寄存器 相关汇编命令 esp和ebp 解析函数栈帧的创建和销毁 学前补充 函数的调用堆栈 什么是函数栈帧 我们在写C 语言代码的时候&#xff0c;经常会把…

ThinkPad E550c

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;…

java8实战 lambda表达式、函数式接口、方法引用双冒号(中)

前言 书接上文&#xff0c;上一篇博客讲到了lambda表达式的应用场景&#xff0c;本篇接着将java8实战第三章的总结。建议读者先看第一篇博客 其他函数式接口例子 上一篇有讲到Java API也有其他的函数式接口&#xff0c;书里也举了2个例子&#xff0c;一个是java.util.functi…