Flink1.14 Source概念入门讲解与源码解析

news2024/12/25 20:41:28

目录

Flink Source概念

Source

Source源码

getBoundedness()

createReader(SourceReaderContext readerContext)

createEnumerator(SplitEnumeratorContext enumContext)

SplitEnumerator restoreEnumerator(SplitEnumeratorContext enumContext, EnumChkT checkpoint) throws Exception,>

SimpleVersionedSerializer getSplitSerializer()

SimpleVersionedSerializer getEnumeratorCheckpointSerializer()

总结

参考


Flink Source概念

Flink的Source主要是由3个核心部分组成:Splits,SplitEnumerator,SourceReader。

  • Split:split是数据源的一部分切片数据,source端将数据进行切片分发,可以并行去读取数据,而split就是一个切片粒度,一般每一次每个slot读取一个split进行处理。
  • SplitEnumerator:SplitEnumerator是一个单例只产生在JobManager中,产生split切片并且分发给sourceReader(TM里面),主要负责负载均衡,维持等待中的split的积压平衡,并且分发split给source Reader。
  • SourceReader:请求split文件并且进行处理,sourceReader是并行运行在TM的source算子中,并且产出并行的时间流/记录流。

(放一下官网的图。。。)

Flink作为批流一体的架构,Data Source API支持数据文件是无界流或者是有界批文件。

对于有界批文件来说,enumerator会产生一系列的split文件,并且每一个split文件明确是有限大小的;而对于无界流来说,则有两种情况 1)splits文件是无限的 2)enumerator不断的产生新的split文件

具体说明一下:

有界文件

Source数据源存在一个URI/Path路径,并且有固定的format去明确如何解析文件。

  • 一个split切片是一个文件,或者是多个文件(一个区域内)。
  • SplitEnumerator会列出目录下所有的文件,当下一个reader需要split切片文件的时候,就会将下一个split发送过去,一旦所有的文件全部发送完成,那么就会发出一个 NoMoreSplits 的标志。
  • SourceReader请求一个split切片,然后读取解析得到的split文件,如果没有更多的split文件后,即收到了 NoMoreSplits 那么就会停止读取。

有界Kafka

同理,只不过每一个split是一个明确的topic分区的end offset。一旦sourceReader达到了end offset,就会完成这个split文件的读取。当所有的split文件完成后,sourceReader就会结束。

无界文件流

无界的情况下,将永远不会产生 NoMoreSplits 的标志,会周期性监控URI/Path路径下是否会产生新的文件。一旦产生了新文件则会生成新的split切片并分发给可用的sourcereaders。

无界Kafka

Source数据源是一个Kafka的Topic文件,或者是一系列Topic/Topic正则。

  • Split切片文件是一个Kafka的Topic分区。
  • SplitEnumerator会连接broker,列出所有订阅的topic分区。enumerator能有选择的重复去发现订阅了的topics新增的分区数据。
  • sourcereader读取分配的split文件(topics 分区)并不会有一个end标志,所以reader永远也不会有end的情况。

Source

在1.14-1.15版本的时候source api是一个工厂模式的接口,用于创建以下的组件。

  • Split Enumerator
  • Source Reader (在1.16版本之后变为通过SourceReaderFactory接口实现
  • Split Serializer
  • Enumerator Checkpoint Serializer

除此之外,Source 还提供了 Boundedness 的特性,从而使得 Flink 可以选择合适的模式来运行 Flink 任务。

Source 实现应该是可序列化的,因为 Source 实例会在运行时被序列化并上传到 Flink 集群。

Source源码

接下来看看source的源码

import org.apache.flink.annotation.Public;
import org.apache.flink.core.io.SimpleVersionedSerializer;

import java.io.Serializable;

/**
 * The interface for Source. It acts like a factory class that helps construct the {@link
 * SplitEnumerator} and {@link SourceReader} and corresponding serializers.
 *
 * @param <T> The type of records produced by the source.
 * @param <SplitT> The type of splits handled by the source.
 * @param <EnumChkT> The type of the enumerator checkpoints.
 */
// 在flink1.16之后,source的接口变为public interface Source<T, SplitT extends SourceSplit, EnumChkT> extends SourceReaderFactory<T, SplitT>
@Public
public interface Source<T, SplitT extends SourceSplit, EnumChkT> extends Serializable {

    /**
     * Get the boundedness of this source.
     *
     * @return the boundedness of this source.
     */
    Boundedness getBoundedness();

    /**
     * Creates a new reader to read data from the splits it gets assigned. The reader starts fresh
     * and does not have any state to resume.
     *
     * @param readerContext The {@link SourceReaderContext context} for the source reader.
     * @return A new SourceReader.
     * @throws Exception The implementor is free to forward all exceptions directly. Exceptions
     *     thrown from this method cause task failure/recovery.
     */
    SourceReader<T, SplitT> createReader(SourceReaderContext readerContext) throws Exception;

    /**
     * Creates a new SplitEnumerator for this source, starting a new input.
     *
     * @param enumContext The {@link SplitEnumeratorContext context} for the split enumerator.
     * @return A new SplitEnumerator.
     * @throws Exception The implementor is free to forward all exceptions directly. * Exceptions
     *     thrown from this method cause JobManager failure/recovery.
     */
    SplitEnumerator<SplitT, EnumChkT> createEnumerator(SplitEnumeratorContext<SplitT> enumContext)
            throws Exception;

    /**
     * Restores an enumerator from a checkpoint.
     *
     * @param enumContext The {@link SplitEnumeratorContext context} for the restored split
     *     enumerator.
     * @param checkpoint The checkpoint to restore the SplitEnumerator from.
     * @return A SplitEnumerator restored from the given checkpoint.
     * @throws Exception The implementor is free to forward all exceptions directly. * Exceptions
     *     thrown from this method cause JobManager failure/recovery.
     */
    SplitEnumerator<SplitT, EnumChkT> restoreEnumerator(
            SplitEnumeratorContext<SplitT> enumContext, EnumChkT checkpoint) throws Exception;

    // ------------------------------------------------------------------------
    //  serializers for the metadata
    // ------------------------------------------------------------------------

    /**
     * Creates a serializer for the source splits. Splits are serialized when sending them from
     * enumerator to reader, and when checkpointing the reader's current state.
     *
     * @return The serializer for the split type.
     */
    SimpleVersionedSerializer<SplitT> getSplitSerializer();

    /**
     * Creates the serializer for the {@link SplitEnumerator} checkpoint. The serializer is used for
     * the result of the {@link SplitEnumerator#snapshotState()} method.
     *
     * @return The serializer for the SplitEnumerator checkpoint.
     */
    SimpleVersionedSerializer<EnumChkT> getEnumeratorCheckpointSerializer();
}

我们一个一个函数来看,毕竟一堆看上去确实感觉挺头疼的。。。。

getBoundedness()

主要是返回数据源是否有界,返回类型是Boundedness的枚举类,值只有两个BOUNDED 和 CONTINUOUS_UNBOUNDED。

具体的接口实现有四类(后面的实现都是有四类,这边只讲fileSource相关的,就不会过多介绍了。。。)

public abstract class AbstractFileSource<T, SplitT extends FileSourceSplit>
        implements Source<T, SplitT, PendingSplitsCheckpoint<SplitT>>, ResultTypeQueryable<T> {
    @Override
    public Boundedness getBoundedness() {
        return continuousEnumerationSettings == null
                ? Boundedness.BOUNDED // 有界
                : Boundedness.CONTINUOUS_UNBOUNDED; // 无界
    }
}

public class DorisSource<OUT> implements Source<OUT, DorisSourceSplit, PendingSplitsCheckpoint>, ResultTypeQueryable<OUT> {
    public Boundedness getBoundedness() {
        return this.boundedness;
    }
}

public class HybridSource<T> implements Source<T, HybridSourceSplit, HybridSourceEnumeratorState> {
    public Boundedness getBoundedness() {
        return ((HybridSource.SourceListEntry)this.sources.get(this.sources.size() - 1)).boundedness;
    }
}

public class NumberSequenceSource
        implements Source<
                        Long,
                        NumberSequenceSource.NumberSequenceSplit,
                        Collection<NumberSequenceSource.NumberSequenceSplit>>,
                ResultTypeQueryable<Long> {

    @Override
    public Boundedness getBoundedness() {
        return Boundedness.BOUNDED;
    }
}

其中,continuousEnumerationSettings主要的作用是设置轮询时间,多久去对于无界的文件进行扫描。

createReader(SourceReaderContext readerContext)

创建一个全新的source reader去读取分配给到它的splits文件,不包含任何状态恢复,返回接口SourceReader。在flink1.16的版本中已经放在了SourceReaderFactory接口中实现。

    // abstractFileSource中的实现 
    @Override
    public SourceReader<T, SplitT> createReader(SourceReaderContext readerContext) {
        // fileSourceReader是一种读取方式,从FileSourceSplit中读取记录
        return new FileSourceReader<>(
                readerContext, readerFormat, readerContext.getConfiguration());
    }

其中,readerContext是Flink运行时source的上下文;readerFormat是BulkFormat<T, SplitT>类型(BulkFormat一次读取一批次的数据并且解析),对于reader而言,BulkFormat类主要是一个工厂以及一个配置的持有者,真正读取文件的其实是 BulkFormat.Reader,这个方法是在BulkFormat类中的 createReader(Configuration, FileSourceSplit)方法创建。

createEnumerator(SplitEnumeratorContext<SplitT> enumContext)

为这个source创建新的SplitEnumerator,开始一个新的input。

    @Override
    public SplitEnumerator<SplitT, PendingSplitsCheckpoint<SplitT>> createEnumerator(
            SplitEnumeratorContext<SplitT> enumContext) {

        final FileEnumerator enumerator = enumeratorFactory.create();

        // read the initial set of splits (which is also the total set of splits for bounded
        // sources)
        final Collection<FileSourceSplit> splits;
        try {
            // TODO - in the next cleanup pass, we should try to remove the need to "wrap unchecked"
            // here
            splits = enumerator.enumerateSplits(inputPaths, enumContext.currentParallelism());
        } catch (IOException e) {
            throw new FlinkRuntimeException("Could not enumerate file splits", e);
        }

        return createSplitEnumerator(enumContext, enumerator, splits, null);
    }

其中,enumerator是由FileEnumerator工厂类产生的,这个类主要任务是找到所有需要读取的文件,切分它们成为FileSourceSplit。并且遍历路径的同时会过滤文件(如果有文件不想要读取可以通过名称进行过滤),决定是否切分文件为多个split,如何去切分的。

splits = enumerator.enumerateSplits(inputPaths, enumContext.currentParallelism());

这里则是进行切分split,里面的函数实现主要是通过递归进行遍历path。顺便提一嘴,具体实现是接口FileEnumerator的具体实现NonSplittingRecursiveEnumerator类。

    @Override
    public Collection<FileSourceSplit> enumerateSplits(Path[] paths, int minDesiredSplits)
            throws IOException {
        final ArrayList<FileSourceSplit> splits = new ArrayList<>();

        for (Path path : paths) {
            final FileSystem fs = path.getFileSystem();
            final FileStatus status = fs.getFileStatus(path);
            addSplitsForPath(status, fs, splits);
        }

        return splits;
    }

    private void addSplitsForPath(
            FileStatus fileStatus, FileSystem fs, ArrayList<FileSourceSplit> target)
            throws IOException {
        if (!fileFilter.test(fileStatus.getPath())) {
            return;
        }
        // 判断是文件还是目录,如果是文件则转化为source split去读取。
        // 比如hdfs的话,就会去获取datanode的host
        if (!fileStatus.isDir()) {
            convertToSourceSplits(fileStatus, fs, target);
            return;
        }

        final FileStatus[] containedFiles = fs.listStatus(fileStatus.getPath());
        for (FileStatus containedStatus : containedFiles) {
            // 递归遍历文件目录
            addSplitsForPath(containedStatus, fs, target);
        }
    }

最后createSplitEnumerator这个函数则是去根据是有界数据还是无界数据进行划分,如果无界数据存在alreadyProcessedPaths也会直接去划分split,如果alreadyProcessedPaths为空,才会去周期性的监控路径是否产生新文件。(后续再讲。。。)

    private SplitEnumerator<SplitT, PendingSplitsCheckpoint<SplitT>> createSplitEnumerator(
            SplitEnumeratorContext<SplitT> context,
            FileEnumerator enumerator,
            Collection<FileSourceSplit> splits,
            @Nullable Collection<Path> alreadyProcessedPaths) {

        // cast this to a collection of FileSourceSplit because the enumerator code work
        // non-generically just on that base split type
        @SuppressWarnings("unchecked")
        final SplitEnumeratorContext<FileSourceSplit> fileSplitContext =
                (SplitEnumeratorContext<FileSourceSplit>) context;

        final FileSplitAssigner splitAssigner = assignerFactory.create(splits);

        if (continuousEnumerationSettings == null) {
            // bounded case
            return castGeneric(new StaticFileSplitEnumerator(fileSplitContext, splitAssigner));
        } else {
            // unbounded case
            if (alreadyProcessedPaths == null) {
                alreadyProcessedPaths = splitsToPaths(splits);
            }

            return castGeneric(
                    new ContinuousFileSplitEnumerator(
                            fileSplitContext,
                            enumerator,
                            splitAssigner,
                            inputPaths,
                            alreadyProcessedPaths,
                            continuousEnumerationSettings.getDiscoveryInterval().toMillis()));
        }
    }

    @SuppressWarnings("unchecked")
    private SplitEnumerator<SplitT, PendingSplitsCheckpoint<SplitT>> castGeneric(
            final SplitEnumerator<FileSourceSplit, PendingSplitsCheckpoint<FileSourceSplit>>
                    enumerator) {

        // cast arguments away then cast them back. Java Generics Hell :-/
        return (SplitEnumerator<SplitT, PendingSplitsCheckpoint<SplitT>>)
                (SplitEnumerator<?, ?>) enumerator;
    }

    private static Collection<Path> splitsToPaths(Collection<FileSourceSplit> splits) {
        return splits.stream()
                .map(FileSourceSplit::path)
                .collect(Collectors.toCollection(HashSet::new));
    }

SplitEnumerator<SplitT, EnumChkT> restoreEnumerator(SplitEnumeratorContext<SplitT> enumContext, EnumChkT checkpoint) throws Exception

主要是通过一个checkpoint去恢复一个枚举器。最后调用的函数与createEnumerator只是多了一个checkpoint.getAlreadyProcessedPaths()参数传递。

    @Override
    public SplitEnumerator<SplitT, PendingSplitsCheckpoint<SplitT>> restoreEnumerator(
            SplitEnumeratorContext<SplitT> enumContext,
            PendingSplitsCheckpoint<SplitT> checkpoint) {

        final FileEnumerator enumerator = enumeratorFactory.create();

        // cast this to a collection of FileSourceSplit because the enumerator code work
        // non-generically just on that base split type
        @SuppressWarnings("unchecked")
        final Collection<FileSourceSplit> splits =
                (Collection<FileSourceSplit>) checkpoint.getSplits();

        return createSplitEnumerator(
                enumContext, enumerator, splits, checkpoint.getAlreadyProcessedPaths());
    }


SimpleVersionedSerializer<SplitT> getSplitSerializer()

主要是为source splits创建一个序列化器,在splits从enumerator到reader的时候或者是当reader进行checkpoint的时候执行。

    @Override
    public SimpleVersionedSerializer<FileSourceSplit> getSplitSerializer() {
        return FileSourceSplitSerializer.INSTANCE;
    }


@PublicEvolving
public final class FileSourceSplitSerializer implements SimpleVersionedSerializer<FileSourceSplit> {

    public static final FileSourceSplitSerializer INSTANCE = new FileSourceSplitSerializer();

SimpleVersionedSerializer<EnumChkT> getEnumeratorCheckpointSerializer()

获取SplitEnumerator checkpoint的序列化器,用于处理SplitEnumerator#snapshotState()方法返回的结果

    @Override
    public SimpleVersionedSerializer<PendingSplitsCheckpoint<SplitT>>
            getEnumeratorCheckpointSerializer() {
        return new PendingSplitsCheckpointSerializer<>(getSplitSerializer());
    }

以上就是Source的接口的所有方法,主要包含创建 SourceReaderSplitEnumerator 和对应get序列化器的方法。

总结

目前可以看出,Souce接口的更新,其实是因为Flink在1.12之前将批处理任务与流处理任务分为两种实现模式。

在底层实现中

DataSet API中Source对应的核心借口是InputFormat,功能上主要有三点:

  1. 描述输入的数据如何被划分为不同的InputSplit,继承于 INputSplitSource
  2. 描述如何从单个InputSplit读取记录,具体包括如何打开一个分配到的InputSplit,如何从这个INputSplit读取一条记录,如何得知记录已经读完和如何关闭这个Inputsplit
  3. 描述如何获取输入数据的统计信息(比如文件的大小、记录的数目)

1、3两点主要会被JobManager/JobMaster在调度Exection时使用,而第2点读取数据功能则会在运行时被TaskManager使用。

DataStream API中 Source 对应的核心接口为 SourceFunction 以及 SourceContext。前者直接继承 Function 接口与 Operator 交互,负责通用的状态管理(比如初始化或取消);后者代表运行时的上下文,负责与单条记录级别的数据的交互。此外还有其他一些辅助类型的类或接口。

运行时,Source 主要通过 SourceContext 来控制数据的输出。从 SourceContext 接口的方法即可以看出,Source 在接受到数据后的主要工作有以下几点:

  1. 从外部摄入数据或者生成数据,输出到下游
  2. 为数据生成 Event Time Timestamp(仅在 Time Characteristic 为 Event Time 时有用)
  3. 计算 Watermark 并输出(仅在 Time Characteristic 为 Event Time 时有用)
  4. 当暂时不会有新数据时将自己标记为 Idle ,以避免下游一直等待自己的 Watermark

综上所述,之前的 Source 接口并不能很好的满足批流一体的发展,所以在 FLIP-27中选择重构Source接口,新接口的核心是通过 SplitEnumerator 和 SplitReader,前者负责发现和分配 Split、触发 Checkpoint 等管理工作,后者负责 Split 的实际读取处理。此外,新增 Operator 间的通信机制(复用大部分现有的 RPC 机制),让 Source Subtask 之间可以协调完成 Event Time 对齐等新特性。最后, SplitReader 底层封装了通用的线程模型,相比之前的 SourceFunction 大大简化了 Source 的实现。

参考

漫谈 Flink Source 接口重构 | 时间与精神的小屋

Flink 源码之新 Source 架构 - 简书

数据源 | Apache Flink

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

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

相关文章

使用Selenium进行网页登录和会话管理

随着互联网的快速发展&#xff0c;网页登录和会话管理是许多网站和应用程序的基本功能。通过网页登录&#xff0c;用户可以访问个人账户、购物车订单、历史记录等个性化信息。为了提高用户体验和效率&#xff0c;自动化登录和会话管理成为一个重要的需求。而Selenium作为一种强…

信创办公–基于WPS的EXCEL最佳实践系列 (限制可录入内容)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;限制可录入内容&#xff09; 目录 应用背景操作过程1、数据有效性&#xff08;支出证明单&#xff09;2、如何完成数据有效性的使用&#xff08;差旅报销申请表&#xff09;3、清除数据验证4、利用圈释无效数据&#xff0c;验…

Docker容器中的SSH免密登录

简介&#xff1a;在日常的开发和测试环境中经常需要创建和管理Docker容器。有时&#xff0c;出于调试或管理的目的&#xff0c;可能需要SSH到容器内部。本文将介绍如何创建一个Docker容器&#xff0c;它在启动时自动运行SSH服务&#xff0c;并支持免密登录。 历史攻略&#xf…

在github上设置不同分支,方便回滚

在github上设置不同分支&#xff0c;方便回滚 步骤可能出现的问题couldnt find remote ref gpuVersion1. 确保您处于正确的分支2. 添加并提交更改&#xff08;如果还未进行&#xff09;3. 推送本地分支到远程仓库4. 验证操作 步骤 之前在github上上传了一个项目代码&#xff0c…

用Win10自带画图3D抠图

文章目录 一、打开“画图3D”二、插入图片三、抠图操作四、保存抠图 一、打开“画图3D” 在搜索框输入“画图3D” 选择彩色水滴图标的软件 二、插入图片 选择“新建” 导航栏“菜单”–>“插入”&#xff0c;选择要扣的图片。&#xff08;我选择了一张自己随意画的图片…

【动态规划刷题 17】回文子串 最长回文子串

647. 回文子串 链接: 647. 回文子串 给你一个字符串 s &#xff0c;请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串&#xff0c;即使是由…

启动 React APP 后经历了哪些过程

本文作者为 360 奇舞团前端开发工程师 前言 本文中使用的React版本为18&#xff0c;在摘取代码的过程中删减了部分代码&#xff0c;具体以源代码为准。 在React 18里&#xff0c;通过ReactDOM.createRoot创建根节点。并且通过调用原型链上的render来渲染。 本文主要是从以下两个…

Python函数绘图与高等代数互融实例(五): 则线图综合案例

Python函数绘图与高等代数互融实例(一):正弦函数与余弦函数 Python函数绘图与高等代数互融实例(二):闪点函数 Python函数绘图与高等代数互融实例(三):设置X|Y轴|网格线 Python函数绘图与高等代数互融实例(四):设置X|Y轴参考线|参考区域 Python函数绘图与高等代数互融实例(五…

iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据

目录 转载&#xff1a;怎么保护苹果手机移动应用程序ipa中文件安全&#xff1f; 前言 1. 对敏感文件进行文件名称混淆 2. 更改文件的MD5值 3. 增加不可见水印处理 3. 对html&#xff0c;js&#xff0c;css等资源进行压缩 5. 删除可执行文件中的调试信息…

关于分布式一致性

一致性&#xff08;consistency&#xff09; 说到一致性&#xff0c;我们可能最先想到的数据库里的事务 这里的讨论的是分布式的一致性&#xff0c;事务就简化一下&#xff0c;只考虑Read/Write 先列举一下事务的种类&#xff1a; 单机的事务&#xff1a;多个复杂事务发生在一…

React组件化开发

1.组件的定义方式 函数组件Functional Component类组件Class Component 2.类组件 export class Profile extends Component {render() {console.log(this.context);return (<div>Profile</div>)} } 组件的名称是大写字符开头&#xff08;无论类组件还是函数组件…

ElementUI之登录与注册

目录 一.前言 二.ElementUI的简介 三.登录注册前端界面的开发 三.vue axios前后端交互--- Get请求 四.vue axios前后端交互--- Post请求 五.跨域问题 一.前言 这一篇的知识点在前面两篇的博客中就已经详细详解啦&#xff0c;包括如何环境搭建和如何建一个spa项目等等知识…

【二叉树魔法:链式结构与递归的纠缠】

本章重点 二叉树的链式存储二叉树链式结构的实现二叉树的遍历二叉树的节点个数以及高度二叉树的创建和销毁二叉树的优先遍历和广度优先遍历二叉树基础oj练习 1.二叉树的链式存储 二叉树的链式存储结构是指&#xff0c;用链表来表示一棵二叉树&#xff0c;即用链来指示元素的逻辑…

GitHub Copilot Chat

9月21日&#xff0c;GitHub在官网宣布&#xff0c;所有个人开发者可以使用GitHub Copilot Chat。用户通过文本问答方式就能生成、检查、分析各种代码。 据悉&#xff0c;GitHub Copilot Chat是基于OpenAI的GPT-4模型打造而成&#xff0c;整体使用方法与ChatGPT类似。例如&…

帆软BI开发-Day2-趋势图的多种变形

前言&#xff1a; 在BI数据展示中&#xff0c;条形图、趋势图无疑是使用场景非常多的两种图形。与条形图不同的是&#xff0c;趋势图更能反馈出一定的客观规律和未来的趋势走向&#xff0c;因此用于作为预警和判异的业务场景&#xff0c;但实际业务场景的趋势图可没你想的那么简…

代码随想录算法训练营 动态规划part14

一、最长公共子序列 1143. 最长公共子序列 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int longestCommonSubsequence(String s1, String s2) {int n s1.length(), m s2.length();char[] cs1 s1.toCharArray(), cs2 s2.toCharArray();int[][] f n…

精华回顾:Web3 前沿创新者在 DESTINATION MOON 共话未来

9 月 17 日&#xff0c;由 TinTinLand 主办的「DESTINATION MOON: Web3 Dev Summit Shanghai 2023」线下活动在上海黄浦如约而至。 本次 DESTINATION MOON 活动作为 2023 上海区块链国际周的 Side Event&#xff0c;设立了 4 场主题演讲与 3 个圆桌讨论&#xff0c;聚集了诸多…

[python 刷题] 42 Trapping Rain Water

[python 刷题] 42 Trapping Rain Water 题目&#xff1a; Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it can trap after raining. 这题的前置我觉得至少还是得做过 11 Container With Most Wat…

神经辐射场(Neural Radiance Field,NeRF)的简单介绍

参考文章&#xff1a;https://arxiv.org/abs/2210.00379 1. 概述 神经辐射场&#xff08;NeRF&#xff09;模型是一种新视图合成方法&#xff0c;它使用体积网格渲染&#xff0c;通过MLP进行隐式神经场景表达&#xff0c;以学习3D场景的几何和照明。   应用&#xff1a;照片…

浅谈双指针算法

目录 算法概述 案例分析 1、删除有序数组中的重复项 2、环形链表 3、盛最多水的容器 4、有效三角形的个数 5、三数之和 6、1089. 复写零 内容总结 算法概述 双指针指的是在遍历元素的过程中&#xff0c;不是使用单个指针进行访问&#xff0c;而是使用两个指针进行访问…