首页 文章 精选 留言 我的

精选列表

搜索[学习],共10000篇文章
优秀的个人博客,低调大师

谢源:计算存储一体化,在存储里做深度学习,架构创新实现下一代AI芯片

谢源教授于1997年获得清华大学电子工程系学士学位,于1999年和2002年获得普林斯顿大学电机工程系硕士和博士学位。于2003年加入宾夕法尼亚州立大学计算机系,2008年获得终身教职,2012年提升正教授。2014年他转入加州大学圣芭芭拉分校电机与计算机工程系(ECE)担任正教授。他同时也是北京大学的兼职教授和台湾新竹清华大学的客座教授。 谢源教授也拥有丰富的工业界经验,他于2002-2003年任职于IBM担任微电子部门全球设计中心的咨询工程师,2012年到2013年期间加入AMD,负责组建和领导AMD北京研发中心的研究部门。他所获得的荣誉包括美国国家自然科学基金会的 CAREER award,入选计算机体系结构三大会议名人堂(Hall of Fame in ISCA/MICRO/HPCA),以及鉴于他在三维芯片设计和架构的贡献当

优秀的个人博客,低调大师

一脸懵逼学习KafKa集群的安装搭建--(一种高吞吐量的分布式发布订阅消息系统)

1:KafKa的官方网址:http://kafka.apache.org/ 开发流程图,如: 2:KafKa的基础知识: 2.1:kafka是一个分布式的消息缓存系统2.2:kafka集群中的服务器都叫做broker2.3:kafka有两类客户端,一类叫producer(消息生产者),一类叫做consumer(消息消费者),客户端和broker服务器之间采用tcp协议连接2.4:kafka中不同业务系统的消息可以通过topic进行区分,而且每一个消息topic都会被分区,以分担消息读写的负载2.5:每一个分区都可以有多个副本,以防止数据的丢失2.6:某一个分区中的数据如果需要更新,都必须通过该分区所有副本中的leader来更新2.7:消费者可以分组,比如有两个消费者组A和B,共同消费一个topic:order_info,A和B所消费的消息不会重复 比如 order_info 中有100个消息,每个消息有一个id,编号从0-99,那么,如果A组消费0-49号,B组就消费50-99号2.8:消费者在具体消费某个topic中的消息时,可以指定起始偏移量 3:KafKa集群的安装搭建,注意区分单节点KafKa集群的搭建。 3.1:kafka集群安装,第一步上传kafka_2.10-0.8.1.1.tgz到虚拟机上面,过程省略,然后进行解压缩操作: 3.2:修改kafka配置文件,修改server.properties 修改如下所示,具体情况可以根据手册修改,详细修改可以参考Kafka的文档: 使用自己部署的Zookeeper集群,修改如下所示: 可以直接搜索:/zookeeper.connect找到所要修改的内容: 将配置好的Kafka复制到另外两个节点上面: [root@master hadoop]# scp -r kafka_2.10-0.8.1.1/ slaver1:/home/hadoop/ [root@master hadoop]# scp -r kafka_2.10-0.8.1.1/ slaver2:/home/hadoop/ 然后修改一下另外两台的broker.id=2和broker.id=3: 3.3:将zookeeper集群启动: [root@master hadoop]# cd /home/hadoop/zookeeper-3.4.5/bin/ [root@master bin]# ./zkServer.sh start [root@slaver2 bin]# ./zkServer.sh status 3.4:在每一台节点上启动broker: bin/kafka-server-start.sh config/server.properties Unrecognized VM option 'UseCompressedOops' Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit. 启动的时候报错了,问题的根本是UseCompressedOops是jdk8的,而我的jdk是7,所以解决一下问题: 原因是jdk的版本不匹配,需要修改一下配置文件修改文件: 去掉这个配置 -XX:+UseCompressedOops 进去以后,搜索一下比较快:/UseCompressedOops,然后看到如下,删除如此配置: [root@master bin]# vim kafka-run-class.sh 其他两个节点的都按照如此删除掉即可: 修改好以后开始跑: 在每一台节点上启动brokerbin/kafka-server-start.sh config/server.properties 然后按照如此将其他两个节点都启动起来,然后复制xshell的连接看一下jps进程启动情况: 三个都启动起来,可以看一下,broker 1,broker 2,broker 3都启动起来了: 可以使用复制的xshell窗口查看jps进程启动情况: 3.5:在kafka集群中创建一个topic: [root@master kafka_2.10-0.8.1.1]# bin/kafka-topics.sh --create --zookeeper master:2181 --replication-factor 3 --partitions 1 --topic order 可以查看一下自己创建的topic: [root@master kafka_2.10-0.8.1.1]# bin/kafka-topics.sh --list --zookeeper master:2181 可以创建多个多个topic,也可以查看一下自己创建的topic: 3.6:用一个producer向某一个topic中写入消息,生产者产生消息,消费者消费消息,如下生产者可以生产: 如下先启动一下生产者,先不生产消息,然后一个消费者,看看是否有输出,然后再生产消息,再去消费者看看消费消息: #生产者 [root@master kafka_2.10-0.8.1.1]# bin/kafka-console-producer.sh --broker-list master:9092 --topic order #消费者 [root@master kafka_2.10-0.8.1.1]# bin/kafka-console-consumer.sh --zookeeper master:2181 --from-beginning --topic order 上面是生产者: 下面是消费者: 3.7:查看一个topic的分区及副本状态信息: 自己可以找任意一个xshell复制连接进程查看: [root@slaver1 kafka_2.10-0.8.1.1]# bin/kafka-topics.sh --describe --zookeeper master:2181 --topic order 4:kafka运行在后台如何操作,如下所示: 1>/dev/null:代表标准输入到这个目录; 2>&1:代表标准输出也到这个目录下面; &:代表这个是后台运行; [root@master kafka_2.10-0.8.1.1]# bin/kafka-server-start.sh config/server.properties 1>/dev/null 2>&1 &

优秀的个人博客,低调大师

App后台开发运维和架构实践学习总结(5)——App产品从需求到研发到开发到上线到产品迭代全过程

前言 如果没有做过开发,研发过产品的人,很难体会做产品的艰难,刚进公司的人,一般充当的是程序开发,我这里说的是开发,它与研发是有区别的. 一个需求下来,如果不能很好地理解产品需求,如果不能很好的驾驭需求实现的逻辑,肆意的根据理解去做技术方面的架构和编码,等到后来发现了不对了再去修改就特别麻烦。 所以我们在实现产品需求时,每一个功能需求,不管是大还是小,都要想商量清楚了,我们在采取编码. 言归正转,那么整个过程一款产品从想法-开发-上线-产品都经历了哪些? 希望能给大家一个好的借鉴作用,总结的不好的,希望给予指正,大家可以畅所欲言. 主要分为以下几步 第一步:需求梳理、分析 第二步:产品原型图绘制 第三步:UI设计 第四步:项目经理&技术负责人对接需求 第五步:技术方案 & 架构设计 第六步:项目排期 & 任务分解 第七步:产品研发阶段 第八步:交付测试阶段 第九步:产品发布上线 第十步:迭代 下面我们来看看具体操作: 第一步:需求梳理、分析 在此假设用户需求分析已经确定 , 接下来根据提炼的真实用户需求来确定产品需求。 产品经理将会根据沟通中的相关资料的word、ppt、jpg等等东西翻译成逻辑语言,最简单的就是产出一张产品功能脑图或者一份功能列表。 ▲产品功能脑图 ▲一份功能列表 初步产品功能需求梳理清楚之后,产品经理持续跟进,反复沟通确定产品原型图。 ▲产品原型图 同时根据具体的项目需求,会搭配一套产品业务流程图 ▲产品业务流程的图 简单点,用墨刀做一份带交互的原型。这个很重要,只有交互原型图上的逻辑跑通了,代码的逻辑差不多也通了,这样能节省很多时间,需求上说的跟实际上做的还是有很大区别的,有时间,需求听起来是很有道理,但是用技术几乎实现不了. ▲墨刀带交互的原型 第三步:UI设计 UI设计,包含风格稿和内页设计。 风格稿会根据产品需求提供的目标用户类型、客户倾向、LOGO等信息,以及确定做风格稿的2-3个页面的原型图,来进行风格稿设计。 待风格稿确认后进行内页设计,包括设计效果、页面元素、弹出页面等等 ▲风格稿 ▲风格稿 所有页面设计完后会统一发给客户做进一步沟通,然后统一修改优化。 ▲Zeplin Zeplin能够帮助前端更好地理解设计师意图,而设计师又能快速得到前端反馈的协作,从而减少设计师与前端的沟通错位,使得两者在“界面元素”和“交互动作”上形成一致。 ▲Zeplin invision用于设计先行能减少后端技术工程问题,设计的迭代越快,软件开发就越能在时间点的把控上做到极简。 ▲invision 设计定稿后并不是设计师的工作结束了,之后还有一段周期的切图、标注工作 。 ▲标注 ▲切图 第四步:项目经理&技术负责人对接需求 项目经理对接上这些需求,第一个工作是细化需求,将这些翻译成技术能更好理解地语言,搭配着原型图或设计稿来召开技术会议,统一讲解新项目的需求。 ▲细化需求 第五步:技术方案 & 架构设计 技术负责人在清楚了解整个项目的需求之后会开始构思整个项目的技术方案,根据产品需求,提供易扩展、可持续迭代的技术框架方案。 ▲整个项目的技术方案(需求文档) ▲可持续迭代的技术框架方案 第六步:项目排期 & 任务分解 同时,项目经理在和研发团队沟通确认后对项目进行分解以及排期,以此来保证项目进度和质量。 ▲项目管理 第七步:产品研发阶段 这个阶段就是各端技术按照排期规划开始编码,期间各种对接、调试以及撕逼。我不是程序猿,这块就不多写了,贴几张他们技术wiki的截图吧。 ▲Wiki对接 Paw 让测试 API 变得轻松愉悦,可以构建内部和外部的资源。它可以在不同的环境下进行测试,也可以引用来自其他请求响应的数据。 ▲PAW 它可以定义不同的环境,于是可以轻松地在开发、临时和生产环境中进行切换,而无需重新配置任何端点(endpoint)。并且还可以在一个请求的消息体中引用另一个请求中返回的值,这能够节省大量时间。 第八步:交付测试阶段 测试工程师基本全程跟进,从最早期对接完详细产品需求之后就开始编写测试用例 ▲测试用例 然后配合项目各个里程碑节点进行功能测试和性能测试,将问题按优先级划分统一反馈 ▲测试过程 第九步:上线 以上均是理想情况下,一个App必经的几个阶段的简洁步骤说明,具体执行依然会根据需求穿插进行。 不同的项目管理模式或许会有完全不同的流程步骤。但是专业性几乎是保证产品质量的唯一准则。 第十步:产品迭代 每次要根据用户反应的问题和增加的功能需求进行产品迭代

优秀的个人博客,低调大师

【福利】BAT架构师分享最全Java架构师学习技能图谱:包含Java编程+网络+设计模式+数据库+分布式等

【福利】总结了一份架构图谱,希望对想成为架构师的朋友有一定的参考和帮助。 我简短谈下目前大家关心的话题:互联网裁员浪潮里,大家会发现一般裁员会先从可替代性的业务性程序员开始,原因很简单,由于日常负责项目大部分都是业务性的,真正有技术实力提升机会非常有限,平时工作繁忙,忽略了提升自己的技术栈。 对此,我想给大家一些过来人的建议,大家除了加班之外,要尽可能多地挤出一些时间来提升自己的技术实力,提升自己在公司的价值度。平衡好工作与生活,关注个人收获与成长,只有自己会得越多,价值越高,才能让你在未来拥有更多的选择与机会,希望我的读者们紧记这一点。以下,enjoy~ Java编程技术点 计算机网络 Java高级技术 经典的设计模式 数据库:MySQL以及 MogoDB等NoSQL 分布式架构涉及的有:分布式redis、分布式session、微服务:dubbo、spring cloud、docker等。 除了上面罗列,我还制作了算法和数据结构、Java并发编程等全套高清架构知识图谱,想要领取完整版,关注【mikechen优知】,私信【架构师知识图谱】。觉得内容不错请点赞支持,更多BAT技术架构+面试真经等干货,查看我的往期博文。 如要转载内容及图片,请注明出处及链接,谢谢。

优秀的个人博客,低调大师

学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?

注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的。要深刻了解这个管道是如何被构建出来的,我们就必须对WebHost和它的创建者WebHostBuilder这个重要的对象具有深刻的理解。[本文已经同步到《ASP.NET Core框架揭秘》之中] 目录 一、WebHost WebHostOptions 构建管道的三个步骤 二、WebHostBuilder WebHost的创建 几个常用的扩展方法 一、WebHost 顾名思义,WebHost被作为Web应用的宿主,应用的启动和关闭都是通过启动或者关闭对应WebHost的方式来实现的。这里所说的WebHost是对所有实现了IWebHost接口的所有类型及其对应对象的统称。IWebHost接口具有如下三个基本成员,其中Start方法用于启动宿主程序。我们编程中通常会调用它的一个扩展方法Run来启动WebHost,实际上背后调用的其实还是这个Start方法。当WebHost启动之后,注册的服务器变开始了针对请求的监听,所以WebHost需要具有与服务器相关的一些特性,这些特性就保存在通过属性ServerFeatures返回的特性集合中。 1: public interface IWebHost : IDisposable 2: { 3: void Start(); 4: IFeatureCollection ServerFeatures { get; } 5: IServiceProvider Services { get; } 6: } 我们多次提到ASP.NET Core管道在构建和进行请求处理过程中广泛使用到了依赖注入。依赖注入只要体现在:ASP.NET Core框架以及应用程序会根据需要注册一系列的服务,这些服务会在WebHost启动的时候被用来创建一个ServiceProvider对象,管道在进行请求处理过程所需的任何服务对象都可以从这个ServiceProvider对象中获取。IWebHost接口的Services属性返回的就是这么一个ServiceProvider对象。 具有如下定义的WebHost类是对IWebHost接口的默认实现,我们默认使用的WebHost就是这么一个对象。一般来说,WebHost是通过对应的WebHostBuilder创建的,当后者通过调用构造函数创建一个WebHost对象的时候,需要提供四个参数,它们分别是直接注册到WebHostBuilder上面的服务(appServices)和由此创建的ServiceProvider(hostingServiceProvider),针对WebHost的选项设置(options)和配置(config)。 1: public class WebHost : IWebHost 2: { 3: public IFeatureCollection ServerFeatures { get; } 4: public IServiceProvider Services { get; } 5: 6: public WebHost( 7: IServiceCollection appServices, 8: IServiceProvider hostingServiceProvider, 9: WebHostOptions options, 10: IConfiguration config); 11: 12: public void Dispose(); 13: public void Start(); 14: } WebHostOptions 顾名思义,一个WebHostOptions对象为构建的WebHost对象提供一些预定义的选项设置。这些选项设置很重要,它们决定由WebHost构建的管道进行内容加载以及异常处理等方面的行为。至于它具体携带着哪些选项设置,我们只需要看看这个类型具有怎样的属性成员。 1: public class WebHostOptions 2: { 3: public string ApplicationName { get; set; } 4: public bool DetailedErrors { get; set; } 5: public bool CaptureStartupErrors { get; set; } 6: public string Environment { get; set; } 7: public string StartupAssembly { get; set; } 8: public string WebRoot { get; set; } 9: public string ContentRootPath { get; set; } 10: 11: public WebHostOptions() 12: public WebHostOptions(IConfiguration configuration) 13: } 如下面的代码片段所示,WebHostOptions具有七个属性成员。这些属性都是可读可写的,我们可以调用默认无参构造函数创建一个空的WebHostOptions对象,通过手工为这些属性赋值的方式来设置对应的选项。除此之外,我们可以将这些选项设置定义在配置中,并利用对应的Configuration对象来创建一个WebHostOptions对象。 构建管道的三个步骤 一般我们开启了作为应用宿主的WebHost,由注册的服务器和中间件构成的整个管道被构建起来,服务器开始绑定到基地址进行请求的监听。接下来我们就来着重聊聊WebHost在开启过程中都做了些什么。总的来说,WebHost的整个开启过程大体上可以分为如下三个步骤: 注册服务:获取Startup对象并利用它完成服务的注册。 中间件注册:利用获取的Startup对象完成中间件的注册。 设置并开启服务器:获取注册到WebHostBuilder上的服务器并为之设置监听地址,最后启动服务器。 接下来我们按照这个步骤定义一个同名的类型来模式真实WebHost的实现逻辑。如下面的代码片段所示,这个模拟的WebHost和真正的WebHost的构造函数具有完全一致的参数列表,我们定义了对应的字段来保存这些参数值。除此之外,我们会创建一个ApplicationLifetime对象并将其注册到提供个ServiceCollection,在WebHost开启和关闭之后我们会利用它发送相应的通知。 1: public class WebHost : IWebHost 2: { 3: private IServiceCollection _appServices; 4: private IServiceProvider _hostingServiceProvider; 5: private WebHostOptions _options; 6: private IConfiguration _config; 7: private ApplicationLifetime _applicationLifetime; 8: 9: public WebHost(IServiceCollection appServices, IServiceProvider hostingServiceProvider, WebHostOptions options, IConfiguration config) 10: { 11: _appServices = appServices; 12: _hostingServiceProvider = hostingServiceProvider; 13: _options = options; 14: _config = config; 15: _applicationLifetime = new ApplicationLifetime(); 16: appServices.AddSingleton<IApplicationLifetime>(_applicationLifetime); 17: } 18: … 19: } 20: 我们接下来看WebHost除Start方法之外的其他成员的定义。只读属性Services返回一个ServiceProvider对象,我们将在完成所有服务注册工作之后利用ServiceCollection对象创建这个对象,所以只要实现具有相关的服务注册,我们就能够利用它得到对应的服务对象。只读属性ServerFeatures返回服务器的特性集合,而服务器本身则直接利用上述这个ServiceProvider获得。当MyWebHost对象因Dispose方法的调用而被回收之后,我们会对ServiceProvider实施回收 工作。在实施回收的前后,我们利用ApplicationLifetime发送相应的信号。 1: public class WebHost : IWebHost 2: { 3: private ApplicationLifetime _applicationLifetime; 4: public IServiceProvider Services { get; private set; } 5: public IFeatureCollection ServerFeatures 6: { 7: get { return this.Services.GetRequiredService<IServer>()?.Features; } 8: } 9: public void Dispose() 10: { 11: _applicationLifetime.StopApplication(); 12: (this.Services as IDisposable)?.Dispose(); 13: _applicationLifetime.NotifyStopped(); 14: } 15: } 16: 真正开启WebHost的实现体现在如下所示的代码片段中。我们直接利用WebHostBuilder提供ServiceProvider获取一个Startup对象,并调用其ConfigureServices方法完成服务的注册,作为参数的ServiceCollection对象也是由WebHostBuilder提供的。当所有的服务注册工作完成之后,我们利用最新的ServiceCollection对象创建一个ServiceProvider对象,并利用此对象对Services属性进行赋值。在后续管道构建过程,以及管道在处理请求过程中所使用的服务均是从这个ServiceProvider中提取的。 1: public class WebHost : IWebHost 2: { 3: private IServiceCollection _appServices; 4: private IServiceProvider _hostingServiceProvider; 5: private WebHostOptions _options; 6: private IConfiguration _config; 7: private ApplicationLifetime _applicationLifetime; 8: 9: public void Start() 10: { 11: //注册服务 12: IStartup startup = _hostingServiceProvider.GetRequiredService<IStartup>(); 13: this.Services = startup.ConfigureServices(_appServices); 14: 15: //注册中间件 16: Action<IApplicationBuilder> configure = startup.Configure; 17: configure = this.Services.GetServices<IStartupFilter>().Reverse().Aggregate(configure, (next, current) => current.Configure(next)); 18: IApplicationBuilder appBuilder = this.Services.GetRequiredService<IApplicationBuilder>(); 19: configure(appBuilder); 20: 21: //为服务器设置监听地址 22: IServer server = this.Services.GetRequiredService<IServer>(); 23: IServerAddressesFeature addressesFeature = server.Features.Get<IServerAddressesFeature>(); 24: if (null != addressesFeature && !addressesFeature.Addresses.Any()) 25: { 26: string addresses = _config["urls"] ?? "http://localhost:5000"; 27: foreach (string address in addresses.Split(';')) 28: { 29: addressesFeature.Addresses.Add(address); 30: } 31: } 32: 33: //启动服务器 34: RequestDelegate application = appBuilder.Build(); 35: ILogger logger = this.Services.GetRequiredService <ILogger<MyWebHost>>(); 36: DiagnosticSource diagnosticSource = this.Services.GetRequiredService<DiagnosticSource>(); 37: IHttpContextFactory httpContextFactory = this.Services.GetRequiredService<IHttpContextFactory>(); 38: server.Start(new HostingApplication(application, logger, diagnosticSource, httpContextFactory)); 39: 40: //对外发送通知 41: _applicationLifetime.NotifyStarted(); 42: } 43: } 44: 当服务注册结束并成功创建出ServiceProvider之后,接下来的工作就是注册中间件了。通过上面的介绍我们知道,中间件的注册既可以利用Startup来完成,也可以利用注册的StartupFilter来实现,为此我们利用最新构建的ServiceProvider获取所有注册的StartupFilter,并结合之前提取的Startup对象创建了一个用于注册中间的委托链(最终体现为一个Action<IApplicationBuilder>对象)。我们最终执行这个委托链完成了对所有中间件的注册,执行过程中作为参数的ApplicationBuilder对象同样是通过ServiceProvider提取出来的。 再此之后,我们利用ServiceProvider提取出注册在WebHostBuiler上的服务器。如果服务器的监听地址尚未指定,我们在开启服务器之前必须指定。通过前面对服务器的介绍,我们知道监听地址保存在服务器的一个名为ServerAddressesFeature的特性中,而用户设置的监听地址则保存在配置中,对应的Key为“urls”,所以我们将从配置中提取的地址列表添加到ServerAddressesFeature特性中。如果监听地址不曾配置,我们会为之指定一个默认的地址,即“http://localhost:5000”。 一切就绪的服务器通过调用Start方法开启,该方法接收一个HttpApplication对象作为参数。通过前面的介绍我们知道这个HttpApplication对象可以视为对所有注册中间件和应用的封装,服务器将接收到的请求传递给它作后续处理。我们默认创建的HttpApplication是一个HostingApplication对象,而构建过程中需要提供四个对象,它们分别是代表中间件链表的RequestDelegate对象,用于日志记录和诊断的Logger和DiagnosticSource,以及用来创建HTTP上下文的HttpContextFactory,除了第一个通过调用ApplicationBuilder的Build方法创建之外,其余的都是通过ServiceProvider提取的。在服务器被成功开启之后,我们利用ApplicationLifetime对外发送应用启动的通知。 二、WebHostBuilder 顾名思义,WebHostBuilder就是WebHost的创建者,所谓的WebHostBuilder是对所有实现了IWebHostBuilder接口的类型以及对应对象的统称。如下面的代码片段所示,IWebHostBuilder接口除了用来创建WebHost的核心方法Build之外,还具有其他一些额外的方法。 1: public interface IWebHostBuilder 2: { 3: IWebHost Build(); 4: IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices); 5: IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory); 6: IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging); 7: string GetSetting(string key); 8: IWebHostBuilder UseSetting(string key, string value); 9: } ConfigureServices方法让我们可以直接将我们所需的服务注册到WebHostBuilder上面。ASP.NET Core具有两种注册服务的途径,一种是将服务注册实现在启动类的ConfigureServices方法中,另一种服务注册的方式就是调用这个方法。对于前者,服务实际上是在开启WebHost的时候调用Startup对象的ConfigureServices进行注册的;至于后者,注册的服务将直接提供给创建的WebHost。UseLoggerFactory 和ConfigureLogging方法与日志记录有关,前者帮助我们设置一个默认的LoggerFactory,后者则对LoggerFactory进行相关设置,最重要的设置就是添加相应的LoggerProvider。GetSetting和UseSetting以键值对的形式获取和设置一些配置。 WebHost的创建 ASP.NET Core定义了一个名为WebHostBuilder的类型作为对IWebHostBuilder接口的默认实现,我们同样采用定义模拟类型的形式来说明WebHostBuilder创建WebHost的实现原理。我们将这个模拟类型命名为,如下的代码片段展示了除Build方法之外的所有成员的定义。 1: public class WebHostBuilder : IWebHostBuilder 2: { 3: private List<Action<ILoggerFactory>> _configureLoggingDelegates = new List<Action<ILoggerFactory>>(); 4: private List<Action<IServiceCollection>> _configureServicesDelegates = new List<Action<IServiceCollection>>(); 5: private ILoggerFactory _loggerFactory = new LoggerFactory(); 6: private IConfiguration _config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build(); 7: 8: public IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging) 9: { 10: _configureLoggingDelegates.Add(configureLogging); 11: return this; 12: } 13: 14: public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices) 15: { 16: _configureServicesDelegates.Add(configureServices); 17: return this; 18: } 19: 20: public string GetSetting(string key) 21: { 22: return _config[key]; 23: } 24: 25: public IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory) 26: { 27: _loggerFactory = loggerFactory; 28: return this; 29: } 30: 31: public IWebHostBuilder UseSetting(string key, string value) 32: { 33: _config[key] = value; 34: return this; 35: } 36: ... 37: } 38: 如上面的代码片段所示,我们创建了一个Configuration类型的字段(_config)来体现应用默认使用的配置,它默认采用环境变量(用于过滤环境变量的前缀为“ASPNETCORE_”)作为配置源,GetSetting和UseSetting方法操作的均为这个对象。另一个字段_loggerFactory表示默认使用的LoggerFactory,UseLoggerFactory方法指定的LoggerFactory用来对这个字段进行赋值。ConfigureLogging和ConfigureServices方法具有类似的定义,调用它们提供的委托对象都保存在一个集合之中,以待后用。 我们实现WebHostBuilder的核心方法Build来创建一个WebHost对象。通过上面的定义我们知道一个WebHostBuilder能够最终运行起来需要从ServiceProvider提供很多必需的服务,而这些服务最初都必需通过WebHostBuilder来注册,所以Build方法除了调用构造函数创建并返回一个WebHost对象之外,余下的工作就是注册这些必需的服务。我们可以简单列一列那些服务是必需的,如下所示的是一个不完全列表。 用于注册服务和中间件的Startup对象。 用来创建Logger的LoggerFactory对象 构建中间件链表的ApplicationBuilder对象 创建HTTP上下文的HttpContextFactory对象 用户实现诊断功能的DiagnosticSource对象 用来保存承载环境的HostingEnvironment对象 如下所示的定义在WebHostBuilder中的Build方法的定义。在这个方法中,我们按照上述这些系统服务以及用户服务(通过调用ConfigureServices方法注册的服务)的注册之后,创建并返回了一个WebHost对象。 1: public class WebHostBuilder : IWebHostBuilder 2: { 3: private List<Action<ILoggerFactory>> _configureLoggingDelegates = new List<Action<ILoggerFactory>>(); 4: private List<Action<IServiceCollection>> _configureServicesDelegates = new List<Action<IServiceCollection>>(); 5: private ILoggerFactory _loggerFactory = new LoggerFactory(); 6: private IConfiguration _config = new ConfigurationBuilder().AddInMemoryCollection().Build(); 7: 8: public IWebHost Build() 9: { 10: //根据配置创建WebHostOptions 11: WebHostOptions options = new WebHostOptions(_config); 12: 13: //注册服务IStartup 14: IServiceCollection services = new ServiceCollection(); 15: if (!string.IsNullOrEmpty(options.StartupAssembly)) 16: { 17: Type startupType = StartupLoader.FindStartupType(options.StartupAssembly, options.Environment); 18: if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType)) 19: { 20: services.AddSingleton(typeof(IStartup), startupType); 21: } 22: else 23: { 24: services.AddSingleton<IStartup>(_ => new ConventionBasedStartup(StartupLoader.LoadMethods(_, startupType, options.Environment))); 25: } 26: } 27: 28: //注册ILoggerFactory 29: foreach (var configureLogging in _configureLoggingDelegates) 30: { 31: configureLogging(_loggerFactory); 32: } 33: services.AddSingleton<ILoggerFactory>(_loggerFactory); 34: 35: //注册服务IApplicationBuilder,DiagnosticSource和IHttpContextFactory 36: services 37: .AddSingleton<IApplicationBuilder>(_ => new ApplicationBuilder(_)) 38: .AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore")) 39: .AddSingleton<IHttpContextFactory, HttpContextFactory>() 40: .AddOptions() 41: .AddLogging() 42: .AddSingleton<IHostingEnvironment, HostingEnvironment>() 43: .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>(); 44: 45: //注册用户调用ConfigureServices方法设置的服务 46: foreach (var configureServices in _configureServicesDelegates) 47: { 48: configureServices(services); 49: } 50: 51: //创建MyWebHost 52: return new WebHost(services, services.BuildServiceProvider(), options, _config); 53: } 54: } 55: 虽然上面提供的WebHost和WebHostBuilder仅仅是WebHost和WebHostBuilder的模拟类。为了让读更加易于理解,我们刻意剔除了很多细节的东西,但是两者从实现原理角度来讲是完全一致的。不仅如此,我们自定义的这两个类型甚至可以执行运行的。 几个常用的扩展方法 WebHostBuilder在内部使用了配置,环境变量是默认采用的配置源,它的两个方法GetSetting和UseSetting以键值对的形式实现对配置项的获取和设置。除了UseSettings方法之外,我们还可以调用WebHostBuilder如下这个扩展方法UseConfiguration来进行配置项的设置,这个方法会将保存在指定Configuration中的配置原封不动地拷贝过来,它最终调用的依旧是UseSettings方法。 1: public static class HostingAbstractionsWebHostBuilderExtensions 2: { 3: public static IWebHostBuilder UseConfiguration(this IWebHostBuilder hostBuilder, IConfiguration configuration); 4: } WebHostBuilder在创建WebHost的时候需要提供一个WebHostOptions对象,该对象最初是根据当前配置创建的。为了方便设置针对WebHostOptions的配置项,ASP.NET Core为我们定义了如下一系列的扩展方法,这些方法最终调用的也是这个UseSettings方法。 1: public static class HostingAbstractionsWebHostBuilderExtensions 2: { 3: public static IWebHostBuilder CaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupErrors); 4: public static IWebHostBuilder UseContentRoot(this IWebHostBuilder hostBuilder, string contentRoot); 5: public static IWebHostBuilder UseEnvironment(this IWebHostBuilder hostBuilder, string environment); 6: public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName); 7: public static IWebHostBuilder UseWebRoot(this IWebHostBuilder hostBuilder, string webRoot); 8: public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls); 9: } 10: 虽然服务器是必需的,但是WebHostBuilder并没有专门定义一个用于注册服务的方法,这是因为服务器也是作为一项基本的服务进行注册的。但是我们可以调用如下一个扩展方法UseServer实现针对服务器的注册,至于另一个扩展方法UseUrls,我们可以调用它来为注册的服务器设置监听地址。 1: public static class HostingAbstractionsWebHostBuilderExtensions 2: { 3: public static IWebHostBuilder UseServer(this IWebHostBuilder hostBuilder, IServer server); 4: public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls); 5: } 作者:蒋金楠 微信公众账号:大内老A 微博: www.weibo.com/artech 如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 原文链接

优秀的个人博客,低调大师

☕【Java技术指南】「Java8技术盲区」在奔向Java13的同时,也让我们仔细研究一下Stream的学习认知!

Java8的功能之最 要说到Java8的技术体系中,最让人难以忘怀的功能,那非Lambda和Stream莫属了。两者结合操作,达成天作之合,有点势不可挡。 它主要用于补充集合类,它的强大,相信用过它的朋友,能明显的感受到,不用使用for循环就能对集合作出很好的操作。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。 这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。 Stream流 Stream流处理的添加是Java 8中的主要新功能之一,要理解这些需要对Java8(lambda表达式、方法引用)有基本的工作知识。 Stream功能介绍 首先,应将Java 8的Stream流与Java原本的 I / O流(例如:FileInputStream等)区分开,两者在本质角度而言是两个东西 简而言之,流是数据源周围的包装器,使我们能够使用该数据源进行操作,并使批量处理方便快捷。 流不存储数据,从这个意义上说,它不是数据结构,它也永远不会修改基础数据源。 此新功能java.util.stream-支持对元素流进行功能样式的操作,例如对集合进行map-reduce转换。 Stream功能定义 Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。 采用Stream API可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。 Stream的功能分析 Stream的操作符从流程操作是否完结大体上分为两种:中间操作符和终止操作符 流程中操作符 对于数据流来说,流程中操作符在执行指定处理程序后,数据流依然可以传递给下一级的操作符。 map(mapToInt,mapToLong,mapToDouble) :转换操作符,把比如A->B,这里默认提供了转int,long,double的操作符。 flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) 拍平操作比如把 int[]{2,3,4} 拍平 变成 2,3,4 也就是从原来的一个数据变成了3个数据,默认提供了拍平成int,long,double的操作符。 limit 限流操作,比如数据流中有10个 我只要出前3个就可以使用。 distint 去重操作,对重复元素去重,底层使用了equals方法。 filter 过滤操作,把不想要的数据过滤。 peek 挑出操作,如果想对数据进行某些操作,如:读取、编辑修改等。 skip 跳过操作,跳过某些元素。 sorted(unordered) 排序操作,对元素排序,前提是实现Comparable接口,当然也可以自定义比较器。 流程终止操作符 数据经过中间加工操作,就轮到终止操作符上场了,终止操作符就是用来对数据进行收集或者消费的,数据到了终止操作这里就不会向下流动了,终止操作符只能使用一次。 collect 收集操作,将所有数据收集起来,这个操作非常重要,官方的提供的Collectors 提供了非常多收集器,可以说Stream 的核心在于Collectors。 count 统计操作,统计最终的数据个数。 findFirst、findAny 查找操作,查找第一个、查找任何一个 返回的类型为Optional。 noneMatch、allMatch、anyMatch 匹配操作,数据流中是否存在符合条件的元素 返回值为bool 值。 min、max 最值操作,需要自定义比较器,返回数据流中最大最小的值。 reduce 规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。 forEach、forEachOrdered 遍历操作,这里就是对最终的数据进行消费了。 toArray 数组操作,将数据流的元素转换成数组。 注意:中间操作符包含8种(排除了parallel,sequential,这两个操作并不涉及到对数据流的加工操作) Stream流的API分析 Stream的创建 通过java.util.Collection.stream()方法用集合创建流 List<String> list = Arrays.asList("a", "b", "c"); // 创建一个顺序流 Stream<String> stream = list.stream(); // 创建一个并行流 Stream<String> parallelStream = list.parallelStream(); 使用java.util.Arrays.stream(T[] array)方法用数组创建流 int[] array={1,3,5,6,8}; IntStream stream = Arrays.stream(array); 使用Stream的静态方法:of()、iterate()、generate() Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6); Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4); stream2.forEach(System.out::println); // 0 3 6 9 Stream<Double> stream3 = Stream.generate(Math::random).limit(3); stream3.forEach(System.out::println); stream和parallelStream的简单区分: 针对于筛选集合中的奇数,两者的处理不同之处: stream是顺序流,由主线程按顺序对流执行操作; parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。 遍历/匹配(foreach/find/match) Stream也是支持类似集合的遍历和匹配元素的,只是Stream中的元素是以Optional类型存在的。Stream的遍历、匹配非常简单。 List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1); // 遍历输出符合条件的元素 list.stream().filter(x -> x > 6).forEach(System.out::println); // 匹配第一个 Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst(); // 匹配任意(适用于并行流) Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).findAny(); // 是否包含符合特定条件的元素 boolean anyMatch = list.stream().anyMatch(x -> x < 6); System.out.println("匹配第一个值:" + findFirst.get()); System.out.println("匹配任意一个值:" + findAny.get()); System.out.println("是否存在大于6的值:" + anyMatch); 筛选(filter) 筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。筛选出Integer集合中大于7的元素,并打印出来 public class StreamTest { public static void main(String[] args) { List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9); Stream<Integer> stream = list.stream(); stream.filter(x -> x > 7).forEach(System.out::println); } } 聚合(max/min/count) max、min、count这些字眼你一定不陌生,没错,在mysql中我们常用它们进行数据统计。Java stream中也引入了这些概念和用法,极大地方便了我们对集合、数组的数据统计工作。 获取String集合中最长的元素。 public class StreamTest { public static void main(String[] args) { List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd"); Optional<String> max = list.stream().max(Comparator.comparing(String::length)); System.out.println("最长的字符串:" + max.get()); } } 输出结果: 最长的字符串:weoujgsd 获取Integer集合中的最大值。 public class StreamTest { public static void main(String[] args) { List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6); // 自然排序 Optional<Integer> max = list.stream().max(Integer::compareTo); // 自定义排序 Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1.compareTo(o2); } }); System.out.println("自然排序的最大值:" + max.get()); System.out.println("自定义排序的最大值:" + max2.get()); } } 计算Integer集合中大于5的元素的个数。 public class StreamTest { public static void main(String[] args) { List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9); long count = list.stream().filter(x -> x > 5).count(); System.out.println("list中大于6的元素个数:" + count); } } 映射(map/flatMap) 映射可以将一个流的元素按照一定的映射规则映射到另一个流中。分为map和flatMap: map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。 英文字符串数组的元素全部改为大写。 String[] strArr = { "abcd", "bcdd", "defde", "fTr" }; List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList()); System.out.println("每个元素大写:" + strList); 整数数组每个元素+3 List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11); List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList()); System.out.println("每个元素+3:" + intListNew); 将两个字符数组合并成一个新的字符数组。 List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7"); List<String> listNew = list.stream().flatMap(s -> { // 将每个元素转换成一个stream String[] split = s.split(","); Stream<String> s2 = Arrays.stream(split); return s2; }).collect(Collectors.toList()); 归并操作(reduce) 归并,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。 List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4); // 求和方式1 Optional<Integer> sum = list.stream().reduce(Integer::sum); // 求和方式2 Optional<Integer> sum2 = list.stream().reduce(Integer::sum); // 求和方式3 Integer sum3 = list.stream().reduce(0, Integer::sum); // 求乘积 Optional<Integer> product = list.stream().reduce((x, y) -> x * y); // 求最大值方式1 Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y); // 求最大值写法2 Integer max2 = list.stream().reduce(1, Integer::max); 统计(count/averaging) Collectors提供了一系列用于数据统计的静态方法: 计数:count 平均值:averagingInt、averagingLong、averagingDouble 最值:maxBy、minBy 求和:summingInt、summingLong、summingDouble 统计以上所有:summarizingInt、summarizingLong、summarizingDouble 案例:统计员工人数、平均工资、工资总额、最高工资。 List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); // 求总数 long count = personList.size(); // 求平均工资 Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary)); // 求最高工资 Optional<Integer> max = personList.stream().map(Person::getSalary).max(Integer::compare); // 求工资之和 int sum = personList.stream().mapToInt(Person::getSalary).sum(); // 一次性统计所有信息 DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary)); System.out.println("员工总数:" + count); System.out.println("员工平均工资:" + average); System.out.println("员工最高工资:" + max.get()); System.out.println("员工工资总和:" + sum); System.out.println("员工工资所有统计:" + collect); 分组(partitioningBy/groupingBy) 分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。 分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。 案例:将员工按薪资是否高于8000分为两部分;将员工按性别和地区分组 List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "Washington")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "New York")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); // 将员工按薪资是否高于8000分组 Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000)); // 将员工按性别分组 Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex)); // 将员工先按性别分组,再按地区分组 Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea))); 接合(joining) joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。 List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "New York")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "Washington")); String names = personList.stream().map(Person::getName).collect(Collectors.joining(",")); System.out.println("所有员工的姓名:" + names); List<String> list = Arrays.asList("A", "B", "C"); String string = list.stream().collect(Collectors.joining("-")); System.out.println("拼接后的字符串:" + string); 排序(sorted) sorted,中间操作。有两种排序: sorted():自然排序,流中元素需实现Comparable接口 sorted(Comparator com):Comparator排序器自定义排序 案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序 List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Sherry", 9000, 24, "female", "New York")); personList.add(new Person("Tom", 8900, 22, "male", "Washington")); personList.add(new Person("Jack", 9000, 25, "male", "Washington")); personList.add(new Person("Lily", 8800, 26, "male", "New York")); personList.add(new Person("Alisa", 9000, 26, "female", "New York")); // 按工资升序排序(自然排序) List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName) .collect(Collectors.toList()); // 按工资倒序排序 List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed()) .map(Person::getName).collect(Collectors.toList()); // 先按工资再按年龄升序排序 List<String> newList3 = personList.stream() .sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName) .collect(Collectors.toList()); // 先按工资再按年龄自定义排序(降序) List<String> newList4 = personList.stream().sorted((p1, p2) -> { if (p1.getSalary() == p2.getSalary()) { return p2.getAge() - p1.getAge(); } else { return p2.getSalary() - p1.getSalary(); } }).map(Person::getName).collect(Collectors.toList()); 提取/组合 流也可以进行合并、去重、限制、跳过等操作。 String[] arr1 = { "a", "b", "c", "d" }; String[] arr2 = { "d", "e", "f", "g" }; Stream<String> stream1 = Stream.of(arr1); Stream<String> stream2 = Stream.of(arr2); // concat:合并两个流 distinct:去重 List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList()); // limit:限制从流中获得前n个数据 List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList()); // skip:跳过前n个数据 List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList()); System.out.println("流合并:" + newList); System.out.println("limit:" + collect); System.out.println("skip:" + collect2); 实际案例操作 遍历集合 日常开发中,我们经常需要需要遍历集合对象中的元素,例如,我们会采用如下方式进行遍历元素,然后过滤出某个字段的集合,当采用 Stream 编程之后,只需要通过一行代码,即可实现: /** * jdk8 从集合对象中获取用户ID集合 * @param userList * @return */ public List<Long> getUserIds(List<User> userList){ List<Long> userIds = userList.stream().map(User::getUserId).collect(Collectors.toList()); return userIds; } 筛选元素 筛选元素,是日常开发中经常会碰到,例如在 jdk8,采用 Stream api,我们只需要通过filter方法来筛选出需要的数据,即可过滤出用户ID不为空的数据。 /** * jdk8 从集合对象中筛选出用户ID不为空的数据 * @param userList * @return */ public List<Long> getUserIds8(List<User> userList){ List<Long> userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toList()); return userIds; } 删除重复的内容 如果你想对返回的集合内容排除重复的数据,操作也很简单,在合并的时候使用Collectors.toSet()即可! /** * jdk8 从集合对象中筛选出用户ID不为空的数据,并进行去重 * @param userList * @return */ public Set<Long> getUserIds(List<User> userList){ Set<Long> userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toSet()); return userIds; } 数据类型转换 在实际的开发过程中,经常会出现数据类型定义不一致的问题,例如有的系统,使用String接受,有的是用Long,对于这种场景,我们需要将其转换,操作也很简单 /** * jdk8 将Long类型数据转换成String类型 * @param userIds * @return */ public List<String> getUserIds10(List<Long> userIds){ List<String> userIdStrs = userIds.stream().map(x -> x.toString()).collect(Collectors.toList()); return userIdStrs; } 数组转集合 我们还会碰到,前端传给我们的是一个数组,但是我们需要转成集合,采用 stream api 操作也很简单! public static void main(String[] args) { //创建一个字符串数组 String[] strArray = new String[]{"a","b","c"}; //转换后的List 属于 java.util.ArrayList 能进行正常的增删查操作 List<String> strList = Stream.of(strArray).collect(Collectors.toList()); } 集合转Map操作 在实际的开发过程中,还有一个使用最频繁的操作就是,将集合元素中某个主键字段作为key,元素作为value,来实现集合转map的需求,这种需求在数据组装方面使用的非常多,尤其是在禁止连表 sql 查询操作的公司,视图数据的拼装只能在代码层面来实现。 例如,下面这段代码,角色表里面关联角色组ID信息,当查询角色信息的时候,需要把角色组名称也展示处理,采用map方式来匹配,效率会非常高。 实际代码案例分享 //角色组ID集合 Set<Long> roleGroupIds = new HashSet<>(); //查询所有的角色信息 List<RoleInfo> dbList = roleInfoMapper.findByPage(request); for (RoleInfo source : dbList) { roleGroupIds.add(source.getRoleGroupId()); RoleInfoDto result = new RoleInfoDto(); BeanUtils.copyProperties(source, result); resultList.add(result); } //查询角色组信息 if (CollectionUtils.isNotEmpty(roleGroupIds)) { List<RoleGroupInfo> roleGroupInfoList = roleGroupInfoMapper.selectByIds(new ArrayList<>(roleGroupIds)); if (CollectionUtils.isNotEmpty(roleGroupInfoList)) { //将List转换成Map,其中id主键作为key,对象作为value Map<Long, RoleGroupInfo> sourceMap = new HashMap<>(); for (RoleGroupInfo roleGroupInfo : roleGroupInfoList) { sourceMap.put(roleGroupInfo.getId(), roleGroupInfo); } //封装角色组名称 for (RoleInfoDto result : resultList) { if (sourceMap.containsKey(result.getRoleGroupId())) { result.setRoleGroupName(sourceMap.get(result.getRoleGroupId()).getName()); } } } } 集合转 map(不分组) 在 jdk8 中,采用 stream api的方式,我们只需要一行代码即可实现,jdk8 将集合转换成Map,其中用户ID作为主键key,如果集合对象有重复的key,以第一个匹配到的为主 public Map<Long, User> getMap(List<User> userList){ Map<Long, User> userMap = userList.stream().collect(Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1)); return userMap; } 打开Collectors.toMap方法源码,一起来看看到底是啥。 public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); } 参数表可以看出: 第一个参数:表示 key 第二个参数:表示 value 第三个参数:表示某种规则 上文中的Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1),表达的意思就是: userId的内容作为key v -> v是表示将元素user作为value。 (k1,k2) -> k1表示如果存在相同的key,将第一个匹配的元素作为内容。 集合转map(分组) 实际的操作中,有一些场景需要我们将相同的key,加入到一个集合,而不是覆盖,哪改如何做呢?而在 jdk8 中,采用 stream api的方式,我们只需要一行代码即可实现。 /** * jdk8 将集合转换成Map,将相同的key,加入到一个集合中,实现分组 * @param userList * @return */ public Map<Long, List<User>> getMapGroup(List<User> userList){ Map<Long, List<User>> userMap = userList.stream().collect(Collectors.groupingBy(User::getUserId)); return userMap; } 分页操作 stream api的强大之处还不仅仅是对集合进行各种组合操作,还支持分页操作。 例如,将如下的数组从小到大进行排序,排序完成之后,从第1行开始,查询10条数据出来,操作如下: //需要查询的数据 List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5,10, 6, 20, 30, 40, 50, 60, 100); List<Integer> dataList= numbers.stream().sorted((x, y) -> x.compareTo(y)).skip(0).limit(10).collect(Collectors.toList()); System.out.println(dataList.toString()); 其中skip参数表示第几行,limit表示查询的数量,类似页容量! 查找与匹配操作 stream api 还支持对集合进行查找,同时还支持正则匹配模式。 allMatch(检查是否匹配所有元素) 是否全部元素都大于2 List<Integer> list = Arrays.asList(10, 5, 7, 3); boolean allMatch = list.stream() .allMatch(x -> x > 2); System.out.println(allMatch); findFirst(返回第一个元素) 获取第一个元素 List<Integer> list = Arrays.asList(10, 5, 7, 3); Optional<Integer> first = list.stream() .findFirst(); Integer val = first.get(); System.out.println(val);//输出10 (可以将流中元素反复结合起来,得到一个值) List<Integer> list = Arrays.asList(10, 5, 7, 3); Integer result = list.stream() .reduce(2, Integer::sum); System.out.println(result);//输出27,其实相当于2+10+5+7+3,就是一个累加 stream api 支持的操作方法非常多,这里只列举了几种类型,具体在使用的时候,可以参考官网接口文档说明!

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册