返回顶部
首页 > 资讯 > 数据库 >Flink入门(五)——DataSet Api编程指南
  • 239
分享到

Flink入门(五)——DataSet Api编程指南

Flink入门(五)——DataSetApi编程指南 2020-07-01 22:07:41 239人浏览 才女
摘要

Apache flink Apache Flink 是一个兼顾高吞吐、低延迟、高性能的分布式处理框架。在实时计算崛起的今天,Flink正在飞速发展。由于性能的优势和兼顾批处理,流处理的特性,Flink可能正在颠覆整个大数据的生态

Flink入门(五)——DataSet Api编程指南

file

Apache Flink 是一个兼顾高吞吐、低延迟、高性能的分布式处理框架。在实时计算崛起的今天,Flink正在飞速发展。由于性能的优势和兼顾批处理,流处理的特性,Flink可能正在颠覆整个大数据的生态。

file

DataSet api

首先要想运行Flink,我们需要下载并解压Flink的二进制包,下载地址如下:https://flink.apache.org/downloads.html

我们可以选择Flink与Scala结合版本,这里我们选择最新的1.9版本Apache Flink 1.9.0 for Scala 2.12进行下载。

下载成功后,在windows系统中可以通过Windows的bat文件或者Cygwin来运行Flink。

linux系统中分为单机,集群hadoop等多种情况。

请参考:Flink入门(三)——环境与部署

Flink的编程模型,Flink提供了不同的抽象级别以开发流式或者批处理应用,本文我们来介绍DataSet API ,Flink最常用的批处理编程模型。

file

Flink中的DataSet程序是实现数据集转换的常规程序(例如,Filter,映射,连接,分组)。数据集最初是从某些来源创建的(例如,通过读取文件或从本地集合创建)。结果通过接收器返回,接收器可以例如将数据写入(分布式)文件或标准输出(例如命令行终端)。Flink程序可以在各种环境中运行,独立运行或嵌入其他程序中。执行可以在本地JVM中执行,也可以在许多计算机的集群上执行。

示例程序

以下程序是WordCount的完整工作示例。您可以复制并粘贴代码以在本地运行它。

Java

public class WordCountExample {
    public static void main(String[] args) throws Exception {
        final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

        DataSet text = env.fromElements(
            "Who's there?",
            "I think I hear them. Stand, ho! Who's there?");

        DataSet> wordCounts = text
            .flatMap(new LineSplitter())
            .groupBy(0)
            .sum(1);

        wordCounts.print();
    }

    public static class LineSplitter implements FlatMapFunction> {
        @Override
        public void flatMap(String line, Collector> out) {
            for (String word : line.split(" ")) {
                out.collect(new Tuple2(word, 1));
            }
        }
    }
}

Scala

import org.apache.flink.api.scala._

object WordCount {
  def main(args: Array[String]) {

    val env = ExecutionEnvironment.getExecutionEnvironment
    val text = env.fromElements(
      "Who's there?",
      "I think I hear them. Stand, ho! Who's there?")

    val counts = text.flatMap { _.toLowerCase.split("\W+") filter { _.nonEmpty } }
      .map { (_, 1) }
      .groupBy(0)
      .sum(1)

    counts.print()
  }
}

数据集转换

数据转换将一个或多个DataSet转换为新的DataSet。程序可以将多个转换组合到复杂的程序集中。

DataSet API 中最重要的就是这些算子,我们将数据接入后,通过这些算子对数据进行处理,得到我们想要的结果。

Java版算子如下:

转换 描述
Map 采用一个数据元并生成一个数据元。data.map(new MapFunction() { public Integer map(String value) { return Integer.parseInt(value); } });
FlatMap 采用一个数据元并生成零个,一个或多个数据元。data.flatMap(new FlatMapFunction() { public void flatMap(String value, Collector out) { for (String s : value.split(" ")) { out.collect(s); } } });
MapPartition 在单个函数调用中转换并行分区。该函数将分区作为Iterable流来获取,并且可以生成任意数量的结果值。每个分区中的数据元数量取决于并行度和先前的 算子操作。data.mapPartition(new MapPartitionFunction() { public void mapPartition(Iterable values, Collector out) { long c = 0; for (String s : values) { c++; } out.collect(c); } });
Filter 计算每个数据元的布尔函数,并保存函数返回true的数据元。 重要信息:系统假定该函数不会修改应用谓词的数据元。违反此假设可能会导致错误的结果。data.filter(new FilterFunction() { public boolean filter(Integer value) { return value > 1000; } });
Reduce 通过将两个数据元重复组合成一个数据元,将一组数据元组合成一个数据元。Reduce可以应用于完整数据集或分组数据集。data.reduce(new ReduceFunction { public Integer reduce(Integer a, Integer b) { return a + b; } });如果将reduce应用于分组数据集,则可以通过提供CombineHintto 来指定运行时执行reduce的组合阶段的方式 setCombineHint。在大多数情况下,基于散列的策略应该更快,特别是如果不同键的数量与输入数据元的数量相比较小(例如1/10)。
ReduceGroup 将一组数据元组合成一个或多个数据元。ReduceGroup可以应用于完整数据集或分组数据集。data.reduceGroup(new GroupReduceFunction { public void reduce(Iterable values, Collector out) { int prefixSum = 0; for (Integer i : values) { prefixSum += i; out.collect(prefixSum); } } });
Aggregate 将一组值聚合为单个值。聚合函数可以被认为是内置的reduce函数。聚合可以应用于完整数据集或分组数据集。Dataset> input = // [...] DataSet> output = input.aggregate(SUM, 0).and(MIN, 2);您还可以使用简写语法进行最小,最大和总和聚合。Dataset> input = // [...] DataSet> output = input.sum(0).andMin(2);
Distinct 返回数据集的不同数据元。它相对于数据元的所有字段或字段子集从输入DataSet中删除重复条目。data.distinct();使用reduce函数实现Distinct。您可以通过提供CombineHintto 来指定运行时执行reduce的组合阶段的方式 setCombineHint。在大多数情况下,基于散列的策略应该更快,特别是如果不同键的数量与输入数据元的数量相比较小(例如1/10)。
Join 通过创建在其键上相等的所有数据元对来连接两个数据集。可选地使用JoinFunction将数据元对转换为单个数据元,或使用FlatJoinFunction将数据元对转换为任意多个(包括无)数据元。请参阅键部分以了解如何定义连接键。result = input1.join(input2) .where(0) // key of the first input (tuple field 0) .equalTo(1); // key of the second input (tuple field 1)您可以通过Join Hints指定运行时执行连接的方式。提示描述了通过分区或广播进行连接,以及它是使用基于排序还是基于散列的算法。有关可能的提示和示例的列表,请参阅“ 转换指南”。 如果未指定提示,系统将尝试估算输入大小,并根据这些估计选择最佳策略。// This executes a join by broadcasting the first data set // using a hash table for the broadcast data result = input1.join(input2, JoinHint.BROADCAST_HASH_FIRST) .where(0).equalTo(1);请注意,连接转换仅适用于等连接。其他连接类型需要使用OuterJoin或CoGroup表示。
OuterJoin 在两个数据集上执行左,右或全外连接。外连接类似于常规(内部)连接,并创建在其键上相等的所有数据元对。此外,如果在另一侧没有找到匹配的Keys,则保存“外部”侧(左侧,右侧或两者都满)的记录。匹配数据元对(或一个数据元和null另一个输入的值)被赋予JoinFunction以将数据元对转换为单个数据元,或者转换为FlatJoinFunction以将数据元对转换为任意多个(包括无)数据元。请参阅键部分以了解如何定义连接键。input1.leftOuterJoin(input2) // rightOuterJoin or fullOuterJoin for right or full outer joins .where(0) // key of the first input (tuple field 0) .equalTo(1) // key of the second input (tuple field 1) .with(new JoinFunction() { public String join(String v1, String v2) { // NOTE: // - v2 might be null for leftOuterJoin // - v1 might be null for rightOuterJoin // - v1 OR v2 might be null for fullOuterJoin } });
CoGroup reduce 算子操作的二维变体。将一个或多个字段上的每个输入分组,然后关联组。每对组调用转换函数。请参阅keys部分以了解如何定义coGroup键。data1.coGroup(data2) .where(0) .equalTo(1) .with(new CoGroupFunction() { public void coGroup(Iterable in1, Iterable in2, Collector out) { out.collect(...); } });
Cross 构建两个输入的笛卡尔积(交叉乘积),创建所有数据元对。可选择使用CrossFunction将数据元对转换为单个数据元DataSet data1 = // [...] DataSet data2 = // [...] DataSet> result = data1.cross(data2);注:交叉是一个潜在的非常计算密集型 算子操作它甚至可以挑战大的计算集群!建议使用crossWithTiny()crossWithHuge()来提示系统的DataSet大小。
Union 生成两个数据集的并集。DataSet data1 = // [...] DataSet data2 = // [...] DataSet result = data1.uNIOn(data2);
Rebalance 均匀地Rebalance 数据集的并行分区以消除数据偏差。只有类似Map的转换可能会遵循Rebalance 转换。DataSet in = // [...] DataSet result = in.rebalance() .map(new Mapper());
Hash-Partition 散列分区给定键上的数据集。键可以指定为位置键,表达键和键选择器函数。DataSet> in = // [...] DataSet result = in.partitionByHash(0) .mapPartition(new PartitionMapper());
Range-Partition Range-Partition给定键上的数据集。键可以指定为位置键,表达键和键选择器函数。DataSet> in = // [...] DataSet result = in.partitionByRange(0) .mapPartition(new PartitionMapper());
Custom Partitioning 手动指定数据分区。 注意:此方法仅适用于单个字段键。DataSet> in = // [...] DataSet result = in.partitionCustom(Partitioner partitioner, key)
Sort Partition 本地按指定顺序对指定字段上的数据集的所有分区进行排序。可以将字段指定为元组位置或字段表达式。通过链接sortPartition()调用来完成对多个字段的排序。DataSet> in = // [...] DataSet result = in.sortPartition(1, Order.ASCENDING) .mapPartition(new PartitionMapper());
First-n 返回数据集的前n个(任意)数据元。First-n可以应用于常规数据集,分组数据集或分组排序数据集。分组键可以指定为键选择器函数或字段位置键。DataSet> in = // [...] // regular data set DataSet> result1 = in.first(3); // grouped data set DataSet> result2 = in.groupBy(0) .first(3); // grouped-sorted data set DataSet> result3 = in.groupBy(0) .sortGroup(1, Order.ASCENDING) .first(3);

数据源

数据源创建初始数据集,例如来自文件或Java集合。创建数据集的一般机制是在InputFORMat后面抽象的 。Flink附带了几种内置格式,可以从通用文件格式创建数据集。他们中的许多人在ExecutionEnvironment上都有快捷方法。

基于文件的:

  • readTextFile(path)/ TextInputFormat- 按行读取文件并将其作为字符串返回。
  • readTextFileWithValue(path)/ TextValueInputFormat- 按行读取文件并将它们作为StringValues返回。StringValues是可变字符串。
  • readCsvFile(path)/ CsvInputFormat- 解析逗号(或其他字符)分隔字段的文件。返回元组或POJO的DataSet。支持基本java类型及其Value对应作为字段类型。
  • readFileOfPrimitives(path, Class)/ PrimitiveInputFormat- 解析新行(或其他字符序列)分隔的原始数据类型(如String或)的文件Integer
  • readFileOfPrimitives(path, delimiter, Class)/ PrimitiveInputFormat- 解析新行(或其他字符序列)分隔的原始数据类型的文件,例如StringInteger使用给定的分隔符。
  • readSequenceFile(Key, Value, path)/ SequenceFileInputFormat- 创建一个JobConf并从类型为SequenceFileInputFormat,Key class和Value类的指定路径中读取文件,并将它们作为Tuple2 返回。

基于集合:

  • fromCollection(Collection) - 从Java Java.util.Collection创建数据集。集合中的所有数据元必须属于同一类型。
  • fromCollection(Iterator, Class) - 从迭代器创建数据集。该类指定迭代器返回的数据元的数据类型。
  • fromElements(T ...) - 根据给定的对象序列创建数据集。所有对象必须属于同一类型。
  • fromParallelCollection(SplittableIterator, Class) - 并行地从迭代器创建数据集。该类指定迭代器返回的数据元的数据类型。
  • generateSequence(from, to) - 并行生成给定间隔中的数字序列。

通用:

  • readFile(inputFormat, path)/ FileInputFormat- 接受文件输入格式。
  • createInput(inputFormat)/ InputFormat- 接受通用输入格式。

例子

ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

// read text file from local files system
DataSet localLines = env.readTextFile("file:///path/to/my/textfile");

// read text file from a hdfs running at nnHost:nnPort
DataSet hdfsLines = env.readTextFile("hdfs://nnHost:nnPort/path/to/my/textfile");

// read a CSV file with three fields
DataSet> csvInput = env.readCsvFile("hdfs:///the/CSV/file")
                           .types(Integer.class, String.class, Double.class);

// read a CSV file with five fields, taking only two of them
DataSet> csvInput = env.readCsvFile("hdfs:///the/CSV/file")
                               .includeFields("10010")  // take the first and the fourth field
                           .types(String.class, Double.class);

// read a CSV file with three fields into a POJO (Person.class) with corresponding fields
DataSet> csvInput = env.readCsvFile("hdfs:///the/CSV/file")
                         .pojoType(Person.class, "name", "age", "zipcode");

// read a file from the specified path of type SequenceFileInputFormat
DataSet> tuples =
 env.readSequenceFile(IntWritable.class, Text.class, "hdfs://nnHost:nnPort/path/to/file");

// creates a set from some given elements
DataSet value = env.fromElements("Foo", "bar", "foobar", "fubar");

// generate a number sequence
DataSet numbers = env.generateSequence(1, 10000000);

// Read data from a relational database using the JDBC input format
DataSet dbData =
    env.createInput(
      JDBCInputFormat.buildJDBCInputFormat()
                     .setDrivername("org.apache.derby.jdbc.EmbeddedDriver")
                     .setDBUrl("jdbc:derby:memory:persons")
                     .setQuery("select name, age from persons")
                     .setRowTypeInfo(new RowTypeInfo(BasicTypeInfo.STRING_TYPE_INFO, BasicTypeInfo.INT_TYPE_INFO))
                     .finish()
    );

// Note: Flink's program compiler needs to infer the data types of the data items which are returned
// by an InputFormat. If this information cannot be automatically inferred, it is necessary to
// manually provide the type information as shown in the examples above.

收集数据源和接收器

通过创建输入文件和读取输出文件来完成分析程序的输入并检查其输出是很麻烦的。Flink具有特殊的数据源和接收器,由Java集合支持以简化测试。一旦程序经过测试,源和接收器可以很容易地被读取/写入外部数据存储(如HDFS)的源和接收器替换。

在开发中,我们经常直接使用接收器对数据源进行接收。

final ExecutionEnvironment env = ExecutionEnvironment.createLocalEnvironment();

// Create a DataSet from a list of elements
DataSet myInts = env.fromElements(1, 2, 3, 4, 5);

// Create a DataSet from any Java collection
List> data = ...
DataSet> myTuples = env.fromCollection(data);

// Create a DataSet from an Iterator
Iterator longit = ...
DataSet myLongs = env.fromCollection(longIt, Long.class);

广播变量

除了常规的 算子操作输入之外,广播变量还允许您为 算子操作的所有并行实例提供数据集。这对于辅助数据集或与数据相关的参数化非常有用。然后,算子可以将数据集作为集合访问。

// 1. The DataSet to be broadcast
DataSet toBroadcast = env.fromElements(1, 2, 3);

DataSet data = env.fromElements("a", "b");

data.map(new RichMapFunction() {
    @Override
    public void open(Configuration parameters) throws Exception {
      // 3. Access the broadcast DataSet as a Collection
      Collection broadcastSet = getRuntimeContext().getBroadcastVariable("broadcastSetName");
    }


    @Override
    public String map(String value) throws Exception {
        ...
    }
}).withBroadcastSet(toBroadcast, "broadcastSetName"); // 2. Broadcast the DataSet

分布式缓存

Flink提供了一个分布式缓存,类似于Apache Hadoop,可以在本地访问用户函数的并行实例。此函数可用于共享包含静态外部数据的文件,如字典或机器学习的回归模型。

ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

// reGISter a file from HDFS
env.registerCachedFile("hdfs:///path/to/your/file", "hdfsFile")

// register a local executable file (script, executable, ...)
env.registerCachedFile("file:///path/to/exec/file", "localExecFile", true)

// define your program and execute
...
DataSet input = ...
DataSet result = input.map(new MyMapper());
...
env.execute();

以上就是DataSet API 的使用,其实和spark非常的相似,我们将数据接入后,可以利用各种算子对数据进行处理。

Flink Demo代码

Flink系列文章:

Flink入门(一)——Apache Flink介绍
Flink入门(二)——Flink架构介绍

Flink入门(三)——环境与部署

Flink入门(四)——编程模型

更多实时计算,Flink,kafka等相关技术博文,欢迎关注实时流式计算

file

您可能感兴趣的文档:

--结束END--

本文标题: Flink入门(五)——DataSet Api编程指南

本文链接: https://lsjlt.com/news/3654.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Flink入门(五)——DataSet Api编程指南
    Apache Flink Apache Flink 是一个兼顾高吞吐、低延迟、高性能的分布式处理框架。在实时计算崛起的今天,Flink正在飞速发展。由于性能的优势和兼顾批处理,流处理的特性,Flink可能正在颠覆整个大数据的生态...
    99+
    2020-07-01
    Flink入门(五)——DataSet Api编程指南
  • PHP入门指南:PHP和Flink
    PHP是一种流行的开源服务器端脚本语言,建议初学者通过学习PHP入门指南,来了解PHP和Flink之间的关联。PHP是一种脚本语言,专门用于Web开发。它常用于动态的网页编程,但也可以在命令行方法进行编写。此外,开发人员可以使用PHP构建应...
    99+
    2023-05-20
    PHP flink 入门指南
  • Spark Streaming 编程入门指南
    Spark Streaming 是核心Spark API的扩展,可实现实时数据流的可伸缩,高吞吐量,容错流处理。可以从许多数据源(例如Kafka,Flume,Kinesis或TCP sockets)中提取数据,并且可以使用复杂的算...
    99+
    2022-02-15
    Spark Streaming 编程入门指南
  • PHP入门指南:HTTP编程
    PHP入门指南:HTTP编程随着互联网的飞速发展,网站已经成为人们交流、获取信息和进行业务交易的重要途径。在网站开发的过程中,PHP作为一种广泛使用的Web编程语言,得到了大量的应用。HTTP(Hypertext Transfer Prot...
    99+
    2023-05-22
    编程 PHP Http
  • PHP入门指南:UDP编程
    PHP是一门十分流行的Web编程语言,早期更被大家认为是一种简单易学的脚本语言。然而,由于其流行度,在过去几年中,它已经成长为一门成熟的编程语言,可以实现各种任务和项目。而在这门语言中,UDP编程是一个十分重要和有趣的主题。UDP(User...
    99+
    2023-05-20
    编程 PHP UDP
  • PHP入门指南:TCP/IP编程
    PHP作为一个流行的服务器端脚本语言,它不仅可以用于Web应用程序的开发,还可以用于进行TCP/IP编程以及网络编程。在本文中,我们将为您介绍TCP/IP编程的基础知识和如何使用PHP进行TCP/IP编程。一、TCP/IP编程的基础知识TC...
    99+
    2023-05-20
    PHP入门 TCP/IP编程 编程指南
  • PHP入门指南:异步编程
    PHP作为一门流行的脚本语言,一直以来都是Web开发的主流语言之一。在Web开发中,异步编程被越来越多地应用于网络爬虫、实时聊天、长轮询等高性能场景。本文将介绍PHP异步编程的相关知识,以便读者掌握异步编程基础,提高Web应用性能。一、异步...
    99+
    2023-05-20
    PHP 异步编程 入门指南
  • Go语言编程入门指南
    Go语言(Golang)是由Google开发的一种编程语言,它的设计简洁、高效,并且具有很强的并发特性,因此受到了许多开发者的青睐。本文将为您提供一份简单的Go语言编程入门指南,介绍基...
    99+
    2024-04-02
  • PHP入门指南:多线程编程
    PHP是一种流行的服务器端编程语言,用于创建Web应用程序和动态网站。虽然PHP本身不支持多线程编程,但它提供了一些工具和扩展,可用于实现非阻塞I/O操作和进程间通信。本文将介绍PHP多线程编程的基本知识和工具。多线程编程基础多线程编程是一...
    99+
    2023-05-20
    PHP(编程语言) 多线程编程(并发编程方式) 入门指南(针对初学者的指导文本)
  • PHP入门指南:Swoole编程框架
    随着互联网技术的不断发展,越来越多的开发者开始涉足PHP编程,而Swoole作为一种高性能的开源框架,它的出现为PHP开发者提供了更多的工具和方法,让PHP编程变得更加快捷、高效。本篇文章将介绍Swoole框架的基本概念和入门使用方法,帮助...
    99+
    2023-05-21
    PHP 入门指南 Swoole编程框架
  • PHP入门指南:ReactPHP编程框架
    随着互联网技术的日益发展,Web应用程序已经渐渐成为了我们日常生活和工作中离不开的一部分。而PHP作为一个广泛应用于Web开发的语言,也在不断地发展和改进。本文将为你介绍一种基于PHP的编程框架——ReactPHP,帮助你更好地理解和掌握它...
    99+
    2023-05-20
    PHP 入门指南 ReactPHP编程框架
  • Python编程入门指南之函数
    目录Python编程:函数定义和调用函数向函数传递信息传递实参:位置实参传递实参:关键字实参传递实参:默认值传递列表禁止函数修改列表传递任意数量实参返回值返回简单值让实参可选返回字典...
    99+
    2024-04-02
  • 阿里云服务器编程入门指南
    阿里云服务器是阿里云提供的一种云服务,可以让你轻松地在云上构建、运行和扩展应用程序。本文将为你提供阿里云服务器编程的入门指南,包括如何创建服务器、如何连接到服务器、如何在服务器上安装和运行应用程序等内容。 阿里云服务器编程入门指南:创建阿里...
    99+
    2023-11-02
    阿里 入门 服务器
  • Golang编程的实用技巧入门指南
    从零开始学习Golang编程的实用技巧 Golang是一种越来越受欢迎的编程语言,其简洁性、高效性和并发性能受到广泛认可。如果你正准备开始学习Golang编程,本文将为你提供一些实用技...
    99+
    2024-02-23
    学习 技巧 golang go语言 标准库
  • PHP入门指南:线程池
    随着互联网时代的到来,网站和应用程序越来越受到人们的欢迎。 在Web开发中,PHP是一个非常流行的脚本语言。PHP是一种解释性语言,它可以在服务器上执行。 由于PHP语言易学易用,因此它成为了PHP开发人员的首选之一。但是,当涉及到高负载应...
    99+
    2023-05-20
    PHP 线程池 入门
  • python爬虫入门教程--正则表达式完全指南(五)
    前言 正则表达式处理文本有如疾风扫秋叶,绝大部分编程语言都内置支持正则表达式,它应用在诸如表单验证、文本提取、替换等场景。爬虫系统更是离不开正则表达式,用好正则表达式往往能收到事半功倍的效果。 介绍正则表达...
    99+
    2022-06-04
    爬虫 入门教程 指南
  • node.js exports对象入门指南:解锁模块化编程大门
    在 Node.js 中,exports 对象是模块化编程的核心,它允许我们创建可重用的代码块并导出它们以供其他模块使用。了解 exports 对象对于构建模块化和可维护的 Node.js 应用程序至关重要。 exports 对象概述 ex...
    99+
    2024-04-02
  • PHP入门指南:进程和线程
    PHP是一种广泛使用的脚本语言,主要用于Web开发和应用程序开发。随着应用程序和Web应用的复杂性增加,程序员们常常需要使用进程和线程来实现更高效的并行计算。本篇文章将介绍PHP进程和线程的相关概念,以及如何在PHP中使用它们。进程和线程的...
    99+
    2023-05-20
    PHP 进程 线程
  • python编程之API入门: (一)使
    在网络编程中,我们会和API打交道。那么,什么是API如何使用API呢?本文分享了一下我对API的理解以及百度地图API的使用。 API是"Application Programming Interface(应用程序编程接口)"的缩写。如...
    99+
    2023-01-31
    入门 python API
  • dotConnect for Oracle入门指南(五):检索和修改数据
    【下载dotConnect for Oracle最新版本】dotConnect for Oracle(原名OraDirect.NET)建立在ADO.NET技术上,为基于Oracle数据库的应用程序提供完整的...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作