作者归档:admin

技术人员该如何站好最后一班岗?

Vue数据双向绑定原理

挑槽、入槽、跳槽,堪称每个技术人员必奏的三部曲,而这三部曲在职场中来回奏,便构成了程序人生。

铁打的硬盘,流水的码农,离职时见人品,作为技术人员该如何做交接,到底该如何站好最后一班岗呢?

 

1. 人品不够,文档来凑。

 

从上家公司离职已经 5 年多啦,记得离职大概没多久,前技术同事微信告诉我:你写的交接文档,在会议上公开表扬,让其它组作为参考。

当时个人感觉没啥,就是写了一堆文档罢了,近期看到其它团队交接的效果,那么的不尽人意。而且秉着吐露真心,认真分享的原则,不妨把那些年写过的交接文档,逐一呈现给你,万一能助你积攒人品、升职加薪呢?

1.1. 作为技术人员离职前的交接,编写交接进展表为了谁?

离职前的交接,非常能展现人品,最重要的原则是:交接时一定要尽力而为。

尽量能打造属于自己的交接计划,按照计划一步一步去落实,并把交接进展维护在 excel 中,如图中的《交接进展表.xlsx》。

编写交接进展表,一方面让大家明确知晓交接的过程与进度,另一方面可供后人按此方式进行无脑式交接(前人栽树后人乘凉)。

1.2. 作为技术人员离职前的交接,编写XX系统_新手入门文档为了谁?

离职前的交接,希望都能编写新手入门之类的傻瓜式文档,该文档编写是个一劳永逸的事情。

倘若后续接手你的是一个新同事,那么就更有价值,按照入门文档,一步一步就能上手开发、提测、上线,这样的文档谁不喜欢?

仅以上面截图为例,新手入门文档中包含了系统的简要说明、功能说明以及功能模块划分,可以让接手的同事对系统有一个全局的认识。

当然,最重要的是要告诉要接手的同事,如何去干活?文档中的应用目录结构介绍以及如何开发、如何编译、如何提测、如何上线,这几大块就显得很重要。

另外,站在团队培养人的成本而言,新手入门文档,不仅仅是为了做好交接,倘若项目组一直就具备该文档,能够让新手快速上手开发业务需求,大概率会降低团队沟通、培训的成本。

1.3. 作为技术人员离职前的交接,编写XX系统_开发生产部署文档为了谁?

离职前的交接,当接手的同事能够按照入门文档开发需求之后,更一步的就是要了解开发、测试、生产部署环境相关的信息。

 

仅以上面截图为例,开发及生产部署文档,也可以理解成环境相关的文档。其主要目的是汇总开发、生产环境部署的机器、应用部署的位置及应用该如何访问的关键信息,让接手的同事,能够清楚当需求开发完成时,应用应该如何部署。

1.4. 作为技术人员离职前的交接,编写XX系统_业务支撑文档为了谁?

离职前的交接,当接手的同事了解完如何入门开发、开发生产部署环境,接下来就要花大量的时间,去了解支撑的业务。

鉴于支撑的业务会较多,作为接手的同事梳理起来会比较头疼,那么一个清晰的文档索引就很重要。

业务支撑文档就是把 SVN 或者 Git 上的产品相关的资料,分门别类把路径整理到文档中,以便接手的同事查阅,以便进行快速深入。

1.5. 作为技术人员离职前的交接,编写XX系统_经验汇总文档为了谁?

离职前的交接,最重要的是分享前车之鉴,对于要接手的同事而言少走弯路,避免再掉坑,绝对是一笔财富。

仅以上面截图为例,系统的经验汇总文档,主要记录平时该注意的事项、项目团队中以往遇到那些坑,以及如何把坑填平的。

有了经验汇总文档,无论是接手的同事,还是新招的同事,再去做需求开发,相信同样问题出错的概率应该会大幅降低。

不过该经验文档离不开一个长期积累的过程,所以程序员要养成一个善于记录的习惯。

 

曹工改bug:centos下,mongodb开机不能自启动,systemctl、rc.local都试了,还是不行,要不要放弃?

2. 人品不够,分享来凑。

 

离职前的交接,梳理文档是一方面,隔三差五的组织分享也是必不可少的环节。

如上面截图所示,主要包含生产部署相关以及业务支撑相关,目的就是把重要的信息,以培训会议的形式再次同步给大家,让团队中的每个人都做到心中有数。

当然,鉴于分享会耗费大家的时间,所以要提前准备好要分享的重要信息,合理安排时间去完成分享。

 

3. 离职之后,保持藕断丝连。

 

如果你之前负责的是重要项目,即使交接做的很成功,但是之前的老同事,偶尔还会给你打电话咨询项目的事情,至少会持续一个月甚至更长。

那么请不要悲伤、愤怒,换个角度去思考,前同事有问题能想到你,说明你在他们心中还是有分量的,或许他们认为你了解的比较透彻,知道问题的解决方案,能够快速帮其解决问题。

 

今日留情面,他日好相见。

 

互联网的圈子真的很小,说不定哪一天又在下一家公司相见啦,所以一定要留有情面,把事情交接好,把最后一班岗站好。

最后一班岗站好,大家心中都有你,有机会就会向你抛橄榄枝。

 

曾经的那些人儿,那些事儿。

 

橄榄枝一:发生在 3 年前,上家公司的技术总监去了知名网购平台,电话问我能不能把简历发来,是否愿意来承担一些事儿?当我接到电话时,瞬间诧异,技术总监都拿到我手机号啦。

橄榄枝二:发生在去年,上家公司的某位高级经理被挖去了新的公司,担任 CTO 职位,由于业务扩展,多次问我是不是可以一起搞一搞?

 

估计很多人都接过橄榄枝,聊橄榄枝不是为了装 B,只是想反思一下橄榄枝背后,是不是和之前交接的过程有点关系呢?

 

4. 铁打的硬盘,流水的码农。

 

作为技术人员请不要:这个我交接给他啦,你直接去找他吧!

作为技术人员请不要:恶意制造交接困难,让交接难上加难!

作为技术人员请做到:站好最后一班岗,今日留情面,他日好相见。

 

本次主要分享了之前交接时的思路以及写过的一些文档,如果感觉有一丝参考价值,那请拿去在团队中实践,沉淀下来的都是财富。

好了,分享就到这里,希望对你有帮助。一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。欢迎关注「一猿小讲」,会持续输出原创精彩分享,敬请期待!

技术人员该如何站好最后一班岗?
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

Api接口签名验证

面向对象存储框架:Obase快速入门

技术人员该如何站好最后一班岗?

在项目中完成对象建模后,可以使用Obase来进行对象的管理(例如对象持久化),本篇教程将创建一个.NET Core控制台应用,来展示Obase的配置和对象的增删改查操作。本篇教程旨在指引简单入门。
本篇教程将以此对象模型展开

class Blog{
    +BlogId:int[文章Id]
    +Url:string[文章地址]
    +Post:sList<Post>[文章评论]
}

class Post{
    +PostId:int[评论Id]
    +Title:string[评论标题]
    +Content:string[评论内容]
    +Blog:Blog[关联文章]
}

Blog "1"-right-"*"  Post
hide empty member

从NuGet安装Obase

项目搭建

  • 打开 Visual Studio
  • 单击“创建新项目”
  • 选择带有 C# 标记的“控制台应用 (.NET Core)” ,然后单击“下一步”
  • 输入“ObaseTutorial” 作为名称,然后单击“创建”
  • 添加对freep.Obase.dll的引用

定义领域实体类

	/// <summary>
    /// 文章
    /// </summary>
    public class Blog
    {
        private int blogId;
        private string url;
        private List<Post> posts;

        /// <summary>
        /// 文章Id
        /// </summary>
        public int BlogId { get => blogId; set => blogId = value; }
        /// <summary>
        /// 文章地址
        /// </summary>
        public string Url { get => url; set => url = value; }
       /// <summary>
       /// 文章评论(注意:关联引用属性需要定义为virtual)
       /// </summary>
        public virtual List<Post> Posts { get => posts; set => posts = value; }
    }

   /// <summary>
    /// 文章评论
    /// </summary>
    public class Post
    {
        private int postId;
        private string title;
        private string content;
        private int blogId;
        private Blog blog;

        /// <summary>
        /// 评论Id
        /// </summary>
        public int PostId { get => postId; set => postId = value; }
        /// <summary>
        /// 评论标题
        /// </summary>
        public string Title { get => title; set => title = value; }
        /// <summary>
        /// 评论内容
        /// </summary>
        public string Content { get => content; set => content = value; }
        /// <summary>
        /// 文章Id
        /// </summary>
        public int BlogId { get => blogId; set => blogId = value; }
        /// <summary>
        /// 关联文章(注意:关联引用属性需要定义为virtual)
        /// </summary>
        public virtual Blog Blog { get => blog; set => blog = value; }
    }

自定义对象上下文

Obase直接与应用程序进行交互的便是ObectContext(对象上下文),项目中可以根据具体情况定义一个或者多个继承于ObjectContext的自定义对象上下文。

Vue数据双向绑定原理

	using freep.Obase;
	using freep.Obase.ExecuteSql;
	using freep.Obase.Odm.Builder;

	/// <summary>
    /// 自定义对象上下文
    /// </summary>
    public class MyContext : ObjectContext
    {
        /// <summary>
        /// 构造函数
        /// </summary>
        public MyContext() : base("user=root;password=;server=localhost;database=ObaseTutorial;SslMode = none;port=3306;", true)
        {
        }
    }

注意:自定义对象上下文通过继承父类的构造函数设置数据源连接字符串(此处为了演示方便,直接将连接字符串作为参数进行传递,实际项目中可以定义到配置文件中)。

配置对象模型

在对象数据模型生成之前,可以对数据源的类型进行设置,以及对象数据模型的配置,配置的类型包括实体类型,关联类型,关联引用,关联端,属性等的配置,本篇只展示最基本的实体类型,关联类型,关联引用的配置。

    /// <summary>
    /// 自定义对象上下文
    /// </summary>
    public class MyContext : ObjectContext
    {
		/// <summary>
        /// 在即将生成对象数据模型并注册到对象上下文之前调用此方法
        /// </summary>
        /// <param name="modelBuilder">建模器</param>
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            //设置模型映射目标源的类型(默认不设置未SQLServer)
            modelBuilder.HasTargetSourceType(eDataSource.MySql);
            //配置对象数据模型
            this.ModelConfiguratoin(modelBuilder);
            base.OnModelCreating(modelBuilder);
        }
        
        /// <summary>
        /// 配置对象数据模型
        /// </summary>
        /// <param name="modelBuilder">建模器</param>
        protected virtual void ModelConfiguratoin(ModelBuilder modelBuilder)
        {
        	//配置实体型
            var blogCfg = modelBuilder.Entity<Blog>();
            //设置实体型的映射数据表
            blogCfg.ToTable("Blog");
            //设置实体型的标识属性
            blogCfg.HasKeyAttribute(p => p.BlogId);
            //设置实体型的标识属性为自增
            blogCfg.HasKeyIsSelfIncreased(true);

            //配置实体型
            var postCfg = modelBuilder.Entity<Post>();
            //设置实体型的映射数据表
            postCfg.ToTable("Post");
            //设置实体型的标识属性
            postCfg.HasKeyAttribute(p => p.PostId);
            //设置实体型的标识属性为自增
            postCfg.HasKeyIsSelfIncreased(true);

            //配置对象间隐式关联类型
            var blogAssPostCfg = modelBuilder.Association<Blog, Post>();
            //设置关联类型的映射数据表
            blogAssPostCfg.ToTable("Post");
            //设置关联映射端1(参照方)的键属性以及在关联表中映射的字段
            blogAssPostCfg.AssociationEnd<Blog>("End1").HasMapping("BlogId", "BlogId");
            //设置关联映射端2(被参照方)的键属性以及在关联表中映射的字段
            //注意:HasDefaultAsNew方法设置一个值,该值指示是否把关联端对象默认视为新对象。当该属性为true时,如果关联端对象未被显式附加到上下文,该对象将被视为新对象实施持久化。
            blogAssPostCfg.AssociationEnd<Post>("End2").HasMapping("PostId", "PostId").HasDefaultAsNew(true);

            //配置实体类型的关联引用属性
            //参数一:关联引用属性的名称 参数二:关联引用是否具有多重性
            //注:此处在配置Blog实体与Post实体关联引用属性Posts
            var blogRefPosts = blogCfg.AssociationReference<Blog, Post>("Posts", true);
            //设置关联引用的本端
            blogRefPosts.HasLeftEnd("End1");
            //设置关联引用的对端
            blogRefPosts.HasRightEnd("End2");
            //设置关联引用属性延迟加载
            blogRefPosts.HasEnableLazyLoading(true);

            //配置实体类型的关联引用属性
            //参数一:关联引用属性的名称 参数二:关联引用是否具有多重性
            //注:此处在配置Post实体与Blog实体关联引用属性Blog
            var postRefBlog = postCfg.AssociationReference<Blog, Post>("Blog", false);
            //设置关联引用的本端(注意此处Post是作为本端的)
            postRefBlog.HasLeftEnd("End2");
            //设置关联引用的对端
            postRefBlog.HasRightEnd("End1");
        }
	}

定义对象集

最终对对象的操作和访问是通过对象上下文提供的对象集,此处我们定义文章和文章评论对象集:

    /// <summary>
    /// 自定义对象上下文
    /// </summary>
    public class MyContext : ObjectContext
    {
        /// <summary>
        /// 文章对象集
        /// </summary>
        public ObjectSet<Blog> Blogs { get; set; }

        /// <summary>
        /// 文章评论对象集  
        /// </summary>
        public ObjectSet<Post> Posts { get; set; }
    }

对象的创建、读取、更新和删除

实例化对象上下文
var myContext = new MyContext();
创建
//实例化对象
Blog blog = new Blog()
{
    Url = "https://www.yuque.com/geekfish/obase/getting-started",
    Posts = new List<Post>() {
        new Post (){  Title= "请问Obase怎么安装?", Content = "暂时只提供dll文件"}
    }
};
//将对象附加到对象上下文
myContext.Blogs.Attach(blog);
//将对象保存到数据源
myContext.SaveChanges();
读取
using System.Linq;

//从持久化源查询数据
Blog firstBlog = myContext.Blogs.OrderBy(p => p.Url).First();
//访问关联引用属性
List<Post> posts = firstBlog.Posts;
更新
 //修改属性
firstBlog.Url = "http://www.test.com/aa.html";
//将对象保存到数据源
myContext.SaveChanges();
删除
//删除指定对象
myContext.Blogs.Remove(firstBlog);
//根据条件删除指定对象
myContext.Blogs.Delete(p => p.BlogId == 1);
//将对象保存到数据源(只有在保存后,数据才真实删除)
myContext.SaveChanges();

面向对象存储框架:Obase快速入门
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

曹工改bug:centos下,mongodb开机不能自启动,systemctl、rc.local都试了,还是不行,要不要放弃?

厉害了!除了find命令,还有这么多文件查找命令,高手必备!

因为我最近忙、所以我写了它

大家好,我是良许。

在系统里查找文件,是所有工程师都必备的技能(不管你用的是 Windows 、Linux、还是 MacOS 系统)。对于 Linux 操作系统,单单一个 find 命令就可以完成非常多的搜索工作。

但是,文件搜索命令远不止一个 find 命令,还有很多。本文就对 Linux 下文件搜索命令进行一个科普,让你能够在短时间内找到自己需要的文件。

1. find

find 命令应该是最经典的命令了,谈到搜索工具第一个想到的肯定是 find 命令。但是,find 命令非常强大,想要把它的功能都介绍一遍,恐怕要写好几篇文章。

所以,这里就偷个懒,介绍最基本的,根据文件名查找文件的方法。假如我们想搜索当前目录(及其子目录)下所有 .sh 文件,可以这样搜索:

2. locate

locate 是另外一个根据文件名来搜索文件的命令。区别于 find 命令,locate 命令无需指定路径,直接搜索即可。

这个命令不是直接去系统的各个角落搜索文件,而是在一个叫 mlocate.db 的数据库下搜索。这个数据库位于 /var/lib/mlocate/mlocate.db ,它包含了系统里所有文件的索引,并且会在每天早上的时候由 cron 工具自动更新一次。

正因为如此,locate 的搜索速度远快于 find 命令,因为它直接在数据库里检索,速度自然更快。

locate 命令在找到文件之后,将直接显示该文件的绝对路径,比如:

但是 locate 命令有个弊端,它无法搜索当天所创建的文件,因为它的数据库一天只在早上更新一次。比如我现在创建一个新文件,locate 没办法搜索到:

为了解决这个问题,我们可以使用 updatedb 命令手动去更新它的数据库:

协议生成器工具

$ sudo updadb

然后,我们就可以搜索到新文件了。

3. which

which 命令主要用来查找可执行文件的位置,它搜索的位置指定在 $PATH$MANPATH 环境变量下的值,默认情况下,which 命令将显示可执行文件的第一个存储位置:

如果某个可执行文件存储在多个位置,可以使用 -a 选项列出所有的位置。

如果你想一次性查找多个文件,可以直接跟在 which 命令后面即可。

4. whereis

whereis 命令会在系统默认安装目录(一般是有root权限时默认安装的软件)查找二进制文件、源码、文档中包含给定查询关键词的文件。(默认目录有 /bin, /sbin, /usr/bin, /usr/lib, /usr/local/man等类似路径)。

一般包含以下三部分内容:

  • 二进制文件的路径
  • 二进制文件的源码路径
  • 对应 man 文件的路径

比如我们现在搜索 ls 命令:

我们可以使用 -b 选项来只搜索可执行文件所在位置,使用 -B 选项指定搜索位置,使用 -f 选项列出文件的信息。

同样地,我们可以使用 -s 限定只搜索源码路径,使用 -m 搜索 man page 路径,使用 -s 指定搜索源代码文件的路径,使用 -M 指定搜索帮助文件的路径。

公众号:良许Linux

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

厉害了!除了find命令,还有这么多文件查找命令,高手必备!
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

对抗代码遗忘的思考——提问回顾与个人总结

【Flutter实战】图片组件及四大案例

使用Apache commons email发送邮件

老孟导读:大家好,这是【Flutter实战】系列文章的第三篇,这一篇讲解图片组件,Image有很多高级用法,希望对您有所帮助。

图片组件是Flutter基础组件之一,和文本组件一样必不可少。图片组件包含Image和Icon两个组件,本质上Icon不属于图片组件,但其外形效果上类似于图片。

在项目中建议优先使用Icon组件,Icon本质上是一种字体,只不过显示的不是文字,而是图标,而Image组件先通过图片解码器将图片解码,所以Icon有如下优点:

  • 通常情况下,图标比图片体积更小,显著的减少App包体积。
  • 图标不会出现失真或者模糊的现象,例如将20×20的图片,渲染在200×200的屏幕上,图片会失真或模糊,而图标是矢量图,不会失真,就像字体一样。
  • 多个图标可以存放在一个文件中,方便管理。
  • 全平台通用。

Image

Image组件用于显示图片,图片的来源可以是网络、项目中图片或者设备上的图片。

加载网络图片:

Image.network(
  'http://pic1.win4000.com/pic/c/cf/cdc983699c.jpg',
)

加载项目中图片:

首先将图片拷贝到项目中,通常情况下,拷贝到assets/images/目录下,assets/images/目录为手动创建,新建的项目默认是没有此目录的。

设置pubspec.yaml配置文件:

assets:
  - assets/images/

或者指定具体图片的名称:

assets:
  - assets/images/aa.jpg

通常情况下,使用第一种方式,因为图片会有很多张,增加一张就这里配置一个太麻烦。

注意:assets前面的空格问题,极容易引发编译异常,正确格式如下:

加载图片:

Image.asset('assets/images/aa.jpg')

加载设备上的图片:

要加载设备(手机)上的图片首先需要获取设备图片的路径,由于不同平台的路径不同,因此路径的获取必须依靠原生支持,如果了解原生(Android和iOS)开发,可以直接使用MethodChannel获取路径,如果不懂原生(Android和iOS)开发,可以使用第三方插件获取路径,这里推荐官方的path_provider

加载设备上的图片:

Image.file(File('path'))

设置图片的大小:

Image.asset('assets/images/aa.jpg',width: 100,height: 200,),

当Image的大小和图片大小不匹配时,需要设置填充模式fit,设置组件大小为150×150,

Container(
  color: Colors.red.withOpacity(.3),
  child: Image.asset('assets/images/aa.jpg',width: 150,height: 150),
)

看到,图片左右两边有空白区域(浅红色填充的区域),如果想要图片充满整个区域,设置如下:

Container(
  color: Colors.red.withOpacity(.3),
  child: Image.asset('assets/images/aa.jpg',width: 150,height: 150,fit: BoxFit.fill,),
)

虽然图片充满整个区域,但图片变形了,使图片等比拉伸,直到两边都充满区域:

Container(
  color: Colors.red.withOpacity(.3),
  child: Image.asset('assets/images/aa.jpg',width: 150,height: 150,fit: BoxFit.cover,),
)

此时,图片未变形且两边都充满区域,不过图片被裁减了一部分。

fit参数就是设置填充方式,其值介绍如下:

  • fill:完全填充,宽高比可能会变。
  • contain:等比拉伸,直到一边填充满。
  • cover:等比拉伸,直到2边都填充满,此时一边可能超出范围。
  • fitWidth:等比拉伸,宽填充满。
  • fitHeight:等比拉伸,高填充满。
  • none:当组件比图片小时,不拉伸,超出范围截取。
  • scaleDown:当组件比图片小时,图片等比缩小,效果和contain一样。

BoxFit.none的裁减和alignment相关,默认居中,

Image.asset(
  'assets/images/aa.jpg',
  width: 150,
  height: 150,
  fit: BoxFit.none,
  alignment: Alignment.centerRight,
),

左边为原图。

设置对齐方式:

Container(
  color: Colors.red.withOpacity(.3),
  child: Image.asset(
    'assets/images/aa.jpg',
    width: 150,
    height: 150,
    alignment: Alignment.centerLeft,
  ),
),

colorcolorBlendMode用于将颜色和图片进行颜色混合,colorBlendMode表示混合模式,下面介绍的混合模式比较多,浏览一遍即可,此属性可以用于简单的滤镜效果。

  • clear:清楚源图像和目标图像。
  • color:获取源图像的色相和饱和度以及目标图像的光度。
  • colorBurn:将目标的倒数除以源,然后将结果倒数。
  • colorDodge:将目标除以源的倒数。
  • darken:通过从每个颜色通道中选择最小值来合成源图像和目标图像。
  • difference:从每个通道的较大值中减去较小的值。合成黑色没有效果。合成白色会使另一张图像的颜色反转。
  • dst:仅绘制目标图像。
  • dstATop:将目标图像合成到源图像上,但仅在与源图像重叠的位置合成。
  • dstIn:显示目标图像,但仅显示两个图像重叠的位置。不渲染源图像,仅将其视为蒙版。源的颜色通道将被忽略,只有不透明度才起作用。
  • dstOut:显示目标图像,但仅显示两个图像不重叠的位置。不渲染源图像,仅将其视为蒙版。源的颜色通道将被忽略,只有不透明度才起作用。
  • dstOver:将源图像合成到目标图像下。
  • exclusion:从两个图像的总和中减去两个图像的乘积的两倍。
  • hardLight:调整源图像和目标图像的成分以使其适合源图像之后,将它们相乘。
  • hue:获取源图像的色相,以及目标图像的饱和度和光度。
  • lighten:通过从每个颜色通道中选择最大值来合成源图像和目标图像。
  • luminosity:获取源图像的亮度,以及目标图像的色相和饱和度。
  • modulate:将源图像和目标图像的颜色分量相乘。
  • multiply:将源图像和目标图像的分量相乘,包括alpha通道。
  • overlay:调整源图像和目标图像的分量以使其适合目标后,将它们相乘。
  • plus:对源图像和目标图像的组成部分求和。
  • saturation:获取源图像的饱和度以及目标图像的色相和亮度。
  • screen:将源图像和目标图像的分量的逆值相乘,然后对结果求逆。
  • softLight:对于低于0.5的源值使用colorDodge,对于高于0.5的源值使用colorBurn。
  • src:放置目标图像,仅绘制源图像。
  • srcATop:将源图像合成到目标图像上,但仅在与目标图像重叠的位置合成。
  • srcIn:显示源图像,但仅显示两个图像重叠的位置。目标图像未渲染,仅被视为蒙版。目标的颜色通道将被忽略,只有不透明度才起作用。
  • srcOut:显示源图像,但仅显示两个图像不重叠的位置。
  • srcOver:将源图像合成到目标图像上。
  • xor:将按位异或运算符应用于源图像和目标图像。

是不是感觉看了和没看差不多,看了也看不懂。正常,估计只有学过视觉算法的才能看懂吧,直接看下各个属性的效果吧:

repeat表示当组件有空余位置时,将会重复显示图片

Image.asset(
  'assets/images/aa.jpg',
  width: double.infinity,
  height: 150,
  repeat: ImageRepeat.repeatX,
)

重复的模式有:

  • repeat:x,y方向都充满。
  • repeatX:x方向充满。
  • repeatY:y方向充满。
  • noRepeat:不重复。

matchTextDirection设置为true时,图片的绘制方向为TextDirection设置的方向,其父组件必须为Directionality

Directionality(
    textDirection: TextDirection.rtl,
    child: Image.asset(
      'assets/images/logo.png',
      height: 150,
      matchTextDirection: true,
    )),

左边为原图,效果是左右镜像。

filterQuality表示绘制图像的质量,从高到低为:high->medium->low->none。越高效果越好,越平滑,当然性能损耗越大,默认是low,如果发现图片有锯齿,可以设置此参数。

[源码解析] GroupReduce,GroupCombine 和 Flink SQL group by

当加载图片的时候回调frameBuilder,当此参数为null时,此控件将会在图片加载完成后显示,未加载完成时显示空白,尤其在加载网络图片时会更明显。因此此参数可以用于处理图片加载时显示占位图片和加载图片的过渡效果,比如淡入淡出效果。

下面的案例是淡入淡出效果:

Image.network(
  'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
  frameBuilder: (BuildContext context, Widget child, int frame,
      bool wasSynchronouslyLoaded) {
    if (wasSynchronouslyLoaded) {
      return child;
    }
    return AnimatedOpacity(
      child: child,
      opacity: frame == null ? 0 : 1,
      duration: const Duration(seconds: 2),
      curve: Curves.easeOut,
    );
  },
)

loadingBuilder参数比frameBuilder控制的力度更细,可以获取图片加载的进度,下面的案例显示了加载进度条:

Image.network(
    'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
    loadingBuilder: (BuildContext context, Widget child,
        ImageChunkEvent loadingProgress) {
  if (loadingProgress == null) {
    return child;
  }
  return Center(
    child: CircularProgressIndicator(
      value: loadingProgress.expectedTotalBytes != null
          ? loadingProgress.cumulativeBytesLoaded /
              loadingProgress.expectedTotalBytes
          : null,
    ),
  );
})

centerSlice用于.9图,.9图用于拉伸图片的特定区域,centerSlice设置的区域(Rect)就是拉伸的区域。.9图通常用于控件大小、宽高比不固定的场景,比如聊天背景图片等。

Container(
    width: 250,
    height: 300,
    decoration: BoxDecoration(
        image: DecorationImage(
            centerSlice: Rect.fromLTWH(20, 20, 10, 10),
            image: AssetImage(
              'assets/images/abc.jpg',
            ),
            fit: BoxFit.fill))),

上面为原图,下面为拉伸的图片。

在使用时大概率会出现如下异常:

这是由于图片比组件的尺寸大,如果使用centerSlice属性,图片必须比组件的尺寸小,一般情况下,.9图的尺寸都非常小。

Icon

Icon是图标组件,Icon不具有交互属性,如果想要交互,可以使用IconButton。

Icon(Icons.add),

设置图标的大小和颜色:

Icon(
  Icons.add,
  size: 40,
  color: Colors.red,
)

上面的黑色为默认大小和颜色。

Icons.add是系统提供的图标,创建Flutter项目的时候,pubspec.yaml中默认有如下配置:

所有的图标在Icons中已经定义,可以直接在源代码中查看,也可以到官网查看所有图标

所有图标效果如下:

案例

聊天背景(.9图实现)

Container(
  width: 200,
  padding: EdgeInsets.only(left: 8,top: 8,right: 20,bottom: 8),
  decoration: BoxDecoration(
      image: DecorationImage(
          centerSlice: Rect.fromLTWH(20, 20, 1, 1),
          image: AssetImage(
            'assets/images/chat.png',
          ),
          fit: BoxFit.fill)),
  child: Text('老孟,专注分享Flutter技术和应用实战。'
      '老孟,专注分享Flutter技术和应用实战。'
      '老孟,专注分享Flutter技术和应用实战。',),
)

背景图片大小是57×80:

右侧三角已经不在中间了,如果想让其一直保持居中,修改拉伸区域:

centerSlice: Rect.fromLTWH(20, 10, 1, 60),

圆形带边框的头像

Container(
  width: 100,
  height: 100,
  padding: EdgeInsets.all(3),
  decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.blue),
  child: Container(
    decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: DecorationImage(
            image: AssetImage('assets/images/aa.jpg'), fit: BoxFit.cover)),
  ),
)

图片占位符:

Image.network(
  'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
  height: 150,
  width: 150,
  fit: BoxFit.cover,
  frameBuilder: (
    BuildContext context,
    Widget child,
    int frame,
    bool wasSynchronouslyLoaded,
  ) {
    if (frame == null) {
      return Image.asset(
        'assets/images/place.png',
        height: 150,
        width: 150,
        fit: BoxFit.cover,
      );
    }
    return child;
  },
)

添加自己的图标库

如果系统提供的图标没有我们想要的图标,这时需要引入第三方库的图标,下面以阿里巴巴的图标库为例。

打开阿里巴巴的图标官网,找到自己想要的图标后,将鼠标放置到图标上,加入购物车:

点击右上角的购物车,然后点击添加至项目:

如果没有添加过项目,需要创建一个新项目:

创建好后加入此项目,跳转到我的项目页面,点击下载:

解压下载的文件,解压出来的文件有好几个,如下图:

选择iconfont.ttf文件拷贝到 Flutter 项目的assets/fonts目录下,assets/fonts目录默认是没有的,需要手动创建,在pubspec.yaml设置如下:

千万注意红框内开头的空格问题,否则编译不通过,family后面跟的字符串最好有意义,后面用图标的时候需要用到。

用法如下:

Icon(IconData(0xe613,fontFamily: 'appIconFonts')

0xe613在下载图标时已经标注,将&#替换为0,如下图:

fontFamily是在pubspec.yaml中设置的family属性,第三方的图标和系统图标一样,可以设置其颜色和大小。

交流

老孟Flutter博客地址(330个控件用法):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

【Flutter实战】图片组件及四大案例
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

涨姿势了解一下Kafka消费位移可好?

mingw32 exception在sjlj与dwarf差别-反汇编分析

【Flutter实战】图片组件及四大案例

sjlj (setjump/longjump)与dwarf-2为mingw32两种异常处理模型的实现。sjlj有着开销,而随linux发行的mingw32开发库包都是用sjlj版编译的,而Qt却采用dwarf-2版,那么两者之间有多少差异,本文就这问题对两版的异常代码的反汇编进行分析比较。

我使用mingw-w65-i686-810的sjlj与dwarf-2两个版本对下面异常代码编译。

__attribute__((dllimport)) int dllfunc();
int main()
{
    dllfunc();
    //_create_locale(LC_ALL, "C");
    printf("abc");
    //return 0;

    try
    {
        try
        {
            throw std::exception();
        }
        catch(std::exception&)
      {
            std::rethrow_exception(std::current_exception());
      }
        
    }
    catch(int)
    {
        
    }
    catch(std::exception& e)
    {
        std::cout << e.what() << std::endl;
    }
    catch(...)
    {
        std::cout << "unknown" << std::endl;
    }
    return 0;
}

代码逻辑:

两层 try/catch,

1. 里层 try/catch

1.1 try块, throw 异常

1.2 catch块, rethrow

2. 外层 try/catch

2.1 有三catch分支。

 

开刷前,先定义一下。

如果将 try/catch 去除 c++语言特性后,基本就是一种由c++库还有c++编译器共同管理的 goto。

throw相当于goto, catch相当于label(一种以类型区分的)。

那么c++编译器与c++库为我们提供了什么样的管理呢?

c++编译器

0. 利用c++支持对象析构进行try块保护。

1. 将 throw 关键字生成汇编 call __cxa_throw,调用 c++库的函数。

2. 为每个catch块生成代码片断,只能通过jmp跳转进来。

2.1 开头 call __cxa_begin_catch。

2.2 结尾 call __cxa_end_catch。

2.3 最后跳出到 try/catch块逻辑代码的下条执行指令。

3. 为同一try/catch块的所有catch块产生分支控制代码。

4. 为try块的析构代码产生跳转入口。

5. 为每一层try/catch块生成 uncaught 代码块,调用 _Unwind_Resume。

c++库:

1. __cxa_throw,马上_Unwind_RaiseException。跳转到当前最里面一层 try/catch的支路控制代码片断。

2. _Unwind_Resume,向上继续展开。

3. std::rethrow_exception,调用 __gcclibcxx_demangle_callback,

3.1 要么有 catch可达跳回到原来代码的控制流,直接离开std::rethrow_exception的调用上下文。

3.2 要么从__gcclibcxx_demangle_callback返回,执行terminate结束进程。

 

使用Apache commons email发送邮件

sjlj 版的反汇编代码比 dwarf-2 版的多了50行。

先来看dwarf-2的反汇编代码 

  1  <+0>:    lea    0x4(%esp),%ecx
  2  <+4>:    and    $0xfffffff0,%esp
  3  <+7>:    pushl  -0x4(%ecx)
  4  <+10>:    push   %ebp
  5  <+11>:    mov    %esp,%ebp
  6  <+13>:    push   %esi
  7  <+14>:    push   %ebx
  8  <+15>:    push   %ecx
  9  <+16>:    sub    $0x2c,%esp
 10  <+19>:    call   0x401890 <__main>
 11  <+24>:    mov    0x4071a4,%eax
 12  <+29>:    call   *%eax
 13  <+31>:    movl   $0x404045,(%esp)
 14  <+38>:    call   0x4027c4 <printf>
 15  <+43>:    movl   $0x4,(%esp)
 16  <+50>:    call   0x4017ac <__cxa_allocate_exception>
 17  <+55>:    mov    %eax,%ebx
 18  <+57>:    mov    %ebx,%ecx
 19  <+59>:    call   0x402890 <std::exception::exception()>
 20  <+64>:    movl   $0x4017d4,0x8(%esp)
 21  <+72>:    movl   $0x4042a8,0x4(%esp)
 22  <+80>:    mov    %ebx,(%esp)
 23  <+83>:    call   0x401794 <__cxa_throw>
 24  <+88>:    mov    $0x0,%eax
 25  <+93>:    jmp    0x401723 <main()+355>
 26  <+98>:    mov    %edx,%ecx
 27  <+100>:    cmp    $0x2,%ecx
 28  <+103>:    je     0x40162b <main()+107>
 29  <+105>:    jmp    0x401663 <main()+163>
 30  <+107>:    mov    %eax,(%esp)
 31  <+110>:    call   0x4017a4 <__cxa_begin_catch>
 32  <+115>:    mov    %eax,-0x1c(%ebp)
 33  <+118>:    lea    -0x28(%ebp),%eax
 34  <+121>:    mov    %eax,(%esp)
 35  <+124>:    call   0x4017cc <_ZSt17current_exceptionv>
 36  <+129>:    lea    -0x28(%ebp),%eax
 37  <+132>:    mov    %eax,(%esp)
 38  <+135>:    call   0x4017c4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE>
 39  <+140>:    mov    %eax,%esi
 40  <+142>:    mov    %edx,%ebx
 41  <+144>:    lea    -0x28(%ebp),%eax
 42  <+147>:    mov    %eax,%ecx
 43  <+149>:    call   0x4017ec <_ZNSt15__exception_ptr13exception_ptrD1Ev>
 44  <+154>:    call   0x40179c <__cxa_end_catch>
 45  <+159>:    mov    %esi,%eax
 46  <+161>:    mov    %ebx,%edx
 47  <+163>:    cmp    $0x1,%edx
 48  <+166>:    je     0x40166f <main()+175>
 49  <+168>:    cmp    $0x2,%edx
 50  <+171>:    je     0x401683 <main()+195>
 51  <+173>:    jmp    0x4016ca <main()+266>
 52  <+175>:    mov    %eax,(%esp)
 53  <+178>:    call   0x4017a4 <__cxa_begin_catch>
 54  <+183>:    mov    (%eax),%eax
 55  <+185>:    mov    %eax,-0x24(%ebp)
 56  <+188>:    call   0x40179c <__cxa_end_catch>
 57  <+193>:    jmp    0x401618 <main()+88>
 58  <+195>:    mov    %eax,(%esp)
 59  <+198>:    call   0x4017a4 <__cxa_begin_catch>
 60  <+203>:    mov    %eax,-0x20(%ebp)
 61  <+206>:    mov    -0x20(%ebp),%eax
 62  <+209>:    mov    (%eax),%eax
 63  <+211>:    add    $0x8,%eax
 64  <+214>:    mov    (%eax),%eax
 65  <+216>:    mov    -0x20(%ebp),%edx
 66  <+219>:    mov    %edx,%ecx
 67  <+221>:    call   *%eax
 68  <+223>:    mov    %eax,0x4(%esp)
 69  <+227>:    movl   $0x6ff07a00,(%esp)
 70  <+234>:    call   0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
 71  <+239>:    movl   $0x4017bc,(%esp)
 72  <+246>:    mov    %eax,%ecx
 73  <+248>:    call   0x4017f4 <_ZNSolsEPFRSoS_E>
 74  <+253>:    sub    $0x4,%esp
 75  <+256>:    call   0x40179c <__cxa_end_catch>
 76  <+261>:    jmp    0x401618 <main()+88>
 77  <+266>:    mov    %eax,(%esp)
 78  <+269>:    call   0x4017a4 <__cxa_begin_catch>
 79  <+274>:    movl   $0x404049,0x4(%esp)
 80  <+282>:    movl   $0x6ff07a00,(%esp)
 81  <+289>:    call   0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
 82  <+294>:    movl   $0x4017bc,(%esp)
 83  <+301>:    mov    %eax,%ecx
 84  <+303>:    call   0x4017f4 <_ZNSolsEPFRSoS_E>
 85  <+308>:    sub    $0x4,%esp
 86  <+311>:    call   0x40179c <__cxa_end_catch>
 87  <+316>:    jmp    0x401618 <main()+88>
 88  <+321>:    mov    %eax,%ebx
 89  <+323>:    call   0x40179c <__cxa_end_catch>
 90  <+328>:    mov    %ebx,%eax
 91  <+330>:    mov    %eax,(%esp)
 92  <+333>:    call   0x402770 <_Unwind_Resume>
 93  <+338>:    mov    %eax,%ebx
 94  <+340>:    call   0x40179c <__cxa_end_catch>
 95  <+345>:    mov    %ebx,%eax
 96  <+347>:    mov    %eax,(%esp)
 97  <+350>:    call   0x402770 <_Unwind_Resume>
 98  <+355>:    lea    -0xc(%ebp),%esp
 99  <+358>:    pop    %ecx
100  <+359>:    pop    %ebx
101  <+360>:    pop    %esi
102  <+361>:    pop    %ebp
103  <+362>:    lea    -0x4(%ecx),%esp
104  <+365>:    ret    

我们的主要代码逻辑只有20-30条指令

 

 当 throw时,__cxa_throw函数是不会返回的, 如同goto最后是跳转到他处,若被本层catch处理完才会跳转回来<+88>。

然后看c++编译器为我们生成的异常代码 。

 

 

 

 

 

 对于没有发生异常时,代码执行路径基本不会去涉及到异常代码支路,开销几近为0,只是代码量增大。

下面来看 sjlj 版的汇编代码,

  1 function main():
  2  <+0>:    lea    0x4(%esp),%ecx
  3  <+4>:    and    $0xfffffff0,%esp
  4  <+7>:    pushl  -0x4(%ecx)
  5  <+10>:    push   %ebp
  6  <+11>:    mov    %esp,%ebp
  7  <+13>:    push   %edi
  8  <+14>:    push   %esi
  9  <+15>:    push   %ebx
 10  <+16>:    push   %ecx
 11  <+17>:    sub    $0x68,%esp
 12  <+20>:    movl   $0x4017ac,-0x44(%ebp)
 13  <+27>:    movl   $0x402958,-0x40(%ebp)
 14  <+34>:    lea    -0x3c(%ebp),%eax
 15  <+37>:    lea    -0x18(%ebp),%ebx
 16  <+40>:    mov    %ebx,(%eax)
 17  <+42>:    mov    $0x4015b4,%edx
 18  <+47>:    mov    %edx,0x4(%eax)
 19  <+50>:    mov    %esp,0x8(%eax)
 20  <+53>:    lea    -0x5c(%ebp),%eax
 21  <+56>:    mov    %eax,(%esp)
 22  <+59>:    call   0x402790 <_Unwind_SjLj_Register>
 23  <+64>:    call   0x4018b0 <__main>
 24  <+69>:    mov    0x406194,%eax
 25  <+74>:    movl   $0xffffffff,-0x58(%ebp)
 26  <+81>:    call   *%eax
 27  <+83>:    movl   $0x404001,(%esp)
 28  <+90>:    call   0x4027e4 <printf>
 29  <+95>:    movl   $0x4,(%esp)
 30  <+102>:    call   0x4017cc <__cxa_allocate_exception>
 31  <+107>:    mov    %eax,-0x60(%ebp)
 32  <+110>:    mov    %eax,%ecx
 33  <+112>:    call   0x4028b0 <std::exception::exception()>
 34  <+117>:    movl   $0x4017f4,0x8(%esp)
 35  <+125>:    movl   $0x404264,0x4(%esp)
 36  <+133>:    mov    -0x60(%ebp),%eax
 37  <+136>:    mov    %eax,(%esp)
 38  <+139>:    movl   $0x1,-0x58(%ebp)
 39  <+146>:    call   0x4017b4 <__cxa_throw>
 40  <+151>:    mov    $0x0,%eax
 41  <+156>:    mov    %eax,-0x60(%ebp)
 42  <+159>:    jmp    0x401733 <main()+547>
 43  <+164>:    lea    0x18(%ebp),%ebp
 44  <+167>:    mov    -0x54(%ebp),%edx
 45  <+170>:    mov    -0x50(%ebp),%ecx
 46  <+173>:    mov    -0x58(%ebp),%eax
 47  <+176>:    test   %eax,%eax
 48  <+178>:    je     0x4015e6 <main()+214>
 49  <+180>:    sub    $0x1,%eax
 50  <+183>:    test   %eax,%eax
 51  <+185>:    je     0x40161b <main()+267>
 52  <+187>:    sub    $0x1,%eax
 53  <+190>:    test   %eax,%eax
 54  <+192>:    je     0x4016f8 <main()+488>
 55  <+198>:    sub    $0x1,%eax
 56  <+201>:    test   %eax,%eax
 57  <+203>:    je     0x401712 <main()+514>
 58  <+209>:    sub    $0x1,%eax
 59  <+212>:    ud2    
 60  <+214>:    mov    %edx,%eax
 61  <+216>:    mov    %ecx,%edx
 62  <+218>:    mov    %edx,%ecx
 63  <+220>:    cmp    $0x2,%ecx
 64  <+223>:    je     0x4015f3 <main()+227>
 65  <+225>:    jmp    0x401642 <main()+306>
 66  <+227>:    mov    %eax,(%esp)
 67  <+230>:    call   0x4017c4 <__cxa_begin_catch>
 68  <+235>:    mov    %eax,-0x1c(%ebp)
 69  <+238>:    lea    -0x28(%ebp),%eax
 70  <+241>:    mov    %eax,(%esp)
 71  <+244>:    call   0x4017ec <_ZSt17current_exceptionv>
 72  <+249>:    lea    -0x28(%ebp),%eax
 73  <+252>:    mov    %eax,(%esp)
 74  <+255>:    movl   $0x2,-0x58(%ebp)
 75  <+262>:    call   0x4017e4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE>
 76  <+267>:    mov    %edx,-0x60(%ebp)
 77  <+270>:    mov    %ecx,-0x64(%ebp)
 78  <+273>:    lea    -0x28(%ebp),%eax
 79  <+276>:    mov    %eax,%ecx
 80  <+278>:    call   0x40180c <_ZNSt15__exception_ptr13exception_ptrD1Ev>
 81  <+283>:    mov    -0x60(%ebp),%eax
 82  <+286>:    mov    %eax,-0x60(%ebp)
 83  <+289>:    mov    -0x64(%ebp),%esi
 84  <+292>:    mov    %esi,-0x64(%ebp)
 85  <+295>:    call   0x4017bc <__cxa_end_catch>
 86  <+300>:    mov    -0x60(%ebp),%eax
 87  <+303>:    mov    -0x64(%ebp),%edx
 88  <+306>:    cmp    $0x1,%edx
 89  <+309>:    je     0x40164e <main()+318>
 90  <+311>:    cmp    $0x2,%edx
 91  <+314>:    je     0x401665 <main()+341>
 92  <+316>:    jmp    0x4016b3 <main()+419>
 93  <+318>:    mov    %eax,(%esp)
 94  <+321>:    call   0x4017c4 <__cxa_begin_catch>
 95  <+326>:    mov    (%eax),%eax
 96  <+328>:    mov    %eax,-0x20(%ebp)
 97  <+331>:    call   0x4017bc <__cxa_end_catch>
 98  <+336>:    jmp    0x4015a7 <main()+151>
 99  <+341>:    mov    %eax,(%esp)
100  <+344>:    call   0x4017c4 <__cxa_begin_catch>
101  <+349>:    mov    %eax,-0x24(%ebp)
102  <+352>:    mov    -0x24(%ebp),%eax
103  <+355>:    mov    (%eax),%eax
104  <+357>:    add    $0x8,%eax
105  <+360>:    mov    (%eax),%eax
106  <+362>:    mov    -0x24(%ebp),%edx
107  <+365>:    mov    %edx,%ecx
108  <+367>:    call   *%eax
109  <+369>:    mov    %eax,0x4(%esp)
110  <+373>:    movl   $0x6ff29a00,(%esp)
111  <+380>:    movl   $0x3,-0x58(%ebp)
112  <+387>:    call   0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
113  <+392>:    movl   $0x4017dc,(%esp)
114  <+399>:    mov    %eax,%ecx
115  <+401>:    call   0x401814 <_ZNSolsEPFRSoS_E>
116  <+406>:    sub    $0x4,%esp
117  <+409>:    call   0x4017bc <__cxa_end_catch>
118  <+414>:    jmp    0x4015a7 <main()+151>
119  <+419>:    mov    %eax,(%esp)
120  <+422>:    call   0x4017c4 <__cxa_begin_catch>
121  <+427>:    movl   $0x404005,0x4(%esp)
122  <+435>:    movl   $0x6ff29a00,(%esp)
123  <+442>:    movl   $0x4,-0x58(%ebp)
124  <+449>:    call   0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
125  <+454>:    movl   $0x4017dc,(%esp)
126  <+461>:    mov    %eax,%ecx
127  <+463>:    call   0x401814 <_ZNSolsEPFRSoS_E>
128  <+468>:    sub    $0x4,%esp
129  <+471>:    movl   $0xffffffff,-0x58(%ebp)
130  <+478>:    call   0x4017bc <__cxa_end_catch>
131  <+483>:    jmp    0x4015a7 <main()+151>
132  <+488>:    mov    %edx,-0x60(%ebp)
133  <+491>:    call   0x4017bc <__cxa_end_catch>
134  <+496>:    mov    -0x60(%ebp),%eax
135  <+499>:    mov    %eax,(%esp)
136  <+502>:    movl   $0xffffffff,-0x58(%ebp)
137  <+509>:    call   0x402788 <_Unwind_SjLj_Resume>
138  <+514>:    mov    %edx,-0x60(%ebp)
139  <+517>:    movl   $0x0,-0x58(%ebp)
140  <+524>:    call   0x4017bc <__cxa_end_catch>
141  <+529>:    mov    -0x60(%ebp),%eax
142  <+532>:    mov    %eax,(%esp)
143  <+535>:    movl   $0xffffffff,-0x58(%ebp)
144  <+542>:    call   0x402788 <_Unwind_SjLj_Resume>
145  <+547>:    lea    -0x5c(%ebp),%eax
146  <+550>:    mov    %eax,(%esp)
147  <+553>:    call   0x402780 <_Unwind_SjLj_Unregister>
148  <+558>:    mov    -0x60(%ebp),%eax
149  <+561>:    lea    -0x10(%ebp),%esp
150  <+564>:    pop    %ecx
151  <+565>:    pop    %ebx
152  <+566>:    pop    %esi
153  <+567>:    pop    %edi
154  <+568>:    pop    %ebp
155  <+569>:    lea    -0x4(%ecx),%esp
156  <+572>:    ret    

下面的分析只列出不同的地方 

 上图的注释有误没有勘误过,lea是不访问内存,通常代替add指令做加法,应该是6条指令要访问内存。

支路控制代码:

 

 

 

 

 可以看出,支路选路控制指令多而且复杂,还有就是跳转多。

最后是函数结束前。

 

 

 

 可以看出在 sjlj 版本中,即使代码不发生异常,函数在进入与离开时都要为登记维护付出一此成本,当涉及异常代码时,支路选路控制更加复杂更多跳转。这里有一个成本比例,你的函数逻辑简单,上面的开销比重就越大,如果是频繁调用的轻量函数就要考虑不用exception这样的error handle。

还有就是当发生异常时,需要交给c++库去管理,不同异常处理模型的实现,有着不同的开销,本文并没有涉及到。只是单纯从c++库以外的代码进行分析,也足够看出他们之间有着一定的差别。

mingw32 exception在sjlj与dwarf差别-反汇编分析
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

[源码解析] GroupReduce,GroupCombine 和 Flink SQL group by

算法漫游指北(第十一篇):归并排序算法描述、动图演示、代码实现、过程分析、复杂度

这一次搞懂SpringMVC原理

一、归并排序

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。

  • 所谓“分”,指的是将一个乱序数列不断进行二分,得到许多短的序列。

  • 所谓“治”,指的是将这些短序列进行两两合并,然后将合并的结果作为新的序列,再与其他序列进行合并,最终得到一个新的序列。

归并排序算法描述

 

  • 把长度为n的输入序列分成两个长度为n/2的子序列;

  • 对这两个子序列分别采用归并排序;

  • 将两个排序好的子序列合并成一个最终的排序序列。

 

归并排序动图演示

 

动画演示图2

 

 

归并排序代码实现

def merge_sort(alist):
    """归并排序"""
    n = len(alist)
    #递归结束条件
    # 剩一个或没有直接返回,不用排序
    if n <= 1:
        return alist
    # 拆分
    mid = n//2
​
    # left 采用归并排序后形成的有序的新的列表
    left_li = merge_sort(alist[:mid])
​
    # right 采用归并排序后形成的有序的新的列表
    right_li = merge_sort(alist[mid:])
​
    # 将两个有序的子序列合并为一个新的整体
    # merge(left, right)
    left_pointer, right_pointer = 0, 0
    result = []
​
    while left_pointer < len(left_li) and right_pointer < len(right_li):
        if left_li[left_pointer] <=  right_li[right_pointer]:
            result.append(left_li[left_pointer])
            left_pointer += 1
        else:
            result.append(right_li[right_pointer])
            right_pointer += 1
    
    #将两个列表按顺序融合为一个列表result
    result += left_li[left_pointer:]
    result += right_li[right_pointer:]
    return result
​

  


归并排序过程分析

示例 针对 arrli = [6,5,3,1,8,7,2,4]进行归并排序

1、拆分数组

假设数组一共有 n 个元素,我们递归对数组进行折半拆分即n//2,直到每组只有一个元素为止。

 

2、合并数组

算法会从最小数组开始有序合并,这样合并出来的数组一直是有序的,所以合并两个有序数组是归并算法的核心,这里用两个简单数组示例:

步骤1:新建一个空数组存放合并结果,用left_pointerright_pointer两个辅助指针记录两个数组当前操作位置;

 

步骤2:从左到右逐一比较两个小数组中的元素,较小的元素先放入新数组,指针移位,直到left_pointerright_pointer指针超出尾部;

 

步骤3:新建一个空数组存放合并结果,用lr两个辅助指针记录两个数组当前操作位置;

 

步骤4:从左到右逐一比较两个小数组中的元素,较小的元素先放入新数组,指针移位,直到lr指针超出尾部;

 

继续比较写入较小的元素到新数组

继续比较写入较小的元素到新数组

【Spring注解驱动开发】在@Import注解中使用ImportBeanDefinitionRegistrar向容器中注册bean

 

指针尚未移到尾部的数组,说明还有剩余元素,将剩余元素合并到新数组尾部。

 

步骤5:新建一个空数组存放合并结果,用lr两个辅助指针记录两个数组当前操作位置;

 

步骤6:从左到右逐一比较两个小数组中的元素,较小的元素先放入新数组,指针移位,直到lr指针超出尾部;

 

将较小的元素写入到新数组

继续比较写入较小的元素到新数组

继续比较写入较小的元素到新数组

继续比较写入较小的元素到新数组

 

步骤7:右边的指针尚未移到尾部的数组,说明还有剩余元素,将剩余元素合并到新数组尾部。

 

完成归并排序,返回排好序的新数组

 

 

归并排序复杂度

 

  • 时间复杂度:O(nlogn)

归并排序把数组一层层折半分组,长度为 n 的数组,折半层数就是 logn,每一层进行操作的运算量是 n,得出时间复杂度 O(nlogn)。

  • 空间复杂度:O(n)

每次归并操作需要创建额外的新数组,占用空间为 n,但这部分额外空间会随着方法的结束而释放,所以只需要算单次归并操作开辟的空间即可,得出空间复杂度 O(n)。

  • 稳定性:稳定

从算法中从左到右逐一比较,较小的先放入新数组,所以两个值相同的元素,排序后依然保持原先后顺序。

 

 

算法漫游指北(第十一篇):归并排序算法描述、动图演示、代码实现、过程分析、复杂度
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

从字符串到常量池,一文看懂String类设计

大数据篇:一文读懂@数据仓库(文字版)

JPS/JPS+ 寻路算法

大数据篇:一文读懂@数据仓库

1 网络词汇总结

  • 人工智能层的:智慧地球、智慧城市、智慧社会
  • 企业层面的:数字互联网,数字经济、数字平台、数字城市、数字政府;
  • 平台层面的:物联网,云计算,大数据,5G,人工智能,机器智能,深度学习,知识图谱
  • 技术层面的:数据仓库、数据集市、大数据平台、数据湖、数据中台、业务中台、技术中台等等

挑重点简介

1.1 数据中台

  1. 数据中台是聚合和治理跨域数据,将数据抽象封装成服务,提供给前台以业务价值的逻辑概念。

  2. 数据中台是一套可持续“让企业的数据用起来”的机制,一种战略选择和组织形式,是依据企业特有的业务模式和组织架构,通过有形的产品和实施方法论支撑,构建一套持续不断把数据变成资产并服务于业务的机制。

  3. 数据中台连接数据前台和后台,突破数据局限,为企业提供更灵活、高效、低成本的数据分析挖掘服务,避免企业为满足具体某部门某种数据分析需求而投放大量高成本、重复性的数据开发成本。

  4. 数据中台是指通过数据技术,对海量数据进行采集、计算、存储、加工,同时统一标准和口径。数据中台把数据统一之后,会形成标准数据,再进行存储,形成大数据资产层,进而为客户提供高效服务。

  5. 数据中台,包括平台、工具、数据、组织、流程、规范等一切与企业数据资产如何用起来所相关的。

可以看出,数据中台是解决如何用好数据的问题,目前还缺乏一个标准,而说到数据中台一定会提及大数据,而大数据又是由数据仓库发展起来的。

1.1.1 数据仓库(Data WareHouse)

  1. 数据仓库,按照传统的定义,数据仓库是一个面向主题的、集成的、非易失的、反映历史变化(随时间变化),用来支持管理人员决策的数据集合。

为企业所有决策制定过程,提供所有系统数据支持的战略集合

  • 面向主题

操作型数据库的数据组织面向事务处理任务,各个业务系统之间各自分离,而数据仓库中的数据是按照一定的主题域进行组织。

主题是一个抽象的概念,是数据归类的标准,是指用户使用数据仓库进行决策时所关心的重点方面,一个主题通常与多个操作型信息系统相关。每一个主题基本对应一个宏观的分析领域。

例如,银行的数据仓库的主题:客户

客户数据来源:从银行储蓄数据库、信用卡数据库、贷款数据库等几个数据库中抽取的数据整理而成。这些客户信息有可能是一致的,也可能是不一致的,这些信息需要统一整合才能完整体现客户。

  • 集成

面向事务处理的操作型数据库通常与某些特定的应用相关,数据库之间相互独立,并且往往是异构的。而数据仓库中的数据是在对原有分散的数据库数据抽取、清理的基础上经过系统加工、汇总和整理得到的,必须消除源数据中的不一致性,以保证数据仓库内的信息是关于整个企业的一致的全局信息。

具体如下:

1:数据进入数据仓库后、使用之前,必须经过加工与集成。

2:对不同的数据来源进行统一数据结构和编码。统一原始数 据中的所有矛盾之处,如字段的同名异义,异名同义,单位不统一,字长不一致等。

3:将原始数据结构做一个从面向应用到面向主题的大转变。

  • 非易失即相对稳定的

操作型数据库中的数据通常实时更新,数据根据需要及时发生变化。数据仓库的数据主要供企业决策分析之用,所涉及的数据操作主要是数据查询,一旦某个数据进入数据仓库以后,一般情况下将被长期保留,也就是数据仓库中一般有大量的查询操作,但修改和删除操作很少,通常只需要定期的加载、刷新。

数据仓库中包括了大量的历史数据。

数据经集成进入数据仓库后是极少或根本不更新的。

  • 随时间变化即反映历史变化

操作型数据库主要关心当前某一个时间段内的数据,而数据仓库中的数据通常包含历史信息,系统记录了企业从过去某一时点(如开始应用数据仓库的时点)到目前的各个阶段的信息,通过这些信息,可以对企业的发展历程和未来趋势做出定量分析和预测。企业数据仓库的建设,是以现有企业业务系统和大量业务数据的积累为基础。数据仓库不是静态的概念,只有把信息及时交给需要这些信息的使用者,供他们做出改善其业务经营的决策,信息才能发挥作用,信息才有意义。而把信息加以整理归纳和重组,并及时提供给相应的管理决策人员,是数据仓库的根本任务。因此,从产业界的角度看,数据仓库建设是一个工程,是一个过程

数据仓库内的数据时限一般在5-10年以上,甚至永不删除,这些数据的键码都包含时间项,标明数据的历史时期,方便做时间趋势分析。

  1. 数据仓库,并不是数据最终目的地,而是为数据最终的目的地做好准备:清洗、转义、分类、重组、合并、拆分、统计等等

通过对数据仓库中数据的分析,可以帮助企业,改进业务流程、控制、成本、提高产品质量等

  1. 主要解决问题:数据报表,数据沉淀,数据计算Join过多,数据查询过慢等问题。

防止烟囱式开发,减少重复开发,开发通用中间层数据,减少重复计算;

将复杂问题简单化,将复杂任务的多个步骤分解到各个层次中,每一层只处理较少的步骤,使单个任务更容易理解;

可进行数据血缘追踪,便于快速定位问题;

整个数据层次清晰,每个层次的数据都有职责定位,便于使用和理解。

  1. 主要价值体现:企业数据模型,这些模型随着前端业务系统的发展变化,不断变革,不断追加,不断丰富和完善,即使系统不再了,也可以在短期内快速重建起来,这也是大数据产品能够快速迭代起来的一个重要原因

总结:数据仓库,即为企业数据的模型沉淀,为了能更快的发展大数据应用,提供可靠的模型来快速迭代。本文也主要为了讲解数据仓库

  • 数仓硬件架构图

  • 数仓功能架构图

  • 数仓流程架构图1

  • 数仓流程架构图2

  • 实时数仓流程架构图

1.1.2 大数据平台(DATA Platform)

  1. 大数据平台则是指以处理海量数据存储、计算及流数据实时计算等场景为主的一套基础设施,包括了统一的数据采集中心、数据计算和存储中心、数据治理中心、运维管控中心、开放共享中心和应用中心。

  2. 大数据平台的建设出发点是节约投资降低成本,但实际上无论从硬件投资还是从软件开发上都远远超过数据仓库的建设,大量的硬件和各种开源技术的组合,增加了研发的难度、调测部署的周期、运维的复杂度,人力上的投入已是最初的几倍;还有很多技术上的困难也非一朝一夕能够突破。

  3. 首先是数据的应用问题,无论是数据仓库还是大数据平台,里面包含了接口层数据、存储层数据、轻度汇总层、重度汇总层、模型层数据、报表层数据等等,各种各样的表有成千上万,这些表有的是中间处理过程,有些是一次性的报表,不同表之间的数据一致性和口径也会不同,而且不同的表不同的字段对数据安全要求级别也不同。

  4. 此外还要考虑多租户的资源安全管理,如何让内部开发者快速获取所需的数据资产目录,如何阅读相关数据的来龙去脉,如何快速的实现开发,这些在大数据平台建设初期没有考虑周全。

  5. 另外一个问题是对外应用,随着大数据平台的应用建设,每一个对外应用都采用单一的数据库加单一应用建设模式,独立考虑网络安全、数据安全、共享安全,逐渐又走向了烟囱似的开发道路。

总结:大数据平台,即为数据一站式服务,提供可视化的数据展示,提取,计算任务安排,资源管理,数据治理,安全措施,共享应用等等。

  • 平台数据流向图

  • 平台流程架构图

1.1.3 数据中台(Data Middle Platform)

  1. 数据中台要解决什么?数据如何安全的、快速的、最小权限的、且能够溯源的被探测和快速应用的问题。

  2. 数据中台不应该被过度的承载平台的计算、存储、加工任务,而是应该放在解决企业逻辑模型的搭建和存储、数据标准的建立、数据目录的梳理、数据安全的界定、数据资产的开放,知识图谱的构建。

  3. 通过一系列工具、组织、流程、规范,实现数据前台和后台的连接,突破数据局限,为企业提供更灵活、高效、低成本的数据分析挖掘服务,避免企业为满足具体某部门某种数据分析需求而投放大量高成本、重复性的数据开发成本。

总结:厚平台,大中台,小前台;没有基础厚实笨重的大数据平台,是不可能构建数据能力强大、功能强大的数据中台的;没有大数据中台,要迅速搭建小快灵的小前台也只是理想化的。

  • 中台架构图

  • 阿里数据中台架构图

2 数据库的”分家”

随着关系数据库理论的提出,诞生了一系列经典的RDBMS,如Oracle,MySQL,SQL Server等。这些RDBMS被成功推向市场,并为社会信息化的发展做出的重大贡献。然而随着数据库使用范围的不断扩大,它被逐步划分为两大基本类型:

  1. 操作型数据库(OLTP)

主要用于业务支撑。一个公司往往会使用并维护若干个数据库,这些数据库保存着公司的日常操作数据,比如商品购买、酒店预订、打车下单、外卖订购等;

  1. 分析型数据库(OLAP)

主要用于历史数据分析。这类数据库作为公司的单独数据存储,负责利用历史数据对公司各主题域进行统计分析;

  • 总结

那么为什么要”分家”?在一起不合适吗?能不能构建一个同样适用于操作和分析的统一数据库?

答案是NO。一个显然的原因是它们会”打架”……如果操作型任务和分析型任务抢资源怎么办呢?再者,它们有太多不同,以致于早已”貌合神离”。接下来看看它们到底有哪些不同吧。

因为主导功能的不同(面向操作/面向分析),两类数据库就产生了很多细节上的差异。就好像玩LOL一个中单一个ADC,肯定有很多行为/观念上的不同

2.1 OLAP 和 OLTP简介

数据处理大致可以分成两大类:

联机事务处理OLTP(on-line transaction processing):是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。系统强调数据库内存效率,强调内存各种指标的命令率,强调绑定变量,强调并发操作。

联机分析处理OLAP(On-Line Analytical Processing):是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。 系统则强调数据分析,强调SQL执行市场,强调磁盘I/O,强调分区等。

2.2 定义差别

对比内容 操作型数据库(OLTP) 分析型数据库(OLAP)
数据内容 当前值 历史的、存档的、归纳的、计算的数据
数据目标 面向业务操作程序,重复处理 面向主题域,分析应用,支持决策
数据特性 动态变化,按字段更新 静态、不能直接更新,只能定时添加、刷新
数据结构 高度结构化、复杂,适合操作计算 简单,适合分析
使用频率 中到低
数据访问量 每个事务只访问少量记录 有的事务可能需要访问大量记录
对响应时间的要求 以秒为单位计算 以秒、分钟、甚至小时为计算单位

2.3 定位差别

对比属性 OLTP OLAP
代表 Mysql Hive
读特性 每次查询只返回少量数据 对大量数据进行汇总
写特性 随机、低延迟写入用户的操作 批量导入
用户 操作人员 决策人员
DB设计 面向应用 面向主题
数据 当前的,最新的细节,二维表 历史的,聚集的,多维表
工作单位 事务性保证 复杂查询
用户数 上千个 上百万个
DB大小 100MB-GB 100GB-TB以上
时间要求 具有实时性 对时间的要求不严格
主要应用 数据库:WEB项目 数据仓库:分析师,挖掘师

2.4 组成差别

对比内容 操作型数据库(OLTP) 分析型数据库(OLAP)
数据时间范围差别 只会存放一定天数的数据 存放的则是数年内的数据
数据细节层次差别 存放的主要是细节数据 也有汇总需求,但汇总数据本身不存储而只存储其生成公式。
这是因为操作型数据是动态变化的,因此汇总数据会在每次查询时动态生成。
存放的既有细节数据,又有汇总数据,对于用户来说,重点关注的是汇总数据部分。
因为汇总数据比较稳定不会发生改变,而且其计算量也比较大(因为时间跨度大),因此它的汇总数据可考虑事先计算好,以避免重复计算。
数据时间表示差别 通常反映的是现实世界的当前状态 既有当前状态,还有过去各时刻的快照。
可以综合所有快照对各个历史阶段进行统计分析

2.5 技术差别

对比内容 操作型数据库(OLTP) 分析型数据库(OLAP)
数据更新差别 允许用户进行增,删,改,查 规范是只能进行查询
数据冗余差别 减少数据冗余,避免更新异常 没有更新操作。因此,减少数据冗余也就没那么重要了

2.6 功能差别

对比内容 操作型数据库(OLTP) 分析型数据库(OLAP)
数据读者差别 使用者是业务环境内的各个角色,如用户,商家,进货商等 只被少量用户(高级管理者)用来做综合性决策
数据定位差别 是为了支撑具体业务创建的,因此也被称为”面向应用型数据库” 是针对各特定业务主题域的分析任务创建的,因此也被称为”面向主题型数据库”

2.7 OLTP数据库三范式介绍

  • 定义:范式可以理解为设计一张数据表的表结构,符合的标准级别。 规范和要求
  • 优点:关系型数据库设计时,遵照一定的规范要求,目的在于降低数据的冗余性。
    • 十几年前,磁盘很贵,为了减少磁盘存储。
    • 以前没有分布式系统,都是单机,只能增加磁盘,磁盘个数也是有限的
    • 一次修改,需要修改多个表,很难保证数据一致性
  • 缺点:范式的缺点是获取数据时,需要通过 Join 拼接出最后的数据。

目前业界范式有:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式 (BCNF)、第四范式(4NF)、第五范式(5NF)。

2.7.1 函数依赖

学号 姓名 系名 班主任 课名 分数
001 张三 古文系 李白 文言文 89
001 张三 古文系 李白 古诗词 78
001 张三 古文系 李白 现代汉语 65
002 李四 古文系 李白 文言文 45
002 李四 古文系 李白 古诗词 78
002 李四 古文系 李白 甲骨文 98
003 王五 数学系 牛顿 高等数学 88
003 王五 数学系 牛顿 数学基础 88
  1. 完全函数依赖:

通过 AB 能推出 C,但是 AB 单独得不到 C,那么可以说:C 完全依赖于 AB

(学号,课名)推出 分数,但是 单独用学号 推不出 分数,那么可以说:分数 完全依赖于(学号,课名)

  1. 部分函数依赖:

通过 AB 能推出 C,通过 单独的A 或者 单独的B 也能推出 C,那么可以说:C 部分依赖于 AB

(学号,课名)推出 姓名,而还可以通过 学号 直接推出 姓名,那么可以说:姓名 部分依赖于(学号,课名)

  1. 传递函数依赖:

通过 A 得到 B,通过 B 得到 C,但是通过 C 不能得到 A,那么可以说:C 传递依赖于 A

通过 学号 推出 系名,系名 推出 系主任,但是 系主任 不能推出 学号,那么可以说:系主任 专递依赖于 学号

2.7.2 三范式区分

2.7.2.1 第一范式:属性不可切割
  • 不符合第一范式表设计
ID 商品 商家ID 用户ID
001 5台电脑 小米_001 00001

如上表格不符合第一范式,商品列中的数据不是原子数据项,是可以进行分割的。

  • 符合第一范式表设计
ID 商品 数量 商家ID 用户ID
001 电脑 5 小米_001 00001

1NF是所有关系数据库的最基本要求

2.7.2.2 第二范式:不能存在”部分函数依赖”
  • 不符合第二范式表设计
学号 姓名 系名 班主任 课名 分数
001 张三 古文系 李白 文言文 89
001 张三 古文系 李白 古诗词 78
001 张三 古文系 李白 现代汉语 65
002 李四 古文系 李白 文言文 45
002 李四 古文系 李白 古诗词 78
002 李四 古文系 李白 甲骨文 98
003 王五 数学系 牛顿 高等数学 88
003 王五 数学系 牛顿 数学基础 88

如上表格不符合第二范式,比如:这张表主键(学号,课名),分数完全依赖于(学号和课名),但是姓名并不完全依赖于(学号和课名)

  • 符合第二范式表设计
学号 课名 分数
001 文言文 89
001 古诗词 78
001 现代汉语 65
002 文言文 45
002 古诗词 78
002 甲骨文 98
003 高等数学 88
003 数学基础 88
学号 姓名 系名 班主任
001 张三 古文系 李白
002 李四 古文系 李白
003 王五 数学系 牛顿
2.7.2.3 第三范式:不能存在”传递函数依赖”
  • 不符合第三范式表设计
学号 姓名 系名 班主任
001 张三 古文系 李白
002 李四 古文系 李白
003 王五 数学系 牛顿

如上表格不符合第三范式,比如:学号–>系名–>系主任,但是系主任推不出学号

  • 符合第三范式表设计
学号 姓名 系名
001 张三 古文系
002 李四 古文系
003 王五 数学系
系名 班主任
古文系 李白
古文系 李白
数学系 牛顿

2.8 OLAP典型架构

OLAP有多种实现方法,根据存储数据的方式不同可以分为ROLAP、MOLAP、HOLAP

名称 描述 细节数据存储位置 聚合后的数据存储位置
ROLAP(Relational OLAP) 基于关系数据库的OLAP实现 关系型数据库 关系型数据库
MOLAP(Multidimensional OLAP) 基于多维数据组织的OLAP实现 数据立方体 数据立方体
HOLAP(Hybrid OLAP) 基于混合数据组织的OLAP实现 关系型数据库 数据立方体
  1. ROLAP(Relational Online Analytical Processing)

ROLAP架构并不会生成实际的多维数据集,而是使用雪花模式以及多个关系表对数据立方体进行模拟,它的OLAP引擎就是将用户的OLAP操作,如上钻下钻过滤合并等,转换成SQL语句提交到数据库中执行,并且提供聚集导航功能,根据用户操作的维度和度量将SQL查询定位到最粗粒度的事实表上去

这种架构下的查询没有MOLAP快速。因为ROLAP中,所有的查询都是被转换为SQL语句执行的。而这些SQL语句的执行会涉及到多个表之间的JOIN操作,没有MOLAP速度快,往往都是通过内存计算实现。(内存的昂贵大家是知道的)

  1. MOLAP(Multidimensional Online Analytical Processing)

MOLAP架构会生成一个新的多维数据集,也可以说是构建了一个实际数据立方体。事先将汇总数据计算好,存放在自己特定的多维数据库中,用户的OLAP操作可以直接映射到多维数据库的访问,不通过SQL访问。(空间换时间,典型代表Kylin)

在该立方体中,每一格对应一个直接地址,且常用的查询已被预先计算好。因此每次的查询都是非常快速的,但是由于立方体的更新比较慢,所以是否使用这种架构得具体问题具体分析。

  1. HOLAP(Hybrid Online Analytical Processing)

这种架构综合参考MOLAP和ROLAP而采用一种混合解决方案,将某些需要特别提速的查询放到MOLAP引擎,其他查询则调用ROLAP引擎。上述MOLAP和ROLAP的结合。它提供了更大的灵活度,MOLAP提供提供了更加快速的响应速度。但是带来的问题是,数据装载的效率非常低,因为其实就是将多维的数据预先填好,但是随着数据量过大维度成本越高,容易引起“数据爆炸”。

2.9 OLAP数据立方体(Data Cube)

OLAP(online analytical processing)是一种软件技术,它使分析人员能够迅速、一致、交互地从各个方面观察信息,以达到深入理解数据的目的。从各方面观察信息,也就是从不同的维度分析数据,因此OLAP也称为多维分析

很多年前,当我们要手工从一堆数据中提取信息时,我们会分析一堆数据报告。通常这些数据报告采用二维表示,是行与列组成的二维表格。但在真实世界里我们分析数据的角度很可能有多个,数据立方体可以理解为就是维度扩展后的二维表格。下图展示了一个三维数据立方体:

更多时候数据立方体是N维的。它的实现有两种方式。其中星形模式就是其中一种,该模式其实是一种连接关系表与数据立方体的桥梁。但对于大多数纯OLAP使用者来讲,数据分析的对象就是这个逻辑概念上的数据立方体,其具体实现不用深究。对于这些OLAP工具的使用者来讲,基本用法是首先配置好维表、事实表,然后在每次查询的时候告诉OLAP需要展示的维度和事实字段和操作类型即可。

02 . Ansible高级用法(运维开发篇)

最常见的五大操作:切片,切块,旋转,上卷,下钻。

2.9.1 切片和切块(Slice and Dice)

在数据立方体的某一维度上选定一个维成员的操作叫切片,而对两个或多个维执行选择则叫做切块。下图逻辑上展示了切片和切块操作:

2.9.2 旋转(Pivot)

旋转就是指改变报表或页面的展示方向。对于使用者来说,就是个视图操作,而从SQL模拟语句的角度来说,就是改变SELECT后面字段的顺序而已。下图逻辑上展示了旋转操作:

2.9.3 上卷和下钻(Rol-up and Drill-down)

上卷可以理解为”无视”某些维度;下钻则是指将某些维度进行细分。下图逻辑上展示了上卷和下钻操作:

2.9.4 Cube 和 Cuboid

Cube(或 Data Cube),即数据立方体,是一种常用于数据分析与索引的技术;它可以对原始数据建立多维度索引。通过 Cube 对数据进行分析,可以大大加快数据的查询效率。

Cuboid 特指在某一种维度组合下所计算的数据。 给定一个数据模型,我们可以对其上的所有维度进行组合。对于 N 个维度来说,组合的所有可能性共有 2 的 N 次方种。对于每一种维度的组合,将度量做 聚合运算,然后将运算的结果保存为一个物化视图,称为 Cuboid。

所有维度组合的 Cuboid 作为一个整体,被称为 Cube。所以简单来说,一个 Cube 就是许多按维度聚合的物化视图的集合。

下面来列举一个具体的例子:

假定有一个电商的销售数据集,其中维度包括 时间(Time)、商品(Item)、地点(Location)和供应商(Supplier),度量为销售额(GMV)。

  • 那么所有维度的组合就有 2 的 4 次方 =16 种
    • 一维度(1D) 的组合有[Time]、[Item]、[Location]、[Supplier]4 种
    • 二维度(2D)的组合 有[Time,Item]、[Time,Location]、[Time、Supplier]、[Item,Location]、 [Item,Supplier]、[Location,Supplier]6 种
    • 三维度(3D)的组合也有 4 种
    • 零维度(0D)的组合有 1 种
    • 四维度(4D)的组合有 1 种

3 数据仓库的演进

4 数据仓库主要用途

大家应该已经意识到这个问题:既然分析型数据库中的操作都是查询,因此也就不需要严格满足完整性/参照性约束以及范式设计要求,而这些却正是分析型数据库精华所在。这样的情况下再将它归为数据库会很容易引起大家混淆,毕竟在绝大多数人心里数据库是可以关系型数据库画上等号的。

  • 那么为什么不干脆叫”面向分析的存储系统”呢?

这就是关于数据仓库最贴切的定义了。事实上数据仓库不应让传统关系数据库来实现,因为关系数据库最少也要求满足第1范式,而数据仓库里的关系表可以不满足第1范式。也就是说,同样的记录在一个关系表里可以出现N次。但由于大多数数据仓库内的表的统计分析还是用SQL,因此很多人把它和关系数据库搞混了。

4.1 支持数据提取

数据提取可以支撑来自企业各业务部门的数据需求。

由之前的不同业务部门给不同业务系统提需求转变为不同业务系统统一给数据仓库提需求,避免烟囱式开发

4.2 支持报表系统

基于企业的数据仓库,向上支撑企业的各部门的统计报表需求,辅助支撑企业日常运营决策。

4.3 支持数据分析

从许多来自不同的企业业务系统的数据中提取出有用的数据并进行清理,以保证数据的正确性,然后经过抽取、转换和装载,即ETL过程,合并到一个企业级的数据仓库里,从而得到企业数据的一个全局视图;

在此基础上利用合适的查询和分析工具、数据挖掘工具、OLAP工具等对其进行分析和处理(这时信息变为辅助决策的知识);

最后将知识呈现给管理者,为管理者的决策过程提供支持 。

4.4 支持数据挖掘

数据挖掘也称为数据库知识发现(Knowledge Discovery in Databases, KDD),就是将高级智能计算技术应用于大量数据中,让计算机在有人或无人指导的情况下从海量数据中发现潜在的,有用的模式(也叫知识)。

Jiawei Han在《数据挖掘概念与技术》一书中对数据挖掘的定义:数据挖掘是从大量数据中挖掘有趣模式和知识的过程,数据源包括数据库、数据仓库、Web、其他信息存储库或动态地流入系统的数据。

4.5 支持数据应用

物联网基于位置数据的旅游客流分析及人群画像

通信基于位置数据的人流监控和预警

银行基于用户交易数据的金融画像应用

电商根据用户浏览和购买行为的用户标签体系及推荐系统

征信机构根据用户信用记录的信用评估

出行基于位置数据的车流量分析,调度预测

5 数据集市

数据集市可以理解为是一种”小型数据仓库”,它只包含单个主题,且关注范围也非全局。

数据集市可以分为两种,一种是独立数据集市(independent data mart),这类数据集市有自己的源数据库和ETL架构;另一种是非独立数据集市(dependent data mart),这种数据集市没有自己的源系统,它的数据来自数据仓库。当用户或者应用程序不需要/不必要/不允许用到整个数据仓库的数据时,非独立数据集市就可以简单为用户提供一个数据仓库的”子集”。

  • 简单理解:
    • 数据集市:部门级别的数据仓库,能为某个局部范围内的管理人员提供服务。
    • 数据仓库:企业级别的数据仓库,能为企业各个部门的运行提供决策支持。

6 建模的基本概念

6.1 关系建模

上图为web应用中的一个建模片段,遵循三范式建模,可以看出,较为松散、零碎, 物理表数量多,而数据冗余程度低。由于数据分布于众多的表中,这些数据可以更为灵活地 被应用,功能性较强。关系模型主要应用与 OLTP 系统中,为了保证数据的一致性以及避免 冗余,所以大部分业务系统的表都是遵循第三范式的。

6.2 维度建模

维度建模(dimensional modeling)是专门用于分析型数据库、数据仓库、数据集市建模的方法

上图为维度模型建模片段,主要应用于 OLAP 系统中,通常以某一个事实表为中心进行表的 组织,主要面向业务,特征是可能存在数据的冗余,但是能方便的得到数据。

关系模型虽然冗余少,但是在大规模数据,跨表分析统计查询过程中,会造成多表关联,这会大大降低执行效率。所以通常我们采用维度模型建模,把相关各种表整理成两种: 事实表和维度表两种

6.3 维度建模的三种模式

  1. 星形模式

星形模式(Star Schema)是最常用的维度建模方式

可以看出,星形模式的维度建模由一个事实表和一组维表成,且具有以下特点:

  1. 维表只和事实表关联,维表之间没有关联;
  2. 每个维表的主码为单列,且该主码放置在事实表中,作为两边连接的逻辑外键;
  3. 以事实表为核心,维表围绕核心呈星形分布;
  1. 雪花模式

雪花模式(Snowflake Schema)是对星形模式的扩展,每个维表可继续向外连接多个子维表。(三范式代表作)

星形模式中的维表相对雪花模式来说要大,而且不满足规范化设计。雪花模型相当于将星形模式的大维表拆分成小维表,满足了规范化设计。然而这种模式在实际应用中很少见,因为这样做会导致开发难度增大,而数据冗余问题在数据仓库里并不严重。

  1. 星座模式

星座模式(Fact Constellations Schema)也是星型模式的扩展。

前面两种维度建模方法都是多维表对应单事实表,但在很多时候维度空间内的事实表不止一个,而一个维表也可能被多个事实表用到。在业务发展后期,星座模式将作为最主要的维度建模。

6.4 维度表和事实表

  1. 维度表(dimension)

表示对分析主题所属类型的描述。比如”昨天早上张三在京东花费200元购买了一个皮包”。那么以购买为主题进行分析,可从这段信息中提取三个维度:时间维度(昨天早上),地点维度(京东), 商品维度(皮包)。通常来说维度表信息比较固定,且数据量小。

  1. 事实表(fact table)

表示对分析主题的度量。比如上面那个例子中,200元就是事实信息。事实表包含了与各维度表相关联的逻辑外键,并通过JOIN方式与维度表关联。事实表的度量通常是数值类型,且记录数会不断增加,表规模迅速增长。

  1. 事实维度举例

昨天我去菜市场买了一只蝙蝠,然后我就被隔离了。

  • 事实:订单==>买蝙蝠这个事

  • 维度:

    • 时间==>昨天
    • 用户==>我
    • 商品==>蝙蝠
    • 地理==>菜市场

6.4.1 维度表

维度表:一般是对事实的描述信息。每一张维表对应现实世界中的一个对象或者概念。 例如:用户、商品、日期、地区等。

常用于一个客观世界的维度描述,往往列比较多。

审视数据的角度

  • 维表的特征:
    • 维表的范围很宽(具有多个属性、列比较多)
    • 跟事实表相比,行数相对较小:通常< 10 万条
    • 静态表示的,名词性质的表

6.4.2 事实表

事实表用于正确的记录既定的已经发生的事实,常用于存储ID和度量值,各种维度外键

事实表中的每行数据代表一个业务事件(下单、支付、退款、评价等)。“事实”这 个术语表示的是业务事件的度量值(可统计次数、个数、件数、金额等),例如,订单事件中的下单金额。

每一个事实表的行包括:具有可加性的数值型的度量值、与维表相连接的外键、通常具 有两个和两个以上的外键、外键之间表示维表之间多对多的关系。

  • 事实表的特征:
    • 非常的大
    • 内容相对的窄:列数较少
    • 经常发生变化,每天会新增加很多
    • 动态表示的,动词性质的表
  1. 事务型事实表(每天导入新增)
    • 以每个事务或事件为单位,例如一个销售订单记录,一笔支付记录等,作为事实表里的 一行数据。一旦事务被提交,事实表数据被插入,数据就不再进行更改,其更新方式为增量 更新
  2. 周期型快照事实表(每日全量)
    • 周期型快照事实表中不会保留所有数据,只保留固定时间间隔的数据,例如每天或者 每月的销售额,或每月的账户余额等
  3. 累积型快照事实表(每天导入新增及变化)
    • 累计快照事实表用于跟踪业务事实的变化。例如,数据仓库中可能需要累积或者存储 订单从下订单开始,到订单商品被打包、运输、和签收的各个业务阶段的时间点数据来跟踪 订单声明周期的进展情况。当这个业务过程进行时,事实表的记录也要不断更新。

6.5 数据分层

  • 为什么分层:
    • 简单化:把复杂的任务分解为多层来完成,每层处理各自的任务,方便定位问题。
    • 减少重复开发:规范数据分层,通过中间层数据,能够极大的减少重复计算,增加结果复用性。
    • 隔离数据:不论是数据异常还是数据敏感性,使真实数据和统计数据解耦。

下面列举常见电商表的分层结构

6.5.1 ODS层

  • 保持数据原貌不做任何修改,起到备份数据的作用。
  • 数据采用压缩,减少磁盘存储空间(例如:原始数据 100G,可以压缩到 10G 左 右)
  • 创建分区表,防止后续的全表扫描

6.5.2 DWD层

DWD 层需构建维度模型,一般采用星型模型,呈现的状态一般为星座模型。

  • 维度建模一般按照四个步骤: 选择业务过程→声明粒度→确认维度→确认事实

  • 选择业务过程

    • 在业务系统中,挑选我们感兴趣的业务线,比如下单业务,支付业务,退款业务,物流 业务,一条业务线对应一张事实表。
  • 声明粒度

    • 数据粒度指数据仓库的数据中保存数据的细化程度或综合程度的级别。

    • 声明粒度意味着精确定义事实表中的一行数据表示什么,应该尽可能选择最小粒度,以 此来应各种各样的需求。

    • 典型的粒度声明如下:

      • 订单中,每个商品项作为下单事实表中的一行,粒度为每次下单
      • 每周的订单次数作为一行,粒度就是每周下单。
      • 每月的订单次数作为一行,粒度就是每月下单
  • 确定维度

    • 维度的主要作用是描述业务是事实,主要表示的是“谁,何处,何时”等信息。
  • 确定事实

    • 此处的“事实”一词,指的是业务中的度量值,例如订单金额、下单次数等。
    • 在 DWD 层,以业务过程为建模驱动,基于每个具体业务过程的特点,构建最细粒度的 明细层事实表。事实表可做适当的宽表化处理。

事实/维度 时间 用户 地区 商品 优惠卷 活动 编码 度量
订单 件数/金额
订单详情 件数/金额
支付 次数/金额
加入购物车 件数/金额
收藏 个数
评价 个数
退款 件数/金额
优惠卷领用 个数

6.5.3 DWS层

  • 统计各个主题对象的当天行为,服务于 DWT 层的主题宽表,以及一些业务明细数据, 应对特殊需求(例如,购买行为,统计商品复购率)。

6.5.4 DWT层

  • 以分析的主题对象为建模驱动,基于上层的应用和产品的指标需求,构建主题对象的全 量宽表。(就是按照维度来决定分析者的角度,如用户->什么时间->下了什么单,支付了什么,加入购物车了什么)

6.5.5 ADS层

对系统各大主题指标分别进行分析。

大数据篇:一文读懂@数据仓库(文字版)
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

多线程实例(一)——遍历文件夹分割文件识别文件内容

C#构造函数 -0028

深入理解JVM(③)低延迟的Shenandoah收集器

默认构造函数

声明基本构造函数的语法就是声明一个与类同名的方法,但该方法没有返回类型:

public class MyClass
{
	public MyClass()
	{
	}
	// rest of class definition
}

如果没有提供任何构造函数,编译器会在后台生成一个默认的构造函数。默认的构造函数,只能把所有的成员字段初始化为标准的默认值。

但是,如果定义了带参数的构造函数,编译器就不会自动提取默认的构造函数。

private或protected构造函数

可以把构造函数定义为private或Protected,这样就限制不相关的类不能访问它。

比如定义private,

public class MyNumber
{
	private int _number;
	private MyNumber(int number) // another overload
	{
		_number = number;
	}
}

在外部代码中,不能使用new关键字实例化MyNumber;但可以编写一个公有静态属性或方法,以实例化该类,比如单例模式

public class Singleton
{
	private static Singleton _instance;
	private int _state;
	private Singleton(int state) => _state = state;
	public static Singleton Instance => _instance ?? (_instance = new Singleton(42));
}

构造函数中调用其他构造函数

如果你对自己有要求 | “回顾,再出发”——记2020软工提问回顾与个人总结

class Car
{
	private string _description;
	private uint _nWheels;
	public Car(string description, uint nWheels)
	{
		_description = description;
		_nWheels = nWheels;
	}
	public Car(string description): this(description, 4)
	{
	}
	// ...
}

通过this关键字调用另一个构造函数,这种语法称为构造函数初始化器。this关键字调用参数最匹配的那个构造函数。

注意,构造函数初始化器在构造函数的函数体之前执行。如:

var myCar = new Car("Proton Persona");

会先调用有两个参数的构造函数,然后调用只有一个参数的构造函数。

静态构造函数

C#可以给类定义无参数的静态构造函数,这种构造函数只执行一次。

静态构造函数只能访问类的静态成员,不能访问类的实例成员。

静态构造函数不能带任何参数,一个类也只能有一个静态构造函数。

在C#中,通常在第一次调用类的任何成员之前,执行静态构造函数。

public enum Color
{
	White,
	Red,
	Green,
	Blue,
	Black
}

  

public static class UserPreferences
{
	public static Color BackColor { get; }
	static UserPreferences()
	{
		DateTime now = DateTime.Now;
		if (now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday)
		{
			BackColor = Color.Green;
		}
		else
		{
			BackColor = Color.Red;
		}
	}
}

  

C#构造函数 -0028
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

Python 为什么推荐蛇形命名法?

如何 SSH 到 Linux 服务器里的特定目录及执行命令?

node实现文件属性批量修改(时间属性)

你是不是有遇到过这样的场景?使用 SSH 命令进入到服务器,然后再用 cd 命令进入到对应目录,再继续进行你的工作。

这种操作对于新手来讲特别常见,良许之前也是这样。在本文,老司机将带你来进行更高效的操作,只需一步即可达到你想要的效果。

而且,不仅仅是实现快速进入到 Linux 服务器特定的目录,还可以实现在连接上服务器的时候即执行一个对应的命令。

低效操作方式

如果你不知道本文介绍的方法,你很可能是分成两步来操作的:

第一步:使用 SSH 命令进入到远程服务器

ssh user@remote-system

第二步:使用 cd 命令进入到你想要的目录

cd <some-directory>

一条命令快速进入到服务器指定目录

上面提到的这种方式当然是可以的,但过于低效。这样操作你需要使用两条命令,但实际上,你完全可以使用一条命令即可实现你想要的效果,比如:

ssh -t pi@192.168.0.116 'cd /home/pi/tests ; bash'

通过这条命令,我们可以直接就进入到树莓派(远程服务器)中对应的目录里(即 /home/pi/tests)。后续你就可以再继续你的工作了。

在这里, -t 选项是表示强制伪终端分配,即使标准输入不是终端。如果不加的话,可能会有如下提示:

Pseudo-terminal will not be allocated because stdin

这里我们再用一个动画来直观地演示这个过程:

除此之外,你还可以使用下面这个命令:

【递归题】正确的打开方式,面试官听了都说精辟

ssh -t pi@192.168.0.116 'cd /home/pi/tests ; exec bash'

或者:

ssh -t pi@192.168.0.116 'cd /home/pi/tests && exec bash -l'

在这里,-l 选项将这个 bash 设置为登录 shell。

在上面的三条命令里,最后的参数都是 bash,是因为我的远程服务器默认的 shell 解释器是 bash 。如果你不知道你远程服务器所使用的 shell 解释器,可以使用以下命令:

ssh -t pi@192.168.0.116 'cd /home/pi/tests && exec $SHELL'

一条命令远程执行服务器命令

正如本文开头所讲的,我们不仅可以使用一条命令进入到远程服务器指定目录,还可以使用一条命令远程执行服务器命令。甚至,我们还可以使用一条命令进入到远程服务器的指定目录,再执行一条命令。

其实所使用的方法都是一样的,比如我们想进入到树莓派的 /home/pi/tests 目录,再执行 ls -al 命令,我们可以这样输入命令:

ssh -t pi@192.168.0.116 'cd /home/pi/tests && ls -al && exec $SHELL'

执行的结果如下:

[Alvin.Alvin-computer]  ssh -t pi@192.168.0.116 'cd /home/pi/tests && ls -al && exec $SHELL'
total 48
drwxr-xr-x  4 pi pi 4096 Apr  5 14:36 .
drwxr-xr-x 21 pi pi 4096 Apr 21 19:26 ..
drwxrwxrwx  7 pi pi 4096 Apr  5 17:28 GIC
drwxrwxrwx  3 pi pi 4096 Apr  5 17:37 gitchat
-rw-r--r--  1 pi pi  474 Apr  5 11:21 liangxu.json
-rwxr-xr-x  1 pi pi 8184 Mar 17 15:34 test
-rwxr-xr-x  1 pi pi 8184 Mar 17 15:34 test2
-rwxr-xr-x  1 pi pi 8184 Mar 17 15:34 test3
-rw-r--r--  1 pi pi  131 Mar 17 15:34 test.c

一个折中的方案

如果你觉得这条命令太长了不好敲,非要先进入到服务器,再 cd 到对应的目录。那么,我们可以修改远程服务器的 .bashrc 文件。

vim ~/.bashrc

将你要执行的命令写在里面。比如在这个场景下,我们可以这样加:

cd /home/pi/tests >& /dev/null

然后我们再执行 :wq 保存文件,再执行以下命令使更改生效:

source ~/.bashrc
或者
. ~/.bashrc

这样,我们一进入到服务器后,就会自动进入到 /home/pi/tests 目录里。如下动图所示:

但是,这个有个明显的弊端,就是我们只能进入到我们指定的目录,如果要换成其它目录,那只能再改 .bashrc 文件了。

公众号:良许Linux

有收获?希望老铁们来个三连击,给更多的人看到这篇文章

如何 SSH 到 Linux 服务器里的特定目录及执行命令?
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

一文看懂《最大子序列和问题》

ZooKeeper使用入门

【译】Announcing Entity Framework Core 5.0 Preview 5

ZooKeeper简介

ZooKeeper是一个分布式的,开源的分布式应用程序协调服务,是Hadoop的子项目之一。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

安装ZooKeeper

操作系统要求

操作系统 客户端 服务端 原生客户端 附加组件
GNU/Linux 开发/生产 开发/生产 开发/生产 开发/生产
Solaris 开发/生产 开发/生产 不支持 不支持
FreeBSD 开发/生产 开发/生产 不支持 不支持
Windows 开发/生产 开发/生产 不支持 不支持
Mac OS X 开发 开发 不支持 不支持

软件要求

Java 8及Java 11以上版本(Java 9和10不支持)

硬件要求

此硬件资源为官网推荐的配置,实际开发过程中不需要这么大,笔者测试1核1G内存20G硬盘的虚拟机即可运行。

  • 2核
  • 2G内存
  • 80G硬盘

下载安装并进行单点配置

  1. 下载页面地址:https://zookeeper.apache.org/releases.html
  2. 官网只提供tar.gz格式的压缩包,windows下载后按照zip之类的解压方式可能会导致解压后的包无法使用,笔者使用Git带的命令行执行linux的解压命令解压后使用,如果没有安装Git则建议使用虚拟机安装Linux使用。以下是正确解压和错误解压后的对比。

  1. 解压后的ZooKeeper默认是无法执行的,需要进行配置,将 apache-zookeeper-3.6.1/conf/zoo_sample.cfg复制一份并重命名为zoo.cfg,没什么特殊需要里边的配置项默认即可,笔者因为是在windows下使用,所以将datadir修改了。配置文件项说明如下:
配置项 说明
tickTime ZooKeeper使用的时间,单位毫秒,一般用于心跳检测,而ZooKeeper中的最小session超时时间是此项的两倍
dataDir 保留内存数据库快照的地址,如果不单独指定,事务日志也会记录在此
clientPort 服务端监听的端口号
initLimit 集群中的follower服务器与leader服务器之间初始连接时的最大心跳数
syncLimit 集群中follower服务器与leader服务器之间通讯时的最大心跳数
  1. 配置完成后即可在bin目录下执行对应的文件启动了,Windows下为zkServer.bat,Linux下为zkServer.sh

ZooKeeper应用

通过zkCli进行使用

  1. ZooKeeper启动后,可以通过bin目录下自带的客户端进行访问,Windows下为zkCli.bat,Linux下为zkCli.sh
  2. 启动时默认连接localhost:2181,如果有需要连接远程或其他端口的情况,可以如下添加参数:
zkCli.sh -server IP:Port
  1. 进入客户端后执行help(此处是一个随意的指令,只要不是zkCli支持的操作都可以)可查看其支持的操作,关于所有操作的介绍请参考官方页面:https://zookeeper.apache.org/doc/current/zookeeperCLI.html

    图解MySQL索引(三)—如何正确使用索引?

  2. 常用操作介绍:

  • 查看节点信息,节点路径不能以“/”结尾
ls /
ls /zookeeper
  • 创建一个节点
create /test
create /test/testa
  • 查看节点状态
stat /test
stat /test/testa
  • 删除节点
# 删除单个空节点
delete /test/testa
delete /test

# 级联删除
deleteall /test

*退出客户端

quit

通过ZooKeeper客户端使用

因为笔者的第一开发语言是Java,这里以Java为例。常用的ZooKeeper Java客户端用zkclient和Apache Curator两种。zkclient是github上的一个开源项目,该项目在2018年10月2日后停止更新;Apache Curator是Apache基金会的开源项目,目前持续更新,推荐使用。常用的分布式RPC框架DUBBO也在2019年1月份推出的2.7.0版本中将默认的ZooKeeper客户端由zkclient切换为Apache Curator,此文中的示例也使用Apache Curator。

  1. 创建一个Maven项目,然后在pom.xml中引用Apache Curator,以下是笔者的文件内容,除了Apache Curator外添加了测试使用的junit并设置了编译使用的java版本。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>apache-curator</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.curator/curator-recipes -->
        <dependency>
            <groupId>org.apache.curator</groupId>
            <artifactId>curator-recipes</artifactId>
            <version>4.3.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.6.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
  1. 之后在src\test\java\目录创建com\aotian\curator\test\Tester.java,文件基本框架如下,主要是创建一个空的测试类
public class Tester {

    @Test
    public void testCurator() {
      
    }

}
  1. 接下来就是使用Apache Curator提供的API对ZooKeeper进行访问了。首先介绍下常用的API
  • 创建客户端
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
curatorFramework.start();
  • 检查节点是否存在,存在的话返回Stat对象,不存在则返回null
curatorFramework.checkExists().forPath("/localhost/aotian");
  • 创建节点,forPath第二个参数可以指定节点内容,不设置时创建空节点
curatorFramework.create().creatingParentContainersIfNeeded().forPath("/localhost/aotian", message.getBytes());
  • 设置节点内容,仅适用于已存在的节点,否则会报错
curatorFramework.setData().forPath("/localhost/aotian", message.getBytes());
  • 获取节点信息,以下代码表示将获取的节点信息保存到result对象。
Stat result = new Stat();
curatorFramework.getData().storingStatIn(result).forPath("/localhost/aotian");
  • 获取节点内容
byte[] results = curatorFramework.getData().forPath("/localhost/aotian");
  1. 完整示例如下,结尾添加了线程睡眠的代码,可以在睡眠时间内通过zkCli查看服务端中的内容。
    @Test
    public void testCurator() {
        // 创建客户端
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient("localhost:2181", retryPolicy);
        curatorFramework.start();
        // 定义节点内容
        String message = "testCurator";
        try {
            // 判断节点是否存在不存在则创建,存在则设置指定值
            Stat a = curatorFramework.checkExists().forPath("/localhost/aotian");
            if (a == null){
                curatorFramework.create()
                        .creatingParentContainersIfNeeded()
                        .forPath("/localhost/aotian", message.getBytes());
            }else{
                curatorFramework.setData().forPath("/localhost/aotian", message.getBytes());
            }

            // 获取节点信息
            Stat result = new Stat();
            curatorFramework.getData().storingStatIn(result).forPath("/localhost/aotian");
            System.out.println(result.getCtime());

            // 获取节点内容
            byte[] results = curatorFramework.getData().forPath("/localhost/aoitan");
            System.out.println(new String(results));

            // 线程睡10S,这段时间内可以通过客户端查看节点内的信息,结束后只能查看到空节点
            Thread.sleep(100000);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            curatorFramework.close();
        }
    }

ZooKeeper集群搭建

ZooKeeper集群中包含两种角色:Leader和Follower,因为ZooKeeper集群是半数节以上节点正常时才会正常提供服务,所以一般ZooKeeper集群中节点数量均为奇数。我们按照最小数量算,准备三台zookeeper服务器。

  1. 分别按照本文一开始的单机配置配置好三个ZooKeeper服务。个人联系或可以在同一台机器上部署三个ZooKeeper,只要解决端口冲突问题即可,实际生产过程中务必使用三台机器进行搭建,否则一旦机器出问题则整个集群瘫痪。
  2. 准备好三台ZooKeeper服务器之后我们准备开始集群的配置,首先我们需要规划好ZooKeeper的ID,然后在datadir属性对应的目录下创建一个myid文件。然后在文件内写上当前服务对应的ID,笔者规划的是0、1、2,所以我需要添加的配置文件如下:
IP地址 文件路径 文件内容
192.168.142.7 /tmp/zookeeper/myid 0
192.168.142.8 /tmp/zookeeper/myid 1
192.168.142.9 /tmp/zookeeper/myid 2

datadir属性默认在/tmp目录下,此目录会被定期清理掉,生产环境不要使用。

3、配置完以上文件后,需要配置之前的zoo.cfg,在最后添加以下内容,其中server.*对应myid文件中的ID号,192.168.142.7是IP地址,2888是ZooKeeper集群的通讯端口,3888是集群选取Leader使用的端口。

server.0=192.168.142.7:2888:3888
server.1=192.168.142.8:2888:3888
server.2=192.168.142.9:2888:3888

4、最后检查防火墙是否开放了2181、2888、3888端口,确认开放后启动ZooKeeper即可。通过执行zkServer.sh status命令可以查看当前机器的状态。

[root@centos-server-01 bin]# ./zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /usr/apache-zookeeper-3.6.0/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: follower

[root@centos-server-02 bin]# ./zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /usr/apache-zookeeper-3.6.0/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: leader

ZooKeeper使用入门
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

Java容器相关知识点整理

tensorflow-TFRecord 文件详解

为什么Web开发人员在2020年不用最新的CSS功能

TFRecord 是 tensorflow 内置的文件格式,它是一种二进制文件,具有以下优点:

1. 统一各种输入文件的操作

2. 更好的利用内存,方便复制和移动

3. 将二进制数据和标签(label)存储在同一个文件中

 

引言

在了解如下操作后进一步详细讲解TFRecord

 

tf.train.Int64List(value=list_data)

它的作用是 把 list 中每个元素转换成 key-value 形式,

注意,输入必须是 list,且 list 中元素类型要相同,且与 Int 保持一致;

# value = tf.constant([1, 2])     ### 这会报错的
ss = 1               ### Int64List 对应的元素只能是 int long,其他同理
tt = 2
out1 = tf.train.Int64List(value = [ss, tt])
print(out1)
# value: 1
# value: 2

ss = [1 ,2]
out2 = tf.train.Int64List(value = ss)
print(out2)
# value: 1
# value: 2

 

同类型的 方法还有 2 个

tf.train.FloatList
tf.train.BytesList

 

tf.train.Feature(int64_list=)

它的作用是 构建 一种类型的特征集,比如 整型

out = tf.train.Feature(int64_list=tf.train.Int64List(value=[33, 22]))
print(out)
# int64_list {
#   value: 33
#   value: 22
# }

也可以是其他类型

tf.train.Feature(float_list=tf.train.FloatList())
tf.train.Feature(bytes_list=tf.train.BytesList())

 

tf.train.Features(feature=dict_data)

它的作用是 构建 多种类型 的特征集,可以 dict 格式表达 多种类型

ut = tf.train.Features(feature={
                            "suibian": tf.train.Feature(int64_list=tf.train.Int64List(value=[1, 2, 4])),
                            "a": tf.train.Feature(float_list=tf.train.FloatList(value=[5., 7.]))
                        })
print(out)
# feature {
#   key: "a"
#   value {
#     float_list {
#       value: 5.0
#       value: 7.0
#     }
#   }
# }
# feature {
#   key: "suibian"
#   value {
#     int64_list {
#       value: 1
#       value: 2
#       value: 4
#     }
#   }
# }

 

tf.train.Example(features=tf.train.Features())

它的作用是创建一个 样本,Example 对应一个样本

example = tf.train.Example(features=
                           tf.train.Features(feature={
                               'a': tf.train.Feature(int64_list=tf.train.Int64List(value=range(2))),
                               'b': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'm',b'n']))
                           }))
print(example)
# features {
#   feature {
#     key: "a"
#     value {
#       int64_list {
#         value: 0
#         value: 1
#       }
#     }
#   }
#   feature {
#     key: "b"
#     value {
#       bytes_list {
#         value: "m"
#         value: "n"
#       }
#     }
#   }
# }

 

一幅图总结一下上面的代码

 

Example 协议块

它其实是一种 数据存储的 格式,类似于 xml、json 等;

用上述方法实现该格式;

一个 Example 协议块对应一个样本,一个样本有多种特征,每种特征下有多个元素,可参看上图;

message Example{
    Features features = 1;
}
message Features{
    map<string,Features> feature = 1;
}
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloateList float_list = 2;
        Int64List int64_list = 3;
    }
}

TFRecord 文件就是以 Example协议块 格式 存储的;

 

TFRecord 文件

该类文件具有写功能,且可以把其他类型的文件转换成该类型文件,其实相当于先读取其他文件,再写入 TFRecord 文件;

该类文件也具有读功能;

 

TFRecord 存储

存储分两步:

1.建立存储器 

2. 构造每个样本的 Example 协议块

 

tf.python_io.TFRecordWriter(file_name)

构造存储器,存储器有两个常用方法

  • write(record):向文件中写入一个样本
  • close():关闭存储器

注意:此处的 record 为一个序列化的 Example,通过 Example.SerializeToString()来实现,它的作用是将 Example 中的 map 压缩为二进制,节约大量空间

 

示例代码1:将 MNIST 数据集保存成 TFRecord 文件

import tensorflow as tf
import numpy as np
import input_data


# 生成整数型的属性
def _int64_feature(value):
    return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))

# 生成字符串类型的属性,也就是图像的内容
def _string_feature(value):
    return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value]))

# 读取图像数据 和一些属性
mniset = input_data.read_data_sets('../../../data/MNIST_data',dtype=tf.uint8, one_hot=True)
images = mniset.train.images
labels = mniset.train.labels
pixels = images.shape[1]        # (55000, 784)
num_examples = mniset.train.num_examples        # 55000

file_name = 'output.tfrecords'          ### 文件名
writer = tf.python_io.TFRecordWriter(file_name)     ### 写入器

for index in range(num_examples):
    ### 遍历样本
    image_raw = images[index].tostring()        ### 图片转成 字符型
    example = tf.train.Example(features = tf.train.Features(feature = {
        'pixel': _int64_feature(pixels),
        'label': _int64_feature(np.argmax(labels[index])),
        'image_raw': _string_feature(image_raw)
    }))
    writer.write(example.SerializeToString())       ### 写入 TFRecord
writer.close()

 

示例代码2:将 csv 保存成 TFRecord 文件

train_frame = pd.read_csv("../myfiles/xx3.csv")
train_labels_frame = train_frame.pop(item="label")
train_values = train_frame.values
train_labels = train_labels_frame.values
print("values shape: ", train_values.shape)     # values shape:  (2, 3)
print("labels shape:", train_labels.shape)      # labels shape: (2,)

writer = tf.python_io.TFRecordWriter("xx3.tfrecords")

for i in range(train_values.shape[0]):
    image_raw = train_values[i].tostring()
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[train_labels[i]]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

示例3:将 png 文件保存成 TFRecord 文件

# filenames = tf.train.match_filenames_once('../myfiles/*.png')
filenames = glob.iglob('..\myfiles\*.png')

writer = tf.python_io.TFRecordWriter('png.tfrecords')

for filename in filenames:
    img = Image.open(filename)
    img_raw = img.tobytes()
    label = 1
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

TFRecord 读取

参考链接:https://www.cnblogs.com/nbk-zyc/p/13159986.html(案例6)、https://www.cnblogs.com/nbk-zyc/p/13168313.html

 

tf.TFRecordReader()

建立读取器,有 read 和 close 方法

tf.parse_single_example(serialized,features=None,name= None)

解析单个 Example 协议块

  • serialized : 标量字符串的Tensor,一个序列化的Example,文件经过文件阅读器之后的value
  • features :字典数据,key为读取的名字,value为FixedLenFeature
  • return : 一个键值对组成的字典,键为读取的名字

features中的value还可以为tf.VarLenFeature(),但是这种方式用的比较少,它返回的是SparseTensor数据,这是一种只存储非零部分的数据格式,了解即可。

tf.FixedLenFeature(shape,dtype)

  • shape : 输入数据的形状,一般不指定,为空列表
  • dtype : 输入数据类型,与存储进文件的类型要一致,类型只能是float32,int 64, string
  • return : 返回一个定长的 Tensor (即使有零的部分也存储)

 

示例代码

filename = 'png.tfrecords'
file_queue = tf.train.string_input_producer([filename], shuffle=True)

reader = tf.TFRecordReader()
key, value = reader.read(file_queue)

### features 的 key 必须和 写入时 一致,数据类型也必须一致,shape 可为 空
dict_data= tf.parse_single_example(value, features={'label': tf.FixedLenFeature(shape=(1,1), dtype=tf.int64),
                                                        'image_raw': tf.FixedLenFeature(shape=(), dtype=tf.string)})
label = tf.cast(dict_data['label'], tf.int32)
img = tf.decode_raw(dict_data['image_raw'], tf.uint8)       ### 将 string、bytes 转换成 int、float

image_tensor = tf.reshape(img, [500, 500, -1])

sess = tf.Session()
sess.run(tf.local_variables_initializer())
tf.train.start_queue_runners(sess=sess)

while 1:
    # print(sess.run(key))        # b'png.tfrecords:0'
    image = sess.run(image_tensor)
    img_PIL = Image.fromarray(image)
    img_PIL.show()

 

参考资料:

https://blog.csdn.net/chengshuhao1991/article/details/78656724

https://www.cnblogs.com/yanshw/articles/12419616.html

Python进阶——详解元类,metaclass的原理和用法

tensorflow-TFRecord 文件详解
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。,

TFRecord 是 tensorflow 内置的文件格式,它是一种二进制文件,具有以下优点:

1. 统一各种输入文件的操作

2. 更好的利用内存,方便复制和移动

3. 将二进制数据和标签(label)存储在同一个文件中

 

引言

在了解如下操作后进一步详细讲解TFRecord

 

tf.train.Int64List(value=list_data)

它的作用是 把 list 中每个元素转换成 key-value 形式,

注意,输入必须是 list,且 list 中元素类型要相同,且与 Int 保持一致;

# value = tf.constant([1, 2])     ### 这会报错的
ss = 1               ### Int64List 对应的元素只能是 int long,其他同理
tt = 2
out1 = tf.train.Int64List(value = [ss, tt])
print(out1)
# value: 1
# value: 2

ss = [1 ,2]
out2 = tf.train.Int64List(value = ss)
print(out2)
# value: 1
# value: 2

 

同类型的 方法还有 2 个

tf.train.FloatList
tf.train.BytesList

 

tf.train.Feature(int64_list=)

它的作用是 构建 一种类型的特征集,比如 整型

out = tf.train.Feature(int64_list=tf.train.Int64List(value=[33, 22]))
print(out)
# int64_list {
#   value: 33
#   value: 22
# }

也可以是其他类型

tf.train.Feature(float_list=tf.train.FloatList())
tf.train.Feature(bytes_list=tf.train.BytesList())

 

tf.train.Features(feature=dict_data)

它的作用是 构建 多种类型 的特征集,可以 dict 格式表达 多种类型

ut = tf.train.Features(feature={
                            "suibian": tf.train.Feature(int64_list=tf.train.Int64List(value=[1, 2, 4])),
                            "a": tf.train.Feature(float_list=tf.train.FloatList(value=[5., 7.]))
                        })
print(out)
# feature {
#   key: "a"
#   value {
#     float_list {
#       value: 5.0
#       value: 7.0
#     }
#   }
# }
# feature {
#   key: "suibian"
#   value {
#     int64_list {
#       value: 1
#       value: 2
#       value: 4
#     }
#   }
# }

 

tf.train.Example(features=tf.train.Features())

它的作用是创建一个 样本,Example 对应一个样本

example = tf.train.Example(features=
                           tf.train.Features(feature={
                               'a': tf.train.Feature(int64_list=tf.train.Int64List(value=range(2))),
                               'b': tf.train.Feature(bytes_list=tf.train.BytesList(value=[b'm',b'n']))
                           }))
print(example)
# features {
#   feature {
#     key: "a"
#     value {
#       int64_list {
#         value: 0
#         value: 1
#       }
#     }
#   }
#   feature {
#     key: "b"
#     value {
#       bytes_list {
#         value: "m"
#         value: "n"
#       }
#     }
#   }
# }

 

一幅图总结一下上面的代码

 

Example 协议块

它其实是一种 数据存储的 格式,类似于 xml、json 等;

用上述方法实现该格式;

一个 Example 协议块对应一个样本,一个样本有多种特征,每种特征下有多个元素,可参看上图;

message Example{
    Features features = 1;
}
message Features{
    map<string,Features> feature = 1;
}
message Feature {
    oneof kind {
        BytesList bytes_list = 1;
        FloateList float_list = 2;
        Int64List int64_list = 3;
    }
}

TFRecord 文件就是以 Example协议块 格式 存储的;

 

TFRecord 文件

该类文件具有写功能,且可以把其他类型的文件转换成该类型文件,其实相当于先读取其他文件,再写入 TFRecord 文件;

该类文件也具有读功能;

 

TFRecord 存储

存储分两步:

1.建立存储器 

2. 构造每个样本的 Example 协议块

 

tf.python_io.TFRecordWriter(file_name)

构造存储器,存储器有两个常用方法

  • write(record):向文件中写入一个样本
  • close():关闭存储器

注意:此处的 record 为一个序列化的 Example,通过 Example.SerializeToString()来实现,它的作用是将 Example 中的 map 压缩为二进制,节约大量空间

 

示例代码1:将 MNIST 数据集保存成 TFRecord 文件

import tensorflow as tf
import numpy as np
import input_data


# 生成整数型的属性
def _int64_feature(value):
    return tf.train.Feature(int64_list = tf.train.Int64List(value = [value]))

# 生成字符串类型的属性,也就是图像的内容
def _string_feature(value):
    return tf.train.Feature(bytes_list = tf.train.BytesList(value = [value]))

# 读取图像数据 和一些属性
mniset = input_data.read_data_sets('../../../data/MNIST_data',dtype=tf.uint8, one_hot=True)
images = mniset.train.images
labels = mniset.train.labels
pixels = images.shape[1]        # (55000, 784)
num_examples = mniset.train.num_examples        # 55000

file_name = 'output.tfrecords'          ### 文件名
writer = tf.python_io.TFRecordWriter(file_name)     ### 写入器

for index in range(num_examples):
    ### 遍历样本
    image_raw = images[index].tostring()        ### 图片转成 字符型
    example = tf.train.Example(features = tf.train.Features(feature = {
        'pixel': _int64_feature(pixels),
        'label': _int64_feature(np.argmax(labels[index])),
        'image_raw': _string_feature(image_raw)
    }))
    writer.write(example.SerializeToString())       ### 写入 TFRecord
writer.close()

 

示例代码2:将 csv 保存成 TFRecord 文件

train_frame = pd.read_csv("../myfiles/xx3.csv")
train_labels_frame = train_frame.pop(item="label")
train_values = train_frame.values
train_labels = train_labels_frame.values
print("values shape: ", train_values.shape)     # values shape:  (2, 3)
print("labels shape:", train_labels.shape)      # labels shape: (2,)

writer = tf.python_io.TFRecordWriter("xx3.tfrecords")

for i in range(train_values.shape[0]):
    image_raw = train_values[i].tostring()
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[train_labels[i]]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

示例3:将 png 文件保存成 TFRecord 文件

# filenames = tf.train.match_filenames_once('../myfiles/*.png')
filenames = glob.iglob('..\myfiles\*.png')

writer = tf.python_io.TFRecordWriter('png.tfrecords')

for filename in filenames:
    img = Image.open(filename)
    img_raw = img.tobytes()
    label = 1
    example = tf.train.Example(
        features=tf.train.Features(
            feature={
                "image_raw": tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }
        )
    )
    writer.write(record=example.SerializeToString())
writer.close()

 

TFRecord 读取

参考链接:https://www.cnblogs.com/nbk-zyc/p/13159986.html(案例6)、https://www.cnblogs.com/nbk-zyc/p/13168313.html

 

tf.TFRecordReader()

建立读取器,有 read 和 close 方法

tf.parse_single_example(serialized,features=None,name= None)

解析单个 Example 协议块

  • serialized : 标量字符串的Tensor,一个序列化的Example,文件经过文件阅读器之后的value
  • features :字典数据,key为读取的名字,value为FixedLenFeature
  • return : 一个键值对组成的字典,键为读取的名字

features中的value还可以为tf.VarLenFeature(),但是这种方式用的比较少,它返回的是SparseTensor数据,这是一种只存储非零部分的数据格式,了解即可。

tf.FixedLenFeature(shape,dtype)

  • shape : 输入数据的形状,一般不指定,为空列表
  • dtype : 输入数据类型,与存储进文件的类型要一致,类型只能是float32,int 64, string
  • return : 返回一个定长的 Tensor (即使有零的部分也存储)

 

示例代码

filename = 'png.tfrecords'
file_queue = tf.train.string_input_producer([filename], shuffle=True)

reader = tf.TFRecordReader()
key, value = reader.read(file_queue)

### features 的 key 必须和 写入时 一致,数据类型也必须一致,shape 可为 空
dict_data= tf.parse_single_example(value, features={'label': tf.FixedLenFeature(shape=(1,1), dtype=tf.int64),
                                                        'image_raw': tf.FixedLenFeature(shape=(), dtype=tf.string)})
label = tf.cast(dict_data['label'], tf.int32)
img = tf.decode_raw(dict_data['image_raw'], tf.uint8)       ### 将 string、bytes 转换成 int、float

image_tensor = tf.reshape(img, [500, 500, -1])

sess = tf.Session()
sess.run(tf.local_variables_initializer())
tf.train.start_queue_runners(sess=sess)

while 1:
    # print(sess.run(key))        # b'png.tfrecords:0'
    image = sess.run(image_tensor)
    img_PIL = Image.fromarray(image)
    img_PIL.show()

 

参考资料:

https://blog.csdn.net/chengshuhao1991/article/details/78656724

https://www.cnblogs.com/yanshw/articles/12419616.html

tensorflow-TFRecord 文件详解
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

.NET Framework、.NET Core 和 .NET 5+ 的产品生命周期

cute-cnblogs 自定义博客园样式美化二期来啦~

过来人告诉你,去工作前最好还是学学Git

cute-cnblogs 自定义博客园样式美化二期来啦~

说明

cute-cnblogs 可爱的博客园样式美化、自定义博客园样式 二期样式已经编写完毕了,如果说 一期样式 给人的感觉是简洁清爽的小婴儿的话,那么 二期样式 就是一个有自己小个性(花样)的小朋友了~

与一期一样,需要文件的可以来 github ,喜欢我写的样式可以帮我点个 star 喔 ღゝ◡╹)ノ

(PS:有什么问题也可以留言到 github issues 中喔~)

好了,让我们撸起袖子开始更换二期样式吧~

博客示例

ღゝ◡╹)ノ 麋鹿鲁哟的博客园

介绍

可爱的博客园样式美化、自定义博客园样式 ღゝ◡╹)ノ

  • 本样式以简约可爱为核心,美化博客园的显示效果,增加自定义导航;
  • 基于博客园主题“Custom”进行的样式修改;
  • 阅读目录导航;
  • 支持响应式;

功能

一期功能

  • 顶部背景点点动效随鼠标而动
  • 导航栏自定义
  • 页面诗意诗句模块
  • 看板娘-猫(ฅ´ω`ฅ) (自行决定是否添加,因为这个有些影响加载速度)
  • 音乐-网易云 (自行决定是否添加)
  • 上吊的猫(PS:回到顶部)(已用其余按钮代替此功能)
  • 底部跳动的鱼<・)))><<
  • 点击页面跳动的小豆子及可爱的文字(自行决定是否添加可爱文字的点击)
  • 评论增加OωO聊天表情
  • 页面不同的导航背景及人物背景

二期增加功能

  • 侧边栏显示
  • 侧边栏公告栏及个人信息显示
  • 阅读目录(标题 h1 > h2 > h3 写了三级目录)
  • ️ 主题皮肤切换(浅白、暗黑、阅读)
  • 仿主播点赞功能点击推荐
  • ️ 赞赏模块功能

模版选定

博客皮肤选定: 博客园 Custom 标准模板(与一期不同喔)

使用方法

基本操作

请按照顺序进行操作喔~

  • 首先记得申请JS权限

  • 其次博客皮肤选择 Custom

  • 在此需要获取数据(不然点击头像的关注会失败)
    找一个没有登陆的浏览器访问自己的博客园,F12弹出窗口,找到 +加关注,复制follow括号里的内容,暂且先存在一个地方

  • 勾选禁用模板默认CSS

    布局之: flex(CSS3新增)

  • 创建一个新随笔(这里使用选项中的TinyMCE(推荐)来编写的) —— “友链”;(注意,此处已与一期不同)
  • 点击 “编辑 HTML 源代码” 插入以下代码,修改自己的文本内容后,点击更新;
  • 只勾选 高级选项中的 “发布”、“允许评论”;
<p style="text-align: center;">欢迎来到我的友链小屋</p>
<div class="friendsbox">
<div id="app">
<h6 style="text-align: center; color: #2daebf;">展示本站所有友情站点,排列不分先后,均匀打乱算法随机渲染的喔!</h6>
<div class="unionbox-box" style="display: flex; flex-wrap: wrap; justify-content: space-between; margin-bottom: 1.5rem; margin-top: 1.5rem;">&nbsp;</div>
<hr style="position: relative; margin: 1rem 0; border: 1px dashed #9eabb3; width: calc(100%); height: 0;" />
<h2 style="text-align: center;">友链信息</h2>
<h5 style="text-align: left; line-height: 30px;">博客名称:<a href="javascript:void(0)">麋鹿鲁哟</a><br />博客网址:<a href="javascript:void(0)">https://www.cnblogs.com/miluluyo/</a><br />博客头像:<a href="javascript:void(0)">https://pic.cnblogs.com/avatar/1273193/20190806180831.png</a><br />博客介绍:<a href="javascript:void(0)">大道至简,知易行难。</a></h5>
<h2 style="text-align: center;">join us</h2>
<h5 style="text-align: center; color: #2daebf;">如需友链,请添加微信(s978761)告知,格式如下</h5>
<table class="table friendstable" style="margin: 0 auto;">
<tbody>
<tr><th><strong>字段</strong></th><th><strong>字义</strong></th></tr>

</tbody>

</table>

</div>

</div>

  • 最后分别复制以下区域代码,并根据参数更改数据(PS:路径可进行更改也可不更改,我觉得更改后速度会快一点,自行down文件上传到博客园文件中,并更改引入路径,文件都在 github 中,喜欢记得给我个star喔~)

页面定制CSS代码

复制 https://blog-static.cnblogs.com/files/miluluyo/cute-cnblogs2.css 的文件内容放到 页面定制CSS代码 区域

博客侧边栏公告

<link href="https://blog-static.cnblogs.com/files/miluluyo/tippy.min.css" rel="stylesheet">
<script src="https://blog-static.cnblogs.com/files/miluluyo/jquery2.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/tippy.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/milusidebar.js"></script>

<script>
milusidebar({
	'names' : '麋鹿鲁哟',/*你的博客园名呐*/
	'notice' : '<b>温馨提示</b><span><a href="https://github.com/miluluyo/cute-cnblogs" target="_black">cute-cnblogs</a> &nbsp;样式已开源</span><b style="margin-top: 3px;"><a style="font-size:10px" href="https://www.cnblogs.com/IsAlpaca/" target="_black">查看一期样式</a></b>',/*里面文字自己可以更改喔*/
	'headerUrl' : 'https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519075219notice5.png',/*这个是公告栏的背景图啦,我觉得这个可爱,如果你有更好看的可以自行更改喔*/
	'follow' : 'a1e76459-101d-47af-a8b6-08d523685c8c', /*还记的开始让你复制follow括号里的内容吗,对,就放到这里就好啦*/
	'sidebarInfo' : [[
	      {'icon':'#icon-github1','url':'https://github.com/miluluyo','title':'github'},
	      {'icon':'#icon-weixin','url':'','title':'微信','classname':'popper_weixin','click':false},
	      {'icon':'#icon-QQ','url':'http://wpa.qq.com/msgrd?v=3&uin=978761587&site=qq&menu=yes','title':'QQ'},
	      {'icon':'#icon-juejin','url':'https://juejin.im/user/5d18adce5188256e98090e33','title':'掘金'}
	  ],[
	      {'icon':'#icon-weibobangding','url':'https://www.weibo.com/6001406082/profile?topnav=1&wvr=6','title':'微博'},
	      {'icon':'#icon-csdn','url':'https://blog.csdn.net/qq_39394518','title':'CSDN'},
	      {'icon':'#icon-bilibili','url':'https://space.bilibili.com/100007925','title':'bilibili'},
	      {'icon':'#icon-yuquemianlogo','url':'https://www.yuque.com/miluluyo','title':'语雀'}
	]],/*这个模块是个人信息内那些小图标们,别忘记更改喔,具体参数,可以参考下面的表格喔*/
	'signature':'靡不有初  鲜克有终',/*来一句你自己喜欢的句子吧*/
	'popper_weixin':'<div class="popper_box"><p><b>很高兴认识你鸭~  (づ。◕ᴗᴗ◕。)づ</b> </p><div class="popper_box_con"><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200614064005qrcode.jpg" alt="">公众号:麋鹿鲁哟</div><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1493340/t_wxh.jpg" alt="">微信号:s978761</div></div><p>(加我记得备注 博客园 喔)</div>',/*这里是微信图标的弹窗内容,可以自行更改内容喔*/
	'portrait':'https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200515061851tx.jpg'
})/*这个是头像图片喔,你可以上传到相册里,然后F12获取,或者使用博客园的那个链接也可以的撒~*/
</script>

参数说明

名称 类型 默认值/实例 描述
names 字符串 ‘麋鹿鲁哟’ 博客园名称
notice 字符串 ‘<b>温馨提示</b><span><a href=”https://github.com/miluluyo/cute-cnblogs” target=”_black”>cute-cnblogs</a>  样式已开源</span><b style=”margin-top: 3px;”><a style=”font-size:10px” href=”https://www.cnblogs.com/IsAlpaca/” target=”_black”>查看一期样式</a></b>’ 公告内容
headerUrl 字符串 ‘https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519075219notice5.png’ 公告栏的背景图
follow 字符串 ‘a1e76459-101d-47af-a8b6-08d523685c8c’ 复制follow括号里的内容,这是关注的那个码
sidebarInfo 数组 [[ {‘icon’:’#icon-github1′,’url’:’https://github.com/miluluyo’,’title’:’github’}, {‘icon’:’#icon-weixin’,’url’:”,’title’:’微信’,’classname’:’popper_weixin’,’click’:false}, {‘icon’:’#icon-QQ’,’url’:’http://wpa.qq.com/msgrd?v=3&uin=978761587&site=qq&menu=yes’,’title’:’QQ’}, {‘icon’:’#icon-juejin’,’url’:’https://juejin.im/user/5d18adce5188256e98090e33′,’title’:’掘金’} ],[ {‘icon’:’#icon-weibobangding’,’url’:’https://www.weibo.com/6001406082/profile?topnav=1&wvr=6′,’title’:’微博’}, {‘icon’:’#icon-csdn’,’url’:’https://blog.csdn.net/qq_39394518′,’title’:’CSDN’}, {‘icon’:’#icon-bilibili’,’url’:’https://space.bilibili.com/100007925′,’title’:’bilibili’}, {‘icon’:’#icon-yuquemianlogo’,’url’:’https://www.yuque.com/miluluyo’,’title’:’语雀’} ]] 个人信息内那些小图标们
icon 图标
url 跳转链接
title 提示名字
classname 要添加的class名
click 是否允许点击跳转
本框架有扩展的icon,文件在 github 中的 icon 文件夹内,可以下载去查看
signature 字符串 ‘靡不有初 鲜克有终’ 个人信息签名 (写一句喜欢的话吧)
popper_weixin 字符串 ‘< div class=”popper_box”>< p>< b>很高兴认识你鸭~ (づ。◕ᴗᴗ◕。)づ< /b> < /p>< div class=”popper_box_con”>< div class=”popper_box_con_li”>< img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200614064005qrcode.jpg” alt=””>公众号:麋鹿鲁哟< /div>< div class=”popper_box_con_li”>< img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1493340/t_wxh.jpg” alt=””>微信号:s978761< /div>< /div>< p>(加我记得备注 博客园 喔)< /div>’ 微信焦点弹窗,内容可自行更改,可以放一些公众号啊啥的~
portrait 字符串 ‘https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200515061851tx.jpg’ 头像图片路径

页首Html代码

  <div id="set_btn_box">
    <div class="set_btn fly_top fadeIn animated">
        <svg class="icon" aria-hidden="true"><use xlink:href="#icon-zhiding"></use></svg>
    </div>
    <div class="set_btn article_icon_btn catalogue_btn">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-dagang"></use></svg>
    </div>
    <div class="set_btn article_icon_btn comment">
        <a href="#comment_form_container"><svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-linedesign-01"></use></svg></a>
    </div>
    <div class="set_btn skin_btn">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-pifu"></use></svg>
    </div>
    <div class="set_btn gratuity">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-dashang"></use></svg>
    </div>
    <div class="set_btn article_icon_btn artice_recommend">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-tuijian2"></use></svg>
    </div>
     <canvas id="thumsCanvas" width="200" height="400" style="width:100px;height:200px"></canvas>
    <div class="set_btn catalogue">
        <svg class="icon" aria-hidden="true" style="color:#97A1A7"><use xlink:href="#icon-cebianlan-"></use></svg>
    </div>
</div>
<script src='https://blog-static.cnblogs.com/files/miluluyo/canvas2.js'></script>
<!--
<link href="//files.cnblogs.com/files/linianhui/lnh.cnblogs.css" rel="stylesheet"/>-->

页脚Html代码

  <style id="ceshicss">
@media (max-width: 767px){
#set_btn_box {width: 100vw;left: 0;right: 0;bottom: 0;background: hsla(0,0%,100%,.6);height: 49px;display: flex;justify-content: space-between;align-items: center;padding: 12px 40px;border-top: 1px solid #e8e8e8;box-sizing: border-box;}
.set_btn {margin-top: 0;}
.set_btn.fly_top.fadeIn.animated {position: absolute;right: 10px;bottom: 60px;}
.container{bottom:50px}}
#mainContent{width:90%}
</style>
<link href="https://blog-static.cnblogs.com/files/miluluyo/tippy.min.css" rel="stylesheet">
<script src="https://unpkg.com/@popperjs/core@2.4.2/dist/umd/popper.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/tippy.js"></script>
<link rel='stylesheet' href='https://cdn.bootcss.com/animate.css/3.7.2/animate.min.css'>
<script src="https://at.alicdn.com/t/font_1825850_klax1ao4o6.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/three.min.js"></script>
<script src='https://blog-static.cnblogs.com/files/miluluyo/star.js'></script>
<link rel="stylesheet" href="https://blog-static.cnblogs.com/files/miluluyo/OwO.min.css" />
<script src="https://blog-static.cnblogs.com/files/miluluyo/OwO2.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/cute-cnblogs2.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/monitoring2.js"></script>

<script>

miluframe({
  Youself:'https://www.cnblogs.com/miluluyo/', /*个人的博客园链接*/
  /*博客园导航信息*/
    custom:[{
      name:'首页',
      link:'https://www.cnblogs.com/miluluyo/',
      istarget:false
    },{
      name:'联系',
      link:'https://msg.cnblogs.com/send/%E9%BA%8B%E9%B9%BF%E9%B2%81%E5%93%9F',
      istarget:true
    },{
      name:'技能树',
      link:'https://miluluyo.github.io/',
      istarget:true
    },{
      name:'留言板',
      link:'https://www.cnblogs.com/miluluyo/p/11578505.html',
      istarget:false
    },{
      name:'相册',
      link:'https://www.cnblogs.com/miluluyo/gallery.html',
      istarget:false
    },{
      name:'友链',
      link:'https://www.cnblogs.com/miluluyo/p/11633791.html',
      istarget:false
    },{
      name:'维护',
      link:'https://www.cnblogs.com/miluluyo/p/12092009.html',
      istarget:false
    },{
      name:'投喂',
      link:'https://www.cnblogs.com/miluluyo/p/gratuity.html',
      istarget:false
    },{
      name:'管理',
      link:'https://i.cnblogs.com/',
      istarget:true
    }],
    /*向别人展示自己的友链信息*/
    resume:{
        "name":"麋鹿鲁哟",
        "link":"https://www.cnblogs.com/miluluyo/",
        "headurl":"https://images.cnblogs.com/cnblogs_com/elkyo/1558759/o_o_my.jpg",
        "introduction":"大道至简,知易行难。"
    },
    /*友链信息*/
    unionbox:[{
        "name":"麋鹿鲁哟",
        "introduction":"生活是没有标准答案的。",
        "url":"https://www.cnblogs.com/miluluyo",
        "headurl":"https://images.cnblogs.com/cnblogs_com/elkyo/1558759/o_o_my.jpg"
      },{
        "name":"麋鹿鲁哟的技能树",
        "introduction":"大道至简,知易行难。",
        "url":"https://miluluyo.github.io/",
        "headurl":"https://images.cnblogs.com/cnblogs_com/elkyo/1558759/o_o_my.jpg"
      }],
    /*友链表格头信息,这个可以忽略*/
    details:[{
        field: 'name',
        literal: '昵称',
      },{
        field: 'introduction',
        literal: '标语',
      },{
        field: 'url',
        literal: '链接地址',
      },{
        field: 'headurl',
        literal: '头像地址',
      }],
    /*浏览器顶部小图标*/
    logoimg:'https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519070633f12.png',
    /*文章页面标题前的图标,此处图标有扩展,下面会提到图标*/
    cuteicon:['icon-caomei','icon-boluo','icon-huolongguo','icon-chengzi','icon-hamigua','icon-lizhi','icon-mangguo','icon-liulian','icon-lizi','icon-lanmei','icon-longyan','icon-shanzhu','icon-pingguo','icon-mihoutao','icon-niuyouguo','icon-xigua','icon-putao','icon-xiangjiao','icon-ningmeng','icon-yingtao','icon-taozi','icon-shiliu','icon-ximei','icon-shizi'],
    /*赞赏,若true则显示此按钮,false则不显示*/
    isGratuity:true,
    /*赞赏按钮焦点显示赞赏内容,内容可自行更改*/
    gratuity:'<div class="popper_box"><p><b>要请我喝奶茶吗  (づ。◕ᴗᴗ◕。)づ</b> </p><div class="popper_box_con"><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053817wx.png" alt="">微信扫码</div><div class="popper_box_con_li"><img src="https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053827zfb.png" >支付宝扫码</div></div><p><b>留下一句你觉得很励志与美的话给我吧~</b>&nbsp;&nbsp;<b><a href="https://www.cnblogs.com/miluluyo/p/12930946.html">GO</a></b></div>'
})
</script>
<!-- 点赞 -->
<canvas width="1777" height="841" style="position: fixed; left: 0px; top: 0px; z-index: 2147483647; pointer-events: none;"></canvas><script src="https://blog-static.cnblogs.com/files/miluluyo/mouse-click.js"></script>

<!-- 以下内容是否添加你随意 -->

<script>
  /*在文章页面添加古诗词*/
  $("#navigator").after('<div class="poem-wrap"><div class="poem-border poem-left"></div><div class="poem-border poem-right"></div><h1>念两句诗</h1><div id="poem_sentence"></div><div id="poem_info"></div></div>')
</script>
<script src="https://sdk.jinrishici.com/v2/browser/jinrishici.js" charset="utf-8"></script>
<script type="text/javascript">
  jinrishici.load(function(result) {
    var sentence = document.querySelector("#poem_sentence")
    var info = document.querySelector("#poem_info")
    sentence.innerHTML = result.data.content
    info.innerHTML = '【' + result.data.origin.dynasty + '】' + result.data.origin.author + '《' + result.data.origin.title + '》'
  });
</script>

<script type="text/javascript">
/* 鼠标特效,我觉得太花哨了就注释了,喜欢的自己打开注释就可以 */
/*var a_idx = 0;
jQuery(document).ready(function($) {
    $("body").click(function(e) {
        var a = new Array("去活出你自己。","今天的好计划胜过明天的完美计划。","不要轻言放弃,否则对不起自己。","紧要关头不放弃,绝望就会变成希望。","如果不能改变结果,那就完善过程。","好好活就是干有意义的事,有意义的事就是好好活!","你真正是谁并不重要,重要的是你的所做所为。","你不想为你的信仰冒一下险吗?难道想等你老了,再后悔莫及吗?","有些鸟儿是关不住的,它的每一根羽毛都闪耀着自由的光辉。","决定我们成为什么样人的,不是我们的能力,而是我们的选择。","掉在水里你不会淹死,呆在水里你才会淹死,你只有游,不停的往前游。","有些路,只能一个人走。","希望你眼眸有星辰,心中有山海。","从此以梦为马,不负韶华。","人的成就和差异决定于其业余时间。","佛不要你皈依,佛要你欢喜。","ダーリンのこと 大好きだよ","小猫在午睡时,地球在转。","我,混世大魔王,申请做你的小熊软糖。","决定好啦,要暗暗努力。","呐,做人呢最紧要开心。","好想邀请你一起去云朵上打呼噜呀。","永远年轻,永远热泪盈眶。","我生来平庸,也生来骄傲。","我走得很慢,但我从不后退。","人间不正经生活手册。","我是可爱的小姑娘,你是可爱。","数学里,有个温柔霸道的词,有且仅有。","吧唧一口,吃掉难过。","你头发乱了哦。","健康可爱,没有眼袋。","日月星辰之外,你是第四种难得。","你是否成为了了不起的成年人?","大家都是第一次做人。","何事喧哗?!","人间有味是清欢。","你笑起来真像好天气。","风填词半句,雪斟酒一壶。","除了自渡,他人爱莫能助。","昨日种种,皆成今我。","一梦入混沌 明月撞星辰","保持独立 适当拥有","谢谢你出现 这一生我很喜欢","做自己就好了 我会喜欢你的","太严肃的话,是没办法在人间寻欢作乐的","愿你余生可随遇而安,步步慢。","黄瓜在于拍,人生在于嗨","奇变偶不变,符号看象限。","从来如此,便对么?","今天我这儿的太阳,正好适合晒钙 你呢","未来可期,万事胜意。","星光不问赶路人 时光不负有心人","我当然不会试图摘月,我要月亮奔我而来","女生要修炼成的五样东西: 扬在脸上的自信,长在心底的善良, 融进血里的骨气,刻进命里的坚强,深到骨子里的教养","燕去燕归,沧海桑田。纵此生不见,平安惟愿","我想认识你 趁风不注意","我一直想从你的窗子里看月亮","长大应该是变温柔,对全世界都温柔。","别在深夜做任何决定","山中何事,松花酿酒,春水煎茶。","桃李春风一杯酒,江湖夜雨十年灯。","欲买桂花同载酒,终不似,少年游。");
        var le = Math.ceil(Math.random()*a.length); 
        var $i = $("<span></span>").text(a[le]);/*a[a_idx]*/
        /*a_idx = (a_idx + 1) % a.length;
        var x = e.pageX,
        y = e.pageY;
        $i.css({
            "z-index": 999999999999999999999999999999999999999999999999999999999999999999999,
            "top": y - 20,
            "left": x,
            "position": "absolute",
            "font-weight": "bold",
            "color": "rgb("+~~(255*Math.random())+","+~~(255*Math.random())+","+~~(255*Math.random())+")"
        });
        $("body").append($i);
        $i.animate({
            "top": y - 180,
            "opacity": 0
        },
        2000,
        function() {
            $i.remove();
        });
    });
});*/
</script>


<!--音乐,只在PC端宽度>1000px时显示-->
<link rel="stylesheet" href="https://blog-static.cnblogs.com/files/miluluyo/APlayer.min.css">
<div id="player" class="aplayer aplayer-withlist aplayer-fixed" data-id="3116636104" data-server="netease" data-type="playlist" data-order="random" data-fixed="true" data-listfolded="true" data-theme="#2D8CF0"></div>
<script src="https://blog-static.cnblogs.com/files/miluluyo/APlayer.min.js"></script>
<script src="https://blog-static.cnblogs.com/files/miluluyo/Meting.min.js"></script>

<!--猫,只在PC端显示,移动端不加载了,因为会卡顿页面-->
<script src="https://eqcn.ajz.miesnfu.com/wp-content/plugins/wp-3d-pony/live2dw/lib/L2Dwidget.min.js"></script>
<script>
  var mobile_flag = isMobile();
  if(mobile_flag){
    //console.info("移动端")
  }else{
    //console.info("PC端")
    L2Dwidget.init({
        "model": {
            "jsonPath": "https://unpkg.com/live2d-widget-model-hijiki/assets/hijiki.model.json",
            "scale": 1
        },
        "display": {
            "position": "left",
            "width": 100,
            "height": 200,
            "hOffset": 70,
            "vOffset": 0
        },
        "mobile": {
            "show": true,
            "scale": 0.5
        },
        "react": {
            "opacityDefault": 0.7,
            "opacityOnHover": 0.2
        }
    });
    window.onload = function(){
      $("#live2dcanvas").attr("style","position: fixed; opacity: 0.7; left: 70px; bottom: 0px; z-index: 1; pointer-events: none;")
    }
  }

</script>

<script>

/*记录访问数据,我用了两个,一个是这个在 https://clustrmaps.com/ 网站,另一个是 https://www.51.la/ 这个网站*/
/*
	第一种:https://clustrmaps.com/
	注册账号,添加自己博客园的链接,选择自定义Customize,
	选择 Image based (basic version for websites that don't support javascript),调整到你喜欢的样式,然后复制
	:这个我插入在了侧边栏的最底部,把生成的代码粘贴到append内,这就完事了
*/
$('#sideBarMain').append('')

/*
	第二种:https://www.51.la/
	注册账号,点控制台,添加统计ID,统计图标显示我是不显示的
	这个目前插入的js好像报错,我的是很早之前生成的,还能用,因此还是推荐用第一种吧

	别的地方也有这种很多统计访问数据的,可以自己找找看呢

*/
</script>

参数说明

名称 类型 默认值/实例 描述
Youself 字符串 https://www.cnblogs.com/miluluyo/ 个人博客园首链接
custom 数组 [{ name:’首页’, link:’https://www.cnblogs.com/miluluyo/’, istarget:false },{ name:’联系’, link:’https://msg.cnblogs.com/send/%E9%BA%8B%E9%B9%BF%E9%B2%81%E5%93%9F’, istarget:true },{ name:’技能树’, link:’https://miluluyo.github.io/’, istarget:true },{ name:’留言板’, link:’https://www.cnblogs.com/miluluyo/p/11578505.html’, istarget:false },{ name:’相册’, link:’https://www.cnblogs.com/miluluyo/gallery.html’, istarget:false },{ name:’友链’, link:’https://www.cnblogs.com/miluluyo/p/11633791.html’, istarget:false },{ name:’维护’, link:’https://www.cnblogs.com/miluluyo/p/12092009.html’, istarget:false },{ name:’投喂’, link:’https://www.cnblogs.com/miluluyo/p/gratuity.html’, istarget:false },{ name:’管理’, link:’https://i.cnblogs.com/’, istarget:true }] 导航信息
name 导航名
link 导航链接
istarget true跳转到新页面上,false当前页面打开
resume 对象 {
“name”:”麋鹿鲁哟”,
“link”:”https://www.cnblogs.com/miluluyo/”,
“headurl”:”https://images.cnblogs.com/cnblogs_com/
elkyo/1558759/o_o_my.jpg”,
“introduction”:”大道至简,知易行难。”
}
自己的友链信息
name 导航名
link 导航链接
headurl 头像
introduction 语录
unionbox 数组 [{
“name”:”麋鹿鲁哟”,
“introduction”:”生活是没有标准答案的。”,
“url”:”https://www.cnblogs.com/miluluyo”,
“headurl”:”https://images.cnblogs.com/cnblogs_com/
elkyo/1558759/o_o_my.jpg”
},{
“name”:”麋鹿鲁哟的技能树”,
“introduction”:”大道至简,知易行难。”,
“url”:”https://miluluyo.github.io/”,
“headurl”:”https://images.cnblogs.com/cnblogs_com/
elkyo/1558759/o_o_my.jpg”
}]
友链数组
name 昵称
introduction 标语
url 链接地址
headurl 头像地址
clicktext 数组 [{ field: ‘name’, literal: ‘昵称’, },{ field: ‘introduction’, literal: ‘标语’, },{ field: ‘url’, literal: ‘链接地址’, },{ field: ‘headurl’, literal: ‘头像地址’, }] 友链表格头信息,这项可以忽略掉
logoimg 字符串 ‘https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200519070633f12.png’ 浏览器顶部小图标
cuteicon 数组 [‘icon-caomei’,’icon-boluo’,’icon-huolongguo’,’icon-chengzi’,’icon-hamigua’,’icon-lizhi’,’icon-mangguo’,’icon-liulian’,’icon-lizi’,’icon-lanmei’,’icon-longyan’,’icon-shanzhu’,’icon-pingguo’,’icon-mihoutao’,’icon-niuyouguo’,’icon-xigua’,’icon-putao’,’icon-xiangjiao’,’icon-ningmeng’,’icon-yingtao’,’icon-taozi’,’icon-shiliu’,’icon-ximei’,’icon-shizi’] 文章页面标题前的图标,此处图标我只放入了一些水果的icon,不过可以自己引入文件进行修改名字添加自己想加的,本框架有扩展的icon,文件在 github 中的 icon 文件夹内,可以下载去查看
gratuity 字符串 ‘<div class=”popper_box”><p><b>要请我喝奶茶吗 (づ。◕ᴗᴗ◕。)づ</b> </p><div class=”popper_box_con”><div class=”popper_box_con_li”><img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053817wx.png” alt=””>微信扫码</div><div class=”popper_box_con_li”><img src=”https://images.cnblogs.com/cnblogs_com/miluluyo/1765646/o_200521053827zfb.png” >支付宝扫码</div></div><p><b>留下一句你觉得很励志与美的话给我吧~</b>  <b><a href=”https://www.cnblogs.com/miluluyo/p/12930946.html”>GO</a></b></div>’ 赞赏按钮焦点显示赞赏内容,内容可自行更改
isGratuity 布尔值 true 默认true,若true则显示此按钮,false则不显示

更换顶部背景图

当前框架使用了一张图片,也可以自己进行更换成随机图片API

在css样式中

 #blogTitle{background:url(https://images.cnblogs.com/cnblogs_com/miluluyo/1764887/o_20051406472117.jpg) center center / cover no-repeat #222;overflow:hidden;width:100%;height:40vh;max-height:40vh;box-shadow:0 1px 2px rgba(150,150,150,.7);       /*搜索这个 更换 background: url() 里的链接 即可*/

最后

更多内容请查看 cute-cnblogs 自定义番外篇
(PS:可以使用番外篇里的随机图片API喔~)

请吃糖

如果您喜欢这里,感觉对你有帮助,并且有多余的软妹币的话,不妨投喂一颗糖喔~

<(▰˘◡˘▰)> 谢谢老板~

微信扫码

支付宝扫码

赞赏的时候,留下一句你觉得很励志与美的话给我吧~

(也可以加一个博客园给我喔,会添加在名字的旁边喔~点击可以跳转~ 例如:去瞧瞧都有谁赞赏了

为了响应大家的号召,方便大家技术交流,之前建立了一个微信群,如果大家有需要可以扫码(或者搜我微信号:s978761)加我好友,我邀请你加入~本群是一个纯交流学习工作的群,不准发布广告、营销相关的信息!对了,加我记得备注上:博客园+名称 加群 喔~

微信号:s978761

公众号:麋鹿鲁哟

cute-cnblogs 自定义博客园样式美化二期来啦~
免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

.Net Core服务监控报警指标上报Prometheus+Grafana

估值大师达摩达兰:估值方法必须向前看

估值大师达摩达兰:估值方法必须向前看
2020-12-03

投诉

查看源网址
阅读数:256
阿斯沃斯·达摩达兰(Aswath Damodaran)已经在纽约大学的斯特恩商学院教会了成千上万的学生如…
来自专辑
巴伦专访

文|《巴伦周刊》撰稿人阿尔·鲁特

编辑|彭韧

新的估值方法要从静态走向动态,从回顾走向前瞻。

阿斯沃斯·达摩达兰(Aswath Damodaran)已经在纽约大学的斯特恩商学院教会了成千上万的学生如何给股票估值。在课堂之外,他是全球最知名的股市估值专家之一,当记者试图了解一家炙手可热的新公司如何与传统分析衡量标准背道而驰时,他往往是首选的信息来源。

这位62岁的教授获得了加州大学洛杉矶分校的硕士学位和博士学位。他著有四本关于股票估值的书。《巴伦周刊》最近采访了达摩达兰达摩达兰不害怕挑战有关有争议股票的假设。在决定一只股票值多少钱时,他的声音和其他人一样重要。

以下是经过编辑的对话。

《巴伦周刊》 :跟我们谈谈如何传授股票估值。

达摩达兰:我所知道的关于估值的一切,都是在教授这门课的过程中学到的。我记得1987年10月,标普500指数一天之内暴跌22% 。第二天,我回到了教室,跟人们谈论市场如何能一下子改变22%?你还能相信价值吗? 

还有上世纪90年代的互联网泡沫。 不管我在1997年是否愿意,我都不得不谈论亚马逊网站。这个公司收入如此微薄,一切去取决于未来,怎么可能想象它现在的价值呢?在1997年,没有任何关于如何评估年轻公司的东西,我不得不即兴创作我所知道的估值。

即兴创作就是关键。通过即兴创作我才自我哄骗地走到今天这一步的。因为每次我遇到一些我从未见过的东西,我就会回到基础,令人惊讶的是,这些基础中有如此多让步空间,以至于我可以延伸它们来满足我需要的任何要求,我面对的任何条件。但对我来说,每一次危机都是一次严峻的考验。它考验着我的信仰,它检验了我的哲学,它教会了我新的东西。

 《巴伦周刊》 :那么最基础的是什么?

达摩达兰:现金流、增长和风险。我告诉人们关于十四世纪一位威尼斯玻璃制造商的事情。当他卖掉自己的生意时,人们并没有检查他的收益报表,他们检查了现金的流入和流出状况,看看这些数字有多稳定。他们也做了他们自己版本的现金贴现估值。我们所做的就是利用这些基础,不断增加新的层次,有时候层次太多了。每当我犹豫不决时,我就会回到这些基础问题上。

 《巴伦周刊》 :你说上世纪80年代的估值很无趣,为什么?

达摩达兰:我认为,上世纪90年代最大的区别在于,公开市场蚕食了曾经的私人市场空间。在上世纪70年代和80年代的IPO中,无论是苹果(Apple)还是微软(Microsoft) ,公司几乎已经完全成型。即使它不赚钱,也离赚钱很近了。

但互联网公司泡沫改变了这一切,并且是永久性改变,从此你可以拥有那些曾经在私人领域停留更长时间的公司,现在它们有上市的能力了。旧的衡量标准——市盈率、市净率、企业价值与税息折旧及摊销前利润(Ebitda)的比率——都是为那些相当成熟的公司设计的。你试试用这些指标,来给一个仍在建立商业模式的年轻公司定价。比如类似于 Uber 这样的公司就是符合这类描述的公司,什么指标都用不上。

《巴伦周刊》 :对。

达摩达兰:当这种情况发生时,人们的反应很奇怪。然后他们说,哦,这些人疯了。他们不理智,他们一定很肤浅。我认为,这是我发现旧式的价值投资者非常令人不安的地方。他们使用他们能找到的度量标准,将这些度量标准刻舟求剑地用在了 Zoom Video Communication (ZM)或者Zillow Group (Z)身上,然后他们说,这些人疯了。 

而我说,看,也许那些人并不疯狂,也许你只是使用了一种不适合衡量这些公司价值的标准。估值仍然是关于现金流,增长和风险,但这些现金流、增长和风险都来自于未来。你必须学会处理和估计它。

 《巴伦周刊》 :在巴伦,我们经常默认使用市盈率。如果价值投资者要更新一下判断估值是否过高的“捷径”,最佳方式是什么?

达摩达兰:我认为金融的一个问题是,它主要是20世纪下半叶在美国发展起来的。20世纪下半叶的美国经济是有史以来最稳定、最均值回归的经济。如果你看看传统的旧式价值投资,那就是投低市盈率股票。它之所以有效,是因为当低市盈率股票回归到平均水平,你就赚到了钱。均值回归会一直有效,直到失效为止。

《巴伦周刊》 :什么意思?

达摩达兰:美国是世界上最大的经济体,但它不是全球经济经济。因此,自2008年以来,我们一直反对使用席勒市盈率(Shiller P/E)和 CAPE (经周期性调整的市盈率) ,因为有很多人仅仅因为标准普尔正常市盈率大概为16,就偷懒地争论说股票被高估了。你从哪里得到的数据?席勒的数据来自于1871年到2009年,得了吧。

《巴伦周刊》 :标准的估值方法必然有一定的价值?

达摩达兰:我不是个学者,我是个实用主义者。我不相信市场是有效率的,但我也不相信许多主动投资行为,至少是现在实践的那些主动投资,能够寻找和发掘这些市场无效率的利润。

但我确实认为,市场总是在传递信息。如果你忽略了这些信息,或者你认为自己比市场更厉害,市场就会把你降低几个档次。因此,我认为这是我最重要的信息:从静态走向动态,从回顾走向前瞻。这让人们感到害怕。

 《巴伦周刊》 :那么,投资者如何才能从静态转向动态呢?

达摩达兰:我们不喜欢犯错。我认为我们使用历史数据的原因并不是因为它们能给我们更好的预测,而是因为如果出了什么差错,我们可以找到能责备的东西。你可以说,看,我只是用了过去的收益,这不是我的错。所以,我们需要做的第一件事就是放弃对它的需求,不让历史数据变成拐杖。 

第二个是,我告诉人们,你不需要变得聪明,但是你需要接受你将会犯错的事实。当你做错事的时候,你必须愿意改变你做事的方式。有一种根深蒂固的观点认为,华尔街有精明钱。而我在寻找谦卑的钱。

 《巴伦周刊》 :你对当今最热门的股票有什么看法?

达摩达兰:如果价格不对,我不会在乎公司有多么伟大,那可不是什么好投资。Facebook 和苹果都在我的投资组合中。亚马逊已经是过去了,我从来没有拥有过谷歌。我必须承认谷歌是我一直低估的公司之一,我低估了那个小小搜索框的能量 。

我喜欢 Facebook,因为我认为它将继续带来广告收入,因为这是世界正在发展的方式。我们从社交媒体获得新闻。我们从社交媒体中获得娱乐。如果有20亿用户每天花一个小时在这个平台上,而且我甚至都还没计算 WhatsApp 未来的收入,我认为这就是一台赚钱机器。

我喜欢苹果公司,不是因为它会成为一个成长型公司。对于苹果公司我是一个现实主义者。这是一家成熟的低增长公司,但它是历史上最伟大的提款机。

 《巴伦周刊》 :那么一些零工经济型公司呢?

达摩达兰:我对 Uber 有点怀疑。很长一段时间以来,我一直告诉人们,我喜欢 Uber 这个成长机器,但不喜欢它的商业模式。它被设计成这样一种方式,可以很快地扩大规模,但是很难赚钱,它几乎没有任何进入的障碍。

2019年,在 Uber 的历史上,首席执行官达拉·科斯罗萨西(Dara Khosrowshahi)第一次说,嘿,我们必须弄清楚如何赚钱。我的回答是,感谢上帝。去年九月,我买入了 Uber。我对它的估值在28到30美元之间。基本上,汽车服务业将由两家公司主导,Uber 和 Lyft。

 《巴伦周刊》 :那么特斯拉呢?

达摩达兰:今年年初,我卖掉了特斯拉。我是去年年中买的,因为我觉得它的股价应该是180美元。我幸运得不可思议。我认为,这场危机以一种奇怪的方式让特斯拉在汽车领域成为了一家更强大的公司,因为相对于它的竞争对手,它是轻资本的。特斯拉CEO埃隆 · 马斯克在帐篷里制造汽车这一事实对他有利。一件让我总是害怕给特斯拉估值的事情是,它总是吸引疯子。

 《巴伦周刊》 :你对当前的市场反弹有什么看法?

达摩达兰:今年将会是糟糕的一年。我们都同意这一点。现在,我们可以讨论收益是下降30% 还是40% ,但是他们会下降得一塌糊涂。真正的讨论应该是未来三年、四年、五年会发生什么。这种损害有多少是永久性的,有多少是短暂的?这才是一个健康的讨论。

当你们讨论的时候,你会发现,有些企业的损失是永久性的。比如邮轮业务,它永远回不到危机前的估值,这是有充分理由的。但范围还更广 。我认为基础设施企业(航空公司和电信公司) ,尤其是那些负债累累的企业,在这次危机之后将被永久性减记,因为人们已经认识到这些企业非常脆弱,它们不是为了经历这样的危机而设计的。

《巴伦周刊》 :谢谢,阿斯沃斯。 

翻译 | 小彩

版权声明:

《巴伦周刊》(barronschina)原创文章,未经许可,不得转载。英文版见2020年6月25日报道“Buying Tesla at $180 and Other Investing Nuggets From NYU Professor Aswath Damodaran”。

(本文内容仅供参考,投资建议不代表《巴伦周刊》倾向;市场有风险,投资须谨慎。)

听说公众号平台改变了推送规则,

得到的“在看”越多,
文章在信息流中越靠前。 
为了更好的相遇,
奖励小巴一个大拇指吧!
也欢迎将本号设为星标



    会计老兵


    分享者
    《会计结账实务大全》作者,《证券市场周刊》特约作者,CPA会计视野前版主,资深网友

    +关注


    免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

    Linux nohup命令详解,终端关闭程序依然可以在执行!

    SpringBoot + Mybatis + Redis 整合入门项目

    大家好,我是良许。

    在工作中,我们很经常跑一个很重要的程序,有时候这个程序需要跑好几个小时,甚至需要几天,这个时候如果我们退出终端,或者网络不好连接中断,那么程序就会被中止。而这个情况肯定不是我们想看到的,我们希望即使终端关闭,程序依然可以在跑。

    这时我们就可以使用 nohup 这个命令。

    nohup 命令是英语词组 no hangup 的缩写,意思是不挂断,也就是指程序不退出。这个命令会使程序忽略 HUP 信号,保证程序能够正常进行。HUP 信号有些人可能比较陌生,它是在终端被中止的时候向它所关联的进程所发出的信号,进程收到这个信号后就会中止运行。所以如果你不希望进程被这个信号干掉的话,就可以忽略这个信号。而 nohup 命令做的就是这个事情。

    本文我们将详细介绍 nohup 命令的具体用法。

    nohup命令基本语法

    nohup 命令的基本语法如下:

    $ nohup command arguments
    

    或者:

    $ nohup options
    

    如果你想要得到更多关于 nohup 的用法介绍,可以查看它的帮助页面:

    $ nohup --help
    

    如果你需要查看它的版本号,可以使用 --version 选项。

    $ nohup --version
    

    使用nohup命令启动一个程序

    如果你需要运行一个程序,即使对应的 Shell 被退出后依然保持运行,可以这样使用 nohup 运行这个程序:

    $ nohup command
    

    当这个程序进行起来之后,这个程序对应的 log 输出及其错误日志都将被记录在 nohup.out 文件里,这个文件一般位于家目录或者当前目录。

    重定向程序的输出

    如果我不想把程序的输出保存在家目录或者当前目录,我想保存在我指定的路径,并且自定义文件名,要怎么操作?这时我们就可以使用重定向操作 >

    比如,我现在有个脚本 myScript.sh 我想把它的输出保存在家目录下的 output 目录下,文件名为 myOutput.txt ,可以这样运行:

    基于NACOS和JAVA反射机制动态更新JAVA静态常量非@Value注解

    $ nohup ./myScript.sh > ~/output/myOutput.txt
    

    使用nohup命令后台启动一个程序

    如果想让程序在后台运行,可以加上 & 符号。但这样运行之后,程序就无影无踪了。想要让程序重新回到终端,可以使用 fg 命令。

    这个命令的输出 log 将保存在 nohup.out 文件里,你可以使用 cat 或其它命令查看。第二行里 8699 这个数字代表这个命令对应的进程号,也就是 pid 。我们可以使用 ps 命令来找到这个进程。

    使用nohup同时运行多个程序

    如果你需要同时跑多个程序,没必要一个个运行,直接使用 && 符号即可。比如,你想同时跑 mkdir ,ping,ls 三个命令,可以这样运行:

    $ nohup bash -c 'mkdir files &&
    ping -c 1 baidu.com && ls'> output.txt
    

    终止跑在后台的进程

    上面有提到,nohup 命令结合 & 符号可以使进程在后台运行,即使关闭了终端依然不受影响。这时,如果想要终止这个进程,要怎么操作呢?

    最简单的当属 kill 命令,相信大家用过很多次了。

    $ kill -9 PID
    

    那要如何找到进程对应的 pid 呢?我们可以使用 ps 命令。

    $ ps aux | grep myScript.sh
    

    或者你使用 pgrep 命令也行。

    接下来,再使用 kill 命令就可以终止该进程了。

    $ kill -9 14942
    

    公众号:良许Linux

    有收获?希望老铁们来个三连击,给更多的人看到这篇文章

    Linux nohup命令详解,终端关闭程序依然可以在执行!
    免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

    学习ASP.NET Core(11)-解决跨域问题与程序部署

    环境篇:Kylin3.0.1集成CDH6.2.0

    Spring Data 教程 – Redis

    环境篇:Kylin3.0.1集成CDH6.2.0

    Kylin是什么?

    Apache Kylin™是一个开源的、分布式的分析型数据仓库,提供Hadoop/Spark 之上的 SQL 查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由 eBay 开发并贡献至开源社区。它能在亚秒内查询巨大的表。

    Apache Kylin™ 令使用者仅需三步,即可实现超大数据集上的亚秒级查询。

    1. 定义数据集上的一个星形或雪花形模型
    2. 在定义的数据表上构建cube
    3. 使用标准 SQL 通过 ODBC、JDBC 或 RESTFUL API 进行查询,仅需亚秒级响应时间即可获得查询结果

    如果没有Kylin

    大数据在数据积累后,需要计算,而数据越多,算力越差,内存需求也越高,询时间与数据量成线性增长,而这些对于Kylin影响不大,大数据中硬盘往往比内存要更便宜,Kylin通过与计算的形式,以空间换时间,亚秒级的响应让人们爱不释手。

    注:所谓询时间与数据量成线性增长:假设查询 1 亿条记录耗时 1 分钟,那么查询 10 亿条记录就需 10分钟,100 亿条记录就至少需要 1 小时 40 分钟。

    http://kylin.apache.org/cn/

    1 Kylin架构

    Kylin 提供与多种数据可视化工具的整合能力,如 Tableau,PowerBI 等,令用户可以使用 BI 工具对 Hadoop 数据进行分析

    1. REST Server REST Server

    是一套面向应用程序开发的入口点,旨在实现针对 Kylin 平台的应用开发 工作。 此类应用程序可以提供查询、获取结果、触发 cube 构建任务、获取元数据以及获取 用户权限等等。另外可以通过 Restful 接口实现 SQL 查询。

    1. 查询引擎(Query Engine)

    当 cube 准备就绪后,查询引擎就能够获取并解析用户查询。它随后会与系统中的其它 组件进行交互,从而向用户返回对应的结果。

    1. 路由器(Routing)

    在最初设计时曾考虑过将 Kylin 不能执行的查询引导去 Hive 中继续执行,但在实践后 发现 Hive 与 Kylin 的速度差异过大,导致用户无法对查询的速度有一致的期望,很可能大 多数查询几秒内就返回结果了,而有些查询则要等几分钟到几十分钟,因此体验非常糟糕。 最后这个路由功能在发行版中默认关闭。

    1. 元数据管理工具(Metadata)

    Kylin 是一款元数据驱动型应用程序。元数据管理工具是一大关键性组件,用于对保存 在 Kylin 当中的所有元数据进行管理,其中包括最为重要的 cube 元数据。其它全部组件的 正常运作都需以元数据管理工具为基础。 Kylin 的元数据存储在 hbase 中。

    1. 任务引擎(Cube Build Engine)

    这套引擎的设计目的在于处理所有离线任务,其中包括 shell 脚本、Java API 以及 MapReduce 任务等等。任务引擎对 Kylin 当中的全部任务加以管理与协调,从而确保每一项任务 都能得到切实执行并解决其间出现的故障。

    2 Kylin软硬件要求

    • 软件要求
      • Hadoop: 2.7+, 3.1+ (since v2.5)
      • Hive: 0.13 – 1.2.1+
      • HBase: 1.1+, 2.0 (since v2.5)
      • Spark (optional) 2.3.0+
      • Kafka (optional) 1.0.0+ (since v2.5)
      • JDK: 1.8+ (since v2.5)
      • OS: Linux only, CentOS 6.5+ or Ubuntu 16.0.4+
    • 硬件要求
      • 最低配置:4 core CPU, 16 GB memory
      • 高负载场景:24 core CPU, 64 GB memory

    3 Kylin单机安装

    3.1 修改环境变量

    vim /etc/profile 
    #>>>注意地址指定为自己的
    #kylin
    export KYLIN_HOME=/usr/local/src/kylin/apache-kylin-3.0.1-bin-cdh60
    export PATH=$PATH:$KYLIN_HOME/bin
        
    #cdh
    export CDH_HOME=/opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373
    
    #hadoop
    export HADOOP_HOME=${CDH_HOME}/lib/hadoop
    export HADOOP_DIR=${HADOOP_HOME}
    export HADOOP_CLASSPATH=${HADOOP_HOME}
    export PATH=$PATH:$HADOOP_HOME/bin
    export PATH=$PATH:$HADOOP_HOME/sbin
        
    #hbase
    export HBASE_HOME=${CDH_HOME}/lib/hbase
    export PATH=$PATH:$HBASE_HOME/bin
        
     #hive
    export HIVE_HOME=${CDH_HOME}/lib/hive
    export PATH=$PATH:$HIVE_HOME/bin
        
    #spark
    export SPARK_HOME=${CDH_HOME}/lib/spark
    export PATH=$PATH:$SPARK_HOME/bin   
    
    #kafka
    export KAFKA_HOME=${CDH_HOME}/lib/kafka
    export PATH=$PATH:$KAFKA_HOME/bin 
    #<<<
    
    source /etc/profile 
    

    3.2 修改hdfs用户权限

    usermod -s /bin/bash hdfs
    su hdfs
    hdfs dfs -mkdir /kylin
    hdfs dfs -chmod a+rwx /kylin
    su
    

    3.3 上传安装包解压

    mkdir /usr/local/src/kylin
    cd /usr/local/src/kylin
    tar -zxvf apache-kylin-3.0.1-bin-cdh60.tar.gz
    cd /usr/local/src/kylin/apache-kylin-3.0.1-bin-cdh60
    

    3.4 Java兼容hbase

    • hbase 所有节点

    在CLASSPATH=${CLASSPATH}:$JAVA_HOME/lib/tools.jar后添加

    >>---
    :/opt/cloudera/parcels/CDH/lib/hbase/lib/*
    <<---
    
    • Kylin节点添加jar包
    cp /opt/cloudera/cm/common_jars/commons-configuration-1.9.cf57559743f64f0b3a504aba449c9649.jar /usr/local/src/kylin/apache-kylin-3.0.1-bin-cdh60/tomcat/lib
    

    这2步不做会引起 Could not find or load main class org.apache.hadoop.hbase.util.GetJavaProperty

    3.5 启动停止

    ./bin/kylin.sh start
    #停止  ./bin/kylin.sh stop
    

    3.6 web页面

    访问端口7070

    账号密码:ADMIN / KYLIN

    4 Kylin集群安装

    4.1 修改环境变量

    vim /etc/profile 
    #>>>注意地址指定为自己的
    #kylin
    export KYLIN_HOME=/usr/local/src/kylin/apache-kylin-3.0.1-bin-cdh60
    export PATH=$PATH:$KYLIN_HOME/bin
        
    #cdh
    export CDH_HOME=/opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373
    
    #hadoop
    export HADOOP_HOME=${CDH_HOME}/lib/hadoop
    export HADOOP_DIR=${HADOOP_HOME}
    export HADOOP_CLASSPATH=${HADOOP_HOME}
    export PATH=$PATH:$HADOOP_HOME/bin
    export PATH=$PATH:$HADOOP_HOME/sbin
        
    #hbase
    export HBASE_HOME=${CDH_HOME}/lib/hbase
    export PATH=$PATH:$HBASE_HOME/bin
        
     #hive
    export HIVE_HOME=${CDH_HOME}/lib/hive
    export PATH=$PATH:$HIVE_HOME/bin
        
    #spark
    export SPARK_HOME=${CDH_HOME}/lib/spark
    export PATH=$PATH:$SPARK_HOME/bin   
    
    #kafka
    export KAFKA_HOME=${CDH_HOME}/lib/kafka
    export PATH=$PATH:$KAFKA_HOME/bin 
    #<<<
    
    source /etc/profile 
    

    4.2 修改hdfs用户权限

    usermod -s /bin/bash hdfs
    su hdfs
    hdfs dfs -mkdir /kylin
    hdfs dfs -chmod a+rwx /kylin
    su
    

    4.3 上传安装包解压

    mkdir /usr/local/src/kylin
    cd /usr/local/src/kylin
    tar -zxvf apache-kylin-3.0.1-bin-cdh60.tar.gz
    cd /usr/local/src/kylin/apache-kylin-3.0.1-bin-cdh60
    

    4.4 Java兼容hbase

    • hbase 所有节点

    在CLASSPATH=${CLASSPATH}:$JAVA_HOME/lib/tools.jar后添加

    vim /opt/cloudera/parcels/CDH/lib/hbase/bin/hbase
    >>---
    :/opt/cloudera/parcels/CDH/lib/hbase/lib/*
    <<---
    
    • Kylin节点添加jar包
    cp /opt/cloudera/cm/common_jars/commons-configuration-1.9.cf57559743f64f0b3a504aba449c9649.jar /usr/local/src/kylin/apache-kylin-3.0.1-bin-cdh60/tomcat/lib
    

    这2步不做会引起 Could not find or load main class org.apache.hadoop.hbase.util.GetJavaProperty

    4.5 修改kylin配置文件

    Kylin根据自己的运行职责状态,可以划分为以下三大类角色

    • Job节点:仅用于任务调度,不用于查询
    • Query节点:仅用于查询,不用于构建任务的调度
    • All节点:模式代表该服务同时用于任务调度和 SQL 查询
      • 2.0以前同一个集群只能有一个节点(Kylin实例)用于job调度(all或者job模式的只能有一个实例)
      • 2.0开始可以多个job或者all节点实现HA
    vim conf/kylin.properties
    >>----
    #指定元数据库路径,默认值为 kylin_metadata@hbase,确保kylin集群使用一致
    kylin.metadata.url=kylin_metadata@hbase
    #指定 Kylin 服务所用的 HDFS 路径,默认值为 /kylin,请确保启动 Kylin 实例的用户有读写该目录的权限
    kylin.env.hdfs-working-dir=/kylin
    kylin.server.mode=all
    kylin.server.cluster-servers=cdh01.cm:7070,cdh02.cm:7070,cdh03.cm:7070
    kylin.storage.url=hbase
    #构建任务失败后的重试次数,默认值为 0
    kylin.job.retry=2
    #最大构建并发数,默认值为 10
    kylin.job.max-concurrent-jobs=10
    #构建引擎间隔多久检查 Hadoop 任务的状态,默认值为 10(s)
    kylin.engine.mr.yarn-check-interval-seconds=10
    #MapReduce 任务启动前会依据输入预估 Reducer 接收数据的总量,再除以该参数得出 Reducer 的数目,默认值为 500(MB)
    kylin.engine.mr.reduce-input-mb=500
    #MapReduce 任务中 Reducer 数目的最大值,默认值为 500
    kylin.engine.mr.max-reducer-number=500
    #每个 Mapper 可以处理的行数,默认值为 1000000,如果将这个值调小,会起更多的 Mapper
    kylin.engine.mr.mapper-input-rows=1000000
    #启用分布式任务锁
    kylin.job.scheduler.default=2
    kylin.job.lock=org.apache.kylin.storage.hbase.util.ZookeeperJobLock
    <<----
    

    4.6 启动停止

    所有Kylin节点

    ./bin/kylin.sh start
    #停止  ./bin/kylin.sh stop
    

    4.7 nginx负载均衡

    yum -y install nginx
    
    vim /etc/nginx/nginx.conf
    >>---http中添加替换内容
    upstream kylin {
            least_conn;
            server 192.168.37.10:7070 weight=8;
            server 192.168.37.11:7070 weight=7;
            server 192.168.37.12:7070 weight=7;
    	}
        server {
            listen       9090;
            server_name  localhost;
    
            location / {
                    proxy_pass http://kylin;
            }
        }
    
    <<---
    
    #重启 nginx 服务
    systemctl restart nginx  
    

    4.8 访问web页面

    访问任何节点的7070端口都可以进入kylin

    访问nginx所在机器9090端口/kylin负载均衡进入kylin

    账号密码:ADMIN / KYLIN

    4 大规模并行处理@列式存储

    自从 10 年前 Hadoop 诞生以来,大数据的存储和批处理问题均得到了妥善解决,而如何高速地分析数据也就成为了下一个挑战。于是各式各样的“SQL on Hadoop”技术应运而生,其中以 Hive 为代表,Impala、Presto、Phoenix、Drill、 SparkSQL 等紧随其后(何以解忧–唯有CV SQL BOY)。它们的主要技术是“大规模并行处理”(Massive Parallel Processing,MPP)和“列式存储”(Columnar Storage)

    大规模并行处理可以调动多台机器一起进行并行计算,用线性增加的资源来换取计算时间的线性下降

    列式存储则将记录按列存放,这样做不仅可以在访问时只读取需要的列,还可以利用存储设备擅长连续读取的特点,大大提高读取的速率。

    这两项关键技术使得 Hadoop 上的 SQL 查询速度从小时提高到了分钟。 然而分钟级别的查询响应仍然离交互式分析的现实需求还很远。分析师敲入 查询指令,按下回车,还需要去倒杯咖啡,静静地等待查询结果。得到结果之后才能根据情况调整查询,再做下一轮分析。如此反复,一个具体的场景分析常常需要几小时甚至几天才能完成,效率低下。 这是因为大规模并行处理和列式存储虽然提高了计算和存储的速度,但并没有改变查询问题本身的时间复杂度,也没有改变查询时间与数据量成线性增长的关系这一事实。

    假设查询 1 亿条记录耗时 1 分钟,那么查询 10 亿条记录就需 10分钟,100 亿条记录就至少需要 1 小时 40 分钟。 当然,可以用很多的优化技术缩短查询的时间,比如更快的存储、更高效的压缩算法,等等,但总体来说,查询性能与数据量呈线性相关这一点是无法改变的。虽然大规模并行处理允许十倍或百倍地扩张计算集群,以期望保持分钟级别的查询速度,但购买和部署十倍或百倍的计算集群又怎能轻易做到,更何况还有 高昂的硬件运维成本。 另外,对于分析师来说,完备的、经过验证的数据模型比分析性能更加重要, 直接访问纷繁复杂的原始数据并进行相关分析其实并不是很友好的体验,特别是在超大规模的数据集上,分析师将更多的精力花在了等待查询结果上,而不是在更加重要的建立领域模型上

    5 Kylin如何解决海量数据的查询问题

    **Apache Kylin 的初衷就是要解决千亿条、万亿条记录的秒级查询问题,其中的关键就是要打破查询时间随着数据量成线性增长的这个规律。根据OLAP分析,可以注意到两个结论: **

    • 大数据查询要的一般是统计结果,是多条记录经过聚合函数计算后的统计值。原始的记录则不是必需的,或者访问频率和概率都极低。

    • 聚合是按维度进行的,由于业务范围和分析需求是有限的,有意义的维度聚合组合也是相对有限的,一般不会随着数据的膨胀而增长。

    **基于以上两点,我们可以得到一个新的思路——“预计算”。应尽量多地预先计算聚合结果,在查询时刻应尽量使用预算的结果得出查询结果,从而避免直 接扫描可能无限增长的原始记录。 **

    举例来说,使用如下的 SQL 来查询 11月 11日 那天销量最高的商品:

    select item,sum(sell_amount)
    from sell_details
    where sell_date='2020-11-11'
    group by item
    order by sum(sell_amount) desc
    

    用传统的方法时需要扫描所有的记录,再找到 11月 11日 的销售记录,然后按商品聚合销售额,最后排序返回。

    假如 11月 11日 有 1 亿条交易,那么查询必须读取并累计至少 1 亿条记录,且这个查询速度会随将来销量的增加而逐步下降。如果日交易量提高一倍到 2 亿,那么查询执行的时间可能也会增加一倍。

    而使用预 计算的方法则会事先按维度 [sell_date , item] 计 算 sum(sell_amount)并存储下来,在查询时找到 11月 11日 的销售商品就可以直接排序返回了。读取的记录数最大不会超过维度[sell_date,item]的组合数。

    显然这个数字将远远小于实际的销售记录,比如 11月 11日 的 1 亿条交易包含了 100万条商品,那么预计算后就只有 100 万条记录了,是原来的百分之一。并且这些 记录已经是按商品聚合的结果,因此又省去了运行时的聚合运算。从未来的发展来看,查询速度只会随日期和商品数目(时间,商品维度)的增长而变化,与销售记录的总数不再有直接联系。假如日交易量提高一倍到 2 亿,但只要商品的总数不变,那么预计算的结果记录总数就不会变,查询的速度也不会变。

    预计算就是 Kylin 在“大规模并行处理”和“列式存储”之外,提供给大数据分析的第三个关键技术。

    6 Kylin 入门案例

    6.1 hive数据准备

    --创建数据库kylin_hive
    create database kylin_hive; 
    
    --创建表部门表dept
    create external table if not exists kylin_hive.dept(
    deptno int,
    dname string,
    loc int )
    row format delimited fields terminated by '\t';
    --添加数据
    INSERT INTO TABLE kylin_hive.dept VALUES(10,"ACCOUNTING",1700),(20,"RESEARCH",1800),(30,"SALES",1900),(40,"OPERATIONS",1700)
    --查看数据
    SELECT * FROM kylin_hive.dept
    
    --创建员工表emp
    create external table if not exists kylin_hive.emp(
    empno int,
    ename string,
    job string,
    mgr int,
    hiredate string, 
    sal double, 
    comm double,
    deptno int)
    row format delimited fields terminated by '\t';
    
    --添加数据
    INSERT INTO TABLE kylin_hive.emp VALUES(7369,"SMITHC","LERK",7902,"1980-12-17",800.00,0.00,20),(7499,"ALLENS","ALESMAN",7698,"1981-2-20",1600.00,300.00,30),(7521,"WARDSA","LESMAN",7698,"1981-2-22",1250.00,500.00,30),(7566,"JONESM","ANAGER",7839,"1981-4-2",2975.00,0.00,20),(7654,"MARTIN","SALESMAN",7698,"1981-9-28",1250.00,1400.00,30),(7698,"BLAKEM","ANAGER",7839,"1981-5-1",2850.00,0.00,30),(7782,"CLARKM","ANAGER",7839,"1981-6-9",2450.00,0.00,10),(7788,"SCOTTA","NALYST",7566,"1987-4-19",3000.00,0.00,20),(7839,"KINGPR","ESIDENT",7533,"1981-11-17",5000.00,0.00,10),(7844,"TURNER","SALESMAN",7698,"1981-9-8",1500.00,0.00,30),(7876,"ADAMSC","LERK",7788,"1987-5-23",1100.00,0.00,20),(7900,"JAMESC","LERK",7698,"1981-12-3",950.00,0.00,30),(7902,"FORDAN","ALYST",7566,"1981-12-3",3000.00,0.00,20),(7934,"MILLER","CLERK",7782,"1982-1-23",1300.00,0.00,10)
    --查看数据
    SELECT * FROM kylin_hive.emp
    

    6.2 创建工程

    • 输入工程名称以及工程描述

    6.3 Kylin加载Hive表

    虽然 Kylin 使用 SQL 作为查询接口并利用 Hive 元数据,Kylin 不会让用户查询所有的 hive 表,因为到目前为止它是一个预构建 OLAP(MOLAP) 系统。为了使表在 Kylin 中可用,使用 “Sync” 方法能够方便地从 Hive 中同步表。

    • 选择项目添加hive数据源

    • 添加数据源表–>hive库名称.表名称(以逗号分隔)

    • 这里只添加了表的Schema元信息,如果需要加载数据,还需要点击Reload Table

    6.4 Kylin添加Models(模型)

    • 填写模型名字

    • 选择事实表,这里选择员工EMP表为事实表

    • 添加维度表,这里选择部门DEPT表为维度表,并选择我们的join方式,以及join连接字段

    • 选择聚合维度信息

    • 选择度量信息

    • 添加分区信息及过滤条件之后“Save”

    6.5 Kylin构建Cube

    Kylin 的 OLAP Cube 是从星型模式的 Hive 表中获取的预计算数据集,这是供用户探索、管理所有 cube 的网页管理页面。由菜单栏进入Model 页面,系统中所有可用的 cube 将被列出。

    • 创建一个new cube

    • 选择我们的model以及指定cube name

    • 添加我们的自定义维度,这里是在创建Models模型时指定的事实表和维度表中取
      • LookUpTable可选择normal或derived(一般列、衍生列)
      • normal纬度作为普通独立的纬度,而derived 维度不会计算入cube,将由事实表的外键推算出

    • 添加统计维度,勾选相应列作为度量,kylin提供8种度量:SUM、MAX、MIN、COUNT、COUNT_DISTINCT、TOP_N、EXTENDED_COLUMN、PERCENTILE
      • DISTINCT_COUNT有两个实现:
        1. 近似实现 HyperLogLog,选择可接受的错误率,低错误率需要更多存储;
        2. 精确实现 bitmap
      • TopN 度量在每个维度结合时预计算,需要两个参数:
        1. 一是被用来作为 Top 记录的度量列,Kylin 将计算它的 SUM 值并做倒序排列,如sum(price)
        2. 二是 literal ID,代表最 Top 的记录,如seller_id
      • EXTENDED_COLUMN
        • Extended_Column 作为度量比作为维度更节省空间。一列和零一列可以生成新的列
      • PERCENTILE
        • Percentile 代表了百分比。值越大,错误就越少。100为最合适的值

    • 设置多个分区cube合并信息

    如果是分区统计,需要关于历史cube的合并,

    这里是全量统计,不涉及多个分区cube进行合并,所以不用设置历史多个cube进行合并

    • Auto Merge Thresholds:

      • 自动合并小的 segments 到中等甚至更大的 segment。如果不想自动合并,删除默认2个选项
    • Volatile Range:

      • 默认为0,会自动合并所有可能的cube segments,或者用 ‘Auto Merge’ 将不会合并最新的 [Volatile Range] 天的 cube segments
    • Retention Threshold:

      • 默认为0,只会保存 cube 过去几天的 segment,旧的 segment 将会自动从头部删除
    • Partition Start Date:

      • cube 的开始日期

    • 高级设置

    暂时也不做任何设

    置高级设定关系到立方体是否足够优化,可根据实际情况将维度列定义为强制维度、层级维度、联合维度

    • Mandatory维度指的是总会存在于group by或where中的维度
    • Hierarchy是一组有层级关系的维度,如国家、省份、城市
    • Joint是将多个维度组合成一个维度



    • 额外的其他的配置属性

    这里也暂时不做配置

    Kylin 允许在 Cube 级别覆盖部分 kylin.properties 中的配置

    • 完成保存配置

    通过Planner计划者,可以看到4个维度,得到Cuboid Conut=15,为2的4次方-1,因为全部没有的指标不会使用,所以结果等于15。

    • 构建Cube

    6.6 数据查询

    • 根据部门查询,部门工资总和
    SELECT  DEPT.DNAME,SUM(EMP.SAL) 
    FROM EMP 
    LEFT JOIN DEPT 
    ON DEPT.DEPTNO = EMP.DEPTNO  
    GROUP BY DEPT.DNAME
    


    7 入门案例构建流程






    • 动画演示

    8 Kylin的工作原理

    就是对数据模型做 Cube 预计算,并利用计算的结果加速查询,具体工作过程如下:

    1. 指定数据模型,定义维度和度量。

    2. 预计算 Cube,计算所有 Cuboid 并保存为物化视图。

    3. 执行查询时,读取 Cuboid,运算,产生查询结果。

    由于 Kylin 的查询过程不会扫描原始记录,而是通过预计算预先完成表的关联、聚合等复杂运算,并利用预计算的结果来执行查询,因此相比非预计算的查询技术,其速度一般要快一到两个数量级,并且这点在超大的数据集上优势更明显。当数据集达到千亿乃至万亿级别时,Kylin 的速度甚至可以超越其他非预计算技术 1000 倍以上。

    9 Cube 和 Cuboid

    Cube(或 Data Cube),即数据立方体,是一种常用于数据分析与索引的技术;它可以对原始数据建立多维度索引。通过 Cube 对数据进行分析,可以大大加快数据的查询效率。

    Cuboid 特指在某一种维度组合下所计算的数据。 给定一个数据模型,我们可以对其上的所有维度进行组合。对于 N 个维度来说,组合的所有可能性共有 2 的 N 次方种。对于每一种维度的组合,将度量做 聚合运算,然后将运算的结果保存为一个物化视图,称为 Cuboid。

    所有维度组合的 Cuboid 作为一个整体,被称为 Cube。所以简单来说,一个 Cube 就是许多按维度聚合的物化视图的集合。

    下面来列举一个具体的例子:

    假定有一个电商的销售数据集,其中维度包括 时间(Time)、商品(Item)、地点(Location)和供应商(Supplier),度量为销售额(GMV)。

    • 那么所有维度的组合就有 2 的 4 次方 =16 种
      • 一维度(1D) 的组合有[Time]、[Item]、[Location]、[Supplier]4 种
      • 二维度(2D)的组合 有[Time,Item]、[Time,Location]、[Time、Supplier]、[Item,Location]、 [Item,Supplier]、[Location,Supplier]6 种
      • 三维度(3D)的组合也有 4 种
      • 零维度(0D)的组合有 1 种
      • 四维度(4D)的组合有 1 种

    10 cube构建算法

    10.1 逐层构建算法

    我们知道,一个N维的Cube,是由1个N维子立方体、N个(N-1)维子立方体、N*(N-1)/2个(N-2)维子立方体、……、N个1维子立方体和1个0维子立方体构成,总共有2^N个子立方体组成。

    在逐层算法中,按维度数逐层减少来计算,每个层级的计算(除了第一层,它是从原始数据聚合而来),是基于它上一层级的结果来计算的。比如,[Group by A, B]的结果,可以基于[Group by A, B, C]的结果,通过去掉C后聚合得来的;这样可以减少重复计算;当 0维度Cuboid计算出来的时候,整个Cube的计算也就完成了。

    每一轮的计算都是一个MapReduce任务,且串行执行;一个N维的Cube,至少需要N次MapReduce Job。

    算法优点:

    1. 此算法充分利用了MapReduce的优点,处理了中间复杂的排序和shuffle工作,故而算法代码清晰简单,易于维护;

      【原创】xenomai内核解析–双核系统调用(一)

    2. 受益于Hadoop的日趋成熟,此算法非常稳定,即便是集群资源紧张时,也能保证最终能够完成。

    算法缺点:

    1. 当Cube有比较多维度的时候,所需要的MapReduce任务也相应增加;由于Hadoop的任务调度需要耗费额外资源,特别是集群较庞大的时候,反复递交任务造成的额外开销会相当可观;

    2. 由于Mapper逻辑中并未进行聚合操作,所以每轮MR的shuffle工作量都很大,导致效率低下。

    3. 对HDFS的读写操作较多:由于每一层计算的输出会用做下一层计算的输入,这些Key-Value需要写到HDFS上;当所有计算都完成后,Kylin还需要额外的一轮任务将这些文件转成HBase的HFile格式,以导入到HBase中去;

    总体而言,该算法的效率较低,尤其是当Cube维度数较大的时候。

    10.2 快速构建算法


    也被称作“逐段”(By Segment) 或“逐块”(By Split) 算法,从1.5.x开始引入该算法,该算法的主要思想是,每个Mapper将其所分配到的数据块,计算成一个完整的小Cube 段(包含所有Cuboid)。每个Mapper将计算完的Cube段输出给Reducer做合并,生成大Cube,也就是最终结果。如图所示解释了此流程。

    与旧的逐层构建算法相比,快速算法主要有两点不同:

    1. Mapper会利用内存做预聚合,算出所有组合;Mapper输出的每个Key都是不同的,这样会减少输出到Hadoop MapReduce的数据量,Combiner也不再需要;

    2. 一轮MapReduce便会完成所有层次的计算,减少Hadoop任务的调配。

    11 备份及恢复

    Kylin将它全部的元数据(包括cube描述和实例、项目、倒排索引描述和实例、任务、表和字典)组织成层级文件系统的形式。然而,Kylin使用hbase来存储元数据,而不是一个普通的文件系统。如果你查看过Kylin的配置文件(kylin.properties),你会发现这样一行:

    ## The metadata store in hbase
    kylin.metadata.url=kylin_metadata@hbase
    

    这表明元数据会被保存在一个叫作“kylin_metadata”的htable里。你可以在hbase shell里scan该htbale来获取它。

    11.1 使用二进制包来备份Metadata Store

    有时你需要将Kylin的Metadata Store从hbase备份到磁盘文件系统。在这种情况下,假设你在部署Kylin的hadoop命令行(或沙盒)里,你可以到KYLIN_HOME并运行:

    ./bin/metastore.sh backup
    

    来将你的元数据导出到本地目录,这个目录在KYLIN_HOME/metadata_backps下,它的命名规则使用了当前时间作为参数:KYLIN_HOME/meta_backups/meta_year_month_day_hour_minute_second,如:meta_backups/meta_2020_06_18_19_37_49/

    11.2 使用二进制包来恢复Metatdara Store

    万一你发现你的元数据被搞得一团糟,想要恢复先前的备份:

    1. 首先,重置Metatdara Store(这个会清理Kylin在hbase的Metadata Store的所有信息,请确保先备份):
    ./bin/metastore.sh reset
    
    1. 然后上传备份的元数据到Kylin的Metadata Store:
    ./bin/metastore.sh restore $KYLIN_HOME/meta_backups/meta_xxxx_xx_xx_xx_xx_xx
    
    1. 等恢复操作完成,可以在“Web UI”的“System”页面单击“Reload Metadata”按钮对元数据缓存进行刷新,即可看到最新的元数据

    做完备份,删除一些文件,然后进行恢复测试,完美恢复,叮叮叮!

    12 kylin的垃圾清理

    Kylin在构建cube期间会在HDFS上生成中间文件;除此之外,当清理/删除/合并cube时,一些HBase表可能被遗留在HBase却以后再也不会被查询;虽然Kylin已经开始做自动化的垃圾回收,但不一定能覆盖到所有的情况;你可以定期做离线的存储清理:

    1. 检查哪些资源可以清理,这一步不会删除任何东西:
    ${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.tool.StorageCleanupJob --delete false
    
    1. 你可以抽查一两个资源来检查它们是否已经没有被引用了;然后加上“–delete true”选项进行清理。
    ${KYLIN_HOME}/bin/kylin.sh org.apache.kylin.tool.StorageCleanupJob --delete true
    

    完成后,中间HDFS上的中间文件和HTable会被移除。

    13 Kylin优化

    13.1 维度优化

    如果不进行任何维度优化,直接将所有的维度放在一个聚集组里,Kylin就会计算所有的维度组合(cuboid)。

    比如,有12个维度,Kylin就会计算2的12次方即4096个cuboid,实际上查询可能用到的cuboid不到1000个,甚至更少。 如果对维度不进行优化,会造成集群计算和存储资源的浪费,也会影响cube的build时间和查询性能,所以我们需要进行cube的维度优化。

    当你在保存cube时遇到下面的异常信息时,意味1个聚集组的维度组合数已经大于 4096 ,你就必须进行维度优化了。

    或者发现cube的膨胀率过大。

    但在现实情况中,用户的维度数量一般远远大于4个。假设用户有10 个维度,那么没有经过任何优化的Cube就会存在 2的10次方 = 1024个Cuboid;虽然每个Cuboid的大小存在很大的差异,但是单单想到Cuboid的数量就足以让人想象到这样的Cube对构建引擎、存储引擎来说压力有多么巨大。因此,在构建维度数量较多的Cube时,尤其要注意Cube的剪枝优化(即减少Cuboid的生成)。

    13.2 使用衍生维度

    • 衍生维度:维表中可以由主键推导出值的列可以作为衍⽣维度。

    • 使用场景:以星型模型接入时。例如用户维表可以从userid推导出用户的姓名,年龄,性别。

    • 优化效果:维度表的N个维度组合成的cuboid个数会从2的N次方降为2。

    衍生维度用于在有效维度内将维度表上的非主键维度排除掉,并使用维度表的主键(其实是事实表上相应的外键)来替代它们。Kylin会在底层记录维度表主键与维度表其他维度之间的映射关系,以便在查询时能够动态地将维度表的主键“翻译”成这些非主键维度,并进行实时聚合。

    虽然衍生维度具有非常大的吸引力,但这也并不是说所有维度表上的维度都得变成衍生维度,如果从维度表主键到某个维度表维度所需要的聚合工作量非常大,则不建议使用衍生维度。

    13.3 使用聚合组(Aggregation group)

    聚合组(Aggregation Group)是一种强大的剪枝工具。聚合组假设一个Cube的所有维度均可以根据业务需求划分成若干组(当然也可以是一个组),由于同一个组内的维度更可能同时被同一个查询用到,因此会表现出更加紧密的内在关联。每个分组的维度集合均是Cube所有维度的一个子集,不同的分组各自拥有一套维度集合,它们可能与其他分组有相同的维度,也可能没有相同的维度。每个分组各自独立地根据自身的规则贡献出一批需要被物化的Cuboid,所有分组贡献的Cuboid的并集就成为了当前Cube中所有需要物化的Cuboid的集合。不同的分组有可能会贡献出相同的Cuboid,构建引擎会察觉到这点,并且保证每一个Cuboid无论在多少个分组中出现,它都只会被物化一次。

    对于每个分组内部的维度,用户可以使用如下三种可选的方式定义,它们之间的关系,具体如下。

    1. 强制维度(Mandatory)

      • 强制维度:所有cuboid必须包含的维度,不会计算不包含强制维度的cuboid。

      • 适用场景:可以将确定在查询时一定会使用的维度设为强制维度。例如,时间维度。

      • 优化效果:将一个维度设为强制维度,则cuboid个数直接减半。

    如果一个维度被定义为强制维度,那么这个分组产生的所有Cuboid中每一个Cuboid都会包含该维度。每个分组中都可以有0个、1个或多个强制维度。如果根据这个分组的业务逻辑,则相关的查询一定会在过滤条件或分组条件中,因此可以在该分组中把该维度设置为强制维度。

    1. 层级维度(Hierarchy),

      • 层级维度:具有一定层次关系的维度。

      • 使用场景:像年,月,日;国家,省份,城市这类具有层次关系的维度。

      • 优化效果:将N个维度设置为层次维度,则这N个维度组合成的cuboid个数会从2的N次方减少到N+1。

    每个层级包含两个或更多个维度。假设一个层级中包含D1,D2…Dn这n个维度,那么在该分组产生的任何Cuboid中, 这n个维度只会以(),(D1),(D1,D2)…(D1,D2…Dn)这n+1种形式中的一种出现。每个分组中可以有0个、1个或多个层级,不同的层级之间不应当有共享的维度。如果根据这个分组的业务逻辑,则多个维度直接存在层级关系,因此可以在该分组中把这些维度设置为层级维度。

    1. 联合维度(Joint),

      • 联合维度:将几个维度视为一个维度。

      • 适用场景:

        1. 可以将确定在查询时一定会同时使用的几个维度设为一个联合维度。
        2. 可以将基数很小的几个维度设为一个联合维度。
        3. 可以将查询时很少使用的几个维度设为一个联合维度。
      • 优化效果:将N个维度设置为联合维度,则这N个维度组合成的cuboid个数会从2的N次方减少到1。

    每个联合中包含两个或更多个维度,如果某些列形成一个联合,那么在该分组产生的任何Cuboid中,这些联合维度要么一起出现,要么都不出现。每个分组中可以有0个或多个联合,但是不同的联合之间不应当有共享的维度(否则它们可以合并成一个联合)。如果根据这个分组的业务逻辑,多个维度在查询中总是同时出现,则可以在该分组中把这些维度设置为联合维度。

    这些操作可以在Cube Designer的Advanced Setting中的Aggregation Groups区域完成,如下图所示。

    聚合组的设计非常灵活,甚至可以用来描述一些极端的设计。假设我们的业务需求非常单一,只需要某些特定的Cuboid,那么可以创建多个聚合组,每个聚合组代表一个Cuboid。具体的方法是在聚合组中先包含某个Cuboid所需的所有维度,然后把这些维度都设置为强制维度。这样当前的聚合组就只能产生我们想要的那一个Cuboid了。

    再比如,有的时候我们的Cube中有一些基数非常大的维度,如果不做特殊处理,它就会和其他的维度进行各种组合,从而产生一大堆包含它的Cuboid。包含高基数维度的Cuboid在行数和体积上往往非常庞大,这会导致整个Cube的膨胀率变大。如果根据业务需求知道这个高基数的维度只会与若干个维度(而不是所有维度)同时被查询到,那么就可以通过聚合组对这个高基数维度做一定的“隔离”。我们把这个高基数的维度放入一个单独的聚合组,再把所有可能会与这个高基数维度一起被查询到的其他维度也放进来。这样,这个高基数的维度就被“隔离”在一个聚合组中了,所有不会与它一起被查询到的维度都没有和它一起出现在任何一个分组中,因此也就不会有多余的Cuboid产生。这点也大大减少了包含该高基数维度的Cuboid的数量,可以有效地控制Cube的膨胀率。

    13.4 并发粒度优化

    当Segment中某一个Cuboid的大小超出一定的阈值时,系统会将该Cuboid的数据分片到多个分区中,以实现Cuboid数据读取的并行化,从而优化Cube的查询速度。具体的实现方式如下:构建引擎根据Segment估计的大小,以及参数“kylin.hbase.region.cut”的设置决定Segment在存储引擎中总共需要几个分区来存储,如果存储引擎是HBase,那么分区的数量就对应于HBase中的Region数量。kylin.hbase.region.cut的默认值是5.0,单位是GB,也就是说对于一个大小估计是50GB的Segment,构建引擎会给它分配10个分区。用户还可以通过设置kylin.hbase.region.count.min(默认为1)和kylin.hbase.region.count.max(默认为500)两个配置来决定每个Segment最少或最多被划分成多少个分区。

    由于每个Cube的并发粒度控制不尽相同,因此建议在Cube Designer 的Configuration Overwrites(上图所示)中为每个Cube量身定制控制并发粒度的参数。假设将把当前Cube的kylin.hbase.region.count.min设置为2,kylin.hbase.region.count.max设置为100。这样无论Segment的大小如何变化,它的分区数量最小都不会低于2,最大都不会超过100。相应地,这个Segment背后的存储引擎(HBase)为了存储这个Segment,也不会使用小于两个或超过100个的分区。我们还调整了默认的kylin.hbase.region.cut,这样50GB的Segment基本上会被分配到50个分区,相比默认设置,我们的Cuboid可能最多会获得5倍的并发量。

    13.5 Row Key优化

    Kylin会把所有的维度按照顺序组合成一个完整的Rowkey,并且按照这个Rowkey升序排列Cuboid中所有的行。

    设计良好的Rowkey将更有效地完成数据的查询过滤和定位,减少IO次数,提高查询速度,维度在rowkey中的次序,对查询性能有显著的影响。

    Row key的设计原则如下:

    1. 被用作where过滤的维度放在前边。

    1. 基数大的维度放在基数小的维度前边。

    13.6 增量cube构建

    构建全量cube,也可以实现增量cube的构建,就是通过分区表的分区时间字段来进行增量构建

    1. 更改model


    1. 更改cube


    14 Kafka 流构建 Cube(Kylin实时案例)

    Kylin v1.6 发布了可扩展的 streaming cubing 功能,它利用 Hadoop 消费 Kafka 数据的方式构建 cube。

    参考:http://kylin.apache.org/blog/2016/10/18/new-nrt-streaming/

    前期准备:kylin v1.6.0 或以上版本 和 可运行的 Kafka(v0.10.0 或以上版本)的 Hadoop 环境

    14.1 Kafka创建Topic

    • 创建样例名为 “kylin_streaming_topic” 具有一个副本三个分区的 topic
    bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic kylin_streaming_topic
    

    • 将样例数据放入 topic,Kylin 有一个实用类可以做这项工作;
    cd $KYLIN_HOME
    ./bin/kylin.sh org.apache.kylin.source.kafka.util.KafkaSampleProducer --topic kylin_streaming_topic --broker cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092
    


    工具每一秒会向 Kafka 发送 100 条记录。直至本案例结束请让其一直运行。

    14.2 用streaming定义一张表

    登陆 Kylin Web GUI,选择一个已存在的 project 或创建一个新的 project;点击 “Model” -> “Data Source”,点击 “Add Streaming Table” 图标

    • 在弹出的对话框中,输入您从 kafka-console-consumer 中获得的样例记录,点击 “»” 按钮,Kylin 会解析 JSON 消息并列出所有的消息
    {"country":"CHINA","amount":41.53789973661185,"qty":6,"currency":"USD","order_time":1592485535129,"category":"TOY","device":"iOS","user":{"gender":"Male","id":"12d127ab-707e-592f-2e4c-69ad654afa48","first_name":"unknown","age":25}}
    
    • 您需要为这个 streaming 数据源起一个逻辑表名;该名字会在后续用于 SQL 查询;这里是在 “Table Name” 字段输入 “STREAMING_SALES_TABLE” 作为样例。

    • 您需要选择一个时间戳字段用来标识消息的时间;Kylin 可以从这列值中获得其他时间值,如 “year_start”,”quarter_start”,这为您构建和查询 cube 提供了更高的灵活性。这里可以查看 “order_time”。您可以取消选择那些 cube 不需要的属性。这里我们保留了所有字段。

    • 注意 Kylin 从 1.6 版本开始支持结构化 (或称为 “嵌入”) 消息,会将其转换成一个 flat table structure。默认使用 “_” 作为结构化属性的分隔符。

    • 点击 “Next”。在这个页面,提供了 Kafka 集群信息;输入 “kylin_streaming_topic” 作为 “Topic” 名;集群有 3 个 broker,其主机名为”cdh01.cm,cdh02.cm,cdh03.cm“,端口为 “9092”,点击 “Save”。

    • 在 “Advanced setting” 部分,”timeout” 和 “buffer size” 是和 Kafka 进行连接的配置,保留它们。

    • 在 “Parser Setting”,Kylin 默认您的消息为 JSON 格式,每一个记录的时间戳列 (由 “tsColName” 指定) 是 bigint (新纪元时间) 类型值;在这个例子中,您只需设置 “tsColumn” 为 “order_time”;

    • 在现实情况中如果时间戳值为 string 如 “Jul 20,2016 9:59:17 AM”,您需要用 “tsParser” 指定解析类和时间模式例如:

    • 点击 “Submit” 保存设置。现在 “Streaming” 表就创建好了。

    14.3 定义数据模型

    • 有了上一步创建的表,现在我们可以创建数据模型了。步骤和您创建普通数据模型是一样的,但有两个要求:

      • Streaming Cube 不支持与 lookup 表进行 join;当定义数据模型时,只选择 fact 表,不选 lookup 表;
      • Streaming Cube 必须进行分区;如果您想要在分钟级别增量的构建 Cube,选择 “MINUTE_START” 作为 cube 的分区日期列。如果是在小时级别,选择 “HOUR_START”。
    • 这里我们选择 13 个 dimension 和 2 个 measure 列:



    保存数据模型。

    14.4 创建 Cube

    Streaming Cube 和普通的 cube 大致上一样. 有以下几点需要您注意:

    • 分区时间列应该是 Cube 的一个 dimension。在 Streaming OLAP 中时间总是一个查询条件,Kylin 利用它来缩小扫描分区的范围。
    • 不要使用 “order_time” 作为 dimension 因为它非常的精细;建议使用 “mintue_start”,”hour_start” 或其他,取决于您如何检查数据。
    • 定义 “year_start”,”quarter_start”,”month_start”,”day_start”,”hour_start”,”minute_start” 作为层级以减少组合计算。
    • 在 “refersh setting” 这一步,创建更多合并的范围,如 0.5 小时,4 小时,1 天,然后是 7 天;这将会帮助您控制 cube segment 的数量。
    • 在 “rowkeys” 部分,拖拽 “minute_start” 到最上面的位置,对于 streaming 查询,时间条件会一直显示;将其放到前面将会帮助您缩小扫描范围。




    保存 cube。

    14.5 运行Cube

    可以在 web GUI 触发 build,通过点击 “Actions” -> “Build”,或用 ‘curl’ 命令发送一个请求到 Kylin RESTful API:

    curl -X PUT --user ADMIN:KYLIN -H "Content-Type: application/json;charset=utf-8" -d '{ "sourceOffsetStart": 0, "sourceOffsetEnd": 9223372036854775807, "buildType": "BUILD"}' http://localhost:7070/kylin/api/cubes/{your_cube_name}/build2
    

    请注意 API 终端和普通 cube 不一样 (这个 URL 以 “build2” 结尾)。

    这里的 0 表示从最后一个位置开始,9223372036854775807 (Long 类型的最大值) 表示到 Kafka topic 的结束位置。如果这是第一次 build (没有以前的 segment),Kylin 将会寻找 topics 的开头作为开始位置。

    在 “Monitor” 页面,一个新的 job 生成了;等待其直到 100% 完成。

    14.6 查看结果

    点击 “Insight” 标签,编写 SQL 运行,例如:

    select minute_start, count(*), sum(amount), sum(qty) from streaming_sales_table group by minute_start order by minute_start
    

    14.7 自动 build

    一旦第一个 build 和查询成功了,您可以按照一定的频率调度增量 build。Kylin 将会记录每一个 build 的 offsets;当收到一个 build 请求,它将会从上一个结束的位置开始,然后从 Kafka 获取最新的 offsets。有了 REST API 您可以使用任何像 Linux cron 调度工具触发它:

    crontab -e
    */5 * * * * curl -X PUT --user ADMIN:KYLIN -H "Content-Type: application/json;charset=utf-8" -d '{ "sourceOffsetStart": 0, "sourceOffsetEnd": 9223372036854775807, "buildType": "BUILD"}' http://localhost:7070/kylin/api/cubes/{your_cube_name}/build2
    

    现在您可以观看 cube 从 streaming 中自动 built。当 cube segments 累积到更大的时间范围,Kylin 将会自动的将其合并到一个更大的 segment 中。

    15 JDBC查询kylin

    • maven依赖
        <dependencies>
            <dependency>
                <groupId>org.apache.kylin</groupId>
                <artifactId>kylin-jdbc</artifactId>
                <version>3.0.1</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <!-- 限制jdk版本插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.0</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <encoding>UTF-8</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
    • java类
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    public class KylinJdbc {
        public static void main(String[] args) throws Exception {
            //Kylin_JDBC 驱动
            String KYLIN_DRIVER = "org.apache.kylin.jdbc.Driver";
            //Kylin_URL
            String KYLIN_URL = "jdbc:kylin://localhost:9090/kylin_hive";
            //Kylin的用户名
            String KYLIN_USER = "ADMIN";
            //Kylin的密码
            String KYLIN_PASSWD = "KYLIN";
            //添加驱动信息
            Class.forName(KYLIN_DRIVER);
            //获取连接
            Connection connection = DriverManager.getConnection(KYLIN_URL, KYLIN_USER, KYLIN_PASSWD);
            //预编译SQL
            PreparedStatement ps = connection.prepareStatement("SELECT sum(sal) FROM emp group by deptno");
            //执行查询
            ResultSet resultSet = ps.executeQuery();
            //遍历打印
            while (resultSet.next()) {
                        System.out.println(resultSet.getInt(1));
            }
        }
    }
    


    环境篇:Kylin3.0.1集成CDH6.2.0
    免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

    如何只用5分钟完成数据 列表、创建页面

    【Flutter实战】六大布局组件及半圆菜单案例

    JAVA设计模式 1【创建型】设计模式介绍、单例模式的理解与使用

    老孟导读:Flutter中布局组件有水平 / 垂直布局组件( RowColumn )、叠加布局组件( StackIndexedStack )、流式布局组件( Wrap )和 自定义布局组件(Flow)。

    水平、垂直布局组件

    Row 是将子组件以水平方式布局的组件, Column 是将子组件以垂直方式布局的组件。项目中 90% 的页面布局都可以通过 Row 和 Column 来实现。

    将3个组件水平排列:

    Row(
      children: <Widget>[
        Container(
          height: 50,
          width: 100,
          color: Colors.red,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.green,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.blue,
        ),
      ],
    )
    

    将3个组件垂直排列:

    Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Container(
          height: 50,
          width: 100,
          color: Colors.red,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.green,
        ),
        Container(
          height: 50,
          width: 100,
          color: Colors.blue,
        ),
      ],
    )
    

    在 Row 和 Column 中有一个非常重要的概念:主轴( MainAxis )交叉轴( CrossAxis ),主轴就是与组件布局方向一致的轴,交叉轴就是与主轴方向垂直的轴。

    具体到 Row 组件,主轴 是水平方向,交叉轴 是垂直方向。而 Column 与 Row 正好相反,主轴 是垂直方向,交叉轴 是水平方向。

    明白了 主轴 和 交叉轴 概念,我们来看下 mainAxisAlignment 属性,此属性表示主轴方向的对齐方式,默认值为 start,表示从组件的开始处布局,此处的开始位置和 textDirection 属性有关,textDirection 表示文本的布局方向,其值包括 ltr(从左到右) 和 rtl(从右到左),当 textDirection = ltr 时,start 表示左侧,当 textDirection = rtl 时,start 表示右侧,

    Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        children: <Widget>[
          Container(
            height: 50,
            width: 100,
            color: Colors.red,
          ),
          Container(
            height: 50,
            width: 100,
            color: Colors.green,
          ),
          Container(
            height: 50,
            width: 100,
            color: Colors.blue,
          ),
        ],
      ),
    )
    

    黑色边框是Row控件的范围,默认情况下Row铺满父组件。

    主轴对齐方式有6种,效果如下图:

    spaceAround 和 spaceEvenly 区别是:

    • spaceAround :第一个子控件距开始位置和最后一个子控件距结尾位置是其他子控件间距的一半。
    • spaceEvenly : 所有间距一样。

    和主轴对齐方式相对应的就是交叉轴对齐方式 crossAxisAlignment ,交叉轴对齐方式默认是居中。Row控件的高度是依赖子控件高度,因此子控件高都一样时,Row的高和子控件高相同,此时是无法体现交叉轴对齐方式,修改3个颜色块高分别为50,100,150,这样Row的高是150,代码如下:

    Container(
          decoration: BoxDecoration(border: Border.all(color: Colors.black)),
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Container(
                height: 50,
                width: 100,
                color: Colors.red,
              ),
              Container(
                height: 100,
                width: 100,
                color: Colors.green,
              ),
              Container(
                height: 150,
                width: 100,
                color: Colors.blue,
              ),
            ],
          ),
        )
    

    主轴对齐方式效果如下图:

    mainAxisSize 表示主轴尺寸,有 min 和 max 两种方式,默认是 maxmin 表示尽可能小,max 表示尽可能大。

    Container(
    	decoration: BoxDecoration(border: Border.all(color: Colors.black)),
    	child: Row(
    		mainAxisSize: MainAxisSize.min,
    		...
    	)
    )
    

    看黑色边框,正好包裹子组件,而 max 效果如下:

    textDirection 表示子组件主轴布局方向,值包括 ltr(从左到右) 和 rtl(从右到左)

    Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        textDirection: TextDirection.rtl,
        children: <Widget>[
          ...
        ],
      ),
    )
    

    verticalDirection 表示子组件交叉轴布局方向:

    • up :从底部开始,并垂直堆叠到顶部,对齐方式的 start 在底部,end 在顶部。
    • down: 与 up 相反。
    Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        verticalDirection: VerticalDirection.up,
        children: <Widget>[
          Container(
            height: 50,
            width: 100,
            color: Colors.red,
          ),
          Container(
            height: 100,
            width: 100,
            color: Colors.green,
          ),
          Container(
            height: 150,
            width: 100,
            color: Colors.blue,
          ),
        ],
      ),
    )
    

    想一想这种效果完全可以通过对齐方式实现,那么为什么还要有 textDirectionverticalDirection 这两个属性,官方API文档已经解释了这个问题:

    This is also used to disambiguate start and end values (e.g. [MainAxisAlignment.start] or [CrossAxisAlignment.end]).

    用于消除 MainAxisAlignment.start 和 CrossAxisAlignment.end 值的歧义的。

    叠加布局组件

    叠加布局组件包含 StackIndexedStack,Stack 组件将子组件叠加显示,根据子组件的顺利依次向上叠加,用法如下:

    Stack(
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Container(
          height: 140,
          width: 140,
          color: Colors.yellow,
        )
      ],
    )
    

    Stack 对未定位(不被 Positioned 包裹)子组件的大小由 fit 参数决定,默认值是 StackFit.loose ,表示子组件自己决定,StackFit.expand 表示尽可能的大,用法如下:

    Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Container(
          height: 140,
          width: 140,
          color: Colors.yellow,
        )
      ],
    )
    

    效果只有黄色(最后一个组件的颜色),并不是其他组件没有绘制,而是另外两个组件被黄色组件覆盖。

    Stack 对未定位(不被 Positioned 包裹)子组件的对齐方式由 alignment 控制,默认左上角对齐,用法如下:

    Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Container(
          height: 140,
          width: 140,
          color: Colors.yellow,
        )
      ],
    )
    

    通过 Positioned 定位的子组件:

    Stack(
      alignment: AlignmentDirectional.center,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Container(
          height: 170,
          width: 170,
          color: Colors.blue,
        ),
        Positioned(
          left: 30,
          right: 40,
          bottom: 50,
          top: 60,
          child: Container(
            color: Colors.yellow,
          ),
        )
      ],
    )
    

    topbottomleftright 四种定位属性,分别表示距离上下左右的距离。

    如果子组件超过 Stack 边界由 overflow 控制,默认是裁剪,下面设置总是显示的用法:

    Stack(
      overflow: Overflow.visible,
      children: <Widget>[
        Container(
          height: 200,
          width: 200,
          color: Colors.red,
        ),
        Positioned(
          left: 100,
          top: 100,
          height: 150,
          width: 150,
          child: Container(
            color: Colors.green,
          ),
        )
      ],
    )
    

    IndexedStack 是 Stack 的子类,Stack 是将所有的子组件叠加显示,而 IndexedStack 通过 index 只显示指定索引的子组件,用法如下:

    class IndexedStackDemo extends StatefulWidget {
      @override
      _IndexedStackDemoState createState() => _IndexedStackDemoState();
    }
    
    class _IndexedStackDemoState extends State<IndexedStackDemo> {
      int _index = 0;
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: <Widget>[
            SizedBox(height: 50,),
            _buildIndexedStack(),
            SizedBox(height: 30,),
            _buildRow(),
          ],
        );
      }
    
      _buildIndexedStack() {
        return IndexedStack(
          index: _index,
          children: <Widget>[
            Center(
              child: Container(
                height: 300,
                width: 300,
                color: Colors.red,
                alignment: Alignment.center,
                child: Icon(
                  Icons.fastfood,
                  size: 60,
                  color: Colors.blue,
                ),
              ),
            ),
            Center(
              child: Container(
                height: 300,
                width: 300,
                color: Colors.green,
                alignment: Alignment.center,
                child: Icon(
                  Icons.cake,
                  size: 60,
                  color: Colors.blue,
                ),
              ),
            ),
            Center(
              child: Container(
                height: 300,
                width: 300,
                color: Colors.yellow,
                alignment: Alignment.center,
                child: Icon(
                  Icons.local_cafe,
                  size: 60,
                  color: Colors.blue,
                ),
              ),
            ),
          ],
        );
      }
    
      _buildRow() {
        return Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.fastfood),
              onPressed: () {
                setState(() {
                  _index = 0;
                });
              },
            ),
            IconButton(
              icon: Icon(Icons.cake),
              onPressed: () {
                setState(() {
                  _index = 1;
                });
              },
            ),
            IconButton(
              icon: Icon(Icons.local_cafe),
              onPressed: () {
                setState(() {
                  _index = 2;
                });
              },
            ),
          ],
        );
      }
    }
    

    流式布局组件

    Wrap 为子组件进行水平或者垂直方向布局,且当空间用完时,Wrap 会自动换行,也就是流式布局。

    创建多个子控件做为 Wrap 的子控件,代码如下:

    环境篇:Kylin3.0.1集成CDH6.2.0

    Wrap(
      children: List.generate(10, (i) {
        double w = 50.0 + 10 * i;
        return Container(
          color: Colors.primaries[i],
          height: 50,
          width: w,
          child: Text('$i'),
        );
      }),
    )
    

    direction 属性控制布局方向,默认为水平方向,设置方向为垂直代码如下:

    Wrap(
      direction: Axis.vertical,
      children: List.generate(4, (i) {
        double w = 50.0 + 10 * i;
        return Container(
          color: Colors.primaries[i],
          height: 50,
          width: w,
          child: Text('$i'),
        );
      }),
    )
    

    alignment 属性控制主轴对齐方式,crossAxisAlignment 属性控制交叉轴对齐方式,对齐方式只对有剩余空间的行或者列起作用,例如水平方向上正好填充完整,则不管设置主轴对齐方式为什么,看上去的效果都是铺满。

    说明 :主轴就是与当前组件方向一致的轴,而交叉轴就是与当前组件方向垂直的轴,如果Wrap的布局方向为水平方向 Axis.horizontal,那么主轴就是水平方向,反之布局方向为垂直方向 Axis.vertical ,主轴就是垂直方向。

    Wrap(
    	alignment: WrapAlignment.spaceBetween,
    	...
    )
    

    主轴对齐方式有6种,效果如下图:

    spaceAroundspaceEvenly 区别是:

    • spaceAround:第一个子控件距开始位置和最后一个子控件距结尾位置是其他子控件间距的一半。
    • spaceEvenly:所有间距一样。

    设置交叉轴对齐代码如下:

    Wrap(
    	crossAxisAlignment: WrapCrossAlignment.center,
    	...
    )
    

    如果 Wrap 的主轴方向为水平方向,交叉轴方向则为垂直方向,如果想要看到交叉轴对齐方式的效果需要设置子控件的高不一样,代码如下:

    Wrap(
      spacing: 5,
      runSpacing: 3,
      crossAxisAlignment: WrapCrossAlignment.center,
      children: List.generate(10, (i) {
        double w = 50.0 + 10 * i;
        double h = 50.0 + 5 * i;
        return Container(
          color: Colors.primaries[i],
          height: h,
          alignment: Alignment.center,
          width: w,
          child: Text('$i'),
        );
      }),
    )
    

    runAlignment 属性控制 Wrap 的交叉抽方向上每一行的对齐方式,下面直接看 runAlignment 6中方式对应的效果图,

    runAlignmentalignment 的区别:

    • alignment :是主轴方向上对齐方式,作用于每一行。
    • runAlignment :是交叉轴方向上将每一行看作一个整体的对齐方式。

    spacingrunSpacing 属性控制Wrap主轴方向和交叉轴方向子控件之间的间隙,代码如下:

    Wrap(
    	spacing: 5,
        runSpacing: 2,
    	...
    )
    

    textDirection 属性表示 Wrap 主轴方向上子组件的方向,取值范围是 ltr(从左到右) 和 rtl(从右到左),下面是从右到左的代码:

    Wrap(
    	textDirection: TextDirection.rtl,
    	...
    )
    

    verticalDirection 属性表示 Wrap 交叉轴方向上子组件的方向,取值范围是 up(向上) 和 down(向下),设置代码如下:

    Wrap(
    	verticalDirection: VerticalDirection.up,
    	...
    )
    

    注意:文字为0的组件是在下面的。

    自定义布局组件

    大部分情况下,不会使用到 Flow ,但 Flow 可以调整子组件的位置和大小,结合Matrix4绘制出各种酷炫的效果。

    Flow 组件对使用转换矩阵操作子组件经过系统优化,性能非常高效。

    基本用法如下:

    Flow(
      delegate: SimpleFlowDelegate(),
      children: List.generate(5, (index) {
        return Container(
          height: 100,
          color: Colors.primaries[index % Colors.primaries.length],
        );
      }),
    )
    

    delegate 控制子组件的位置和大小,定义如下 :

    class SimpleFlowDelegate extends FlowDelegate {
      @override
      void paintChildren(FlowPaintingContext context) {
        for (int i = 0; i < context.childCount; ++i) {
          context.paintChild(i);
        }
      }
    
      @override
      bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
        return false;
      }
    }
    

    delegate 要继承 FlowDelegate,重写 paintChildrenshouldRepaint 函数,上面直接绘制子组件,效果如下:

    只看到一种颜色并不是只绘制了这一个,而是叠加覆盖了,和 Stack 类似,下面让每一个组件有一定的偏移,SimpleFlowDelegate 修改如下:

    class SimpleFlowDelegate extends FlowDelegate {
      @override
      void paintChildren(FlowPaintingContext context) {
        for (int i = 0; i < context.childCount; ++i) {
          context.paintChild(i,transform: Matrix4.translationValues(0,i*30.0,0));
        }
      }
    
      @override
      bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
        return false;
      }
    }
    

    每一个子组件比上一个组件向下偏移30。

    仿 掘金-我的效果

    效果如下:

    到拿到一个页面时,先要将其拆分,上面的效果拆分如下:

    总体分为3个部分,水平布局,红色区域圆形头像代码如下:

    _buildCircleImg() {
      return Container(
        height: 60,
        width: 60,
        decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(image: AssetImage('assets/images/logo.png'))),
      );
    }
    

    蓝色区域代码如下:

    _buildCenter() {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Text('老孟Flutter', style: TextStyle(fontSize: 20),),
          Text('Flutter、Android', style: TextStyle(color: Colors.grey),)
        ],
      );
    }
    

    绿色区域是一个图标,代码如下:

    Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),
    

    将这3部分组合在一起:

    Container(
      color: Colors.grey.withOpacity(.5),
      alignment: Alignment.center,
      child: Container(
        height: 100,
        color: Colors.white,
        child: Row(
          children: <Widget>[
            SizedBox(
              width: 15,
            ),
            _buildCircleImg(),
            SizedBox(
              width: 25,
            ),
            Expanded(
              child: _buildCenter(),
            ),
            Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,),
            SizedBox(
              width: 15,
            ),
          ],
        ),
      ),
    )
    

    最终的效果就是开始我们看到的效果图。

    水平展开/收起菜单

    使用Flow实现水平展开/收起菜单的功能,代码如下:

    class DemoFlowPopMenu extends StatefulWidget {
      @override
      _DemoFlowPopMenuState createState() => _DemoFlowPopMenuState();
    }
    
    class _DemoFlowPopMenuState extends State<DemoFlowPopMenu>
        with SingleTickerProviderStateMixin {
      //动画必须要with这个类
      AnimationController _ctrlAnimationPopMenu; //定义动画的变量
      IconData lastTapped = Icons.notifications;
      final List<IconData> menuItems = <IconData>[
        //菜单的icon
        Icons.home,
        Icons.new_releases,
        Icons.notifications,
        Icons.settings,
        Icons.menu,
      ];
    
      void _updateMenu(IconData icon) {
        if (icon != Icons.menu) {
          setState(() => lastTapped = icon);
        } else {
          _ctrlAnimationPopMenu.status == AnimationStatus.completed
              ? _ctrlAnimationPopMenu.reverse() //展开和收拢的效果
              : _ctrlAnimationPopMenu.forward();
        }
      }
    
      @override
      void initState() {
        super.initState();
        _ctrlAnimationPopMenu = AnimationController(
          //必须初始化动画变量
          duration: const Duration(milliseconds: 250), //动画时长250毫秒
          vsync: this, //SingleTickerProviderStateMixin的作用
        );
      }
    
    //生成Popmenu数据
      Widget flowMenuItem(IconData icon) {
        final double buttonDiameter =
            MediaQuery.of(context).size.width * 2 / (menuItems.length * 3);
        return Padding(
          padding: const EdgeInsets.symmetric(vertical: 8.0),
          child: RawMaterialButton(
            fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,
            splashColor: Colors.amber[100],
            shape: CircleBorder(),
            constraints: BoxConstraints.tight(Size(buttonDiameter, buttonDiameter)),
            onPressed: () {
              _updateMenu(icon);
            },
            child: Icon(icon, color: Colors.white, size: 30.0),
          ),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Flow(
            delegate: FlowMenuDelegate(animation: _ctrlAnimationPopMenu),
            children: menuItems
                .map<Widget>((IconData icon) => flowMenuItem(icon))
                .toList(),
          ),
        );
      }
    }
    

    FlowMenuDelegate 定义如下:

    class FlowMenuDelegate extends FlowDelegate {
      FlowMenuDelegate({this.animation}) : super(repaint: animation);
      final Animation<double> animation;
    
      @override
      void paintChildren(FlowPaintingContext context) {
        double x = 50.0; //起始位置
        double y = 50.0; //横向展开,y不变
        for (int i = 0; i < context.childCount; ++i) {
          x = context.getChildSize(i).width * i * animation.value;
          context.paintChild(
            i,
            transform: Matrix4.translationValues(x, y, 0),
          );
        }
      }
    
      @override
      bool shouldRepaint(FlowMenuDelegate oldDelegate) =>
          animation != oldDelegate.animation;
    }
    

    半圆菜单展开/收起

    代码如下:

    import 'dart:math';
    
    import 'package:flutter/material.dart';
    
    class DemoFlowMenu extends StatefulWidget {
      @override
      _DemoFlowMenuState createState() => _DemoFlowMenuState();
    }
    
    class _DemoFlowMenuState extends State<DemoFlowMenu>
        with TickerProviderStateMixin {
      //动画需要这个类来混合
      //动画变量,以及初始化和销毁
      AnimationController _ctrlAnimationCircle;
    
      @override
      void initState() {
        super.initState();
        _ctrlAnimationCircle = AnimationController(
            //初始化动画变量
            lowerBound: 0,
            upperBound: 80,
            duration: Duration(milliseconds: 300),
            vsync: this);
        _ctrlAnimationCircle.addListener(() => setState(() {}));
      }
    
      @override
      void dispose() {
        _ctrlAnimationCircle.dispose(); //销毁变量,释放资源
        super.dispose();
      }
    
      //生成Flow的数据
      List<Widget> _buildFlowChildren() {
        return List.generate(
            5,
            (index) => Container(
                  child: Icon(
                    index.isEven ? Icons.timer : Icons.ac_unit,
                    color: Colors.primaries[index % Colors.primaries.length],
                  ),
                ));
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          children: <Widget>[
            Positioned.fill(
              child: Flow(
                delegate: FlowAnimatedCircle(_ctrlAnimationCircle.value),
                children: _buildFlowChildren(),
              ),
            ),
            Positioned.fill(
              child: IconButton(
                icon: Icon(Icons.menu),
                onPressed: () {
                  setState(() {
                    //点击后让动画可前行或回退
                    _ctrlAnimationCircle.status == AnimationStatus.completed
                        ? _ctrlAnimationCircle.reverse()
                        : _ctrlAnimationCircle.forward();
                  });
                },
              ),
            ),
          ],
        );
      }
    }
    

    FlowAnimatedCircle 代码如下:

    class FlowAnimatedCircle extends FlowDelegate {
      final double radius; //绑定半径,让圆动起来
      FlowAnimatedCircle(this.radius);
    
      @override
      void paintChildren(FlowPaintingContext context) {
        if (radius == 0) {
          return;
        }
        double x = 0; //开始(0,0)在父组件的中心
        double y = 0;
        for (int i = 0; i < context.childCount; i++) {
          x = radius * cos(i * pi / (context.childCount - 1)); //根据数学得出坐标
          y = radius * sin(i * pi / (context.childCount - 1)); //根据数学得出坐标
          context.paintChild(i, transform: Matrix4.translationValues(x, -y, 0));
        } //使用Matrix定位每个子组件
      }
    
      @override
      bool shouldRepaint(FlowDelegate oldDelegate) => true;
    }
    

    交流

    老孟Flutter博客地址(330个控件用法):http://laomengit.com

    欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

    【Flutter实战】六大布局组件及半圆菜单案例
    免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

    Spring Data 教程 – Redis