gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具
    • SVG转PDF/Word
    • SVG转Draw.io可二次编辑格式
    • js代码混淆

面试高频问题:说一下 HTTP 1.0、HTTP 1.1、HTTP 2 以及 HTTP 3 的发展以及变化

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  
  • 作者: east
  • ( 页面15 )
面试 1月 5,2025

面试高频问题:说一下 HTTP 1.0、HTTP 1.1、HTTP 2 以及 HTTP 3 的发展以及变化

1. HTTP 1.0:基础协议的诞生(1991年)

HTTP 1.0 是最早的 HTTP 协议版本,由万维网的发明人蒂姆·伯纳斯-李(Tim Berners-Lee)在 1991 年提出并应用于最初的 Web 服务。作为一个简单的基于文本的协议,HTTP 1.0 在当时的互联网环境下解决了浏览器和服务器之间的数据传输问题。

特点
  • 请求/响应模型:HTTP 1.0 是基于请求-响应模型的。当客户端向服务器发送请求时,服务器会根据请求返回响应。这是 HTTP 协议的核心理念。
  • 无状态性:HTTP 是无状态的,即每一次请求都是独立的,服务器不会记住上一次请求的状态。
  • 文本协议:所有的 HTTP 消息都是纯文本的,便于人类阅读和调试。
  • 一次请求/一次响应:HTTP 1.0 协议设计中,客户端发起请求时,服务器处理该请求并返回响应后,连接就会关闭。如果客户端需要请求多个资源(如 HTML 页面中的图片、CSS 文件、JavaScript 文件等),就需要建立多个连接。
限制与问题
  • 每个请求需要建立新连接:在 HTTP 1.0 中,每个请求和响应都需要建立一个新的 TCP 连接。随着 Web 内容日益复杂,页面上需要加载的资源数量增加,每个请求都要重新建立连接,导致了大量的网络开销和性能瓶颈。
  • 头部冗余:每个 HTTP 请求和响应都带有完整的头部信息,但这些头部信息往往重复,增加了传输的数据量。

2. HTTP 1.1:改进与优化(1997年)

HTTP 1.1 是对 HTTP 1.0 的重要改进,由 IETF(互联网工程任务组)在 1997 年发布,并迅速成为 Web 上的标准协议。与 HTTP 1.0 相比,HTTP 1.1 引入了多个优化和新特性,主要是为了解决性能瓶颈和提高多用户的并发性能。

主要特点
  • 持久连接(Persistent Connection):HTTP 1.1 引入了持久连接的概念。通过将“Connection: close”头去除,HTTP 1.1 默认为持久连接。这样,客户端和服务器之间可以复用同一个连接,减少了每个请求都建立新连接的开销。一个 TCP 连接可以处理多个请求和响应,直到客户端或服务器主动关闭连接。
  • 管道化(Pipelining):管道化技术允许客户端在等待服务器响应之前,先后发送多个请求,而无需等待每个请求的响应。这有助于减少请求延迟,提升性能。然而,管道化存在问题,比如响应顺序问题,当一个请求的响应延迟时,后续请求的响应也会被阻塞。
  • 缓存控制:HTTP 1.1 引入了更加精细的缓存控制机制,包括 Cache-Control、ETag、Last-Modified 等头部字段,使得浏览器能够更好地控制缓存,从而减少不必要的请求和数据传输。
  • 更多的状态码:HTTP 1.1 扩展了状态码的定义,增加了一些新的响应状态码,例如 409 Conflict、411 Length Required 和 416 Range Not Satisfiable 等,提供了更好的错误处理机制。
  • Chunked Transfer Encoding:HTTP 1.1 支持分块传输编码(Chunked Transfer Encoding),允许服务器分块发送响应数据,客户端可以在接收到部分数据时开始处理,提升了响应的速度。
限制与问题
  • Head-of-line Blocking(HOLB):尽管 HTTP 1.1 引入了持久连接和管道化技术,管道化模式存在一个重大问题:如果其中一个请求的响应延迟,则后续的请求也会被阻塞,这被称为“Head-of-line Blocking”。这种情况在请求较多时会显著影响性能。
  • TCP 连接的限制:虽然持久连接降低了每个请求的连接开销,但仍然受到 TCP 的限制,浏览器通常会为每个域名建立有限数量的连接,导致需要加载多个资源时性能依然受限。

3. HTTP 2:性能革命(2015年)

随着 Web 技术的发展和用户需求的提升,HTTP 1.1 逐渐暴露出一些性能瓶颈,尤其是在处理复杂页面和高并发场景时。为了提升性能,HTTP 2 在 2015 年正式发布,成为现代 Web 上广泛采用的协议。HTTP 2 主要通过优化传输层、提高并发性和减少延迟来改进 HTTP 1.1 的局限。

主要特点
  • 二进制协议:HTTP 2 完全摒弃了 HTTP 1.x 的文本协议,采用了二进制协议。二进制协议相比文本协议具有更高的传输效率,并且解析更为简便,可以避免出现由于文本格式导致的错误和性能损失。
  • 多路复用(Multiplexing):HTTP 2 允许在一个 TCP 连接上并发地发送多个请求和响应,并且它们的顺序不会相互干扰。这样可以有效解决 HTTP 1.1 中的 Head-of-line Blocking 问题,多个请求可以同时发出并独立处理。
  • 流控制:HTTP 2 引入了流控制机制,可以有效控制数据流的传输速率,防止一方发送数据过快导致另一方处理不过来,从而避免网络阻塞。
  • 头部压缩:HTTP 2 使用 HPACK 压缩算法来压缩 HTTP 请求和响应的头部信息,减少了由于重复的头部信息造成的开销,提高了传输效率。
  • 服务器推送(Server Push):HTTP 2 支持服务器推送机制,允许服务器主动向客户端推送资源,而不需要客户端明确请求。这有助于减少延迟,特别是在页面加载时,服务器可以提前将客户端可能需要的资源推送到浏览器中。
  • 优先级和依赖关系:HTTP 2 允许客户端指定请求的优先级,服务器可以根据优先级来处理请求,这对于高性能的应用尤为重要,能够显著提升页面加载速度。
限制与问题
  • 依然依赖 TCP:HTTP 2 尽管引入了多路复用和流控制等优化机制,但它仍然基于 TCP 连接,这意味着它仍然无法克服 TCP 协议在高延迟和网络丢包情况下的局限性。
  • 服务器推送的问题:虽然服务器推送可以减少某些情况下的请求次数,但它也存在一定的问题,例如推送资源可能没有被客户端需要,从而浪费带宽。

4. HTTP 3:基于 QUIC 协议的新时代(2020年)

HTTP 3 是基于 Google 提出的 QUIC(Quick UDP Internet Connections)协议的 HTTP 协议新版本,旨在进一步提升 Web 性能,特别是在高延迟和不稳定网络环境下。HTTP 3 仍然基于 HTTP 2 的多路复用和流控制等特性,但它摒弃了传统的 TCP 协议,转而使用 QUIC 协议。

主要特点
  • 基于 QUIC 协议:HTTP 3 使用 QUIC 协议而不是 TCP。QUIC 是一个基于 UDP 的传输协议,具有低延迟和更快连接建立的优势。由于 QUIC 可以在用户端和服务器之间建立加密的传输通道,并且实现了多路复用和流控制机制,它能够有效解决 HTTP 2 中 TCP 带来的瓶颈。
  • 更快的连接建立:QUIC 协议比 TCP 协议建立连接的速度更快,因为它集成了 TLS(传输层安全协议)和握手过程,减少了连接时延。QUIC 在首次连接时仅需一次握手就可以完成加密,避免了 TCP 中多次握手的延迟。
  • 内建加密:QUIC 协议内建加密功能,确保了 HTTP 3 传输的数据始终是加密的,这比传统的 HTTP 协议更加安全。
  • 更强的抗丢包能力:由于QUIC 基于 UDP 协议,它比 TCP 更加高效地处理丢包和网络延迟问题。在 TCP 中,丢包会导致整个连接的阻塞(因为 TCP 是面向流的协议,每次丢包都需要等待重传),而 QUIC 可以独立地重传丢失的数据包,不会影响到其他数据流的传输,因此在不稳定网络环境下性能更好。
  • 减少的延迟:QUIC 支持快速恢复连接,它在中断后可以快速恢复,并且减少了重新握手的延迟。在传统的 TCP 中,每次建立新连接时需要经过三次握手,TLS 也需要额外的加密和握手过程,而 QUIC 能够在同一连接中直接进行加密和身份验证,大大减少了延迟。
  • 多路复用与流控制:与 HTTP 2 类似,HTTP 3 支持多路复用,在同一个连接上同时传输多个数据流,而且每个流可以独立地处理,避免了 HTTP 2 中的 Head-of-Line Blocking 问题。每个数据流在 QUIC 中是独立的,即使一个流丢包或出现延迟,其他流也不会受到影响。
  • 内建加密:HTTP 3 默认启用了加密,使用的是 TLS 1.3 协议。这意味着所有的 HTTP 3 流量都是加密的,确保了更高的安全性和隐私保护。
  • 更快的连接恢复和更低的延迟:相比于传统的 TCP + TLS,QUIC 协议能够提供更快速的连接建立和恢复,因为它减少了多个协议层之间的交互和握手过程。QUIC 采用了基于 UDP 的流量控制和拥塞控制,减少了连接建立的时延,特别是在需要频繁断开和重新连接的移动网络环境中表现尤为突出。
  • 抗丢包能力更强:QUIC 可以更好地应对丢包情况,在不影响其他流的情况下重传丢失的包,极大地提升了数据传输的可靠性和稳定性。
  • 自动加密:HTTP 3 强制使用加密连接,这使得 Web 安全性得到更大的提升,同时也避免了多次加密层的开销,提升了性能。
  • 广泛的支持尚需时间:虽然主要浏览器和服务器已经开始支持 HTTP 3,但因为 QUIC 协议基于 UDP,这意味着防火墙和网络运营商的支持仍然是一个挑战。某些网络可能会阻止 UDP 流量,这可能影响到 HTTP 3 的普及程度。
  • 过渡期的兼容性问题:虽然大多数现代浏览器和服务器已经开始支持 HTTP 3,但对于仍然使用 HTTP 1.1 和 HTTP 2 的环境,仍然需要维持向后兼容。服务器和客户端需要根据协议版本的支持情况选择适当的协议版本,可能会导致一些额外的复杂性。
  • HTTP 1.0 为 Web 的初步发展奠定了基础,但它的局限性很快暴露,尤其是每次请求都需要建立一个新的连接的问题。
  • HTTP 1.1 通过引入持久连接、管道化和缓存控制等特性,显著提高了性能,尤其是在客户端与服务器之间频繁通信的情况下。
  • HTTP 2 则通过采用二进制协议、支持多路复用和服务器推送等技术,进一步提高了 Web 应用的响应速度和效率。
  • HTTP 3 采用基于 UDP 的 QUIC 协议,解决了 HTTP 2 中的 TCP 拥塞问题,进一步降低了延迟,并提高了抗丢包能力,尤其在移动网络和高延迟环境下表现尤为出色。
作者 east
Flink, tdengine 1月 3,2025

Flink读取TDEngine数据实例,解决com.taosdata.jdbc.rs.RestfulDatabaseMetaData@38af9828 is not serializable. The object probably contains or references non serializable fields错误

用flink读取TDEngine,运行报错:
com.taosdata.jdbc.rs.RestfulDatabaseMetaData@38af9828 is not serializable. The object probably contains or references non serializable fields

这意味着 com.taosdata.jdbc.rs.RestfulDatabaseMetaData 类的对象无法被序列化,而 Flink 的作业中涉及到的某些操作需要将对象传递到不同的任务中,这就要求对象是可序列化的(即实现了 Serializable 接口)。在 Flink 中,所有要在分布式环境中传输或持久化的对象都必须是可序列化的。

  • RestfulDatabaseMetaData 是 TDengine JDBC 驱动中的一个类,它可能没有实现 Serializable 接口,因此在需要将该类对象传输到其他机器时,Flink 无法进行序列化。

解决方法是

使用 transient 关键字避免对不可序列化对象进行传递。

通过标记 connection、preparedStatement 和 resultSet 为 transient,这些对象不会被 Flink 传递到 Task Manager。

完整可执行代码如下:


import org.apache.flink.streaming.api.functions.source.RichParallelSourceFunction;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class TDengineSourceFunction extends RichParallelSourceFunction<RunData> {

    private transient Connection connection;        // 使用 transient 避免序列化
    private transient PreparedStatement preparedStatement;
    private transient ResultSet resultSet;
    private String query;
    private volatile boolean isRunning = true;

    private String jdbcUrl;
    private String user;

    private String password;


    public TDengineSourceFunction(String jdbcUrl, String user, String password, String query) {
        this.query = query;
        this.jdbcUrl = jdbcUrl;
        this.user = user;
        this.password = password;

        // JDBC连接参数在open()方法中初始化
    }

    @Override
    public void open(org.apache.flink.configuration.Configuration parameters) throws Exception {
        super.open(parameters);
        Class.forName("com.taosdata.jdbc.rs.RestfulDriver");
        // 在这里初始化数据库连接
        this.connection = DriverManager.getConnection(jdbcUrl, user, password);
        // 准备SQL查询语句
        this.preparedStatement = connection.prepareStatement(query);
        this.resultSet = preparedStatement.executeQuery();
    }

    @Override
    public void run(SourceContext<RunData> sourceContext) throws Exception {
        while (isRunning && resultSet.next()) {
            // 从ResultSet中提取数据并转换为RunData对象
            RunData data = convertResultSetToData(resultSet);
            // 将数据发送到Flink的处理流中
            if (data != null) {
                sourceContext.collect(data);
            }
        }
    }

    @Override
    public void cancel() {
        isRunning = false;
        // 关闭资源
        try {
            if (resultSet != null) resultSet.close();
            if (preparedStatement != null) preparedStatement.close();
            if (connection != null) connection.close();
        } catch (SQLException e) {
            // 处理关闭资源时的异常
            e.printStackTrace();
        }
    }

    private RunData convertResultSetToData(ResultSet resultSet) throws SQLException {
        // 提取单行数据
      
        // 将数据转换为 RunData 对象


      //  return new RunData(......);
        return null;
    }
}
作者 east
Flink 1月 3,2025

解决flink读取TDEngine的数据Could not initialize class com.taosdata.jdbc.TSDBJNIConnector

需要用flink读取TDEngine的数据,用jdbc方式连接,运行报错:Could not initialize class com.taosdata.jdbc.TSDBJNIConnector

JDBC-JNI的方式需要 TDengine-client(使用JDBC-JNI时必须,使用JDBC-RESTful时非必须),所以采用JDBC-RESTful 的方式,原因是一开始想用
JDBC-JNI 的方式,想改用 JDBC-RESTful 代码没改干净。



通过指定URL获取连接,如下所示:

Class.forName("com.taosdata.jdbc.rs.RestfulDriver");
String jdbcUrl = "jdbc:TAOS-RS://taosdemo.com:6041/test?user=root&password=taosdata";
Connection conn = DriverManager.getConnection(jdbcUrl);
Class.forName("com.taosdata.jdbc.rs.RestfulDriver");String jdbcUrl = "jdbc:TAOS-RS://taosdemo.com:6041/test?user=root&password=taosdata";Connection conn = DriverManager.getConnection(jdbcUrl);

以上示例,使用 JDBC-RESTful 的 driver,建立了到 hostname 为 taosdemo.com,端口为 6041,数据库名为 test 的连接。这个 URL 中指定用户名(user)为 root,密码(password)为 taosdata。

使用 JDBC-RESTful 接口,不需要依赖本地函数库。与 JDBC-JNI 相比,仅需要:

  1. driverClass 指定为“com.taosdata.jdbc.rs.RestfulDriver”;
  2. jdbcUrl 以“jdbc:TAOS-RS://”开头;
  3. 使用 6041 作为连接端口。

按上面的方式修改,果然没有再报上面的错误。

作者 east
Flink 1月 3,2025

flink运行报错:java.lang.IllegalStateException: No ExecutorFactory found to execute the application

在本地运行flink代码,报错“
java.lang.IllegalStateException: No ExecutorFactory found to execute the application ”

通常是由于缺少必要的 Flink 依赖项导致的。具体来说,Flink 需要特定的执行器工厂来运行应用程序,而这些依赖项可能未正确包含在您的项目中。

原因分析

  1. 缺少 Flink 运行时依赖:
    • 您的代码片段看起来是基于 Flink 的流处理 API 编写的。如果项目缺少 Flink 运行时的相关依赖(例如 flink-java, flink-streaming-java, 和 flink-clients),Flink 将无法找到执行器工厂来启动作业。
  2. 依赖版本不匹配:
    • 如果您使用的 Flink 版本与代码不兼容,也可能导致类似的问题。确保所有 Flink 相关依赖的版本一致。
  3. 缺少必要的插件或扩展:
    • 某些情况下,特定的 Flink 插件或扩展可能缺失,导致执行器工厂无法加载。

解决方案

确保您的项目中包含了所有必要的 Flink 依赖项。以下是使用 Maven 的示例 pom.xml 配置,确保包含了 Flink 的核心和流处理依赖:

<dependencies>
    <!-- Flink 核心依赖 -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-java</artifactId>
        <version>1.16.2</version> <!-- 请根据需要替换为合适的版本 -->
    </dependency>

    <!-- Flink 流处理依赖 -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-streaming-java_2.12</artifactId>
        <version>1.16.2</version> <!-- 版本需与 flink-java 一致 -->
    </dependency>

    <!-- Flink 客户端依赖(如果需要远程提交作业) -->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-clients_2.12</artifactId>
        <version>1.16.2</version>
    </dependency>



    <!-- 如果使用自定义 Source 和 Sink,确保它们所在的依赖已添加 -->
</dependencies>
作者 east
Java, 面试 12月 29,2024

java多线程的实现的各种方法?

​

在 Java 中,实现多线程主要有以下几种方法:

  • 继承 Thread 类:通过创建一个继承自 Thread 类的子类,并重写 run () 方法来定义线程的执行逻辑。然后创建该子类的实例,并调用 start () 方法启动线程。例如,以下代码创建了一个简单的线程类:

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程正在运行");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}
  • 实现 Runnable 接口:创建一个实现 Runnable 接口的类,并重写 run () 方法。然后创建该类的实例,并将其作为参数传递给 Thread 类的构造函数,最后调用 start () 方法启动线程。例如:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程正在运行");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
    }
}
  • 使用 Callable 和 Future 接口:Callable 接口类似于 Runnable 接口,但它可以返回一个结果。Future 接口用于获取 Callable 任务的结果。可以通过创建一个实现 Callable 接口的类,并重写 call () 方法来定义线程的执行逻辑和返回结果。然后使用 ExecutorService 提交 Callable 任务,并通过 Future 获取任务的结果。例如:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1 + 1;
    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(new MyCallable());
        Integer result = future.get();
        System.out.println("结果:" + result);
        executor.shutdown();
    }
}
  • 使用线程池:线程池可以管理和复用线程,减少线程创建和销毁的开销。可以通过使用 ExecutorService 接口和其实现类来创建线程池,并提交任务到线程池中执行。例如:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) { executor.execute(() -> {
System.out.println(“线程正在运行”);
});
}
executor.shutdown();
}
}

作者 east
Java, 面试 12月 29,2024

面试必问题:单例模式的5种写法

​

单例模式主要有以下几种分类:

  • 饿汉式单例:在类加载时就创建单例对象,优点是实现简单,线程安全,缺点是如果单例对象在程序运行过程中一直未被使用,会造成资源浪费。例如:
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
  • 懒汉式单例:在第一次调用 getInstance 方法时才创建单例对象,优点是可以延迟加载,节省资源,但是在多线程环境下需要考虑线程安全问题。例如:

public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 双重检查锁单例:在懒汉式单例的基础上,通过双重检查锁机制来提高性能并保证线程安全。例如:

public class Singleton {
    private volatile static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 静态内部类单例:利用静态内部类的特性来实现单例,既保证了线程安全,又实现了延迟加载。例如:

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
  • 枚举单例:通过枚举类型来实现单例,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。例如:

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 单例对象的方法
    }
}

在多线程环境下,保证单例模式的线程安全可以采用以下几种方法:

  • 使用 synchronized 关键字:在获取单例对象的方法上添加 synchronized 关键字,如懒汉式单例中的 getInstance 方法,这样可以保证在同一时刻只有一个线程能够访问该方法,从而避免多个线程同时创建单例对象。但是这种方式会降低性能,因为每次调用该方法都需要获取锁。
  • 双重检查锁机制:在懒汉式单例的基础上,通过双重检查锁机制来减少同步的开销。在第一次检查实例是否为 null 时,如果不为 null,则直接返回实例,不需要获取锁;只有在第一次检查为 null 时,才进入同步块再次检查并创建实例。同时,需要将实例变量声明为 volatile,以保证可见性和禁止指令重排序。
  • 静态内部类:利用静态内部类的特性,在类加载时不会立即初始化内部类,只有在第一次调用 getInstance 方法时,才会加载并初始化内部类,从而创建单例对象。由于类加载过程是线程安全的,所以这种方式可以保证单例的线程安全,并且实现了延迟加载。
  • 枚举:枚举类型在 Java 中是天然的单例,因为 Java 虚拟机在加载枚举类时会保证只有一个实例存在,并且枚举类的实例是线程安全的,所以可以通过枚举来实现单例模式,避免了复杂的线程安全处理。

​

作者 east
bug清单, 大数据开发 12月 25,2024

解决idea运行scala代码报错:scala: No ‘scala-library*.jar’ in Scala compiler classpath in Scala SDK Maven: org.scala-lang:scala-library:2.11.12

这个错误信息表明,Scala 编译器的 classpath 中缺少必要的 scala-library*.jar 文件,特别是 Scala SDK 所需的 scala-library 版本 2.11.12。错误发生的原因通常是因为项目的构建配置缺失或错误,导致 Scala 编译器无法找到正确的库文件。

分析原因:

  1. Maven 配置问题: 错误信息中提到 Maven: org.scala-lang:scala-library:2.11.12,这表明你的项目在使用 Maven 来管理依赖。可能是 pom.xml 文件中的 Scala 依赖配置不正确,导致缺少 scala-library JAR 文件。
  2. IDE 配置问题: 另一个可能的原因是 IntelliJ IDEA 中的 Scala SDK 配置不完整或错误。Scala SDK 包括了编译器和库,如果 SDK 配置不正确,IDE 就无法正确找到必要的库文件。

解决方案:

1. 检查 pom.xml 文件中的 Scala 依赖

确保 pom.xml 中包含了正确版本的 Scala 依赖。如果没有,请添加类似下面的配置:

<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-library</artifactId>
    <version>2.11.12</version>
</dependency>

如果你使用的是其他 Scala 版本(如 2.13 或 3.x),需要替换为相应的版本号。

2. 确保项目中配置了正确的 Scala 编译器和 SDK

  • 在 IntelliJ IDEA 中,检查你是否已经配置了 Scala SDK。
    • 打开 File -> Project Structure -> Modules -> 选择你的模块 -> Dependencies,确保选择了正确的 Scala SDK。
    • 你也可以在 Project Structure 中检查 Scala SDK 的版本是否与你项目中使用的版本匹配。

3. 更新或重新下载 Scala 库

  • 在 IntelliJ IDEA 中,尝试通过右键点击项目根目录并选择 Maven -> Reimport 来重新加载依赖。
  • 如果仍然无法解决问题,可以尝试删除 ~/.m2/repository/org/scala-lang/scala-library/ 下的对应版本文件,然后重新构建项目。

如果上面的方法都没办法解决,可以删除
File>>Project Structure>>Libraries中删除默认的scala编译library,替换成本地的scala-sdk 。

首先在Global Libraries中添加本地scala-sdk

Modules -> 选择你的模块 -> Dependencies,确保选择本地 Scala SDK。


作者 east
Java 12月 22,2024

大厂面试手撕面试题:反转链表中前 K 个节点(亲测可用的java实现)

我们需要反转链表中前 k 个节点,且可能会有多个这样的反转段(每 k 个节点为一组)。如果链表的剩余部分不满 k 个节点,我们就不对其进行反转,直接保留原样。

解题思路:

  1. 链表结构:
    • 我们有一个单链表,定义为 ListNode 类型。每个节点包含一个值 val 和指向下一个节点的指针 next。
    • 目标是反转链表中前 k 个节点,并对每一组长度为 k 的节点进行反转,直到链表遍历完。
  2. 关键步骤:
    • 判断当前段是否满 k 个节点: 在反转之前,我们必须确认当前的节点数是否足够 k 个。如果不足,直接跳过该段,保持原链表顺序。
    • 反转当前 k 个节点: 如果当前段正好有 k 个节点,我们就进行反转。
    • 连接反转后的链表: 反转后的链表需要和之前的链表正确连接,以保证链表结构的完整性。
  3. 具体步骤:
    • 维护一个 dummy 虚拟节点,它的 next 指针指向链表头。这样可以避免链表头部的特殊情况处理,使得头节点的反转与其他节点反转保持一致。
    • 使用 prevGroupEnd 指针来标记上一个反转段的最后一个节点,它在反转完成后需要连接到当前反转段的最后一个节点。
    • 对每一组长度为 k 的节点进行反转。具体做法是使用 prev 和 curr 两个指针,通过迭代的方式完成每组的反转。
    • 完成一组反转后,更新 prevGroupEnd 和 head,继续处理下一组。
  4. 终止条件:
    • 当链表遍历完,或者剩下的节点不足 k 个时,停止反转操作。

代码详解:

public class Solution {
    
    public ListNode reverseKGroup(ListNode head, int k) {
        // 如果链表为空或 k 为 1,直接返回头节点
        if (head == null || k == 1) return head;
        
        // 创建一个虚拟节点,用于简化操作
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        
        // prevGroupEnd 用于指向前一组反转后链表的末尾节点
        ListNode prevGroupEnd = dummy;
        
        // 当前头节点 head
        while (head != null) {
            // 找到当前组的最后一个节点
            ListNode groupEnd = head;
            int count = 1;
            // 计算当前组的节点数量
            while (groupEnd != null && count < k) {
                groupEnd = groupEnd.next;
                count++;
            }
            
            // 如果当前组的节点数不足 K 个,不需要反转,直接返回
            if (groupEnd == null) break;
            
            // 反转当前组的节点
            ListNode nextGroupStart = groupEnd.next;  // 保存下一个组的起始节点
            ListNode prev = nextGroupStart;  // prev 初始指向下一组的起始节点
            ListNode curr = head;  // curr 初始指向当前组的头节点
            
            // 反转当前组的节点
            while (curr != nextGroupStart) {
                ListNode temp = curr.next;  // 暂存当前节点的下一个节点
                curr.next = prev;  // 将当前节点的 next 指向 prev(反转)
                prev = curr;  // prev 更新为当前节点
                curr = temp;  // curr 更新为下一个节点
            }
            
            // 将上一组的尾节点连接到当前反转后的组的头节点
            prevGroupEnd.next = groupEnd;
            // 当前组的头节点(反转后)需要指向下一组的起始节点
            head.next = nextGroupStart;
            
            // 更新 prevGroupEnd 和 head,准备处理下一组
            prevGroupEnd = head;
            head = nextGroupStart;
        }
        
        return dummy.next; // 返回新的链表头节点
    }

    // 辅助方法:打印链表
    public void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        
        // 创建一个链表 1 -> 2 -> 3 -> 4 -> 5
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);
        
        int k = 3;
        
        // 输出原链表
        System.out.print("Original List: ");
        solution.printList(head);
        
        // 调用反转链表前 K 个节点的方法
        ListNode newHead = solution.reverseKGroup(head, k);
        
        // 输出反转后的链表
        System.out.print("List after reversing first " + k + " nodes: ");
        solution.printList(newHead);
    }
}

详细解析:

  1. 虚拟节点 dummy:
    • dummy 节点的作用是简化链表头部的操作,避免对链表头的特殊处理。通过将 dummy.next 设置为链表的头节点,我们可以统一处理链表的反转和连接。
  2. 变量 prevGroupEnd:
    • prevGroupEnd 用于连接每次反转后组的最后一个节点。初始化时,它指向虚拟节点 dummy。
    • 在每次反转完成后,prevGroupEnd 被更新为当前反转组的头节点 head。
  3. 变量 groupEnd 和 count:
    • groupEnd 用于记录当前反转组的最后一个节点。如果当前组不足 k 个节点,则不进行反转。
    • count 用来计算当前组的节点数。如果 count 达到 k,则进行反转操作。
  4. 反转操作:
    • 反转时,通过三个指针 prev(指向反转后的部分),curr(当前处理的节点),和 temp(暂存当前节点的下一个节点)来完成每一组的节点反转。
    • 完成反转后,将 prevGroupEnd 的 next 指向当前反转组的尾节点 groupEnd,并将 head.next 指向剩余部分的起始节点。
  5. 循环和终止条件:
    • 每次反转 k 个节点后,将 head 更新为剩余部分的起始节点,继续进行下一组的处理。
    • 当剩余节点不足 k 个时,循环终止。

时间复杂度:

  • O(n): 每个节点会被访问并反转一次,总时间复杂度为 O(n),其中 n 是链表的节点数。

空间复杂度:

  • O(1): 只使用了常数的额外空间,除了输入的链表本身。

示例:

对于链表 1 -> 2 -> 3 -> 4 -> 5 和 k = 3,执行反转操作后,链表将变为 3 -> 2 -> 1 -> 4 -> 5。

作者 east
Java 12月 22,2024

大厂面试手撕面试题:删除字符串中的指定字符(亲测可用的java实现)

要求:给定一个字符串 s 和一个字符 t,要求删除字符串中所有出现的字符 t,并返回处理后的新字符串。

Java代码实现:

public class Solution {
    public static String removeCharacter(String s, char t) {
        // 使用 StringBuilder 来构建结果字符串
        StringBuilder result = new StringBuilder();

        // 遍历原字符串,拼接不等于 t 的字符
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) != t) {
                result.append(s.charAt(i));
            }
        }

        // 返回结果字符串
        return result.toString();
    }

    public static void main(String[] args) {
        String s = "hello world";
        char t = 'o';
        
        String result = removeCharacter(s, t);
        System.out.println(result); // 输出 "hell wrld"
    }
}

代码解释:

  1. removeCharacter 方法:
    • 输入:一个字符串 s 和一个字符 t。
    • 使用 StringBuilder 来构建新的字符串,这样在处理大量字符时会更加高效。
    • 遍历原字符串 s,将不等于 t 的字符添加到 StringBuilder 中。
    • 最终通过 toString() 返回结果。
  2. main 方法:
    • 示例:给定字符串 "hello world",删除字符 'o' 后,结果是 "hell wrld"。

时间复杂度:

  • 遍历一次字符串,时间复杂度为 O(n),其中 n 是字符串 s 的长度。

空间复杂度:

  • 由于使用了 StringBuilder 来存储新的字符串,空间复杂度为 O(n),其中 n 是字符串 s 的长度。
作者 east
Java, 面试 12月 22,2024

大厂面试手撕面试题:一个字符串中最长的没有重复字符的子串(亲测可用的java实现)

要解决 “求解一个字符串中最长的没有重复字符的子串” 的问题,可以使用 滑动窗口(Sliding Window)算法。这个算法通过维护一个动态窗口来跟踪当前没有重复字符的子串,然后逐步扩展窗口来找到最长的子串。

思路:

  1. 使用两个指针,left 和 right,表示当前窗口的左右边界。
  2. 通过哈希集合(HashSet 或 HashMap)来存储窗口中的字符,确保每个字符在窗口内唯一。
  3. 从左到右遍历字符串,每次右指针right向右移动,检查字符是否已经在窗口中存在。如果存在,就将左指针left向右移动,直到窗口中没有重复字符。
  4. 更新最长子串的长度。

Java实现:

import java.util.HashSet;

public class LongestSubstring {
    public static int lengthOfLongestSubstring(String s) {
        // 哈希集合用于存储窗口中的字符
        HashSet<Character> set = new HashSet<>();
        
        // 定义左右指针和最大长度
        int left = 0;
        int maxLength = 0;
        
        // 右指针向右移动,遍历字符串
        for (int right = 0; right < s.length(); right++) {
            // 如果右指针指向的字符在集合中,说明有重复字符
            // 移动左指针直到去除重复字符
            while (set.contains(s.charAt(right))) {
                set.remove(s.charAt(left));
                left++;
            }
            
            // 将当前字符加入集合
            set.add(s.charAt(right));
            
            // 更新最大长度
            maxLength = Math.max(maxLength, right - left + 1);
        }
        
        return maxLength;
    }

    public static void main(String[] args) {
        String s = "abcabcbb";
        System.out.println("The length of the longest substring without repeating characters is: " + lengthOfLongestSubstring(s));
    }
}

解释:

  1. HashSet<Character> set = new HashSet<>();:
    • 用于存储当前窗口内的字符,确保窗口内的字符唯一。
  2. left 和 right 指针:
    • right指针用来遍历字符串。
    • left指针控制窗口的起始位置,确保窗口内没有重复字符。
  3. 滑动窗口的调整:
    • 当遇到重复字符时,移动left指针,直到窗口内不再有重复字符。
    • 每次移动时,都会更新maxLength,保持最大窗口的长度。
  4. 时间复杂度:
    • 最坏情况下,left 和 right 每个指针最多各走一次字符串,因此时间复杂度是 O(n),其中 n 是字符串的长度。

示例输出:

对于输入 "abcabcbb",输出结果是:

The length of the longest substring without repeating characters is: 3 

该子串是 "abc",其长度为 3。

作者 east
Java 12月 22,2024

大厂面试手撕面试题:冒泡排序(亲测可用的java实现)

冒泡排序(Bubble Sort)是一种简单的排序算法。它通过重复遍历待排序的列表,比较相邻元素并交换它们的位置,直到整个列表排序完成。每次遍历都会把当前未排序部分的最大值“冒泡”到数组的最后。

思路:

  1. 从数组的第一个元素开始,逐个比较相邻的元素。
  2. 如果当前元素大于下一个元素,交换它们的位置。
  3. 每一轮遍历后,最大的元素会被“冒泡”到数组的末尾。
  4. 每次遍历的长度逐渐减少,因为已经排序好的元素已经“浮到”数组的末尾。
  5. 直到没有需要交换的元素时,排序完成。

冒泡排序的优化:

  • 如果在某次遍历中没有进行交换,说明数组已经是有序的,可以提前结束排序,避免不必要的遍历。

Java 完整代码实现:

public class BubbleSort {
    public static void main(String[] args) {
        // 测试数组
        int[] arr = {5, 2, 9, 1, 5, 6};
        
        // 调用冒泡排序
        bubbleSort(arr);
        
        // 打印排序后的数组
        System.out.println("Sorted array:");
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
    
    // 冒泡排序方法
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        
        // 外层循环控制排序的轮数
        for (int i = 0; i < n - 1; i++) {
            boolean swapped = false;  // 标记是否发生了交换
            
            // 内层循环进行相邻元素比较和交换
            for (int j = 0; j < n - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换 arr[j] 和 arr[j + 1]
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                    swapped = true;
                }
            }
            
            // 如果没有发生交换,说明数组已经有序,提前结束
            if (!swapped) {
                break;
            }
        }
    }
}

代码解释:

  1. 主方法 (main):初始化一个整数数组并调用 bubbleSort 方法进行排序。排序后打印数组。
  2. 冒泡排序方法 (bubbleSort):
    • 外层循环控制排序的轮次,每一轮确保将最大的元素“冒泡”到末尾。
    • 内层循环执行相邻元素的比较和交换。如果当前元素比下一个元素大,则交换。
    • swapped 是一个标记,用来检查在某一轮遍历中是否发生了交换。如果某轮没有交换,说明数组已经是有序的,可以提前结束排序。

时间复杂度:

  • 最坏情况下,时间复杂度为 O(n^2),即当数组是逆序时。
  • 最好情况下(当数组已经是有序的),时间复杂度为 O(n),因为只会进行一次遍历且没有发生任何交换。

空间复杂度:

  • O(1),因为该算法是原地排序,不需要额外的空间。
作者 east
Java 12月 22,2024

大厂面试手撕面试题:快速排序(亲测可用的java实现)

快速排序(Quick Sort)是一种常用的排序算法,采用分治法(Divide and Conquer)进行排序。其基本思路是通过选择一个基准元素(pivot),将待排序的数组分成两部分,一部分所有元素都小于基准元素,另一部分所有元素都大于基准元素。然后递归地对这两部分继续进行排序,最终达到排序整个数组的效果。

快速排序的步骤:

  1. 选择基准元素:选择数组中的一个元素作为基准元素(常见的选择有第一个元素、最后一个元素、随机选择等)。
  2. 分区操作:将数组分成两部分,小于基准的放左边,大于基准的放右边。基准元素最终的位置已经确定。
  3. 递归排序:对基准元素左侧和右侧的子数组进行递归调用快速排序,直到子数组的大小为1或0,排序完成。

时间复杂度:

  • 最佳情况:O(n log n),发生在每次分割时都能平衡地分成两部分。
  • 最坏情况:O(n^2),当数组已经有序或反向有序时,每次选择的基准元素都可能是最小或最大的元素,从而导致不均匀的分割。
  • 平均情况:O(n log n),在大多数情况下,快速排序的时间复杂度表现良好。

空间复杂度:

  • 快速排序是原地排序,只需要 O(log n) 的栈空间来存储递归调用的状态。
  • 空间复杂度主要取决于递归的深度,最坏情况下是 O(n),但平均情况下是 O(log n)。

快速排序的Java实现代码:

public class QuickSort {

    // 主函数:调用快速排序
    public static void quickSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        quickSortHelper(arr, 0, arr.length - 1);
    }

    // 快速排序的核心递归函数
    private static void quickSortHelper(int[] arr, int left, int right) {
        if (left < right) {
            // 分区操作,返回基准元素的正确位置
            int pivotIndex = partition(arr, left, right);
            // 递归对基准元素左侧和右侧的子数组排序
            quickSortHelper(arr, left, pivotIndex - 1);
            quickSortHelper(arr, pivotIndex + 1, right);
        }
    }

    // 分区操作,返回基准元素的最终位置
    private static int partition(int[] arr, int left, int right) {
        // 选择最右边的元素作为基准元素
        int pivot = arr[right];
        int i = left - 1; // i 指向比基准小的元素区域的最后一个元素
        for (int j = left; j < right; j++) {
            if (arr[j] < pivot) {
                // 交换 arr[i + 1] 和 arr[j]
                i++;
                swap(arr, i, j);
            }
        }
        // 将基准元素放到正确位置
        swap(arr, i + 1, right);
        return i + 1; // 返回基准元素的索引
    }

    // 交换数组中两个元素的位置
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 主函数入口,打印排序后的结果
    public static void main(String[] args) {
        int[] arr = {3, 6, 8, 10, 1, 2, 1};
        System.out.println("Original array: ");
        printArray(arr);
        
        quickSort(arr);

        System.out.println("Sorted array: ");
        printArray(arr);
    }

    // 打印数组的辅助函数
    private static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}

代码解析:

  • quickSort:是快速排序的入口方法,接收一个数组作为参数,检查数组是否为空或大小小于2,若满足条件则无需排序,直接返回。
  • quickSortHelper:是一个递归函数,通过分区操作将数组划分为左右两部分,然后递归地对这两部分继续排序。
  • partition:分区操作方法,它选择数组的最后一个元素作为基准元素,并通过两端的指针进行交换,将小于基准的元素放到左边,大于基准的元素放到右边。最终,基准元素被放到正确位置并返回其索引。
  • swap:交换数组中两个元素的辅助函数。
  • printArray:打印数组内容的辅助函数。

复杂度分析:

  • 时间复杂度:
    • 最佳情况:O(n log n),当每次分区都能将数组平衡地分成两部分。
    • 最坏情况:O(n^2),当每次分区都选到数组的极端元素(如最小或最大),导致每次只能减少一个元素的排序,递归的深度为 n。
    • 平均情况:O(n log n),是快速排序最常见的表现。
  • 空间复杂度:
    • 最好和平均情况下:O(log n),由于递归的深度受限于树的高度。
    • 最坏情况下:O(n),当数组已经是排序好的,或者每次选到的基准元素极端时,递归的深度为 n。
作者 east

上一 1 … 14 15 16 … 93 下一个

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 解决gitlab配置Webhooks,提示 Invalid url given的问题
  • 如何在Chrome中设置启动时自动打开多个默认网页
  • spark内存溢出怎样区分是软件还是代码原因
  • MQTT完全解析和实践
  • 解决运行Selenium报错:self.driver = webdriver.Chrome(service=service) TypeError: __init__() got an unexpected keyword argument ‘service’
  • python 3.6使用mysql-connector-python报错:SyntaxError: future feature annotations is not defined
  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?

文章归档

  • 2025年10月
  • 2025年8月
  • 2025年7月
  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (45)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (5)
  • 大数据开发 (494)
    • CDH (6)
    • datax (4)
    • doris (31)
    • Elasticsearch (15)
    • Flink (79)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (41)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (100)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (36)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (71)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (8)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.