全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2022-10-24_Java Stream 的操作这么多,其实只有两大类,看完这篇就清晰了

您的位置:首页 >> 新闻 >> 行业资讯

Java Stream 的操作这么多,其实只有两大类,看完这篇就清晰了 本文为掘金社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究! 我们在前面几篇关于 Java 集合框架中 List、Set、Map 这些容器的文章中,已经给大家演示过一些 Stream 操作了,这篇文章给大家详细梳理一下。 前文回顾 Java 做项目能用到 List 的场景,这篇总结全了 有 List 了为什么还要有 Set?Java 容器 Set 的核心通关指南 Java 做项目能用到 Map 哪些功能?这篇总结全了 由于 Stream 提供的操作过多,本节内容是 Stream API 中常用操作的学习和理解,下面会专门再有一篇文章介绍在项目开发中那些高频使用的,利用 Stream 处理对象集合的使用示例。 本文大纲如下: Java 的 Stream API 提供了一种处理对象集合的函数式方法。 Stream 是和 Lambda 表达式等其他几个函数式编程特性一起在 Java 8 被引入的。这个篇教程将解释 Stream API 提供的这些函数式方法是如何工作的,以及怎么使用它们。 注意,Java 的 Stream API 与 Java IO 的 InputStream 和 OutputStream 没有任何关系,不要因为名字类似造成误解。 InputStream 和 OutputStream 是与字节流有关,而 Java 的 Stream API 用于处理对象流。 Stream 的定义Java 的 Stream 是一个能够对其元素进行内部迭代的组件,这意味着它可以自己迭代其元素。相反地,当我们使用 Collection 的迭代功能,例如,从 Collection 获取Iterator 或者使用 Iterable 接口 的 forEach 方法这些方式进行迭代时,我们必须自己实现集合元素的迭代逻辑。 当然集合也支持获取 Stream 完成迭代,这些我们在介绍集合框架的相关章节都介绍过。 流处理我们可以将 Listener 方法或者叫处理器方法附加到 Stream 上。当 Stream 在内部迭代元素时,将以元素为参数调用这些处理器。Stream 会为流中的每个元素调用一次处理器。所以每个处理器方法都可以处理 Stream 中的每个元素,我们把这称为流处理。 流的多个处理器方法可以形成一个调用链。链上的前一个处理器处理流中的元素,返回的新元素会作为参数传给链中的下一个处理器处理。当然,处理器可以返回相同的元素或新元素,具体取决于处理器的目的和用途。 怎么获取流有很多方法获取 Stream ,一般最常见的是从 Collection 对象中获取 Stream。下面是一个从 List 对象获取 Stream 的例子。 ListStringitems=newArrayListString items.add("one"); items.add("two"); items.add("three"); StreamStringstream=items.stream(); 集合对象都实现了 Collection 接口,所以通过接口里定义的 stream 方法获救获取到由集合元素构成的 Steam。 流处理的构成在对流进行处理时,不同的流操作以级联的方式形成处理链。一个流的处理链由一个源(source),0 到多个中间操作(intermediate operation)和一个终结操作(terminal operation)完成。 源:源代表 Stream 中元素的来源,比如我们上面看到的集合对象。中间操作:中间操作,在一个流上添加的处理器方法,他们的返回结果是一个新的流。这些操作是延迟执行的,在终结操作启动后才会开始执行。终结操作:终结流操作是启动元素内部迭代、调用所有处理器方法并最终返回结果的操作。概念听起来有点模糊,我们通过流处理的例子再理解一下。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Stream; publicclassStreamExamples{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("ONE"); stringList.add("TWO"); stringList.add("THREE"); StreamStringstream=stringList.stream(); longcount=stream .map((value)-value.toLowerCase()) .count(); System.out.println("count="+count); } } map() 方法的调用是一个中间操作。它只是在流上设置一个 Lambda 表达式,将每个元素转换为小写形式。而对 count() 方法的调用是一个终结操作。此调用会在内部启动迭代,开始流处理,这将导致每个元素都转换为小写然后计数。 将元素转换为小写实际上并不影响元素的计数。转换部分只是作为 map() 是一个中间操作的示例。 流的中间操作Stream API 的中间(非终结)流操作是转换或者过滤流中元素的操作。当我们把中间操作添加到流上时,我们会得到一个新的流作为结果。下面是一个添加到流上的中间操作的示例,它的执行结果会产生一个新的流。 ListStringstringList=newArrayList(); stringList.add("ONE"); stringList.add("TWO"); stringList.add("THREE"); StreamStringstream=stringList.stream(); StreamStringstringStream= stream.map((value)-value.toLowerCase()); 上面例子中,流上添加的 map() 调用,此调用实际上返回一个新的 Stream 实例,该实例表示原始字符串流应用了 map 操作后的新流。 只能将单个操作添加到给定的 Stream 实例上。如果需要将多个操作链接在一起,则只能将第二个操作应用于第一个操作产生的 Stream 实例上。 StreamStringstringStream1= stream.map((value)-value.toLowerCase()); Stream?StringstringStream2= stringStream1.map((value)-value.toUpperCase()); 注意第二个 map() 调用是如何在第一个 map() 调用返回的 Stream 上进行调用的。 我们一般是将 Stream 上的所有中间操作串联成一个调用链: StreamStringstream1=stream .map((value)-value.toLowerCase()) .map((value)-value.toUpperCase()) .map((value)-value.substring(0,3)); 以 map方法为代表流间操作方法的参数,是一个函数式接口,我们可以直接用 Lambda 表达式作为这些操作的参数。所以在介绍 Lambda 的那一节我们也说过,Lambda 一般是和流操作就结合起来用的。 **参考--Java 的函数式接口: **http://tutorials.jenkov.com/java-functional-programming/functional-interfaces.html 下面我们说一下常用的流的中间操作。 mapmap() 方法将一个元素转换(或者叫映射)到另一个对象。例如,一个字符串列表,map() 可以将每个字符串转换为小写、大写或原始字符串的子字符串,或完全不同的东西。 ListStringlist=newArrayListString StreamStringstream=list.stream(); StreamStringstreamMapped=stream.map((value)-value.toUpperCase()); filterfilter() 用于从 Stream 中过滤掉元素。 filter 方法接受一个 Predicate (也是一个函数式接口),filter() 为流中的每个元素调用 Predicate。如果元素要包含在 filter() 返回结果的流中,则 Predicate 应返回 true。如果不应包含该元素,则 Predicate 应返回 false。 StreamStringlongStringsStream=stream.filter((value)-{ //元素长度大于等于3,返回true,会被保留在filter产生的新流中。 returnvalue.length()=3; }); 比如 Stream 实例应用了上面这个 filter 后,filter 返回的结果流里只会包含长度不小于 3 的元素。 flatMapflatMap方法接受一个 Lambda 表达式, Lambda 的返回值必须也是一个stream类型,flatMap方法最终会把所有返回的stream合并。map 与 flatMap 方法很像,都是以某种方式转换流中的元素。如果需要将每个元素转换为一个值,则使用 map 方法,如果需要将每个元素转换为多个值组成的流,且最终把所有元素的流合并成一个流,则需要使用 flatMap 方法。 在效果上看是把原来流中的每个元素进行了“展平” importjava.util.ArrayList; importjava.util.Arrays; importjava.util.List; importjava.util.stream.Stream; publicclassStreamFlatMapExamples{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayListString stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); stream.flatMap((value)-{ String[]split=value.split(""); returnArrays.asList(split).stream(); }).forEach((value)-System.out.println(value)); } } 在上面的例子中,每个字符串元素被拆分成单词,变成一个 List,然后从这个 List 中获取并返回流,flatMap 方法最终会把这些流合并成一个,所以最后用流终结操作 forEach 方法,遍历并输出了每个单词。 One flew over the cuckoo's nest To kill a muckingbird Gone with the wind distinctdistinct() 会返回一个仅包含原始流中不同元素的新 Stream 实例,任何重复的元素都将会被去掉。 ListStringstringList=newArrayListString stringList.add("one"); stringList.add("two"); stringList.add("three"); stringList.add("one"); StreamStringstream=stringList.stream(); ListStringdistinctStrings=stream .distinct() .collect(Collectors.toList()); System.out.println(distinctStrings); 在这个例子中,元素 "one" 在一开始的流中出现了两次,原始流应用 distinct 操作生成的新流中将会丢弃掉重复的元素,只保留一个 "one" 元素。所以这个例子最后的输出是: [one,two,three] limitlimit 操作会截断原始流,返回最多只包含给定数量个元素的新流。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Stream; publicclassStreamLimitExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayListString stringList.add("one"); stringList.add("two"); stringList.add("three"); stringList.add("one"); StreamStringstream=stringList.stream(); stream.limit(2) .forEach(element-System.out.println(element)); } } 这个例子中,因为对原始流使用了 limit(2) 操作,所以只会返回包含两个元素的新流,随后使用 forEach 操作将它们打印了出来。程序最终将会输出: one two peekpeek() 方法是一个以 Consumer (java.util.function.Consumer,Consumer 代表的是消费元素但不返回任何值的方法) 作为参数的中间操作,它返回的流与原始流相同。当原始流中的元素开始迭代时,会调用 peek 方法中指定的 Consumer 实现对元素进行处理。 正如 peek 操作名称的含义一样,peek() 方法的目的是查看流中的元素,而不是转换它们。跟其他中间操作的方法一样,peek() 方法不会启动流中元素的内部迭代,流需要一个终结操作才能开始内部元素的迭代。 peek() 方法在流处理的 DEBUG 上的应用甚广,比如我们可以利用 peek() 方法输出流的中间值,方便我们的调试。 Stream.of("one","two","three","four").filter(e-e.length()3) .peek(e-System.out.println("Filteredvalue:"+e)) .map(String::toUpperCase) .peek(e-System.out.println("Mappedvalue:"+e)) .collect(Collectors.toList()); 上面的例子会输出以下调试信息。 Filteredvalue:three Mappedvalue:THREE Filteredvalue:four Mappedvalue:FOUR 流的终结操作Stream 的终结操作通常会返回单个值,一旦一个 Stream 实例上的终结操作被调用,流内部元素的迭代以及流处理调用链上的中间操作就会开始执行,当迭代结束后,终结操作的返回值将作为整个流处理的返回值被返回。 longcount=stream .map((value)-value.toLowerCase()) .map((value)-value.toUpperCase()) .map((value)-value.substring(0,3)) .count(); Stream 的终结操作 count() 被调用后整个流处理开始执行,最后将 count() 的返回值作为结果返回,结束流操作的执行。这也是为什么把他们命名成流的终结操作的原因。 上面例子,应用的中间操作 map 对流处理的结果并没有影响,这里只是做一下演示。 下面我们把常用的流终结操作说一下。 anyMatchanyMatch() 方法以一个 Predicate (java.util.function.Predicate 接口,它代表一个接收单个参数并返回参数是否匹配的函数)作为参数,启动 Stream 的内部迭代,并将 Predicate 参数应用于每个元素。如果 Predicate 对任何元素返回了 true(表示满足匹配),则 anyMatch() 方法的结果返回 true。如果没有元素匹配 Predicate,anyMatch() 将返回 false。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Stream; publicclassStreamAnyMatchExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayListString stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); booleananyMatch=stream.anyMatch((value)-value.startsWith("One")); System.out.println(anyMatch); } } 上面例程的运行结果是 true , 因为流中第一个元素就是以 "One" 开头的,满足 anyMatch 设置的条件。 allMatchallMatch() 方法同样以一个 Predicate 作为参数,启动 Stream 中元素的内部迭代,并将 Predicate 参数应用于每个元素。如果 Predicate 为 Stream 中的所有元素都返回 true,则 allMatch() 的返回结果为 true。如果不是所有元素都与 Predicate 匹配,则 allMatch() 方法返回 false。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Stream; publicclassStreamAllMatchExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayListString stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); booleanallMatch=stream.allMatch((value)-value.startsWith("One")); System.out.println(allMatch); } } 上面的例程我们把流上用的 anyMatch 换成了 allMatch ,结果可想而知会返回 false,因为并不是所有元素都是以 "One" 开头的。 noneMatchMatch 系列里还有一个 noneMatch 方法,顾名思义,如果流中的所有元素都与作为 noneMatch 方法参数的 Predicate 不匹配,则方法会返回 true,否则返回 false。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Stream; publicclassStreamNoneExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("abc"); stringList.add("def"); StreamStringstream=stringList.stream(); booleannoneMatch=stream.noneMatch((element)-{ return"xyz".equals(element); System.out.println("noneMatch="+noneMatch);//输出noneMatch=true } } collectcollect() 方法被调用后,会启动元素的内部迭代,并将流中的元素收集到集合或对象中。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Collectors; importjava.util.stream.Stream; publicclassStreamCollectExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); ListStringstringsAsUppercaseList=stream .map(value-value.toUpperCase()) .collect(Collectors.toList()); System.out.println(stringsAsUppercaseList); } } collect() 方法将收集器 -- Collector (java.util.stream.Collector) 作为参数。在上面的示例中,使用的是 Collectors.toList() 返回的 Collector 实现。这个收集器把流中的所有元素收集到一个 List 中去。 countcount() 方法调用后,会启动 Stream 中元素的迭代,并对元素进行计数。 importjava.util.ArrayList; importjava.util.Arrays; importjava.util.List; importjava.util.stream.Stream; publicclassStreamExamples{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayListString stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); longcount=stream.flatMap((value)-{ String[]split=value.split(""); returnArrays.asList(split).stream(); }).count(); System.out.println("count="+count);//count=14 } } 上面的例程中,首先创建一个字符串 List ,然后获取该 List 的 Stream,为其添加了 flatMap() 和 count() 操作。 count() 方法调用后,流处理将开始迭代 Stream 中的元素,处理过程中字符串元素在 flatMap() 操作中被拆分为单词、合并成一个由单词组成的 Stream,然后在 count() 中进行计数。所以最终打印出的结果是 count = 14。 findAnyfindAny() 方法可以从 Stream 中找到单个元素。找到的元素可以来自 Stream 中的任何位置。且它不提供从流中的哪个位置获取元素的保证。 importjava.util.ArrayList; importjava.util.List; importjava.util.Optional; importjava.util.stream.Stream; publicclassStreamFindAnyExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("one"); stringList.add("two"); stringList.add("three"); stringList.add("one"); StreamStringstream=stringList.stream(); OptionalStringanyElement=stream.findAny(); if(anyElement.isPresent()){ System.out.println(anyElement.get()); }else{ System.out.println("notfound"); } } } findAny() 方法会返回一个 Optional,意味着 Stream 可能为空,因此没有返回任何元素。我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素。 Optional 类是一个可以为 null 的容器对象。如果值存在则 isPresent() 方法会返回true,调用get()方法会返回容器中的对象,否则抛出异常:NoSuchElementException findFirstfindFirst() 方法将查找 Stream 中的第一个元素,跟 findAny() 方法一样,也是返回一个 Optional,我们可以通过 Optional 的 isPresent() 方法检查是否找到了元素。 importjava.util.ArrayList; importjava.util.List; importjava.util.Optional; importjava.util.stream.Stream; publicclassStreamFindFirstExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("one"); stringList.add("two"); stringList.add("three"); stringList.add("one"); StreamStringstream=stringList.stream(); OptionalStringanyElement=stream.findFirst(); if(anyElement.isPresent()){ System.out.println(anyElement.get()); }else{ System.out.println("notfound"); } } } forEachforEach() 方法我们在介绍 Collection 的迭代时介绍过,当时主要是拿它来迭代 List 的元素。它会启动 Stream 中元素的内部迭代,并将 Consumer (java.util.function.Consumer, 一个函数式接口,上面介绍过) 应用于 Stream 中的每个元素。 注意 forEach() 方法的返回值是 void。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Stream; publicclassStreamExamples{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayListString stringList.add("one"); stringList.add("two"); stringList.add("three"); StreamStringstream=stringList.stream(); stream.forEach(System.out::println); } } 注意,上面例程中 forEach 的参数我们直接用了Lambda 表达式引用方法的简写形式。 minmin() 方法返回 Stream 中的最小元素。哪个元素最小是由传递给 min() 方法的Comparator 接口实现来确定的。 importjava.util.ArrayList; importjava.util.List; importjava.util.Optional; importjava.util.stream.Stream; publicclassStreamMinExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("abc"); stringList.add("def"); StreamStringstream=stringList.stream(); //作为min方法参数的Lambda表达式可以简写成String::compareTo //OptionalStringmin=stream.min(String::compareTo); OptionalStringmin=stream.min((val1,val2)-{ returnval1.compareTo(val2); StringminString=min.get(); System.out.println(minString);//abc } } min() 方法返回的是一个 Optional ,也就是它可能不包含结果。如果为空,直接调用 Optional 的 get() 方法将抛出 异常--NoSuchElementException。比如我们把上面的 List 添加元素的两行代码注释掉后,运行程序就会报 Exceptioninthread"main"java.util.NoSuchElementException:Novaluepresent atjava.util.Optional.get(Optional.java:135) atcom.example.StreamMinExample.main(StreamMinExample.java:21) 所以最好先用 Optional 的 ifPresent() 判断一下是否包含结果,再调用 get() 获取结果。 max与 min() 方法相对应,max() 方法会返回 Stream 中的最大元素,max() 方法的参数和返回值跟 min() 方法的也都一样,这里就不再过多阐述了,只需要把上面求最小值的方法替换成求最大值的方法 max() 即可。 OptionalStringmin=stream.max(String::compareTo); reducereduce() 方法,是 Stream 的一个聚合方法,它可以把一个 Stream 的所有元素按照聚合函数聚合成一个结果。reduce()方法接收一个函数式接口 BinaryOperator 的实现,它定义的一个apply()方法,负责把上次累加的结果和本次的元素进行运算,并返回累加的结果。 importjava.util.ArrayList; importjava.util.List; importjava.util.Optional; importjava.util.stream.Stream; publicclassStreamReduceExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); OptionalStringreduced=stream.reduce((value,combinedValue)-combinedValue+"+"+value); //写程序的时候记得别忘了reduced.ifPresent()检查结果里是否有值 System.out.println(reduced.get()); } } reduce() 方法的返回值同样是一个 Optional 类的对象,所以在获取值前别忘了使用 ifPresent() 进行检查。 streadm 实现了多个版本的reduce() 方法,还有可以直接返回元素类型的版本,比如使用 reduce 实现整型Stream的元素的求和 importjava.util.ArrayList; importjava.util.List; publicclassIntegerStreamReduceSum{ publicstaticvoidmain(String[]args){ ListIntegerintList=newArrayList(); intList.add(10); intList.add(9); intList.add(8); intList.add(7); Integersum=intList.stream().reduce(0,Integer::sum); System.out.printf("List求和,总和为%s\n",sum); } } toArraytoArray() 方法是一个流的终结操作,它会启动流中元素的内部迭代,并返回一个包含所有元素的 Object 数组。 ListStringstringList=newArrayListString stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream=stringList.stream(); Object[]objects=stream.toArray(); 不过 toArray 还有一个重载方法,允许传入指定类型数组的构造方法,比如我们用 toArray 把流中的元素收集到字符串数组中,可以这么写: String[]strArray=stream.toArray(String[]::new); 流的拼接Java 的Stream 接口包含一个名为 concat() 的静态方法,它可以将两个流连接成一个。 importjava.util.ArrayList; importjava.util.List; importjava.util.stream.Collectors; importjava.util.stream.Stream; publicclassStreamConcatExample{ publicstaticvoidmain(String[]args){ ListStringstringList=newArrayList(); stringList.add("Oneflewoverthecuckoo'snest"); stringList.add("Tokillamuckingbird"); stringList.add("Gonewiththewind"); StreamStringstream1=stringList.stream(); ListStringstringList2=newArrayList(); stringList2.add("LordoftheRings"); stringList2.add("PlanetoftheRats"); stringList2.add("PhantomMenace"); StreamStringstream2=stringList2.stream(); StreamStringconcatStream=Stream.concat(stream1,stream2); ListStringstringsAsUppercaseList=concatStream .collect(Collectors.toList()); System.out.println(stringsAsUppercaseList); } } 从数组创建流上面关于 Stream 的例子我们都是从 Collection 实例的 stream() 方法获取的集合包含的所有元素的流,除了这种方法之外,Java 的 Stream 接口中提供了一个名为 of 的静态方法,能支持从单个,多个对象或者数组对象快速创建流。 importjava.util.stream.Stream; publicclassStreamExamples{ publicstaticvoidmain(String[]args){ StreamStringstream1=Stream.of("one","two","three"); StreamStringstream2=Stream.of(newString[]{"one","two"}); System.out.println(stream1.count());//输出3 System.out.println(stream2.count());//输出2 } } 总结上面我们把 Stream 的两大类操作:流的中间操作、流的终结操作都有哪些方法给大家列举了一遍,让大家对 Stream 能完成的操作有了大致的印象。不过为了讲解这些操作用的都是非常简单的例子,流操作的数据也都是简单类型的,主要的目的是让大家能更快速地理解 Stream 的各种操作应用在数据上后,都有什么效果。 下一篇我会演示一些在项目开发中我们会高频用到的,使用 Stream 完成各种复杂操作的示例,让大家做项目的时候可以直接进行参考,进一步提升你用 Java 编程、开发项目的体验。 阅读原文

上一篇:2025-07-12_2025上半年海外精选项目TOP 20,集齐全球脑洞 下一篇:2020-05-09_最近,这个“玩”游戏的小瓶子吸引了我的注意

TAG标签:

17
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价