Java知识要点及面试题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 该文档涵盖Java后端开发的关键知识点,包括Java基础、JVM、多线程、MySQL、Redis、Spring框架、Spring Cloud、Kafka及分布式系统设计。针对每个主题,文档列举了重要概念及面试常问问题,帮助读者全面掌握相关技术并准备面试。例如,Java基础部分涉及面向对象编程、数据类型、异常处理等;JVM部分则讲解内存结构、类加载机制及垃圾回收算法。此外,还介绍了多线程的生命周期、同步机制及线程池使用,数据库设计与优化,以及分布式系统中的微服务、RPC调用和负载均衡等。

Java知识要点

这份《服务端开发与面试知识手册》涵盖了Java后端开发的诸多领域,包括但不限于Java基础、JVM、多线程、MySQL、Redis、Spring、Spring Cloud、Kafka、分布式系统设计等。以下是关键要点和可能会被问到的面试题的总结:

Java基础

  • 关键要点
  • 面向对象编程(OOP):封装、继承、多态
  • 数据类型:基本数据类型和包装类
  • 集合框架:ArrayList、LinkedList、HashMap等
  • 异常处理:try-catch、finally、throw、throws
  • 泛型:泛型类、泛型方法
  • 反射:Class对象、反射API
  • 注解:自定义注解、内置注解
  • 面试题
  • StringStringBuilderStringBuffer 的区别?
  • Java中==equals()的区别?
  • Java中的垃圾回收机制?
  • 解释Java中的多线程支持。
  • 解释Java内存模型。

JVM

  • 关键要点
  • JVM内存结构:堆、栈、方法区等
  • 类加载机制:类的加载、链接和初始化
  • 垃圾回收算法:标记-清除、复制、标记-整理
  • 性能调优:JVM参数、诊断工具
  • 面试题
  • JVM中堆和栈有什么区别?
  • 如何判断一个对象是否可回收?
  • Java中类加载的过程是怎样的?
  • 介绍几种常见的垃圾回收器。

多线程

  • 关键要点
  • 线程的生命周期和状态
  • 同步机制:synchronized、Lock
  • 线程池:创建、使用和调优
  • 并发工具类:CountDownLatch、CyclicBarrier、Semaphore
  • 面试题
  • 如何在Java中创建线程?
  • 解释synchronized和Lock的区别。
  • 什么是线程安全?
  • 讲一下Java中的线程池。

MySQL

  • 关键要点
  • 数据库设计:范式、索引、分区
  • SQL优化:查询优化、索引优化
  • 事务处理:ACID、锁、事务隔离级别
  • 集群和高可用:主从复制、分库分表
  • 面试题
  • 如何优化SQL查询性能?
  • 解释MySQL中的索引是如何工作的。
  • 什么是事务,以及它有哪些特性?
  • 如何设计一个支持高并发的数据库系统?

Redis

  • 关键要点
  • 数据类型:字符串、哈希、列表、集合、有序集合
  • 持久化:RDB、AOF
  • 缓存模式:缓存穿透、缓存雪崩、缓存击穿
  • 集群模式:主从复制、哨兵系统、集群
  • 面试题
  • Redis有哪些数据类型?
  • Redis的持久化机制是怎样的?
  • 如何处理Redis缓存与数据库数据一致性问题?
  • Redis集群如何工作?

Spring

  • 关键要点
  • 依赖注入(DI)和控制反转(IoC)
  • AOP(面向切面编程)
  • MVC架构:DispatcherServlet、Controller、ViewResolver
  • 数据访问:JdbcTemplate、MyBatis、JPA
  • 面试题
  • 什么是Spring框架和它的工作原理?
  • 解释Spring中的依赖注入。
  • AOP在Spring中是如何实现的?
  • Spring MVC的工作原理是什么?

Spring Cloud

  • 关键要点
  • 服务发现:Eureka、Zookeeper
  • 配置中心:Config Server、Spring Cloud Bus
  • 断路器:Hystrix、Sentinel
  • API网关:Zuul、Gateway
  • 面试题
  • 解释一下Spring Cloud中的服务发现机制。
  • Spring Cloud Config是如何工作的?
  • 什么是断路器模式,并且如何在Spring Cloud中实现?
  • 描述一下Spring Cloud Gateway的功能。

Kafka

  • 关键要点
  • 消息传递保证:ack、offset、rebalance
  • 高可用性:副本、Leader选举、分区
  • 性能优化:批处理、压缩、索引
  • 集群搭建和监控
  • 面试题
  • Kafka如何保证消息的可靠性传输?
  • Kafka中的分区重平衡是什么?
  • Kafka中的副本机制是如何工作的?
  • 如何搭建一个高可用的Kafka集群?

分布式系统设计

  • 关键要点
  • 系统拆分:微服务、DDD(领域驱动设计)
  • 数据管理:分库分表、分布式事务
  • 服务调用:RPC、RESTful API、gRPC
  • 性能优化:缓存、负载均衡、异步处理
  • 面试题
  • 如何设计一个高并发的分布式系统?
  • 解释一下分布式事务的处理方式。
  • 在分布式系统中,如何实现服务的负载均衡?
  • 描述一下你在项目中是如何处理数据一致性问题的?

这些要点和面试题覆盖了Java后端开发的主要领域,准备这些内容将有助于你在面试中表现出色。

Java面试题

由于解答所有面试题需要较长时间,我会依次详细解答,但请注意可能需要多个回复来完成所有问题的解答。

Java基础

StringStringBuilderStringBuffer 的区别?

  • String:不可变的字符序列,每次修改都会生成一个新的字符串对象,适合少量的字符串操作。
  • StringBuilder:可变的字符序列,可以进行高效的字符串拼接,非线程安全,适合单线程环境下的大量字符串操作。
  • StringBuffer:和StringBuilder类似,但是线程安全的,适合多线程环境下的字符串操作。

Java中==equals()的区别?

  • ==:用于比较两个引用是否指向同一对象(对于基本数据类型是比较值)。
  • equals():一个方法,用于比较对象内容是否相等。默认行为是比较引用,但可以被覆写。

Java中的垃圾回收机制?

Java的垃圾回收机制主要依赖垃圾收集器(GC)来实现,它会自动回收不再被引用的对象以释放内存。Java的垃圾回收器有多种,如Serial、Parallel、CMS、G1等,每种收集器的工作原理和适用场景不同。

解释Java中的多线程支持。

Java通过Thread类和Runnable接口支持多线程。线程可以共享类的静态成员和静态方法,但每个线程有独立的程序计数器、栈和局部变量。Java提供了同步机制(如synchronized关键字)来避免并发问题。

解释Java内存模型。

Java内存模型规定了线程如何以及何时可以看到由其他线程修改的变量更新。它包括内存、线程以及锁等概念,确保在并发环境中数据的一致性和内存可见性。

JVM

JVM中堆和栈有什么区别?

  • 堆(Heap):Java虚拟机管理的内存区域,存放对象实例和数组。
  • 栈(Stack):线程私有的内存区域,存放局部变量、方法调用、操作数栈、动态链接和返回地址。

如何判断一个对象是否可回收?

主要通过可达性分析(Reachability Analysis),如果对象没有任何引用,则可以被垃圾回收器回收。

Java中类加载的过程是怎样的?

类加载过程包括加载、链接(验证、准备、解析)和初始化三个主要步骤。

介绍几种常见的垃圾回收器。

  • Serial:单线程的收集器,适用于小型应用。
  • Parallel:多线程的收集器,提高垃圾回收的效率。
  • CMS(Concurrent Mark Sweep):并发标记清除收集器,减少停顿时间。
  • G1(Garbage-First Collector):服务器端的收集器,可以指定停顿时间目标。

线上问题排查

  1. ss -antp > $DUMP_DIR/ss.dump 2>&1

通过查看进程, 能看到打开了哪些文件, 可以以进程的维度来查看整个资源的使用情况, 包括每条网络连接、每个打开的文件句柄。 同时, 也可以很容易的看到连接到了哪些服务器、使用了哪些资源。 这个命令在资源非常多的情况下,输出稍慢。

2、free -h > $DUMP_DIR/free.dump 2>&1

内存问题,free 命令能够大体展现操作系统的内存概况,这是故障排查中一个非常重要的点,比如 SWAP 影响了 GC,SLAB 区挤占了 JVM 的内存

3、使用 top 命令,查找到使用 CPU 最多的某个进程,记录它的 pid。

top -Hp $pid

再次使用 top 命令,加 -H 参数,查看某个进程中使用 CPU 最多的某个线程,记录线程的 ID。

4、

${JDK_BIN}jstat -gcutil $PID > $DUMP_DIR/jstat-gcutil.dump 2>&1 ${JDK_BIN}jstat -gccapacity $PID > $DUMP_DIR/jstat-gccapacity.dump 2>&1

jstat 将输出当前的 gc 信息。一般基本能大体看出一个端倪,如果不能,可将借助 jmap 来进行分析。

5、堆信息

${JDK_BIN}jmap $PID > $DUMP_DIR/jmap.dump 2>&1 ${JDK_BIN}jmap -heap $PID > $DUMP_DIR/jmap-heap.dump 2>&1 ${JDK_BIN}jmap -histo $PID > $DUMP_DIR/jmap-histo.dump 2>&1 ${JDK_BIN}jmap -dump:format=b,file=$DUMP_DIR/heap.bin $PID > /dev/null 2>&1

jmap 将会得到当前 Java 进程的 dump 信息。如上所示,其实最有用的就是第 4 个命令,但是前面三个能够让你 初步对系统概况进行大体判断。因为,第 4 个命令产生的文件,一般都非常的大。而且,需要下载下来,导入 MAT 这样的工具进行深入分析,才能获取结果。这是分析内存泄漏一个必经的过程。

6、JVM 执行栈

${JDK_BIN}jstack $PID > $DUMP_DIR/jstack.dump 2>&1

jstack 将会获取当时Java 进程的执行栈。一般会多次取值,我们这里取一次即可。这些信息非常有用,能够还原 Java 进程中的线程情况。

我们在 jstack 日志搜关键字DEAD,以及中找到了 CPU 使用最多的几个线程id。

7、top -Hp $PID -b -n 1 -c > $DUMP_DIR/top-$PID.dump 2>&1

为了能够得到更加精细的信息,我们使用 top 命令,来获取进程中所有线程的 CPU 信息,这样,就可以看到资源 到底耗费在什么地方了。

8、高级替补

kill -3 $PID

有时候,jstack 并不能够运行,有很多原因,比如 Java 进程几乎不响应了等之类的情况。我们会尝试向进程发送 kill -3 信号,这个信号将会打印 jstack 的 trace 信息到日志文件中,是 jstack 的一个替补方案。

gcore -o $DUMP_DIR/core $PID

对于 jmap 无法执行的问题,也有替补,那就是 GDB 组件中的 gcore,将会生成一个 core 文件。我们可以使用如下的命令去生成 dump:

${JDK_BIN}jhsdb jmap --exe ${JDK}java --core $DUMP_DIR/core --binaryheap

9、内存泄漏的现象

稍微提一下 jmap 命令,它在 9 版本里被干掉了,取而代之的是 jhsdb,你可以像下面的命令一样使用。

jhsdb jmap --heap --pid 37340 jhsdb jmap --pid 37288 jhsdb jmap --histo --pid 37340 jhsdb jmap --binaryheap --pid 37340

一般内存溢出,表现形式就是 Old 区的占用持续上升,即使经过了多轮 GC 也没有明显改善。比如ThreadLocal里面的GC Roots,内存泄漏的根本就是,这些对象并没有切断和 GC Roots 的关系,可通过一些工具,能够看到它们的联系。

使用 MAT 分析堆快照,发现很多地方可以通过代码优化,那些占用内存特别多的对象:

1、select * 全量排查,只允许获取必须的数据

2、报表系统中cache实际的命中率并不高,将Guava 的 Cache 引用级别改成弱引用(WeakKeys) 3、限制报表导入文件大小,同时拆分用户超大范围查询导出请求。 每一步操作都使得JVM使用变得更加可用,一系列优化以后,机器相同压测数据性能提升了数倍。

4、建议关闭 SWAP 分区,swap 是很多性能场景的万恶之源,建议禁用。在高并发 SWAP 绝对能让你体验到它魔鬼性的一面:进程倒是死不 了了,但 GC 时间长的却让人无法忍受。

多线程

如何在Java中创建线程?

可以通过继承Thread类或实现Runnable接口来创建线程。

解释synchronizedLock的区别。

  • synchronized:Java内置的同步机制,通过锁定一段代码块或方法实现同步。
  • Lock:Java并发包中的锁接口,提供了比synchronized更灵活的锁定机制。

什么是线程安全?

线程安全是指当多个线程访问同一个对象时,如果不用额外的同步措施,程序的行为仍然是正确的。

讲一下Java中的线程池。

线程池用于管理和复用线程对象,可以减少创建和销毁线程的开销。Java中的Executor框架提供了线程池的管理功能。

由于解答内容非常长,我将在下一个回复中继续解答剩余的面试题。

接下来继续解答剩余的面试题。

说明著名的 HashMap 高并发引起的死循环

HashMap在高并发情况下引起的死循环问题是一个经典的多线程安全问题。这个问题主要出现在JDK1.7及之前的版本中,由于HashMap并非线程安全的数据结构,因此在高并发环境下对其进行操作可能会导致数据结构的不一致,进而引发死循环。以下是关于这个问题的详细说明:

一、HashMap数据结构简介

HashMap是Java中的一种数据结构,它用于存储键值对,并提供了快速的插入、删除和查找操作。在HashMap中,数据的存储位置是根据键的哈希值来计算的,具有相同哈希值的元素会被放置在同一个桶(bucket)中。

二、高并发下的死循环问题

在高并发场景下,多个线程可能同时尝试修改HashMap,这可能导致以下问题:

  1. 链表形成环状结构:当多个线程尝试将元素插入到同一个桶中时,由于HashMap使用链表处理哈希冲突,每个节点都有指向下一个节点的指针。如果不同线程同时修改这个指针,可能会导致链表形成环状结构。当进行遍历时,get或迭代操作就会陷入无限循环。
  2. 扩容过程中的数据迁移问题:当HashMap中的元素数量达到阈值后,会自动触发扩容操作。在扩容过程中,会创建一个新的更大容量的数组,并将原有数组中的所有元素重新分配到新的数组中。如果多个线程同时执行扩容操作,可能导致数据迁移不完整或出现逻辑错误,形成无效的链接关系,进而引发死循环。

三、解决方案

为了避免HashMap在高并发场景下出现死循环问题,可以采取以下解决方案:

  1. 使用线程安全的ConcurrentHashMap:ConcurrentHashMap是线程安全的数据结构,它提供了更高的并发性和线程安全性。通过分段锁机制或其他并发控制手段,它保证了在多线程环境下的正确操作。
  2. 加锁机制:另一种解决方案是在对HashMap进行操作时使用加锁机制,如ReentrantLock等,来保证对HashMap的操作是线程安全的。但这种方法可能会影响性能,并且需要谨慎处理锁的粒度和加锁时机。

四、其他注意事项

值得注意的是,在JDK1.8及以后的版本中,HashMap已经对插入方式进行了改进,采用了尾插法而非头插法,这在一定程度上减少了死循环问题的发生。然而,即使在JDK1.8中,HashMap仍然不是线程安全的,因此在高并发场景下仍需谨慎使用。

综上所述,HashMap在高并发情况下引起的死循环问题是一个严重的多线程安全问题。为了避免这个问题,建议使用线程安全的ConcurrentHashMap或采用加锁机制来保证操作的线程安全性。

MySQL

如何优化SQL查询性能?

  • 使用索引来加速查询。
  • 避免使用SELECT *,指定需要的列。
  • 优化JOIN操作,确保ON语句中的列有索引。
  • 使用查询缓存。
  • 定期清理数据库,比如优化数据表。

解释MySQL中的索引是如何工作的。

索引加快了数据库的检索速度,因为索引为数据集创建了一个快速查找的路径。最常见的索引类型是B-Tree索引。

什么是事务,以及它有哪些特性?

事务是一组原子性的SQL操作,要么全部成功,要么全部失败。事务具有以下四个特性,通常缩写为ACID:

  1. 原子性(Atomicity):事务是最小的执行单位,不容许分割。
  2. 一致性(Consistency):事务必须使数据库从一个一致性状态转换到另一个一致性状态。
  3. 隔离性(Isolation):事务之间的操作是隔离的,不受其他事务的干扰。
  4. 持久性(Durability):事务一旦提交,它对数据库的改变就是永久的。

如何设计一个支持高并发的数据库系统?

  • 采用高性能的硬件。
  • 优化数据库配置和SQL查询。
  • 使用索引来提高查询效率。
  • 读写分离。
  • 使用缓存来减轻数据库压力。
  • 采用分布式数据库系统。

Redis

Redis有哪些数据类型?

  • 字符串(String)
  • 哈希(Hash)
  • 列表(List)
  • 集合(Set)
  • 有序集合(Sorted Set)

Redis的持久化机制是怎样的?

Redis支持两种持久化机制:

  1. RDB(Redis Database):在指定的时间间隔内生成数据快照。
  2. AOF(Append Only File):记录每次写操作命令,重启时重放这些命令来恢复数据。

如何处理Redis缓存与数据库数据一致性问题?

  • 使用Redis事务来保证操作的原子性。
  • 使用Redis的发布/订阅功能来同步更新数据库和缓存。
  • 利用消息队列来异步更新数据库和缓存。

Redis集群如何工作?

Redis集群通过分片(sharding)来提供一定程度的自动分区,每个节点负责一部分数据。集群能够处理节点故障转移和自动复制。

Spring

什么是Spring框架和它的工作原理?

Spring是一个开源的Java平台,它提供了全面的基础设施支持,用于开发Java应用程序。Spring框架的核心是控制反转(IoC)和依赖注入(DI)。

解释Spring中的依赖注入。

依赖注入是一种设计模式,允许用户在运行时将组件连接在一起,而不是在编译时。Spring通过DI容器来自动装配Bean的依赖。

AOP在Spring中是如何实现的?

Spring AOP 通过使用代理来实现面向切面编程。它允许用户定义方法拦截器,并在方法执行前后添加额外的行为。

Spring MVC的工作原理是什么?

Spring MVC是一种基于Java的实现MVC的框架。它通过前端控制器DispatcherServlet接收所有请求,然后根据请求映射将请求转发到相应的处理器。

Spring Cloud

解释一下Spring Cloud中的服务发现机制。

Spring Cloud中的服务发现机制主要通过Eureka实现,服务实例将自己注册到Eureka服务器,其他服务通过Eureka服务器查询需要的服务实例。

Spring Cloud Config是如何工作的?

Spring Cloud Config是一个配置管理工具,它允许用户集中管理所有环境的配置。配置服务器包含配置文件,客户端可以拉取这些配置。

什么是断路器模式,并且如何在Spring Cloud中实现?

断路器模式是一种软件设计模式,用于处理应用程序中的延迟和故障。在Spring Cloud中,Hystrix是一个实现断路器模式的库。

描述一下Spring Cloud Gateway的功能。

Spring Cloud Gateway是一个微服务API网关,提供路由功能,可以将请求路由到正确的服务实例。

Kafka

Kafka如何保证消息的可靠性传输?

Kafka通过使用副本机制、消息确认和日志存储来保证消息的可靠性。

Kafka中的分区重平衡是什么?

分区重平衡是Kafka处理消费者组中的消费者实例变化时,重新分配分区给新的消费者实例的过程。

Kafka中的副本机制是如何工作的?

Kafka中的副本机制确保每个消息都被存储多个副本,以提高容错性。副本分为领导者副本和追随者副本,领导者副本负责处理读写请求。

如何搭建一个高可用的Kafka集群?

搭建高可用的Kafka集群需要多个Broker,并且每个Broker都有其他Broker的副本。此外,需要合理配置Zookeeper。

分布式系统设计

如何设计一个高并发的分布式系统?

设计高并发的分布式系统需要考虑负载均衡、缓存、数据库优化、消息队列、分布式存储等多个方面。

解释一下分布式事务的处理方式。

分布式事务可以通过两阶段提交、三阶段提交、补偿事务(TCC)、本地消息表、最终一致性等方法来处理。

在分布式系统中,如何实现服务的负载均衡?

服务的负载均衡可以通过DNS轮询、Nginx、硬件负载均衡器或者服务网格(如Istio)来实现。

描述一下你在项目中是如何处理数据一致性问题的?

数据一致性问题可以通过分布式事务、事件驱动架构、最终一致性模型、冲突解决策略等方法来处理。


相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
8天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
13天前
|
存储 缓存 Oracle
Java I/O流面试之道
NIO的出现在于提高IO的速度,它相比传统的输入/输出流速度更快。NIO通过管道Channel和缓冲器Buffer来处理数据,可以把管道当成一个矿藏,缓冲器就是矿藏里的卡车。程序通过管道里的缓冲器进行数据交互,而不直接处理数据。程序要么从缓冲器获取数据,要么输入数据到缓冲器。
Java I/O流面试之道
|
10天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
33 4
|
10天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
50 4
|
1月前
|
存储 安全 算法
Java面试题之Java集合面试题 50道(带答案)
这篇文章提供了50道Java集合框架的面试题及其答案,涵盖了集合的基础知识、底层数据结构、不同集合类的特点和用法,以及一些高级主题如并发集合的使用。
83 1
Java面试题之Java集合面试题 50道(带答案)
|
23天前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
50 5
|
22天前
|
存储 Java
[Java]面试官:你对异常处理了解多少,例如,finally中可以有return吗?
本文介绍了Java中`try...catch...finally`语句的使用细节及返回值问题,并探讨了JDK1.7引入的`try...with...resources`新特性,强调了异常处理机制及资源自动关闭的优势。
18 1
|
1月前
|
Java 程序员
Java 面试高频考点:static 和 final 深度剖析
本文介绍了 Java 中的 `static` 和 `final` 关键字。`static` 修饰的属性和方法属于类而非对象,所有实例共享;`final` 用于变量、方法和类,确保其不可修改或继承。两者结合可用于定义常量。文章通过具体示例详细解析了它们的用法和应用场景。
28 3
|
2月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
410 37