Netty官方 入门示例详解,字节跳动面试多久出结果

  • Netty没有强制性的外部依赖关系。运行Netty只需JDK 1.5(对于Netty 4+,则为1.6)或更高版本。

如果使用 Maven 进行项目开发管理,则 Netty 也提供了 Maven 依赖。

Maven 依赖可以从 Netty 官网下载页中获取:https://netty.io/downloads.html,如下所示:

io.netty

netty

X.Y.Z.Q

compile

netty:如果 Netty 是 4.0 以下版本,则 artifactId值写

netty,如果 Netty 是 4.0 及以上版本,则 写 netty-all。

X.Y.Z.Q:netty 版本号自己填写具体版本即可

如果进一步学习可以下载 源码 ,下载包中有更多官方示例

在这里插入图片描述

业务问题


如今,我们使用通用应用程序或库相互通信。例如,我们经常使用HTTP客户端库从Web服务器检索信息并通过Web服务调用远程过程调用。但是,通用协议其实现有时不能很好地扩展。 就像我们不使用通用HTTP服务器来交换大文件,电子邮件消息和近乎实时的消息(例如财务信息和多人游戏数据)一样。所需要的是专门用于特殊目的的高度优化的协议实现。例如,您可能想要实现针对基于AJAX的聊天应用程序,媒体流或大文件传输进行了优化的HTTP服务器。您甚至可能想要设计和实现完全适合您的需求的全新协议。另一个不可避免的情况是,您必须处理旧的专有协议以确保与旧系统的互操作性。在这种情况下重要的是,我们可以在不牺牲最终应用程序的稳定性和性能的情况下,以多快的速度实现该协议。

解决方案-Netty


Netty项目致力于提供一个异步事件驱动的网络应用程序框架和工具,以快速开发可维护的高性能和高可扩展性协议服务器和客户端。

换句话说,Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了网络编程,例如TCP和UDP套接字服务器的开发。

“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty经过精心设计,结合了从许多协议(例如FTP,SMTP,HTTP以及各种基于二进制和文本的旧式协议)的实施中获得的经验。结果,Netty成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法。

一些用户可能已经发现其他声称具有相同优势的网络应用程序框架,并且您可能想问一下Netty与他们有何不同。答案是它所基于的哲学。Netty旨在从第一天开始就API和实施方面为您提供最舒适的体验。这不是有形的东西,但是您会认识到,当您阅读本指南并与Netty一起使用时,这种哲学将使您的工作更加轻松。

入门


本章将通过简单的示例介绍Netty的核心构造,以使您快速入门。在本章结尾时,您将能够立即在Netty之上编写客户端和服务器。

如果您喜欢自上而下的学习方法,则可能要从第2章,体系结构概述开始,然后回到此处。

开始之前

运行本章中的示例的最低要求只有两个;Netty和JDK 1.6或更高版本的最新版本。Netty的最新版本可在项目下载页面中找到。要下载正确版本的JDK,请访问您首选的JDK供应商的网站。

在阅读时,您可能对本章介绍的类有更多疑问。如果您想进一步了解它们,请参考API参考。为了方便起见,本文档中的所有类名都链接到在线API参考。另外,请不要犹豫,与Netty项目社区联系,并让我们知道是否有任何不正确的信息,语法或拼写错误,以及您是否有任何好的想法来帮助改进文档。

编写 Discard Server

世界上最简单的协议不是“ Hello,World!”。但是DISCARD。这是一个协议,它丢弃任何收到的数据而没有任何响应。

要实现DISCARD协议,您只需要忽略所有接收到的数据。让我们直接从处理程序实现类开始,用它处理Netty生成的I/O事件。

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;

import io.netty.channel.ChannelInboundHandlerAdapter;

/**

  • Handles a server-side channel.

*/

public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)

// Discard the received data silently.

((ByteBuf) msg).release(); // (3)

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)

// Close the connection when an exception is raised.

cause.printStackTrace();

ctx.close();

}

}

  1. DiscardServerHandler extends ChannelInboundHandlerAdapter, which is an implementation of ChannelInboundHandler. ChannelInboundHandler
provides various event handler methods that you can override. For now,  
it is just enough to extend ChannelInboundHandlerAdapter rather than  
to implement the handler interface by yourself.
  1. We override the channelRead() event handler method here. This method is called with the received message, whenever new data is
received from a client. In this example, the type of the received  
message is ByteBuf.
  1. To implement the DISCARD protocol, the handler has to ignore the received message. ByteBuf is a reference-counted object which has to
be released explicitly via the release() method. Please keep in mind  
that it is the handler’s responsibility to release any  
reference-counted object passed to the handler. Usually, channelRead()  
handler method is implemented like the following:
  1. DiscardServerHandler继承ChannelInboundHandlerAdapter,也是ChannelInboundHandler的一个实现类。ChannelInboundHandler提供了可以覆盖的各种事件处理程序方法。目前,只需要继承ChannelInboundHandlerAdapter而不需要自己实现处理程序接口就可以了。

  2. 我们在channelRead()这里重写事件处理程序方法。每当从客户端接收到新数据时,只要接收到消息就会调用此方法。在此示例中,接收到的消息的类型为ByteBuf。

  3. 为了实现DISCARD协议,处理程序必须忽略收到的消息。ByteBuf是一个引用计数对象,必须通过该release()方法显式释放。请记住,释放任何传递给处理程序的引用计数对象是处理程序的职责。通常,channelRead()处理程序方法的实现如下:

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

try {

// Do something with msg

} finally {

ReferenceCountUtil.release(msg);

}

}

  1. The exceptionCaught() event handler method is called with a Throwable when an exception was raised by Netty due to an I/O error or
by a handler implementation due to the exception thrown while  
processing events. In most cases, the caught exception should be  
logged and its associated channel should be closed here, although the  
implementation of this method can be different depending on what you  
want to do to deal with an exceptional situation. For example, you  
might want to send a response message with an error code before  
closing the connection.

So far so good. We have implemented the first half of the DISCARD

server. What’s left now is to write the main() method which starts the

server with the DiscardServerHandler.

  1. Netty由于I / O错误可抛出异常时或由处理器实现类在处理事件中引发的异常 exceptionCaught()事件处理方法会被调用。在大多数情况下,应该记录捕获的异常,并在此处关闭其关联的通道,尽管此方法的实现可能会有所不同,具体取决于您要处理特殊情况时要采取的措施。例如,您可能想在关闭连接之前发送带有错误代码的响应消息。

到现在为止还挺好。我们已经实现了DISCARD服务器的前半部分。现在剩下的就是编写使用main()来启动服务器的方法DiscardServerHandler。

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.ChannelFuture;

import io.netty.channel.ChannelInitializer;

import io.netty.channel.ChannelOption;

import io.netty.channel.EventLoopGroup;

import io.netty.channel.nio.NioEventLoopGroup;

import io.netty.channel.socket.SocketChannel;

import io.netty.channel.socket.nio.NioServerSocketChannel;

/**

  • Discards any incoming data.

*/

public class DiscardServer {

private int port;

public DiscardServer(int port) {

this.port = port;

}

public void run() throws Exception {

EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

ServerBootstrap b = new ServerBootstrap(); // (2)

b.group(bossGroup, workerGroup)

.channel(NioServerSocketChannel.class) // (3)

.childHandler(new ChannelInitializer() { // (4)

@Override

public void initChannel(SocketChannel ch) throws Exception {

ch.pipeline().addLast(new DiscardServerHandler());

}

})

.option(ChannelOption.SO_BACKLOG, 128) // (5)

.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

// Bind and start to accept incoming connections.

ChannelFuture f = b.bind(port).sync(); // (7)

// Wait until the server socket is closed.

// In this example, this does not happen, but you can do that to gracefully

// shut down your server.

f.channel().closeFuture().sync();

} finally {

workerGroup.shutdownGracefully();

bossGroup.shutdownGracefully();

}

}

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

int port = 8080;

if (args.length > 0) {

port = Integer.parseInt(args[0]);

}

new DiscardServer(port).run();

}

}

  1. NioEventLoopGroup is a multithreaded event loop that handles I/O operation. Netty provides various EventLoopGroup implementations for
different kind of transports. We are implementing a server-side  
application in this example, and therefore two NioEventLoopGroup will  
be used. The first one, often called ‘boss’, accepts an incoming  
connection. The second one, often called ‘worker’, handles the traffic  
of the accepted connection once the boss accepts the connection and  
registers the accepted connection to the worker. How many Threads are  
used and how they are mapped to the created Channels depends on the  
EventLoopGroup implementation and may be even configurable via a  
constructor.
  1. ServerBootstrap is a helper class that sets up a server. You can set up the server using a Channel directly. However, please note that
this is a tedious process, and you do not need to do that in most  
cases.
  1. Here, we specify to use the NioServerSocketChannel class which is used to instantiate a new Channel to accept incoming connections.
  1. The handler specified here will always be evaluated by a newly accepted Channel. The ChannelInitializer is a special handler that is
purposed to help a user configure a new Channel. It is most likely  
that you want to configure the ChannelPipeline of the new Channel by  
adding some handlers such as DiscardServerHandler to implement your  
network application. As the application gets complicated, it is likely  
that you will add more handlers to the pipeline and extract this  
anonymous class into a top-level class eventually.
  1. You can also set the parameters which are specific to the Channel implementation. We are writing a TCP/IP server, so we are allowed to
set the socket options such as tcpNoDelay and keepAlive. Please refer  
to the apidocs of ChannelOption and the specific ChannelConfig  
implementations to get an overview about the supported ChannelOptions.
  1. Did you notice option() and childOption()? option() is for the NioServerSocketChannel that accepts incoming connections.
childOption() is for the Channels accepted by the parent  
ServerChannel, which is NioServerSocketChannel in this case.
  1. We are ready to go now. What’s left is to bind to the port and to start the server. Here, we bind to the port 8080 of all NICs (network
interface cards) in the machine. You can now call the bind() method as  
many times as you want (with different bind addresses.)

Congratulations! You’ve just finished your first server on top of

Netty

  1. NioEventLoopGroup是处理I / O操作的多线程事件循环。NettyEventLoopGroup为不同类型的传输提供了各种实现。在此示例中,我们正在实现服务器端应用程序,因此NioEventLoopGroup将使用两个。第一个通常称为“boss,接受传入的连接。第二个通常称为“worker”,一旦boss接受了连接并且向工作进程worker注册接受的连接,便处理已接受连接的流量。使用多少线程以及如何将它们映射到创建的Channels取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。

  2. ServerBootstrap是设置服务器的帮助程序类。您可以Channel直接使用直接设置服务器。但是,请注意,这是一个单调乏味的过程,在大多数情况下,您无需这样做。

  3. 在这里,我们指定使用NioServerSocketChannel用于实例化新的类Channel以接受传入的连接。

  4. 在此指定的处理程序将始终由新接受的评估Channel。该ChannelInitializer是意要帮助用户配置一个新的特殊处理程序Channel。您很可能希望通过添加一些处理程序(例如实现您的网络应用程序)来配置ChannelPipeline新Channel的DiscardServerHandler。随着应用程序变得复杂,您可能会向管道添加更多处理程序,并最终将此匿名类提取到顶级类中。

  5. 您还可以设置特定于Channel实现的参数。我们正在编写一个TCP / IP服务器,因此可以设置诸如tcpNoDelay和的套接字选项keepAlive。请参考的apidocsChannelOption和特定的ChannelConfig实现以获取有关support的概述ChannelOption。

  6. 您注意到option()和childOption()了吗?option()用于接受传入连接的NioServerSocketChannel。childOption()用于父ServerChannel接受的通道,在本例中是NioServerSocketChannel。

  7. 我们现在准备开始。剩下的就是绑定到端口并启动服务器。在这里,我们绑定到计算机8080中所有NIC(网络接口卡)的端口。现在,您可以根据需要bind()多次调用该方法(使用不同的绑定地址。)

恭喜你!您刚刚在Netty上完成了第一台服务器。

查看接收到的数据

现在,我们已经编写了第一台服务器,我们需要测试它是否确实有效。测试它的最简单方法是使用telnet命令。例如,您可以telnet localhost 8080在命令行中输入并输入一些内容。

但是,我们可以说服务器工作正常吗?我们真的不知道这是因为它是一个废弃服务器。您根本不会得到任何回应。为了证明它确实有效,让我们修改服务器以打印收到的内容。

我们已经知道,channelRead()每当收到数据时都会调用该方法。让我们将一些代码放入DiscardServerHandler的channelRead()方法中:

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ByteBuf in = (ByteBuf) msg;

try {

while (in.isReadable()) { // (1)

System.out.print((char) in.readByte());

System.out.flush();

}

} finally {

ReferenceCountUtil.release(msg); // (2)

}

}

  1. 这个低效的循环实际上可以简化为: System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))

  2. 或者,您可以在in.release()这里进行。

如果再次运行telnet命令,您将看到服务器打印收到的内容。

丢弃服务器的完整源代码位于io.netty.example.discard分发包中。

编写一个Echo服务器

到目前为止,我们一直在使用数据而没有任何响应。但是,服务器通常应该响应请求。让我们学习如何通过实现ECHO协议将响应消息写入客户端,在此将所有接收到的数据发送回去。

与前面几节中实现的丢弃服务器的唯一区别在于,它会将接收到的数据发回,而不是将接收到的数据打印到控制台。因此,再次修改该channelRead()方法就可以了:

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ctx.write(msg); // (1)

ctx.flush(); // (2)

}

  1. A ChannelHandlerContext object provides various operations that enable you to trigger various I/O events and operations. Here, we
invoke write(Object) to write the received message in verbatim. Please  
note that we did not release the received message unlike we did in the  
DISCARD example. It is because Netty releases it for you when it is  
written out to the wire.
  1. ctx.write(Object) does not make the message written out to the wire. It is buffered internally and then flushed out to the wire by
ctx.flush(). Alternatively, you could call ctx.writeAndFlush(msg) for  
brevity.

If you run the telnet command again, you will see the server sends

back whatever you have sent to it.

The full source code of the echo server is located in the

io.netty.example.echo package of the distribution.

  1. 一个ChannelHandlerContext对象提供的各种操作,使您可以触发各种I / O的事件和操作。在这里,我们调用write(Object)以逐字书写接收到的消息。请注意,我们没有像DISCARD示例中那样释放收到的消息。这是因为Netty在将其写到网络时会为您释放它。

  2. ctx.write(Object)不会将消息写到网络上。它在内部进行缓冲,然后通过ctx.flush()冲洗到网络上。或者,为了简洁些,您可以调用ctx.writeAndFlush(msg)。

如果再次运行telnet命令,您将看到服务器发送回您发送给它的任何内容。

回显服务器的完整源代码位于io.netty.example.echo分发包中。

编写时间服务器

The protocol to implement in this section is the TIME protocol. It is

different from the previous examples in that it sends a message, which

contains a 32-bit integer, without receiving any requests and closes

the connection once the message is sent. In this example, you will

learn how to construct and send a message, and to close the connection

on completion.

Because we are going to ignore any received data but to send a message

as soon as a connection is established, we cannot use the

channelRead() method this time. Instead, we should override the

channelActive() method. The following is the implementation:

本节中要实现的协议是TIME协议。它与前面的示例不同之处在于,它不包含任何请求就发送包含32位整数的消息,并在发送消息后关闭连接。在此示例中,您将学习如何构造和发送消息,以及如何在完成时关闭连接。

因为我们将忽略任何接收到的数据,而是在建立连接后立即发送消息,所以这次我们不能使用channelRead()该方法。相反,我们应该重写该channelActive()方法。以下是实现:

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

@Override

public void channelActive(final ChannelHandlerContext ctx) { // (1)

final ByteBuf time = ctx.alloc().buffer(4); // (2)

time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));

final ChannelFuture f = ctx.writeAndFlush(time); // (3)

f.addListener(new ChannelFutureListener() {

@Override

public void operationComplete(ChannelFuture future) {

assert f == future;

ctx.close();

}

}); // (4)

}

@Override

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {

cause.printStackTrace();

ctx.close();

}

}

  1. 如所解释的,channelActive()当建立连接并准备产生流量时,将调用该方法。让我们在该方法写一个代表当前时间的32位整数。

  2. 要发送新消息,我们需要分配一个包含消息的新缓冲区。我们将要写入一个32位整数,因此我们需要一个ByteBuf容量至少为4个字节。通过ChannelHandlerContext.alloc()获取ByteBufAllocator并分配一个新的缓冲区。

  3. 和往常一样,我们编写构造的消息。

但是,等等,翻转在哪里?java.nio.ByteBuffer.flip()在NIO中发送消息之前,我们不是已经调用过了java.nio.ByteBuffer.flip()在NIO中发送消息之前?ByteBuf没有这样的方法,因为它有两个指针:一个用于读操作,另一个用于写操作。当您向ByteBuf写入内容时,writer索引会增加,而reader索引不变。reader索引和writer索引分别表示消息的开始和结束位置。

相比之下,NIO缓冲区没有提供一种清晰的方法来确定消息内容在哪里开始和结束,而不用调用flip方法。如果您忘记翻转缓冲区,因为不会发送任何内容或不正确的数据,您将陷入麻烦。这样的错误在Netty中不会发生,因为我们对不同的操作类型有不同的指针。你会发现,当你习惯它时,它会让你的生活变得更轻松——一种没有flip的环境!

需要注意的另一点是ChannelHandlerContext.write()(和writeAndFlush())方法返回一个ChannelFuture。AChannelFuture表示尚未发生的I / O操作。这意味着,由于Netty中的所有操作都是异步的,因此可能尚未执行任何请求的操作。例如,以下代码甚至在发送消息之前就可能关闭连接:

Channel ch = …;

ch.writeAndFlush(message);

ch.close();

因此,您需要在ChannelFuture完成后调用close()方法,该方法通过write()方法返回,并在完成写操作后通知其侦听器。请注意,close()也可能不会立即关闭连接,并且返回ChannelFuture。

How do we get notified when a write request is finished then? This is

as simple as adding a ChannelFutureListener to the returned

ChannelFuture. Here, we created a new anonymous ChannelFutureListener

which closes the Channel when the operation is done.

Alternatively, you could simplify the code using a pre-defined

listener:

当写请求完成时,我们如何得到通知?这就像将添加一个ChannelFutureListener到return ChannelFuture一样简单。在这里,我们创建了一个新的匿名类操作ChannelFutureListener,该匿名类Channel操作将在操作完成时关闭。

另外,您可以使用预定义的侦听器简化代码:

f.addListener(ChannelFutureListener.CLOSE);

要测试我们的时间服务器是否按预期工作,可以使用UNIXrdate命令:

$ rdate -o -p

其中是您在main()方法中指定的端口号,通常为localhost。

编写时间客户端

与DISCARD和ECHO服务器不同,我们需要该TIME协议的客户端,因为人类无法将32位二进制数据转换为日历中的日期。在本节中,我们讨论如何确保服务器正常工作,并学习如何使用Netty编写客户端。

服务器和Netty中的客户机之间的最大和唯一的区别是不同的Bootstrap,与 使用的Channel实现类。请看下面的代码:

public class TimeClient {

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

String host = args[0];

int port = Integer.parseInt(args[1]);

EventLoopGroup workerGroup = new NioEventLoopGroup();

try {

Bootstrap b = new Bootstrap(); // (1)

b.group(workerGroup); // (2)

b.channel(NioSocketChannel.class); // (3)

b.option(ChannelOption.SO_KEEPALIVE, true); // (4)

b.handler(new ChannelInitializer() {

@Override

public void initChannel(SocketChannel ch) throws Exception {

ch.pipeline().addLast(new TimeClientHandler());

}

});

// Start the client.

ChannelFuture f = b.connect(host, port).sync(); // (5)

// Wait until the connection is closed.

f.channel().closeFuture().sync();

} finally {

workerGroup.shutdownGracefully();

}

}

}

  1. BootstrapServerBootstrap除了用于非服务器通道(例如客户端通道或无连接通道)之外,其他方面与之相似。

  2. 如果仅指定一个EventLoopGroup,它将同时用作老板组和工人组。但是,老板工人并不用于客户端。

  3. 相反的NioServerSocketChannel,NioSocketChannel被用来创建一个客户端Channel。

  4. 请注意,与ServerBootstrap不同,我们在这里没有使用childOption(),因为客户端SocketChannel没有父级。

  5. 我们应该调用connect()方法而不是bind()方法。

如您所见,它与服务器端代码并没有真正的区别。怎么样实现ChannelHandler?它应该从服务器接收一个32位整数,将其转换为人类可读的格式,打印转换后的时间,然后关闭连接:

import java.util.Date;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {

@Override

public void channelRead(ChannelHandlerContext ctx, Object msg) {

ByteBuf m = (ByteBuf) msg; // (1)

try {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

1200页Java架构面试专题及答案

小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞

百度、字节、美团等大厂常见面试题

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-fdy9F5IA-1712803302182)]
[外链图片转存中…(img-MbEBc52p-1712803302182)]
[外链图片转存中…(img-JAZeQ0Dm-1712803302183)]
[外链图片转存中…(img-FOxIMitB-1712803302183)]
[外链图片转存中…(img-vFWzpTyb-1712803302183)]
[外链图片转存中…(img-gevBhQyl-1712803302184)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-E0eQGArE-1712803302184)]

1200页Java架构面试专题及答案

小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞

[外链图片转存中…(img-ikBVPu8t-1712803302184)]

[外链图片转存中…(img-8R2GIv47-1712803302184)]

百度、字节、美团等大厂常见面试题

[外链图片转存中…(img-TtrwsCjv-1712803302185)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-kLaeDiDG-1712803302185)]

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值