Python--flask使用 SQLAlchemy查询数据库最近时间段或之前的数据
博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!
说明
在操作数据库的时候,有一个需求是展示最近的记录,就需要使用查询最近几天的数据
思路
获取当前时间戳,根据时间来计算,然后查询范围 from datetime import datetime, timedelta time_now = datetime.now() #最近30天数据 model_user = User.query.filter(User.create_time >= time_now - timedelta(days=30)).all() #最近一周数据 model_user = User.query.filter(User.create_time >= time_now - timedelta(days=7)).all() #最近1天数据 model_user = User.query.filter(User.create_time >= time_now - timedelta(days=1)).all() #最近12小时 model_user = User.query.filter(User.create_time >= time_now - timedelta(hours=12)).all() #最近半小时 model_user = User.query.filter(User.create_time >= time_now - timedelta(seconds=30)).all()
感谢 万能的网络
以及勤劳的自己
0. 前言
这是C# 基础知识系列的最后一个内容讲解篇,下一篇是基础知识-实战篇。这一篇主要讲解一下C#程序的结构和主要编程工具。
1. 工具
工欲善其事必先利其器,在实际动手之前我们先来看看想要编写一套C#程序需要做哪些准备吧。
1.1 选择
C# 的sdk在之前的某个时间点分为了三个方向: .net framework 4.X .net core mono UWP
有人可能问了,.net和C#是什么关系。首先需要明确一个概念,C#是一门编程语言,.net是一个CLR,即公共语言运行库。这部分概念比较抽象,不过我这么说大家可能会理解里面的关系了:C#是.net支持的一门语言,于此同时.net还支持F#,Visual C++等。也就是说,.net是一个平台,而C#是运行在这个平台上的语言。
不过,通常上因为.net支持的其他语言并不如C#出名,所以很多人就忽略了两者之间的区别,有时候也用.net代指C#了。
讲了这么多废话,回到之前。.net分了三个方向,第一个方向是沿用之前版本,继续深耕Windows环境。.net core是微软为了跨平台战略而开发的,目前支持 Windows/Linux/Mac等系统。后续的文章也将基于.net core进行。Mono是早期在Linux环境中支持.net framework的一套SDK,后来微软把这项技术合并到了.net发展战略里了,现在支持Android、Mac应用开发。最后一个是UWP,通用Windows平台,据说是为了物联网研究的。
嗯,因为我的工作方向是 BS,也就是做网站开发的,再加上需要接触一些Linux的系统,所以整体就在.net core这个框架下进行后续开发和工作了。换句话说,整个C# 系列都是在.net core上运行的。
1.2 开发工具
在选择了.net core之后,我们就要选择一个适合我们的开发工具了。
1.2.1 Visual Studio
先放一张Visual Studio 2019的图。
这是到目前(2020年05月05日)为止,微软开发的最强大的编译器。在编译器界,Visual Studio有着“宇宙第一编译器”的美名。当然最早是因为其安装包十分巨大,动则2~3G的全量安装,再加上其昂贵的正版授权价格,所以被称为第一编译器。后来随着微软更改了安装方式,改为在线安装然后模块化安装,让程序员可以用哪些装哪些,减少了安装包的大小,同时进一步增强了作为编译器的功能,添加了Community版本(社区版,免费的)之后,第一编译器就成了名副其实的美名。
下图是 Visual Studio支持的功能:
具体的安装就不演示了,这部分网上教程也比较多。如果有疑问的话,可以留言或者私信我哦。 当然了,即使是旗舰版,Visual Studio2019在网上也有很多激活码 ,不过对于公司的话,这部分可能会有法律风险。个人学习研究的话,没事。笑XD
1.2.2 Visual Studio Code
嗯,Code是微软开发的另一个神器,凭借着其轻量、开源和免费这几大特性迅速占领了开发者们的内心。再加上其丰富的插件支持,更是让人欲罢不能。
Code支持跨平台,Windows/Linux/Mac都支持。下图是示意图:
1.2.3 Jetbrains Rider
这是捷克一家软件公司开发的编译器,支持跨平台。语言的智能提示比VS好一点,而且也轻一点。这是它功能特定的宣传图:
可以说功能非常强劲,当然了也是收费的。目前市面上没有很稳定的科学方法。咳咳,我啥也没说。
1.2.4 总结
如果是初学者,我建议使用Visual Studio ,不愧与第一编译器的美名。如果是在Linux环境的话,初学者建议试试Rider。
当然了,对于极客或者Linux环境但预算不高的小伙伴可以试试VS Code,搭配好插件功能也不比另外的编译器差到哪里去。 注:Rider单个软件的订阅是 139美元一年(首年),不过据说有免费的方式。
关于选择编译器的问题,如果是Linux环境,需要事先安装 .net core sdk。这部分内容请参照微软的官方,搜索关键词:net core
这个页面点进去就行,如果在安装过程中有疑问欢迎留言,私信我。
对于Visual Studio则没有这部分烦恼了,只需要在安装过程中选中 net core开发支持即可。
当然,还有更究极的开发方式:使用文本编辑器,然后用命令行dotnet执行。嗯,这样很牛但是挺累的,我就不推荐给你们了。
2. 项目类型
这是在.net core上支持的一些通用的项目类型,我们常用是 console类型(一个控制台程序)、mvc(asp.net core版的mvc项目)、web (一个网站项目)等。
具体的项目情况会在后续的篇幅中介绍,记住net core支持这些类型的项目即可。
3. 总结
这一篇稀里糊涂的介绍完了,之后还有一个实战系列。完结之后,C#的基础知识系列就暂时告一段落了,不过后续还有可能会针对单个知识点的深入讲解,按照计划如果知识点比较大将会新开一个系列,比如之前的多线程和任务那一章,其实是C# 并发编程系列的开篇 。不过下一个系列应该是 C# 数据访问系列。这个系列讲解如何通过ADO.net、EF、Nhibernate等框架操作数据库。
那么,敬请期待第一个实战篇吧,亲爱的小伙伴们。
最后的最后,有问题可以随时回复或私信。当然了,我写的有错误或者不严谨或遗漏的地方也请指出哦。 更多内容烦请关注 我的博客《高先生小屋》
害,这年头算法真的不好学,但是笔试面试又非常爱考,那咋办呢?我来给你推荐几本算法学习好书吧,都是我当年秋招复习时用的,算法导论什么的都给我吃灰去吧!!
算法书单
算法图解
黄小斜的推荐语:这本书太适合入门了,特别是对于计算机非科班的我来说,用它来学算法的感觉非常酸爽,首先是图解的方法非常简单易懂,并且这本书里讲的算法都是非常常见和实用的,对你打好算法基础很有帮助!
本书示例丰富,图文并茂,以让人容易理解的方式阐释了算法,旨在帮助程序员在日常项目中更好地发挥算法的能量。书中的前三章将帮助你打下基础,带你学习二分查找、大O表示法、两种基本的数据结构以及递归等。余下的篇幅将主要介绍应用广泛的算法,具体内容包括:面对具体问题时的解决技巧,比如,何时采用贪婪算法或动态规划;散列表的应用;图算法;Kzui近邻算法。 作者简介
Aditya Bhargava,软件工程师,兼具计算机科学和美术方面的教育背景,在adit.io撰写编程方面的博客。
啊哈算法
黄小斜的推荐语:这本书怎么说呢,算法书里的一股清流,既有入门的算法内容,又有一些比较进阶的算法知识,比如动态规划、DFS和BFS这类算法,这些算法自己学起来真的头大,所以就特别需要别人来讲解,这本书真心把这些复杂的算法讲的很简单易懂了。需要参加算法面试笔试的同学必看!
《啊哈!算法》是一本充满智慧和趣味的算法入门书。没有枯燥的描述,没有难懂的公式,一切以实际应用为出发点,通过幽默的语言配以可爱的插图来讲解算法。你更像是在阅读一个个轻松的小故事或是在玩一把趣味解谜游戏,在轻松愉悦中便掌握算法精髓,感受算法之美。
《啊哈!算法》中涉及的数据结构有栈、队列、链表、树、并查集、堆和图等;涉及的算法有排序、枚举、深度和广度优先搜索、图的遍历,当然还有图论中不可以缺少的四种路径算法、两种生成树算法、割点与割边算法、二分图的匹配算法等。
作者简介
纪磊,网名啊哈磊。曾在中科院玩过单片机。武汉大学历史上以本科生身份加入MSRA(微软亚洲研究院)的小伙伴,在机器学习组从事搜索引擎方面的研究。
漫画算法
黄小斜的推荐语:小灰的漫画算法书,这个不用我多说了吧,计算机图书霸榜多时,小灰真的tql,膜拜。内容也非常棒,现在又出了Python版本,没买的赶紧了!
本书通过虚拟的主人公小灰的心路历程,用漫画的形式讲述了算法和数据结构的基础知识、复杂多变的算法面试题目及算法的实际应用场景。 作者简介
魏梦舒(@程序员小灰):微信公众号“程序员小灰”的作者,多年的软件行业从业经验,先后在京东金融和摩拜科技从事研发工作,对算法有一定的兴趣和经验。
趣学算法
本书可作为程序员的学习用书,也适合从未有过编程经验但又对算法有强烈兴趣的初学者使用,同时也可作为高等院校计算机、数学及相关专业的师生用书和培训学校的教材。 作者简介
陈小玉,副教授,硕士,高级程序员,研究方向:智能计算、机器学习与数据挖掘。主讲《数据结构》、《算法设计与分析》、《人工智能》等专业课程,并发表过多篇计算机专业论文和项目。
这一份MySQL书单,可以帮你搞定90%以上的面试题!
送你一份Redis书单,以后使用缓存的问题不用再问我啦!
学习Spring,看这几本书就够了
觉得有用就点个在看吧!
序列化和反序列化是Java中最基础的知识点,也是很容易被大家遗忘的,虽然天天使用它,但并不一定都能清楚的说明白。我相信很多小伙伴们掌握的也就几句概念、关键字(Serializable)而已,如果深究问一下序列化和反序列化是如何实现、使用场景等,就可能不知所措了。在每次我作为面试官,考察Java基础时,通常都会问到序列化、反序列化的知识点,用以衡量其Java基础如何。当被问及Java序列化是什么?反序列化是什么?什么场景下会用到?如果不用它,会出现什么问题等,一般大家回答也就是几句简单的概念而已,有的工作好几年的应聘者甚至连概念都说不清楚,一脸闷逼。
本文就序列化和反序列化展开深入的探讨,当被别人问及时,不至于一脸闷逼、尴尬,或许会为你以后的求职面试中增加一点点筹码。
/*
* 获取类对象的三种方式
* */
public static void main(String[] args) throws Exception {
//1、Class.forName 【建议】
Class> clazz1 = Class. forName ( "com.czxy.demo2.Person" );
//2、类名.class 【为了简化】
Class
clazz2 = Person. class ;
//3、对象名.getClass() 【拿到了对象】
Person p = new Person();
Class extends Person> clazz3 = p.getClass();
//4、子类对象名.getClass() 【拿到了对象】
Person p = new Person();
Class extends Person> clazz3 = p.getClass().getSuperClass();
// 以上三种类对象获取方式,获取到的Class对象都是堆内存的同一个
System. out .println(clazz1==clazz2);
System. out .println(clazz1==clazz3);
} 入门总结
1、获取
1.1、获取类对象
1.2、获取类中方法
1.3、(private)设置类外可用-暴力反射
2、执行
2.1、获取类中构造方法
2.2、根据构造方法创建对象
2.3、调用方法
反射高级1-获取所有成员方法&字段&构造方法 需求说明:获取某个类中所有的方法 & 所有的字段 &所有构造方法
get Methods(); 获取当前类及父类中所有的public修饰的方法
getDe claredMethods(); 获取当前类中所有的方法(无视访问权限)
getField s (); 获取当前类及父类中所有的public修饰的字段
getDeclaredField s (); 获取当前类中的所有字段(无视访问权限修饰符)
getConstructor s (); 获取当前类中所有的public修饰的构造方法
getDeclaredConstructor s (); 获取当前类中所有构造方法(无视访问权限修饰符)
总结:
所有未加 Declared的方法,都能获取当前类及父类所有public内容
所有加 Declared的方法,都能获取当前类所有内容
代码示例:
public static void main(String[] args) throws Exception {
//1、获取
//1.1、获取类对象
Class> clazz = Class. forName ( "com.czxy.demo2.Person" );
//1.2、通过类对象,获取所有符合要求的方法
Method[] marr = clazz.getDeclaredMethods();
//2、执行
//2.1、通过类对象获取构造方法
Constructor> c = clazz.getConstructor();
//2.2、创建对象
Object person = c.newInstance();
//2.3、调用
for (Method m : marr) {
//方法名: getName()
System. out .println(m.getName());
//执行
try {
m.invoke(person);
} catch (IllegalAccessException e) {
//能进入该异常的都是private,设置类外可用,再次调用即可
m.setAccessible( true );
m.invoke(person);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
可能你会有几年的项目经验,可能在你公司里面也是技术领域的佼佼者,但是这篇二分查找树,你可能不一定都会。
咱们废话不多说,开整!
二分查找树是个啥?
二分查找树(binary search tree),也叫二分搜索树。可以说是二叉树的一个应用,也是二叉树的一种数据结构,如图:
特点:
对于每一个结点,左孩子小于该节点,有孩子大于该结点。
既然是二叉树,同样是一种动态数据结构,可以使用结点类来存放每个结点,如下: class Node{ E e; Node left; //左孩子 Node right; //右孩子 }
树的这种结构,非常适合递归来实现各种操作,往往也是令人迷惑的场面。如果没有养成递归思想,对树的这种递归操作往往会感到迷惑,下面将剖析各种递归操作。
二分查找树的 add 操作
递归开始于 根节点,通过 return 将 新结点 链入到二叉树中。其中的情形 ① 和 ② 是递归的基准情形,递归调用在情形 ③ 和 ④ 中进行。 private Node add(Node node, E e) { if(node == null) { size++; return new Node(e); } if(e.compareTo(node.e) < 0) { node.left = add(node.left, e); } if(e.compareTo(node.e) > 0){ node.right = add(node.right, e); } return node; }
Tips:
比较结点值得大小时,我们不能使用基本的操作符(我们类型选用泛型,只能是包装类和引用类,对象大小比较不能用基本操作符);需要使用 comparable 类中得 comparaTo()
二分查找树的 contains 操作
二分查找树给二叉树了一个存储的顺序,使得我们对二叉树的操作变得更简单。
查询操作未对二叉树进行更改,所以不需要 return 结点,只需要通过返回的 布尔值 来判度查找结果。 private boolean contains(Node node, E e) { if(node == null) { return false; }else if(e.compareTo(node.e) == 0) { return true; }else if(e.compareTo(node.e) < 0) { return contains(node.left, e); }else{ return contains(node.right, e); } }
二分查找树的深度遍历
先根遍历(DLR),又名先序遍历:先访问根结点,再遍历左子树,遍历右子树。
图中出现的二叉树,使用先根遍历结果为:53 12 9 14 64 78 private void perOrder(Node node) { //DLR if(node == null) return; else { System.out.print(node.e+" "); perOrder(node.left); perOrder(node.right); } }
中序遍历(LDR),又名对称遍历:先遍历左子树,再访问根结点,遍历右子树。
图中出现的二叉树,使用中遍历结果为:9 12 14 53 64 78 private void inOrder(Node node) { //LDR if(node == null) return; else { inOrder(node.left); System.out.print(node.e+" "); inOrder(node.right); } }
后序遍历(LRD):先遍历左子树,再遍历右子树,再访问根结点。
图中出现的二叉树,使用后序遍历结果为:9 14 12 78 64 53 private void postOrder(Node node) { //LRD if(node == null) return; else { postOrder(node.left); postOrder(node.right); System.out.print(node.e+" "); } }
看了三种遍历方式,发现虽然顺序有差异,但在编程时,集中体现为打印语句的顺序不同;正是由于递归调用前后语句的执行深度和顺序不同,支持了树的深度遍历。
Tips:
深度遍历是根据递归来定义的,默认的方向是从左到右遍历,也可以从右向左遍历,称这种遍历顺序为逆序遍历。
深度优先遍历非递归实现
先序遍历(DLR):利用队列来辅助实现遍历
算法思路
将根结点入队,当队列不为空时,重复下面步骤:
① 出队队头结点
② 打印队头结点
③ 判断队头结点左子树是否为空。如果左子树为空,入队队头结点的右子树(若右子树不为空);否则,入队队头结点的左子树和右子树(若右子树不为空),同时,保证左子树永远在队头 public void preOrder() { //先序遍历(前序遍历) if(root == null) return; Queue que = new LinkedList<>(); que.add(root); while(!(que.isEmpty())) { Node node = que.remove(); if(node.left == null) { System.out.print(node.e+" "); if(node.right != null) que.add(node.right); }else { System.out.print(node.e+" "); int n = que.size(); que.add(node.left); if(node.right != null) que.add(node.right); for(int i = 0; i < n; i++) { que.add(que.remove()); } } } }
中序遍历(LDR):利用栈来实现遍历
算法思路
从根节点开始,当结点不为空或者栈不为空时,重复下面步骤:
① 当前结点不为空,入队当前结点,遍历左子树至空树
② 当前结点为空,出队栈顶结点并打印,遍历右子树 public void inOrder() { Stack stack = new Stack<>(); Node node = root; while(node != null || !(stack.isEmpty())) { if(node != null) { stack.push(node); node = node.left; }else { node = stack.pop(); System.out.print(node.e+" "); node = node.right; } } }
后序遍历(LRD):利用栈来实现遍历
算法思路
从根节点开始,当结点不为空或者栈不为空时,重复下面步骤:
① 当前结点不为空,入栈当前结点和有孩子(右孩子不为空),遍历右子树至空树
② 当前结点为空,栈顶元素右子树为空或者右孩子刚访问过了,出栈并打印栈顶结点,将当前结点设置为刚被访问;否则,遍历右子树 public void postOrder() { //后序遍历(后根遍历) Stack stack = new Stack<>(); Node node = root; Node visited = null; while(node!=null || !stack.isEmpty()) { if(node != null) { stack.push(node); if(node.right != null) stack.push(node.right); node = node.left; }else { node = stack.pop(); if(node.right == null || node.right == visited) { System.out.print(node.e+" "); visited = node; node = null; }else { stack.push(node); node = node.right; } } } }
二分查找树广度优先遍历
广度优先遍历,又名层次遍历,按照每一层一次遍历所有结点,这里我们借助队列来实现。
从根结点开始,首先入队根结点,重复下面步骤:如果队头结点左右孩子不为空,出队头结点并打印,入队左右孩子结点;否则出队头结点并打印。 public void levelOrder() { //层序遍历 Queue temporary = new LinkedList<>(); temporary.add(root); while(!temporary.isEmpty()) { Node node = temporary.remove(); if(node.left != null) temporary.add(node.left); if(node.right != null) { temporary.add(node.right); } System.out.print(node.e+" "); } }
minimum 和 maximum
minimum 和 maximum 表示二叉查找树中的最小值和最大值,关于最大值和最小值有两种操作,查找二叉查找树中的最大值和最小值,并可以删除最大值,最小值。
查询 minimum 和 maximum
二叉查找树中的最大值和最小值,必定为深度最大的左子树和深度最大的右子树,这样直接使用递归就可以进行操作,查询并没有改变二叉树,所以无需改变根结点。
private Node minimum(Node node) { if(node.left != null) { return minimum(node.left); }else { return node; } } private Node maximum(Node node) { if(node.right != null) { return maximum(node.right); }else { return node; } }
删除 minimum 和 maximum
删除操作会改变二叉查找树的结点,首先遍历左(右)子树,至空树(即到达二分查找树的最大和最小值结点),将最小值(最大值)接点的右子树链入到前驱结点。 private Node removeMinimum(Node node) { if(node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; }else { node.left = removeMinimum(node.left); return node; } } private Node removeMaximum(Node node) { if(node.right == null) { Node leftNode = node.right; node.left = null; size--; return leftNode; }else{ node.right = removeMaximum(node.right); return node; } }
Tips:
删除结点时,并不能采用循环迭代方法查找并将结点值置为 null 来完成删除;我们在所有的二叉树代码中看到的都是结点的引用,并不能删除结点,我们只能改变结点的引用,将其指向 null,并将删除结果链入二叉树中,没有引用指向的结点 ,会被垃圾回收机制回收。
删除二叉树的任意结点
首先,我们了解一下删除的规则:
① 要删除的结点没有右子树,直接将要删除结点的左子树链入到前驱结点的左子树
② 要删除的结点没有左子树,直接将要删除结点的右子树链入到前驱结点的右子树
③ 要删除的结点有右子树,找到其右子树中的最小值结点,用最小值结点替换要删除的结点,同时,将右子树中的最小值结点删除掉
在删除结点时,我们首先要定位删除结点,根据结点值来遍历左子树和右子树,来找到并删除该节点。 private Node removeNode(Node node,E e) { if(node == null) return null; if(e.compareTo(node.e) > 0) { node.right = removeNode(node.right,e); return node; }else if(e.compareTo(node.e) < 0) { node.left = removeNode(node.left,e); return node; }else{ if(node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; } if(node.right == null) { Node leftNode = node.left; node.left = null; size--; return leftNode; } Node minNode = minimum(node.right); minNode.right = removeMinimum(node.right); minNode.left = node.left; return minNode; } }
打印树形二分查找树
通过二叉树中结点的深度不同,利用逆中序遍历,可以打印逆置 90° 的二叉树:
打印顺序:78 64 53 14 12 9(逆中序遍历)。每次只打印一个结点。 public void printTree() { printTree(root, 0); } private void printTree(Node node, int n) { if(node == null) { return; } printTree(node.right, n+1); for(int i = 0; i < n; i++) { System.out.print("t"); } System.out.println(node.e); printTree(node.left, n+1); }
全部代码 import java.util.Queue; import java.util.Stack; import java.util.LinkedList; public class BST >{ private int size; private Node root; private class Node{ public E e; public Node left; public Node right; public Node(E e) { this.e = e; left = null; right = null; } } public BST() { root = null; size = 0; } ///获取size public int getSize() { return size; } boolean isEmpty() { return size == 0; } ///二分查找树添加操作 public void add(E e) { root = add(root, e); } private Node add(Node node, E e) { if(node == null) { size++; return new Node(e); } if(e.compareTo(node.e) < 0) { node.left = add(node.left, e); } if(e.compareTo(node.e) > 0){ node.right = add(node.right, e); } return node; } ///二分查找树查询操作 public boolean contains(E e) { return contains(root, e); } private boolean contains(Node node, E e) { if(node == null) { return false; }else if(e.compareTo(node.e) == 0) { return true; }else if(e.compareTo(node.e) < 0) { return contains(node.left, e); }else{ return contains(node.right, e); } } ///二分查找树的遍历操作 /*public void preOrder() { //先序遍历(前序遍历) perOrder(root); }*/ public void preOrder() { //先序遍历(前序遍历) if(root == null) return; Queue que = new LinkedList<>(); que.add(root); while(!(que.isEmpty())) { Node node = que.remove(); System.out.print(node.e+" "); if(node.left == null) { if(node.right != null) que.add(node.right); }else { int n = que.size(); que.add(node.left); if(node.right != null) que.add(node.right); for(int i = 0; i < n; i++) { que.add(que.remove()); } } } } private void perOrder(Node node) { //DLR if(node == null) return; else { System.out.print(node.e+" "); perOrder(node.left); perOrder(node.right); } } public void inOrder() { //中序遍历(对称遍历) inOrder(root); } /*public void inOrder() { Stack stack = new Stack<>(); Node node = root; while(node != null || !(stack.isEmpty())) { if(node != null) { stack.push(node); node = node.left; }else { node = stack.pop(); System.out.print(node.e+" "); node = node.right; } } }*/ private void inOrder(Node node) { //LDR if(node == null) return; else { inOrder(node.left); System.out.print(node.e+" "); inOrder(node.right); } } public void postOrder() { //后序遍历(后根遍历) postOrder(root); } /*public void postOrder() { //后序遍历(后根遍历) Stack stack = new Stack<>(); Node node = root; Node visited = null; while(node!=null || !stack.isEmpty()) { if(node != null) { stack.push(node); if(node.right != null) stack.push(node.right); node = node.left; }else { node = stack.pop(); if(node.right == null || node.right == visited) { System.out.print(node.e+" "); visited = node; node = null; }else { stack.push(node); node = node.right; } } } }*/ private void postOrder(Node node) { //LRD if(node == null) return; else { postOrder(node.left); postOrder(node.right); System.out.print(node.e+" "); } } public void levelOrder() { //层序遍历 Queue temporary = new LinkedList<>(); temporary.add(root); while(!temporary.isEmpty()) { Node node = temporary.remove(); if(node.left != null) temporary.add(node.left); if(node.right != null) { temporary.add(node.right); } System.out.print(node.e+" "); } } ///获得最小值 public E minimum() { if(size == 0) throw new IllegalArgumentException("Error:size为零"); return minimum(root).e; } private Node minimum(Node node) { if(node.left != null) { return minimum(node.left); }else { return node; } } ///获得最大值 public E maximum() { if(size == 0) throw new IllegalArgumentException("Error:size为零"); return maximum(root).e; } private Node maximum(Node node) { if(node.right != null) { return maximum(node.right); }else { return node; } } ///删除最小值 public void removeMinimum() { if(size == 0) throw new IllegalArgumentException("Error:size为零"); root = removeMinimum(root); } private Node removeMinimum(Node node) { if(node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; }else { node.left = removeMinimum(node.left); return node; } } ///删除最大值 public void removeMaximum() { if(size == 0) throw new IllegalArgumentException("Error:size为零"); root = removeMaximum(root); } private Node removeMaximum(Node node) { if(node.right == null) { Node leftNode = node.right; node.left = null; size--; return leftNode; }else{ node.right = removeMaximum(node.right); return node; } } public boolean removeNode(E e) { if(contains(e)) { root = removeNode(root, e); return true; }else return false; } private Node removeNode(Node node,E e) { if(node == null) return null; if(e.compareTo(node.e) > 0) { node.right = removeNode(node.right,e); return node; }else if(e.compareTo(node.e) < 0) { node.left = removeNode(node.left,e); return node; }else{ if(node.left == null) { Node rightNode = node.right; node.right = null; size--; return rightNode; } if(node.right == null) { Node leftNode = node.left; node.left = null; size--; return leftNode; } Node minNode = minimum(node.right); minNode.right = removeMinimum(node.right); minNode.left = node.left; return minNode; } } ///打印树形二叉树 public void printTree() { printTree(root, 0); } private void printTree(Node node, int n) { if(node == null) { return; } printTree(node.right, n+1); for(int i = 0; i < n; i++) { System.out.print("t"); } System.out.println(node.e); printTree(node.left, n+1); } }
至此,关于二分查找树的实现思路和源码都讲完了。 原文链接: https://blog.csdn.net/weixin_42089228/java/article/details/106290387
文源网络,仅供学习之用,如有侵权请联系删除。
我将面试题和答案都整理成了PDF文档,还有一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,但不仅限于此。
关注公众号【java圈子】获取资料,还有优质文章每日送达。
一、数据结构与算法基础 说一下几种常见的排序算法和分别的复杂度。 用Java写一个冒泡排序算法 描述一下链式存储结构。 如何遍历一棵二叉树? 倒排一个LinkedList。 用Java写一个递归遍历目录下面的所有文件。
二、Java基础 接口与抽象类的区别? Java中的异常有哪几类?分别怎么使用? 常用的集合类有哪些?比如List如何排序? ArrayList和LinkedList内部的实现大致是怎样的?他们之间的区别和优缺点? 内存溢出是怎么回事?请举一个例子? ==和equals的区别? hashCode方法的作用? NIO是什么?适用于何种场景? HashMap实现原理,如何保证HashMap的线程安全? JVM内存结构,为什么需要GC? NIO模型,select/epoll的区别,多路复用的原理 Java中一个字符占多少个字节,扩展再问int, long, double占多少字节 创建一个类的实例都有哪些办法? final/finally/finalize的区别? Session/Cookie的区别? String/StringBuffer/StringBuilder的区别,扩展再问他们的实现? Servlet的生命周期? 如何用Java分配一段连续的1G的内存空间?需要注意些什么? Java有自己的内存回收机制,但为什么还存在内存泄露的问题呢? 什么是java序列化,如何实现java序列化?(写一个实例)? String s = new String("abc");创建了几个 String Object?
三、JVM JVM堆的基本结构。 JVM的垃圾算法有哪几种?CMS垃圾回收的基本流程? JVM有哪些常用启动参数可以调整,描述几个? 如何查看JVM的内存使用情况? Java程序是否会内存溢出,内存泄露情况发生?举几个例子。 你常用的JVM配置和调优参数都有哪些?分别什么作用? JVM的内存结构? 常用的GC策略,什么时候会触发YGC,什么时候触发FGC?
四、多线程/并发 如何创建线程?如何保证线程安全? 如何实现一个线程安全的数据结构 如何避免死锁 Volatile关键字的作用? HashMap在多线程环境下使用需要注意什么?为什么? Java程序中启动一个线程是用run()还是start()? 什么是守护线程?有什么用? 什么是死锁?如何避免 线程和进程的差别是什么? Java里面的Threadlocal是怎样实现的? ConcurrentHashMap的实现原理是? sleep和wait区别 notify和notifyAll区别 volatile关键字的作 ThreadLocal的作用与实现 两个线程如何串行执行 上下文切换是什么含义 可以运行时kill掉一个线程吗? 什么是条件锁、读写锁、自旋锁、可重入锁? 线程池ThreadPoolExecutor的实现原理?
五、Linux使用与问题分析排查 使用两种命令创建一个文件? 硬链接和软链接的区别? Linux常用命令有哪些? 怎么看一个Java线程的资源耗用? Load过高的可能性有哪些? /etc/hosts文件什么做用? 如何快速的将一个文本中所有“abc”替换为“xyz”? 如何在log文件中搜索找出error的日志? 发现磁盘空间不够,如何快速找出占用空间最大的文件? Java服务端问题排查(OOM,CPU高,Load高,类冲突) Java常用问题排查工具及用法(top, iostat, vmstat, sar, tcpdump, jvisualvm, jmap, jconsole) Thread dump文件如何分析(Runnable,锁,代码栈,操作系统线程ID关联) 如何查看Java应用的线程信息?
六、框架使用 描述一下Hibernate的三个状态? Spring中Bean的生命周期。 SpringMVC或Struts处理请求的流程。 Spring AOP解决了什么问题?怎么实现的? Spring事务的传播属性是怎么回事?它会影响什么? Spring中BeanFactory和FactoryBean有什么区别? Spring框架中IOC的原理是什么? spring的依赖注入有哪几种方式 struts工作流程 用Spring如何实现一个切面? Spring 如何实现数据库事务? Hibernate对一二级缓存的使用,Lazy-Load的理解; mybatis如何实现批量提交?
七、数据库相关 MySQL InnoDB、Mysaim的特点? 乐观锁和悲观锁的区别? 数据库隔离级别是什么?有什么作用? MySQL主备同步的基本原理。 select * from table t where size > 10 group by size order by size的sql语句执行顺序? 如何优化数据库性能(索引、分库分表、批量操作、分页算法、升级硬盘SSD、业务优化、主从部署) SQL什么情况下不会使用索引(不包含,不等于,函数) 一般在什么字段上建索引(过滤数据最多的字段) 如何从一张表中查出name字段不包含“XYZ”的所有行? MySQL,B+索引实现,行锁实现,SQL优化 Redis,RDB和AOF,如何做高可用、集群 如何解决高并发减库存问题 mysql存储引擎中索引的实现机制; 数据库事务的几种粒度; 行锁,表锁;乐观锁,悲观锁
八、网络协议和网络编程 TCP建立连接的过程。 TCP断开连接的过程。 浏览器发生302跳转背后的逻辑? HTTP协议的交互流程。HTTP和HTTPS的差异,SSL的交互流程? Rest和Http什么关系? 大家都说Rest很轻量,你对Rest风格如何理解? TCP的滑动窗口协议有什么用?讲讲原理。 HTTP协议都有哪些方法? 交换机和路由器的区别? Socket交互的基本流程? http协议(报文结构,断点续传,多线程下载,什么是长连接) tcp协议(建连过程,慢启动,滑动窗口,七层模型) webservice协议(wsdl/soap格式,与rest协议的区别) NIO的好处,Netty线程模型,什么是零拷贝
九、Redis等缓存系统/中间件/NoSQL/一致性Hash等 列举一个常用的Redis客户端的并发模型。 HBase如何实现模糊查询? 列举一个常用的消息中间件,如果消息要保序如何实现? 如何实现一个Hashtable?你的设计如何考虑Hash冲突?如何优化? 分布式缓存,一致性hash LRU算法,slab分配,如何减少内存碎片 如何解决缓存单机热点问题 什么是布隆过滤器,其实现原理是? False positive指的是? memcache与redis的区别 zookeeper有什么功能,选举算法如何进行 map/reduce过程,如何用map/reduce实现两个数据源的联合统计
十、设计模式与重构 你能举例几个常见的设计模式 你在设计一个工厂的包的时候会遵循哪些原则? 你能列举一个使用了Visitor/Decorator模式的开源项目/库吗? 你在编码时最常用的设计模式有哪些?在什么场景下用? 如何实现一个单例? 代理模式(动态代理) 单例模式(懒汉模式,并发初始化如何解决,volatile与lock的使用) JDK源码里面都有些什么让你印象深刻的设计模式使用,举例看看?
上面的这些面试题都整理成了PDF文档,希望能帮助到你面试前的复习并找到一个好的工作,相对来说也节省了你在网上搜索资料的时间来学习!!!
附欢迎关注我的公种号:it资源之家 ,扫描下面二维码即可领取更多一线大厂Java面试题资料!
最后
欢迎大家评论区一起交流,相互提升;整理资料不易,如果喜欢文章记得点个赞哈,感谢大家支持!!!
一、认识二叉树
首先,在了解 mysql 中的 B+ 树之前,我们需要搞懂什么是二叉树。二叉树是一种常见的非线形数据结构,数据是以一对多的形态组织起来的,我画了一张图来帮助你理解:
在二叉树中,有一种比较特殊的,也是最常用的二叉树,那就是二叉搜索树,也叫做二叉查找树。它最大的特点是:对于树中的任意一个节点,假如节点值为 x,其左子树节点的值必须小于 x,其右子树节点的值必须大于 x,就像下图的这几种数据排列结构:
那为什么需要使用二叉搜索树这种结构呢,它有什么好处吗?我们知道,常见的线性表结构,例如链表,要查找一个数据,必须从头开始遍历链表,在最坏的情况下,需要遍历整个链表才能够找到需要的数据。
而使用二叉搜索树这种结构,在树结构趋于平衡的情况下,借助二分的思想,每次查找数据的时候,都会舍弃掉另一个子树,所以在平均情况下,我们只需要在 O(logn)(也就是树的高度)的时间复杂度内就可以查找到数据。
二、为什么会选择 B+ 树
在了解了二叉搜索树之后,我们就为学习 B+ 树打下了坚实的基础。只不过先别着急,我们再来明确一个问题,为什么 mysql 会选择 B+ 树做为其索引模型呢?其他的数据结构不行吗?
要搞懂这个问题,我们先想想,mysql 中最常见的操作是什么?既然是数据库,最常见的操作当然是数据查询了,好的,我以最常见的两条 sql 查询语句为例: select * from T where id = 1; select * from T where id > 10 and id < 20;
一个是等值查询,一个是范围查询。
支持快速查询的常见数据结构有哈希表、平衡二叉查找树、跳表,我们依次来看看这几种结构能否做为 B+ 树的索引模型。
如果使用哈希表,虽然等值查询非常高效,但是数据的排列是无序的,所以并不支持范围查询。
如果使用平衡二叉查找树,例如红黑树、AVL 树等,可以在接近 O(logn) 的时间内找到数据,但是对于树结构来说,范围查询仍然是很低效的,因为只能中序遍历一棵树得到一个有序的数据集,然后再依次查找。
如果使用跳表,等值查询的效率和平衡二叉查找树差不多,并且也支持范围查询,例如下图中,我们查找节点 7(红色粗线为查找路径),如果需要范围查询的话,可以顺着原始链表依次遍历下去,因为链表节点之间是有序的。
这样来说的话,跳表也是可以做为索引模型的,但 mysql 还是选择了 B+ 树,实际上 B+ 树和跳表的设计思想有一些类似的地方,我们现在来看看 B+ 树是什么样子的。
三、B+ 树模型
1. B+ 树的优点
对数据构建索引,我们可以使用平衡二叉查找树,并且稍微做一下改造,把树的叶子节点使用链表串联,并且是从小到达顺序排列的,那么这种结构就能够支持等值查询和范围查询了,如下图:
当查找到一个节点之后,我们继续向后遍历,就能够实现高效的范围查询了。值得一提的是,这里串联叶子节点的链表,应该使用双向链表,方便在数据查询后进行升序或者降序操作。
这种结构虽然高效,但存在一个致命的问题,那就是太消耗内存空间了。
假如我们给 1000 万条数据建立索引,每个节点假设大约占用 16 字节的空间,那么构建一个索引大概就需要 150MB 内存,实际上我们还会给很多张表的很多字段建立索引,这样的话内存空间消耗还是太大了。
所以我们可以根据空间换时间的思想,使用磁盘来代替内存,磁盘是一种慢速的存储设备,造价也比内存低廉很多,因此我们可以将数据存储到磁盘上,只不过这样数据查询的速度就会慢一些了。
针对这种数据存储的方式,如果我们还是用上述的那种二叉树结构的话,每访问一层节点就对应着一次磁盘 IO,那这样的查询速度还是太慢了。因此我们可以改造一下上图中的这个结构,将二叉变成 n 叉,这样每一层节点存储的数据变多了,树的高度也降低了,访问磁盘的次数变少了,相应的查询性能就能够得到提升。
比如存储 30 个数据,构建二叉树的高度是 5,而 5 叉树的高度仅为 3:
如果数据量再大一点,就更能看出差别了,比如我们构建的是 100 叉树,那么存储 1 亿个数据,树的高度也只是 5 ,这样的话磁盘 IO 的操作次数就被大大的降低了。
那么在实际的应用中,到底应该构建多少叉树呢?是不是树的节点越多,即 n 越大越好?
我们知道,操作系统对磁盘的访问是以页为单位读取的,每页的大小通常是 4KB,也就是说我们只需要将 n 叉树的每个节点存储为一页大小左右,这样每次访问都能够整页读取,不会进行多余的磁盘 IO 操作。
假如每页的大小可以存储 3 个数据,那么最终的 B+ 树结构就是这个样子:
2. B+ 树的缺点
这样来看的话,似乎 B+ 树已经比较完美的解决了数据索引的问题,但是,天下没有免费的午餐,B+ 树对查询操作有了很大的提升,但同时也降低了数据插入和删除的效率。
这个问题似乎不难理解,当我们不断插入数据的时候,B+ 树中的节点肯定会越来越多,直到大于了页大小,这时,为了维护查询的效率,不产生多余的 IO 操作,我们不得不进行节点的重构。
假如叶子节点的数量是 m,当节点数量大于 m 的时候,该节点就会分裂,从叶子节点的最中间的那个节点,让其成为父节点,节点左右的值,分别成为新的左右子节点;如果上一层又超过了限制,则继续向上进行分裂,直到影响到根节点,参照下面的图就很容易理解了:
删除也是类似的道理,当叶子节点过少,例如少于 m / 2 的时候,就可以将节点合并至旁边的兄弟节点。你可以自己参照插入的思路,想想删除是怎么进行节点重构的。
好了,这篇文章讲述了 mysql 的索引模型 B+ 树,首先需要了解一下二叉树,这是学习 B+ 树的前提,然后我以两个最常见的查询 sql,向你描述了为什么其他的常见数据结构不适合用来做索引,然后由此引出了 B+ 树。
根据数据查询和存储的特点,对平衡二叉树逐步改造成了 B+ 树,B+ 树对数据查询起到了很好的作用,但是它也带有副作用,那就是对插入删除操作有影响,于是需要进行节点的重构。 原文链接: https://www.jianshu.com/p/e966112dbf8c
文源网络,仅供学习之用,如有侵权请联系删除。
我将面试题和答案都整理成了PDF文档,还有一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,但不仅限于此。
关注公众号【java圈子】获取资料,还有优质文章每日送达。
本文源码: GitHub·点这里 || GitEE·点这里
一、场景分析
1、场景案例
互联网行业的朋友一定了解或者听说过下列几个场景:
阿里 :千人千面,意思不同用户使用阿里相关的产品感觉是不一样的,例如支付宝首页的推荐内容,和其他相关推荐流信息是完全不同的。
腾讯 :社交广告,不同用户的朋友圈或者其他媒体场景下的广告信息是不同的,会基于用户特征推荐。
头条 :信息价值,根据用户浏览信息,分析用户相关喜好,针对分析结果推荐相关的信息流,越关注某类内容,获取相关的信息越多。
如上几种场景的逻辑就是:基于不断分析用户的行为,生成用户的特征画像,然后再基于用户标签,定制化的推荐相关内容。
2、基本概念
通过上面的场景,衍生出来两个概念:
用户画像
用户画像,作为一种勾画目标用户、联系用户诉求与设计方向的有效工具,把该用户相关联的数据的可视化的展现,就形成了用户画像。用户画像在各领域得到了广泛的应用,最初是在电商领域得到应用的,在大数据时代背景下,用户信息充斥在网络中,将用户的每个具体信息抽象成标签,利用这些标签将用户形象具体化,从而为用户提供有针对性的服务。
标签数据
标签在生活中非常常见,比如商品标签,个人标签,行业标签,例如提到996就想到程序员,提到程序员就想到格子衫。
标签是把分散的多方数据进行整合纳入统一的技术平台,并对这些数据进行标准化和细分,进行结构化存储和更新管理,让业务线可以把这些细分结果推向现有的互动营销环境里的平台,产生价值,这些数据称为标签数据,也就是常说的标签库。数据标签的概念也是在最近几年大数据的发展中不断火热起来的。
标签价值 精细运营的基础,有效提高流量精准和效率。 帮助产品快速定位需求人群,进行精准营销; 能帮助客户更快切入到市场周期中; 深入的预测分析客户并作出及时反应; 基于标签的开发智能推荐系统; 基于某类用户的分析,洞察行业特征;
标签的核心价值,或者说最常用的场景:实时智能推荐,精准化数字营销。
二、数据标签
1、标签划分
属性标签
属性标签是变化最小的,例如用户实名认证之后,基于身份信息获取相关:性别,生日,出生年月,年龄,等相关标签。变动频率小,且最具有精准性。
行为标签
行为标签就是用户通过在产品上的一系列操作,基于行为日志分析得出:例如购买能力、消费爱好、季节性消费标签等。在信息流的APP上,通过相关浏览行为,不断推荐用户感兴趣的内容就是基于该逻辑。
规则标签
根据业务场景需求,配置指定规则,基于规则生成分析结果,例如: 近7天活跃用户:近7天,每天都登录的用户作为规则生成; 丢失用户:六个月内没有任何操作,可以发放高额优惠劵; 潜在用户:使用或产生浏览数据,但是未发生任何交易行为;
这类标签可以基于动态的规则配置,经过计算和分析,生成描述结果,也就是规则标签。
拟合标签
拟合类的标签最具有复杂性,通过用户上述几种标签,智能组合分析,给的预测值,例如:未婚、浏览相关婚礼内容,通过分析预测用户将要举办婚礼,得到一个拟合结果:预测将要结婚。这个预测逻辑也可以反向执行,用户购买婴儿用品:预测已婚已育。
这就是数据时代常说的一句话:用户在某个应用上一通操作之后,算法分析的结果可能比用户对自己的描述还要真实。
2、标签加工流程
数据采集
数据采集的渠道相对较多,比如同一APP内的各种业务线:购物、支付、理财、外卖、信息浏览等等。通过数据通道传输到统一的数据聚合平台。有了这些海量日志数据的支撑,才具有数据分析的基础条件。不管是数据智能,深度学习,算法等都是建立在海量数据的基础条件上,这样才能获取具有价值的分析结果。
数据加工
结合如上业务,通过对海量数据的加工,分析和提取,获取相对精准的用户标签,这里还有关键的一步,就是对已有的用户标签进行不断的验证和修复,尤其是规则类和拟合类的相关标签。
标签库
通过标签库,管理复杂的标签结果,除了复杂的标签,和基于时间线的标签变,标签数据到这里,已经具有相当大的价值,可以围绕标签库开放一些收费服务,例如常见的,用户在某电商APP浏览某些商品,可以在某信息流平台看到商品推荐。大数据时代就是这么令人感觉智能和窒息。
标签业务
数据走了一大圈转换成标签,自然还是要回归到业务层面,通过对标签数据的用户的分析,可以进行精准营销,和智能推荐等相关操作,电商应用中可以提高成交量,信息流中可以更好的吸引用户。
应用层
把上述业务开发成服务,集成到具有的应用层面,不断提升应用服务的质量,不断的吸引用户,提供服务。当然用户的数据不断在应用层面产生,在转到数据采集服务中,最终形成完整的闭环流程。
3、应用案例
从流程和业务层面描述都是简单的,到开发层面都会变得复杂和不好处理,这可能就是产品和开发之间的隔阂。
标签的数据类型
不同标签的分析结果需要用不同的数据类型描述,在标签体系中,常用描述标签的数据类型如下:枚举、数值、日期、布尔、文本类型。不同的类型需要不一样的分析流程。
商品和标签
这里提供一个基础案例,用商品的标签来分析商品,例如通过商品产地,价格,状态等条件,来查询产品库有多少符合条件的商品。
数据表设计
主要分四张表:标签分类,标签库,标签值,标签数据。 CREATE TABLE `tc_tag_catalog` ( `id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `catalog_name` VARCHAR (50) NOT NULL DEFAULT '' COMMENT '名称', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `state` INT (1) DEFAULT '1' COMMENT '状态1启用,2禁用', PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '标签层级目录'; CREATE TABLE `tc_tag_cloud` ( `id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `catalog_id` INT (11) NOT NULL COMMENT '目录ID', `tag_name` VARCHAR (100) DEFAULT '' COMMENT '标签名称', `tag_code` INT (11) DEFAULT NULL COMMENT '标签编码', `bind_column` VARCHAR (100) DEFAULT '' COMMENT '绑定数据列', `data_type` INT (2) NOT NULL COMMENT '1枚举,2数值,3日期,4布尔,5值类型', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `remark` VARCHAR (150) DEFAULT NULL COMMENT '备注', `state` INT (1) DEFAULT '1' COMMENT '状态1启用,2禁用', PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '标签云'; CREATE TABLE `tc_tag_data_enum` ( `tag_code` INT (11) NOT NULL COMMENT '标签编码', `data_value` VARCHAR (150) NOT NULL COMMENT '枚举值', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', KEY `tag_code_index` (`tag_code`) USING BTREE ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '标签枚举值'; CREATE TABLE `tc_tag_data_set` ( `id` INT (11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `product_name` VARCHAR (100) DEFAULT '' COMMENT '商品名称', `unit_price` DECIMAL (10, 2) DEFAULT '0.00' COMMENT '单价', `is_shelves` INT (1) DEFAULT '1' COMMENT '是否上架:1否,2是', `origin_place` VARCHAR (100) DEFAULT '' COMMENT '产地', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '标签数据集';
模拟入参接口
这里的参数应该是基于需求,动态选取,进行组织到一起:
例如图片中这里给定的标签值列表,称为枚举值。 @RestController public class AnalyzeController { @Resource private TagDataSetService tagDataSetService ; @GetMapping("/analyze") public String analyze (){ List tagParamList = new ArrayList<>() ; TagParam tagParam1 = new TagParam(1,"产地","origin_place") ; List valueList1 = new ArrayList<>() ; valueList1.add("深圳"); valueList1.add("广东"); tagParam1.setValueList(valueList1); tagParamList.add(tagParam1) ; TagParam tagParam2 = new TagParam(2,"价格","unit_price") ; List valueList2 = new ArrayList<>() ; valueList2.add("1999"); tagParam2.setValueList(valueList2); tagParamList.add(tagParam2) ; TagParam tagParam3 = new TagParam(3,"生产日期","create_time") ; List valueList3 = new ArrayList<>() ; valueList3.add("2020-05-01 13:43:54"); tagParam3.setValueList(valueList3); tagParamList.add(tagParam3) ; TagParam tagParam4 = new TagParam(4,"是否上架","is_shelves") ; List valueList4 = new ArrayList<>() ; valueList4.add("1"); tagParam4.setValueList(valueList4); tagParamList.add(tagParam4) ; TagParam tagParam5 = new TagParam(5,"产品名称","product_name") ; List valueList5 = new ArrayList<>() ; valueList5.add("智能"); tagParam5.setValueList(valueList5); tagParamList.add(tagParam5) ; Integer count = tagDataSetService.analyze(tagParamList) ; return "Result:" + count ; } }
参数解析查询
通过对参数的解析,最终形成查询的SQL语句,获取精准的结果数据。 @Service public class TagDataSetServiceImpl extends ServiceImpl implements TagDataSetService { @Resource private TagDataSetMapper tagDataSetMapper ; @Override public Integer analyze(List tagParamList) { StringBuffer querySQL = new StringBuffer() ; for (TagParam tagParam:tagParamList){ querySQL.append(" AND ") ; querySQL.append(tagParam.getBindColumn()) ; // 1枚举,2数值,3日期,4布尔,5值类型 List valueList = tagParam.getValueList(); switch (tagParam.getDataType()){ case 1: querySQL.append(" IN (") ; for (int i = 0 ; i < valueList.size() ;i++){ if (i != valueList.size()-1){ querySQL.append("'").append(valueList.get(i)).append("',"); } else { querySQL.append("'").append(valueList.get(i)).append("'"); } } querySQL.append(" )") ; break; case 2: querySQL.append("=").append(tagParam.getValueList().get(0)) ; break; case 3: querySQL.append(">='").append(tagParam.getValueList().get(0)).append("'") ; break; case 4: querySQL.append("=").append(tagParam.getValueList().get(0)) ; break; case 5: querySQL.append(" LIKE '%").append(tagParam.getValueList().get(0)).append("%'") ; break; default: break; } } /* 最终执行的 SQL SELECT COUNT(*) FROM tc_tag_data_set WHERE 1 = 1 AND origin_place IN ('深圳', '广东') AND unit_price = 1999 AND create_time >= '2020-05-01 13:43:54' AND is_shelves = 1 AND product_name LIKE '%智能%' */ String whereCondition = String.valueOf(querySQL); return tagDataSetMapper.analyze(whereCondition); } }
可能有人会说这不就是个查询流程吗?如果有这样的疑问,把上述案例换成用户查询,标签数据的价值会更直观。
三、智能画像
1、基本概念
用户画像
作为一种勾画目标用户、联系用户诉求与设计方向的有效工具,用户画像在各领域得到了广泛的应用。最初是在电商领域得到应用的,在大数据时代背景下,用户信息充斥在网络中,将用户的每个具体信息抽象成标签,利用这些标签将用户形象具体化,从而为用户提供有针对性的服务。
行业画像
通过行业属性标签,行业下用户标签的综合分析,生成行业分析报告,提供极有价值的导向,这是最近两年极其热门的应用。
画像补全
通过不断分析用户数据,丰富标签库,使用户的画像更加丰富立体。
2、画像报告
通过标签数据的分析,生成一份分析报告,报告内容包含丰富的用户标签统计数据。
例如:90后画像报告
这个报告,互联网用户一定或多或少都看到过。主要是一些标签统计,共性标签展示,或者哪些群体对90后三观影响最大,收入来源,学历等各种分析解读。
四、源代码地址 GitHub·地址 https://github.com/cicadasmile/data-manage-parent GitEE·地址 https://gitee.com/cicadasmile/data-manage-parent
简介
在之前的文章中,我们介绍了JDK14中jstat工具的使用,本文我们再深入探讨一下jstack工具的使用。
jstack工具主要用来打印java堆栈信息,主要是java的class名字,方法名,字节码索引,行数等信息。
更多精彩内容且看: 区块链从入门到放弃系列教程-涵盖密码学,超级账本,以太坊,Libra,比特币等持续更新 Spring Boot 2.X系列教程:七天从无到有掌握Spring Boot-持续更新 Spring 5.X系列教程:满足你对Spring5的一切想象-持续更新 java程序员从小工到专家成神之路(2020版)-持续更新中,附详细文章教程 更多内容请访问 www.flydean.com
jstack的命令格式 Usage: jstack [-l][-e]
(to connect to running process) Options: -l long listing. Prints additional information about locks -e extended listing. Prints additional information about threads -? -h --help -help to print this help message
jstack的参数比较简单,l可以包含锁的信息,e包含了额外的信息。
jstack的使用
我们举个例子: jstack -l -e 53528
输出结果如下: 2020-05-09 21:46:51 Full thread dump Java HotSpot(TM) 64-Bit Server VM (14.0.1+7 mixed mode, sharing): Threads class SMR info: _java_thread_list=0x00007fda0660eb00, length=14, elements={ 0x00007fda04811000, 0x00007fda05845800, 0x00007fda05012000, 0x00007fda05847800, 0x00007fda05843800, 0x00007fda05854800, 0x00007fda0481f000, 0x00007fda0481f800, 0x00007fda04018800, 0x00007fda041ff800, 0x00007fda05a28800, 0x00007fda05b1a800, 0x00007fda05b1d800, 0x00007fda042be000 } "Reference Handler" #2 daemon prio=10 os_prio=31 cpu=0.67ms elapsed=66335.21s allocated=0B defined_classes=0 tid=0x00007fda04811000 nid=0x4603 waiting on condition [0x000070000afe1000] java.lang.Thread.State: RUNNABLE at java.lang.ref.Reference.waitForReferencePendingList(java.base@14.0.1/Native Method) at java.lang.ref.Reference.processPendingReferences(java.base@14.0.1/Reference.java:241) at java.lang.ref.Reference$ReferenceHandler.run(java.base@14.0.1/Reference.java:213) Locked ownable synchronizers: - None ... "VM Thread" os_prio=31 cpu=1433.78ms elapsed=66335.22s tid=0x00007fda0506b000 nid=0x4803 runnable "GC Thread#0" os_prio=31 cpu=18.63ms elapsed=66335.23s tid=0x00007fda0502a800 nid=0x3203 runnable "GC Thread#1" os_prio=31 cpu=19.64ms elapsed=66334.06s tid=0x00007fda050e5800 nid=0x9d03 runnable "GC Thread#2" os_prio=31 cpu=17.72ms elapsed=66334.06s tid=0x00007fda05015000 nid=0x6203 runnable "GC Thread#3" os_prio=31 cpu=14.57ms elapsed=66332.78s tid=0x00007fda05138800 nid=0x6503 runnable "G1 Main Marker" os_prio=31 cpu=0.25ms elapsed=66335.23s tid=0x00007fda05031000 nid=0x3303 runnable "G1 Conc#0" os_prio=31 cpu=14.85ms elapsed=66335.23s tid=0x00007fda05031800 nid=0x4b03 runnable "G1 Refine#0" os_prio=31 cpu=3.25ms elapsed=66335.23s tid=0x00007fda0583a800 nid=0x4a03 runnable "G1 Young RemSet Sampling" os_prio=31 cpu=5929.79ms elapsed=66335.23s tid=0x00007fda0505a800 nid=0x3503 runnable "VM Periodic Task Thread" os_prio=31 cpu=21862.12ms elapsed=66335.13s tid=0x00007fda0505b000 nid=0xa103 waiting on condition JNI global refs: 43, weak refs: 45
输出的结果我们可以分为下面几个部分:
JVM虚拟机信息
第一部分是JVM虚拟机的信息 2020-05-09 21:46:51 Full thread dump Java HotSpot(TM) 64-Bit Server VM (14.0.1+7 mixed mode, sharing):
上面显示了虚拟机的thread dump时间和虚拟机的版本等信息。
Threads class SMR info
第二部分是JVM中非JVM(非VM和非GC的线程)的内部线程信息。 Threads class SMR info: _java_thread_list=0x00007fda0660eb00, length=14, elements={ 0x00007fda04811000, 0x00007fda05845800, 0x00007fda05012000, 0x00007fda05847800, 0x00007fda05843800, 0x00007fda05854800, 0x00007fda0481f000, 0x00007fda0481f800, 0x00007fda04018800, 0x00007fda041ff800, 0x00007fda05a28800, 0x00007fda05b1a800, 0x00007fda05b1d800, 0x00007fda042be000 }
这些elements是和后面线程的tid相匹配的。表示的是本地线程对象的地址,注意这些不是线程的ID。
大家可能注意到了里面写的是SMR, SMR全称是Safe Memory Reclamation。
什么是SMR呢?简单点讲就是安全的内存分配,一般这个问题会出现在非自动GC的编程语言中如C++。在这些语言中,需要自己来为对象分配内存和销毁对象,这样就可能导致在多线程的环境中,一个地址可能被分配给了多个对象,从而出现了内存分配的不安全。
线程信息
第三部分就是线程的具体信息了: "Reference Handler" #2 daemon prio=10 os_prio=31 cpu=0.67ms elapsed=66335.21s allocated=0B defined_classes=0 tid=0x00007fda04811000 nid=0x4603 waiting on condition [0x000070000afe1000] java.lang.Thread.State: RUNNABLE at java.lang.ref.Reference.waitForReferencePendingList(java.base@14.0.1/Native Method) at java.lang.ref.Reference.processPendingReferences(java.base@14.0.1/Reference.java:241) at java.lang.ref.Reference$ReferenceHandler.run(java.base@14.0.1/Reference.java:213) Locked ownable synchronizers: - None
按照字段的顺序,我们可以把线程信息分为下面几个部分: 线程名字:例如Reference Handler 线程的ID:例如#2 是否守护线程:例如daemon,daemon threads是低优先级的thread,它的作用是为User Thread提供服务。 因为daemon threads的低优先级,并且仅为user thread提供服务,所以当所有的user thread都结束之后,JVM会自动退出,不管是否还有daemon threads在运行中。 优先级:例如prio=10 OS线程的优先级:例如os_prio=31 cpu时间:线程获得CPU的时间,例如cpu=0.67ms elapsed:线程启动后经过的wall clock time allocated:本线程分配的分配的bytes数 defined_classes:本线程定义的class个数 注意'allocated=' 和 'defined_classes=' 必须要开启 -XX:+PrintExtendedThreadInfo才会输出数据。 Address:java线程的地址,例如:tid=0x00007fda04811000 OS线程ID:例如nid=0x4603 线程状态:例如waiting on condition 最新的Java堆栈指针:最新的java堆栈指针SP,例如:[0x000070000afe1000]
Thread Stack Trace
接下来就是线程的堆栈信息: java.lang.Thread.State: RUNNABLE at java.lang.ref.Reference.waitForReferencePendingList(java.base@14.0.1/Native Method) at java.lang.ref.Reference.processPendingReferences(java.base@14.0.1/Reference.java:241) at java.lang.ref.Reference$ReferenceHandler.run(java.base@14.0.1/Reference.java:213)
上面的例子是线程的堆栈信息,并且列出来了线程的状态。
Locked Ownable Synchronizer
接下来的部分是该线程拥有的,可用的用于同步的排它锁对象。
Ownable Synchronizer是一个同步器,这个同步器的同步属性是通过使用AbstractOwnableSynchronizer或者它的子类来实现的。
例如ReentrantLock和ReentrantReadWriteLock中的write-lock(注意不是read-lock,因为需要排它性)就是两个例子。
JVM Threads
接下来是JVM的线程信息,因为这个线程是JVM内部的,所以没有线程ID: "VM Thread" os_prio=31 cpu=1433.78ms elapsed=66335.22s tid=0x00007fda0506b000 nid=0x4803 runnable "GC Thread#0" os_prio=31 cpu=18.63ms elapsed=66335.23s tid=0x00007fda0502a800 nid=0x3203 runnable "GC Thread#1" os_prio=31 cpu=19.64ms elapsed=66334.06s tid=0x00007fda050e5800 nid=0x9d03 runnable "GC Thread#2" os_prio=31 cpu=17.72ms elapsed=66334.06s tid=0x00007fda05015000 nid=0x6203 runnable "GC Thread#3" os_prio=31 cpu=14.57ms elapsed=66332.78s tid=0x00007fda05138800 nid=0x6503 runnable "G1 Main Marker" os_prio=31 cpu=0.25ms elapsed=66335.23s tid=0x00007fda05031000 nid=0x3303 runnable "G1 Conc#0" os_prio=31 cpu=14.85ms elapsed=66335.23s tid=0x00007fda05031800 nid=0x4b03 runnable "G1 Refine#0" os_prio=31 cpu=3.25ms elapsed=66335.23s tid=0x00007fda0583a800 nid=0x4a03 runnable "G1 Young RemSet Sampling" os_prio=31 cpu=5929.79ms elapsed=66335.23s tid=0x00007fda0505a800 nid=0x3503 runnable "VM Periodic Task Thread" os_prio=31 cpu=21862.12ms elapsed=66335.13s tid=0x00007fda0505b000 nid=0xa103 waiting on condition
JNI References
最后一部分是JNI(Java Native Interface)引用的信息,注意这些引用可能会导致内存泄露,因为这些native的引用并不会被自动垃圾回收。 JNI global refs: 43, weak refs: 45
总结
jstack是分析线程的非常强大的工具,希望大家能够使用起来。 本文作者:flydean程序那些事
本文链接: http://www.flydean.com/jdk14-jvm-jstack/
本文来源:flydean的博客
欢迎关注我的公众号:程序那些事,更多精彩等着您! 方法区中主要回收:1.废弃常量;2.无用的类。
但是 判断它们废弃或无用了并不一定会被回收 。
1.废弃常量:
对于常量池中的常量a,如果没有任何对象引用该常量的话,就表示它是一个废弃常量。
2.无用的类:
需满足:
1.Java堆中没有任何该类的实例;
2.该类的类加载器已被回收;
3.该类的Class对象没在任何地方被引用,且无法在任何地方通过反射访问该类的方法。(?)
Python--报错TabError: inconsistent use of tabs and spaces in indentation
博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!
说明
在运行flask框架的时候报错,死活看不出来,看到资料说是由于混用了tab键和4个空格键造成的,只用一个就可以解决这个问题
思路
出现这个问题是由于在网上编辑了python代码,然后在网上的编辑器里面看不出来,找错误着了半天,原来就是字面意思,把代码全选到pycharm里面,发现代码格式全乱,使用快捷键整理代码alt+command+L,问题解决
感谢 万能的网络
以及勤劳的自己
1 概述
最近研究JFX,写出来了但是打包不了,这。。。尴尬。。。
IDEA的文档 说只支持Java8打成jar包:
尝试过直接使用Maven插件的package,不行,也尝试过Build Artifacts,也不行,各种奇奇怪怪的问题。包括下图中的 Error....fx:deploy is not available in this JDK
不过幸好文档末尾提到可以使用一些 第三方工具 :
因此记录一下使用IDEA打包JFX11工程的过程。
2 环境 IDEA 2020.1 OpenJDK 11 OpenJFX 11
只需安装IDEA与JDK即可,JFX可以在pom.xml中引入。
3 创建工程
选择Maven,选择Create from archetype,然后Add Archetype并填上GroupId: org.openjfx
与ArtifactId: javafx-maven-archetypes
还有version: 0.0.1
下一步自定义,这里为了方便就直接默认了。
然后修改archetypeArtifactId为 javafx-archetype-fxml
完成即可。
4 检查文件
Maven同步完后,检查项目目录下的module-info.java与pom.xml,其中pom.xml不应该是下图这样的:
而应该是这样的,包含了javafx.controls与javafx.fxml依赖,还有javafx-maven-plugin插件:
同时src下的module-info.java应该长这样:
module-info.java包含了所需要的javafx.controls与javafx.fxml这两个模块。
5 修改插件依赖
默认的javafx-maven-plugin版本应该为0.0.1:
整个去掉,修改为:
org.openjfx javafx-maven-plugin 0.0.4 true 2 true true launcher output outputzip org.openjfx.App 其中launcher为可执行文件名,jlinkImageName为Image输出目录名,jlinkZipName为zip文件名,mainClass为主类。
6 运行并打包
从右侧栏的Maven通过javafx插件运行并打包:
7 测试
根据修改插件时的目录,进入对应的target下的子目录直接运行即可: 项目目录/target/output/bin/launcher
注意这个是跨平台的,只需具备JDK11环境即可,发布只需要把zip文件发布。
8 demo github 码云
9 扩展阅读
这篇文章是OpenJFX的文档,主要介绍了: 通过三种方式(JFX+Maven+Gradle)运行Hello World 构建运行镜像(CLI+Maven+Gradle+自定义镜像) IntelliJ/NetBeans/Eclipse通过三种方式(IDE自身+Maven+Gradle)运行与打包JFX
链接 戳这里 。
一、命令模式的概念 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
二、什么时候使用命令模式
调用者与实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。 我通常这么理解命令模式,某些业务场景, 一族对象 会有 一组命令 ,以命令的角度为主定义命令对象(有执行和撤回两种操作),然后把执行的过程委托给对象。
什么是一族对象?举个例子,就是奔驰汽车、宝马汽车、奥迪汽车等。
什么是一组命令?举个例子,就是远程控制汽车时的开门命令、开空调命令、开天窗命令等。
这种情况就可以使用命令模式。使用命令模式有降低耦合、可扩展、可读性高、使用方便(可撤回)等等的好处。
具体我们来看一下,如何用Java代码实现命令模式。
三、怎么使用命令模式
3.1 实现方式
还是以上面的需求为例子,使用命令模式对上面的业务进行实现,下面是类图和代码:
代码如下: // 车辆抽象类 public abstract class Vehicle { abstract void openDoor(); abstract void openWindow(); } public class Audi extends Vehicle { void openDoor() { System.out.println("奥迪:开门。"); } void openWindow() { System.out.println("奥迪:开窗。"); } } public class Benz extends Vehicle { void openDoor() { System.out.println("奔驰:开门。"); } void openWindow() { System.out.println("奔驰:开窗。"); } } // 命令模式接口 public interface Command { void execute(); } // 开门命令 public class OpenDoorCommand implements Command { private Vehicle vehicle; public OpenDoorCommand(Vehicle vehicle) { this.vehicle = vehicle; } public void execute() { vehicle.openDoor(); } } // 开窗命令 public class OpenWindowCommand implements Command { private Vehicle vehicle; public OpenWindowCommand(Vehicle vehicle) { this.vehicle = vehicle; } public void execute() { vehicle.openWindow(); } } public class Client { public static void main(String[] args) { // 创建开门命令 Command openDoorCommand = new OpenDoorCommand(new Benz()); // 创建开窗命令 Command openWindowCommand = new OpenWindowCommand(new Audi()); // 执行2个命令 openDoorCommand.execute(); openWindowCommand.execute(); } } //输出: //奔驰:开门。 //奥迪:开窗。
3.2 命令模式的好处
使用命令的模式的好处如下: 可读性高: 从语义上可直接阅读到动作,对外提供了统一的表现; 拓展性高:可以很方便地添加新的命令,也可以控制命令的撤回或重复执行; 耦合性低:解除了调用者与具体实现的耦合, 屏蔽了底层的复杂实现;
3.3 注意事项
功能足够简单。如果功能本身比较简单,则不建议引入命令模式,它会带来更多的复杂性,以及更高的开发成本。
命令的意义不清晰或经常变化时,则不太适合使用命令模式。
一般来说,尽量设计傻瓜式命令,它只懂得调用一个接收者的一个行为(单一职责)。
四、总结
如何实现撤销命令,是一直值得讨论的问题,面相扩展性来说,可以遵循一个设计方式:
不要只是记录最后一个命令,而使用一个堆栈(后进先出)记录操作过的每一个命令。然后,不管什么时候执行撤销,你都可以从堆栈中取出最上层的命令,然后执行undo()方法来撤销它。
最后说一些其他的,命令模式还可以有更多的用途,比如使用在队列请求和日志请求。
想象一下,有一个工作队列(先进先出),你在某一端添加命令,然后另一端则是线程,线程从队列中取出一个命令,然后调用它的execute()方法,等待这个调用完成,然后丢弃该命令,执行下一个……
在想象一下,某些应用需要我们将所有的动作都记录在日志中,并能在系统死机后,重新调用这些动作恢复到之前的状态。我们可以在执行命令的时候,将历史记录存储在磁盘中。一旦系统死机重启后,我们就可以将命令对象读取出来重新执行execute()方法。
以上就是我对命令模式的一些理解,有不足之处请大家指出,谢谢。
Python--linux下安装依赖时报错command 'gcc' failed with exit status 1
博客说明 文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!
说明
在一个新的linux上部署flask项目时出现这个问题,原因是缺少依赖
思路
可以安装以下的依赖 yum install -y libffi-devel python-devel openssl-devel
如果是python3 yum install -y python3-devel
建议
在切换新系统的时候,可以把自己以前经常或者必要的包记录下来,然后同一一起安装,这样可以加快我们开发环境的搭建
感谢 万能的网络
以及勤劳的自己
fpm5.6 curl - L - o / tmp / redis . tar . gz https : //github.com/phpredis/phpredis/archive/2.2.7.tar.gz && tar xfz / tmp / redis . tar . gz && rm - r / tmp / redis . tar . gz && mv phpredis - 2.2 . 7 / usr / src / php / ext / redis && docker - php - ext - install redis
fpm7.2 pecl install - o - f redis && rm - rf / tmp / pear && docker - php - ext - enable redis
ps:
https://stackoverflow.com/questions/31369867/how-to-install-php-redis-extension-using-the-official-php-docker-image-approach
2020年5月14日,aelf推出CCTP《跨链转账标准协议》,为证明锁仓合约及CCTP的足够安全,以及让用户们更加全面地了解CCTP的落地实践,aelf 决定举办第一期黑客悬赏活动。
此次黑客悬赏活动将作为赏金任务基于aelf DAO进行发布,确保活动流程执行与赏金发放等事宜的员全程公开透明。本次活动将分为两期举行,本期的悬赏金额为88888 ELF,期待全球黑客前来挑战!
为了保障用户在参与锁仓映射等链上事务时,其资产安全能得到保障,ELF Token锁仓映射使用了Merkle Proof流程,数据完全在链上公开并无法伪造。首先基于ETH网络部署锁仓合约和Merkle tree合约,在限定时间内锁定测试Token ELF,统一批次生成Merkle tree root后,用户即可获得Merkle Proof所需数据。然后将Merkle tree root数据(Merkle tree 的根哈希值)记录在aelf网络后,即可开启映射功能。aelf网络对用户映射账户数据进行验证后,即自动将对应的测试Token ELF转账至相应账户地址,完成映射。锁仓期满后,用户可在ETH锁仓合约中领取回token,且只能由原用户领回,以此保障用户资产的安全。参与悬赏任务的黑客可基于此ELF Token锁仓映射实现逻辑寻找底层技术上的突破点。
活动时间:
2020年5月29日 20:00-6月5日12:00(GMT +8)
活动规则:
(1)参赛形式
▴ 个人或小组参赛任选 ,可自由组队,不限人数。
(2)悬赏规则
▴ 本次悬赏活动主要针对 CCTP《跨链转账标准协议》,利用 ETH 及 aelf 项目本身的漏洞不计入悬赏。
▴ 5月29日 12:00,aelf已将88888测试token ELF 锁入锁仓合约;
▴ 6月3日 12:00,生成Merkle tree;
▴ 6月5日12:00,通过CCTP映射88888测试Token ELF到aelf测试网;
▴ 任何黑客能够在活动期间破坏CCTP并成功映射,将测试Token ELF盗取至自己的账户地址,则aelf基金会将发放等额ERC20 ELF Token赏金;
▴ 活动截止仍无黑客攻克CCTP,则aelf基金会取回赏金。
(3)参赛合约信息
以太坊测试网:
▴ 以太坊测试网锁仓合约地址:
0xF135B2Eba5D3B690576a5C93062f992b592c0a04
▴ Merkle Tree生成合约地址:
0xd31363f276AcABc2b8866736ED28F11b09C0cf02
aelf测试网:
▴ 侧链token映射合约地址:
RXcxgSXuagn8RrvhQAV81Z652EEYSwR6JLnqHYJ5UVpEptW8Y
▴ 合约代码:
https://explorer-test-side01.aelf.io/contract?#https%3A%2F%2Fexplorer-test-side01.aelf.io%2Fviewer%2Fviewer.html%3Faddress%3DRXcxgSXuagn8RrvhQAV81Z652EEYSwR6JLnqHYJ5UVpEptW8Y
▴ 合约接口文档:
https://github.com/AElfProject/aelf-boilerplate/tree/token-swap/chain/contract/AElf.Contracts.TokenSwapContract
(4) 赏金领取
▴ 国内用户添加微信:a439714 (大鱼同学)
▴ 海外用户添加telegram:dorisYG(Doris Guo)
(5)攻克证明
为了防止恶意领取赏金,请在领取赏金前出示攻克证明,凭证由 aelf 技术团队审核,通过后可发放赏金。
出示攻克证明时至少提供以下内容:
▴ 攻克过程代码
▴ 如何攻克的讲解说明文档
▴ 个人/团队介绍
▴ ELF收款地址
(6)有奖转发
▴ 转发活动至朋友圈集赞20个,凭截图领取aelf周边公仔1个。
▴ 微博、技术博客、媒体号等转发活动文章/海报、分享参赛心得推荐活动,凭借宣发链接领取aelf周边公仔1个。
▴ 限量5个,前5名完成任务用户获得
▴ 仅限国内用户
▴ 领奖加微信:zhizunxiaobao617(aelf小仙女)
aelf 衷心欢迎广大用户积极参与本次黑客赏金活动,希望用户们可以在体验中更加深入的了解CCTP,也欢迎大家提出有建设性的优化建议促进 aelf 生态的发展!
Java中的锁的概念挺多,从思想的角度,分为各种锁。下面让我们来分别认识一下他们。
可重入锁、不可重入锁
我们一般用的都是可重入锁。可重入锁指的是当一个线程获取到一个锁后,如果再次获取这个锁的话,依然可以获取到。
而不可重入锁,指的是当一个线程获取到一个锁后,如果再次获取这个锁的话,就不能再获取到了。不可重入锁会导致死锁,所以我们一般是不会使用不可重入锁的。
公平锁、非公平锁
AQS框架可以实现公平锁和非公平锁。AQS框架中包含三个主要元素,一个是状态status,用来表示表示锁是否被占用;另一个是当前线程;最后一个是队列,用来保存被阻塞的线程。
如果用来保存被阻塞线程的队列是按顺序获取锁的,队首线程先获取锁,那么这就是公平锁,每个线程都能够按照进入队列的顺序获取到锁。
如果用来保存被阻塞线程的队列不是按照顺序来获取锁的,而是大家一起竞争锁,那么就是非公平锁,这样可能会导致某些线程一直获取不到锁而被饿死。
非公平锁原理
线程A进入AQS后,发现state=0,则采用CAS方式更改state=1,并设置自己为当前线程。
线程B进入AQS后,发现state=1,没有办法修改,只能去队列排队了。
线程A执行完成,将state设为0,抹掉自己的痕迹,唤醒线程B。
线程C进入AQS,发现state=0,果断CAS为1,将自己设为加锁线程。线程 B醒来后发现state=1了,只能继续排队。
公平锁原理
线程A进入AQS,发现state=0,则采用CAS方式更改state=1,并设置自己为当前线程。
线程B进入AQS后,发现state=1,没有办法修改,只能去队列排队了。
线程A执行完,设置state为0,抹掉自己痕迹,唤醒线程B。线程C发现state=0,但自己不是队列第一位,去排队。
线程B被唤醒后,发现state=0,并且自己是队首,则设置state=1,设置自己为加锁线程。
悲观锁、乐观锁、自旋锁
悲观锁和乐观锁也是一种不同维度的锁的思想。
悲观锁指的是在执行操作之前加锁,来保证操作只有获取到锁的线程才能执行。这样的话,由于有加锁操作,所以效率可能会低,但是保证安全。比如synchronized和ReentrantLock都是悲观锁。
乐观锁在执行之前不加锁,而在执行的时候进行判断,比如CAS就是典型的乐观锁。执行的时候先比较线程工作内存是否为最新数据,如果是最新数据才进行更新,如果不是最新数据,工作线程内存中的数据已经过期,则交给开发者去处理。
提到CAS,有人可能会想到自旋锁。synchronized中的轻量级锁不就是采用了CAS自旋锁的方式么。
这里,我们要区分好CAS和自旋的概念。CAS是compare and swap,即比较并交换。它是一次操作,一次比较并交换操作。而自旋指的是多次执行同样的操作。它们两个是不同的概念。而自旋锁指的是用自旋的方式多次执行CAS操作。
所以,CAS是乐观锁,而多次CAS,也就是多次执行乐观锁,就成了自旋锁。
独占锁、共享锁
独占锁、共享锁也是思想成本的概念。
独占锁指的是只有一个线程可以拥有这个锁来执行相应操作。比如:synchronized和ReentrantLock,以及读写锁中的写锁。
共享锁指的是可以有多个线程同时拥有这个锁。比如:读写锁中的读锁,是允许多个线程一同获取到的。还有Semaphore(信号量),因为允许多个线程通过,所以也是共享锁。
总结
之所以有这么多锁的概念,都是从不同维度对锁进行的分类。这些都是概念层面上的东西,可以有不同的实现方式。
而这些锁的本质其实都是一个框架,这个框架的功能就是在合适的时机让某一个或者某些线程执行,而另一个或者另一些线程阻塞。
而Java并发库中的锁,让哪个线程执行、哪个线程阻塞是由AQS来决定的,而AQS是基于共享变量和LockSupport来实现的。
没错,锁就是用来控制线程阻塞和执行的一套框架。 文源网络,仅供学习之用,如有侵权请联系删除。
我将面试题和答案都整理成了PDF文档,还有一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,但不仅限于此。
关注公众号【java圈子】获取资料,还有优质文章每日送达。
作者:Jay_huaxiao https://juejin.im/post/5d214639e51d4550bf1ae8df
1.spring的生命周期
Spring作为当前Java最流行、最强大的轻量级容器框架,了解熟悉spring的生命周期非常有必要;
首先容器启动后,对bean进行初始化 按照bean的定义,注入属性 检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean,如BeanNameAware等 以上步骤,bean对象已正确构造,通过实现BeanPostProcessor接口,可以再进行一些自定义方法处理。如:postProcessBeforeInitialzation。 BeanPostProcessor的前置处理完成后,可以实现postConstruct,afterPropertiesSet,init-method等方法, 增加我们自定义的逻辑, 通过实现BeanPostProcessor接口,进行postProcessAfterInitialzation后置处理 接着Bean准备好被使用啦。 容器关闭后,如果Bean实现了DisposableBean接口,则会回调该接口的destroy()方法 通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻
2.TCP三次握手,四次挥手
tcp的三次握手四次挥手是每个程序员都应该熟悉的。
三次握手 : 第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SEND 状态 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态。 第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输。
四次挥手 : 第一次挥手(FIN=1,seq=a),发送完毕后,客户端进入 FIN_WAIT_1 状态 第二次挥手(ACK=1,ACKnum=a+1),发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态 第三次挥手(FIN=1,seq=b),发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。 第四次挥手(ACK=1,ACKnum=b+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
3.线程池执行流程图
线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,这避免了在处理短时间任务时创建与销毁线程的代价。线程池执行流程是每个开发必备的。
执行流程 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
JDK提供了四种拒绝策略处理类 AbortPolicy(抛出一个异常,默认的) DiscardPolicy(直接丢弃任务) DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池) CallerRunsPolicy(交给线程池调用所在的线程进行处理
4.JVM内存结构
JVM内存结构是Java程序员必须掌握的基础。《 46张PPT弄懂JVM、GC算法和性能调优! 》推荐看下。 程序计数器(PC 寄存器)
程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、异常处理、线程恢复等基础功能都需要依赖计数器完成。
Java虚拟机栈 与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期与线程相同 每个方法被执行的时候都会创建一个"栈帧",用于存储局部变量表(包括参数)、操作数栈、动态链接、方法出口等信息。每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 局部变量表存放各种基本数据类型boolean、byte、char、short等
本地方法栈
与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。
Java堆 GC堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。 其大小通过-Xms(最小值)和-Xmx(最大值)参数设置,-Xms为JVM启动时申请的最小内存,-Xmx为JVM可申请的最大内存。 由于现在收集器都是采用分代收集算法,堆被划分为新生代和老年代。新生代由S0和S1构成,可通过-Xmn参数来指定新生代的大小。 所有对象实例以及数组都在堆上分配。 Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。
方法区 也称”永久代” ,它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。 运行时常量池:是方法区的一部分,其中的主要内容来自于JVM对Class的加载。 Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。
5.Java内存模型
Java的多线程之间是通过共享内存进行通信的,在通信过程中会存在一系列如可见性、原子性、顺序性等问题,而JMM就是围绕着多线程通信以及与其相关的一系列特性而建立的模型。JMM定义了一些语法集,这些语法集映射到Java语言中就是 volatile 、 synchronized 等关键字。 Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
6.springMVC执行流程图
User向服务器发送request,前端控制Servelt DispatcherServlet捕获; DispatcherServlet对请求URL进行解析,调用HandlerMapping获得该Handler配置的所有相关的对象,最后以HandlerExecutionChain对象的形式返回. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller) Handler执行完成后,返回一个ModelAndView对象到DispatcherServlet 根据返回的ModelAndView,选择一个适合的ViewResolver ViewResolver 结合Model和View,来渲染视图 将渲染结果返回给客户端。
7.JDBC执行流程
JDBC执行流程: 连接数据源 为数据库传递查询和更新指令 处理数据库响应并返回的结果
8.spring cloud组件架构
Spring Cloud 是一个基于 Spring Boot 实现的云原生应用开发工具,它为基于JVM的云原生应用开发中涉及的配置管理、服务发现、熔断器、智能路由、微代理、控制总线、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Eureka 负责服务的注册与发现。 Hystrix 负责监控服务之间的调用情况,起到熔断,降级作用。 Spring Cloud Config 提供了统一的配置中心服务。 所有对外的请求和服务,我们都通过Zuul来进行转发,起到 API 网关的作用 最后我们使用 Sleuth+Zipkin 将所有的请求数据记录下来,方便我们进行后续分析。 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。它是一个基于HTTP和TCP的客户端负载均衡器。 Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。
9.dubbo 调用
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的远程服务调用方案,这容易和负载均衡弄混,负载均衡是对外提供一个公共地址,请求过来时通过轮询、随机等,路由到不同server。
Provider: 暴露服务的服务提供方。 Consumer: 调用远程服务的服务消费方。 Registry: 服务注册与发现的注册中心。 Monitor: 统计服务的调用次调和调用时间的监控中心。 Container: 服务运行容器。
推荐去我的博客阅读更多:
1. Java JVM、集合、多线程、新特性系列教程
2. Spring MVC、Spring Boot、Spring Cloud 系列教程
3. Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4. Java、后端、架构、阿里巴巴等大厂最新面试题
觉得不错,别忘了点赞+转发哦!
怎么解决缓存雪崩?
事前(redis挂前):除了redis高可用,主从+哨兵或者redis cluster避免全盘崩溃
事中(最重要保证数据库不能挂):系统本地也需要启用缓存,数据库ehcache缓存,少量的map结构数据可以用ConcurrentHashMap,
然后加上hystrix限流和降级,避免mysql挂了
事后(redis挂后):redis持久化,恢复缓存数据
怎么解决缓存穿透?
比如说查询的数据是数据库没有的,但是这种查询请求非常多,
这种条件及返回结果,可以设置一个unkown之类的,只要这种条件不在查询数据库就算解决了
dubbo的工作原理是什么?
服务注册,注册中心,消费者,代理通信,负载均衡
ps. 详细来说dubbo分为10层:
1.service层,接口层,留给我们实现的接口
2.config层,配置层,任何一个框架都需要提供配置文件
3.proxy层,服务代理层,无论是消费者还是生产者,dubbo都会生成代理,代理之间进行网络通信
4.registry层,注册层,provider注册自己作为一个服务,consumer就可以找注册中心去寻找自己要调用的服务
5.cluster层,集群层,provider可以部署在多台机器上,组成集群
6.monitor层,监控层,consumer调用provider,统计信息监控
7.protocol层,远程调用层,负责具体的provider和consumer之间调用接口的网络通信
8.exchange层,信息交换层,封装请求响应模式,同步转异步
9.transport层,网络传输层,抽象mina和netty为统一接口
10.serialize层,数据序列化层
注册中心挂了可以继续通信吗?
可以,因为初始化的时候,消费者将提供者的信息拉取到了本地缓存。
dubbo都支持什么通信协议和序列化协议?
协议+序列化:
1.默认dubbo协议,单一长连接,NIO异步通信,基于hessian作为序列化协议,适用场景:传输数据量小(100k以内,数据量大长连接会导致并发降低),并发高
2.rmi协议,java二进制序列化,多个短连接,适用于文件传输
3.hessian协议(支持跨语言,序列化速度较慢),多个短连接,适用文件传输
4.http协议,走json序列化
5.webservice协议,走SOAP文本序列化
dubbo支持哪些负载均衡,高可用,动态代理策略?
负载均衡策略:
1.默认 随机调用,可以对生产者配置权重
2.均衡模式
3.自动感知,给不活跃(性能查,接收较少请求的机器)的机器更少请求
4.一致性hash,相同参数的请求分发到一个机器过去
集群容错策略:
1.失败自动切换(默认)
2.一次调用失败就立即失败
3.出现异常时忽略
4.失败了后台记录日志,定送重发,适合写消息队列
5.冰箱调用多个provider,一个成功就返回
6.逐个调用所有provider
动态代理策略:
默认使用javassist动态字节码生成,创建代理类,可以通过spi扩展机制配置自己的动态代理策略
SPI是什么,dubbo是怎么用SPI的?
中文是 服务提供接口 ;
意思就是对于很多组件,dubbo保留一个接口和多个实现,然后在系统运行的时候动态根据配置去找到对应的实现类,如果不配置就用默认实现
基于dubbo怎么做服务治理,服务降级和重试?
1.服务治理(怎么管理好那么多的服务?):
a调用链路自动生成:基于dubbo,对各个服务间的调用自动记录下来,自动生成各个服务之间的依赖和调用链路
b服务访问压力和时长统计:按照服务倍访问多少次,以及完整链路的时长,这样可以知道系统的访问压力
c服务分层避免循环依赖,调用链路监控和报警,服务可用性监控(成功率)
2.服务降级
就是降级后调用对应的接口mock
3.服务重试
失败重试+超时重试
分布式服务接口的幂等性如何设计?(例如怎么保证不重复扣款)
从本质上说,保证幂等性主要就3点:
1.对于每个请求必须有一个唯一的标识;
2.处理完请求,必须有一个记录来标识这个请求处理过了,例如数据库插入流水;
3.每次接收请求需要判断之前是否处理过。
所以说可以通过数据库做,可以通过分布式锁;
分布式系统中接口调用如何保证顺序性?
1.和MQ章节说的那样,将请求都放在一个队列
2.可以用分布式锁保证100%的顺序性,但是会使系统吞吐量和并发降低;
如何设计一个类似dubbo的rpc框架?
可以参考dubbo的分层来讲:
1.首先服务需要注册,那得有注册中心,可以用zookeeper做
2.消费者得去注册中心拿服务,服务存在多个机器上,得基于动态代理,就是接口在本地的一个代理,通过它获取服务地址
3.给哪个机器发送,需要负载均衡算法
4.怎么发送?用netty,nio;发送什么格式数据?hessian序列化协议数据
5.服务这边也一样,需要动态代理监听网络端口,接收请求后调用对应接口;
上面就是最简单的rpc框架的思路;还可以加上降级 限流之类的;
zookeeper都有哪些使用场景?
最常使用场景:
1.分布式的协调
a系统通过mq给b系统发送请求,a在zk上注册监听器,b系统消费后,a可以收到通知
2.分布式锁
3.配置信息管理:作为dubbo注册中心
zk和redis的分布式锁比较?
1.性能比较:
redis分布式锁,需要自己不断尝试获取锁,比较消耗性能
zk分布式锁,获取不到锁,注册个监听器,性能开销小,别人释放了能感知到
2.客户端挂了释放锁的处理比较:
redis获取锁的客户端挂了,只能等待超时时间后释放
zk的话因为是创建临时节点(创建一个节点就是上一个锁),客户端挂机了节点没了自动释放锁
所以个人认为zk分布式锁比redis分布式锁牢靠,模型简单易用
我实际项目中使用的都是redis分布式锁。
redis分布式锁,官方叫RedLock算法,有3个重要的考量点:互斥,不能死锁,容错(大部分节点或者这个锁就可以加可以释放)
redis分布式锁最基本的实现方式: 在redis创建一个key加锁 SET key value(随机值) NX PX 30000 //NX意思是key不存在才能设置成功,PX 30000 是说30秒后自动释放 释放就是删除key,用lua脚本删除,判断value一样才删除
setnx的时候为什么要用随机值?
因为加上自己操作时间太长,超过30秒,自己操作完准备删锁了,实际上锁超时释放了,如果不加随机值,可能就把后面别人加的锁干掉了
RedLock算法
假设redis cluster中有5个master实例
1.获取当前时间戳,单位毫秒
2.和基本实现一样,轮流尝试在每个master节点创建锁,过期时间较短,一般几十毫秒
3.尝试在大多数节点上建立一个锁,比如5个节点要求是3个(为什么是大多数?是为了规避宕机的风险,5个宕机2个依然可以成功)
4.客户端计算好建立好锁的时间,小于超时时间就算成功
5.锁创建失败就依次删除这个锁
6.别人建立了,你就不断轮询去尝试获取锁
分布式session如何实现?
常见方案:
1.tomcat + redis(也可以连哨兵)
基于tomcat原生的session支持,用TomcatRedisSessionManager,让部署的tomcat都将session存到redis;
缺点是和web容器耦合,如果要迁移到jetty怎么办?全部再配置一边吗,所以很麻烦;
2.spring session + redis
通过spring session直接写到redis不需要和容器打交道。 文源网络,仅供学习之用,如有侵权请联系删除。
我将面试题和答案都整理成了PDF文档,还有一套学习资料,涵盖Java虚拟机、spring框架、Java线程、数据结构、设计模式等等,但不仅限于此。
关注公众号【java圈子】获取资料,还有优质文章每日送达。
CREATE TABLE `xcxmall_lepin_receive_activity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `type` varchar(255) CHARACTER SET utf8 DEFAULT '', `type_name` varchar(255) DEFAULT '', `user_id` int(10) DEFAULT NULL, `store_id` int(10) DEFAULT NULL, `date` date DEFAULT NULL, `addtime` int(11) DEFAULT NULL, `money` decimal(10,2) DEFAULT '0.00', `order_no` varchar(255) DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=145 DEFAULT CHARSET=utf8mb4;
上面是对应的数据库表
INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (125, 'percent2', '满券', 435, 4, '2020-06-06', 1590659535, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (126, 'percent1', '0.5元红包', 433, 4, '2020-06-02', 1590659539, 0.50, 'PA202005280552199335'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (127, 'percent2', '满券', 433, 4, '2020-06-03', 1590659565, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (128, 'percent1', '0.5元红包', 433, 4, '2020-06-04', 1590659582, 0.50, 'PA202005280553025136'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (129, 'percent2', '满券', 433, 4, '2020-06-01', 1590659607, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (130, 'percent1', '0.5元红包', 433, 4, '2020-06-05', 1590659619, 0.50, 'PA202005280553389437'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (131, 'percent4', '5元红包', 433, 4, '2020-06-11', 1590659621, 5.00, 'PA202005280553417708'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (132, 'percent2', '满券', 436, 4, '2020-06-10', 1590659630, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (133, 'percent1', '0.5元红包', 436, 4, '2020-06-14', 1590659635, 0.50, 'PA202005280553551209'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (134, 'percent1', '0.5元红包', 433, 4, '2020-06-08', 1590659637, 0.50, 'PA2020052805535694610'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (135, 'percent1', '0.5元红包', 433, 4, '2020-06-14', 1590659638, 0.50, 'PA2020052805535863911'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (136, 'percent2', '满券', 433, 4, '2020-06-09', 1590659640, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (137, 'percent4', '5元红包', 433, 4, '2020-06-13', 1590659673, 5.00, 'PA2020052805543328312'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (138, 'percent2', '满券', 434, 4, '2020-06-07', 1590661112, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (139, 'percent2', '满券', 434, 4, '2020-06-10', 1590661115, 0.00, ''); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (140, 'percent1', '0.5元红包', 434, 4, '2020-06-02', 1590661129, 0.50, 'PA2020052806184919513'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (141, 'percent1', '0.5元红包', 434, 4, '2020-06-14', 1590661138, 0.50, 'PA2020052806185847114'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (142, 'percent4', '5元红包', 433, 4, '2020-06-12', 1590664582, 5.00, 'PA2020052807162200215'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (143, 'percent4', '5元红包', 434, 4, '2020-06-11', 1590717125, 5.00, 'PA202005290952049720'); INSERT INTO `xl-pp`.`xcxmall_lepin_receive_activity`(`id`, `type`, `type_name`, `user_id`, `store_id`, `date`, `addtime`, `money`, `order_no`) VALUES (144, 'percent2', '满券', 434, 4, '2020-06-03', 1590717127, 0.00, '');
我这边使用的YII2框架,代码 public static function getDays($user_id=0){ $sql = " SELECT user_id, days, COUNT(*) AS num FROM (SELECT user_id, @cont_day := (CASE WHEN (@last_uid = user_id AND DATEDIFF(login_dt, @last_dt) = 1) THEN (@cont_day + 1) ELSE 1 END) AS days, (@cont_ix := (@cont_ix + IF(@cont_day = 1, 1, 0))) AS cont_ix, @last_uid := user_id, @last_dt := login_dt FROM (SELECT user_id, DATE(date) AS login_dt FROM xcxmall_lepin_receive_activity ORDER BY user_id, date ) AS t, (SELECT @last_uid := '', @last_dt := '', @cont_ix := 0, @cont_day := 0) AS t1) AS t2 where user_id={$user_id} GROUP BY user_id, days"; $result = Yii::$app->db->createCommand($sql)->queryAll(); $days_arr = []; if($result){ $days_arr = array_column($result,"days"); rsort($days_arr); } $day = 0; if($days_arr){ $day = $days_arr[0]; } return $day; }
思考----这里使用的sql 来实现 ,其实可以通过 另外一种方式来实现,
第一种方案:通过循环,把连续的时间 放到一个数组里面,然后再比较大小,
具体代码自己去实现
第二种方案:使用中间表,表里需要存储两个字段,一个字段时候 连续签到
一个是总的签到数量(第一个连续的总数), 一个是断的数量,当中途断了,就从0开始,只要 断>总的时候,就更新总的,
这样就可以计算出总的连续签到天数
0、适用版本
Java 8+
1、触发时机 扩容 remove
2、扩容触发
扩容发生时,一颗红黑树会被拆分成1条或2条链表。此时通过判断链表长度是否小于“阈值”决定是否链表化。该阈值为6,也就是我们常说的,红黑树节点数小于等于6个就链表化。 // jdk-11, HashMap.java, 2212-2229 if (loHead != null) { if (lc <= UNTREEIFY_THRESHOLD) // 链表化阈值 UNTREEIFY_THRESHOLD = 6 tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } }
3、remove触发
如果 remove 的 key 是保存在红黑树里的,那么会调用 removeTreeNode 。这个方法的大致流程是: 前提:红黑树本身即是红黑树,又是双向链表 从双向链表中移除该结点(不影响红黑树) 判断红黑树结构是否满足链表化条件 如果满足,通过双向链表生成新链表,并替换红黑树。退出 如果不满足,从红黑树中删除该结点,用新的 root 结点替换旧的。结束
3.1 判断条件 // jdk-11, HashMap.java, 2095-2102 if (root == null || (movable && (root.right == null || (rl = root.left) == null || rl.left == null))) { tab[index] = first.untreeify(map); // too small return; }
关键逻辑 root.right == null // 右孩子为空 或 || root.left == null // 左孩子为空 或 || root.left.left == null // 左孙子为空
也就是说满足ABF三个结点任何一个为空时,会进行链表化。我们来考虑一下什么情况可以满足这个条件。
3.2 最小临界情况
如上图所示,红黑树达到不满足链表化条件的临界情况,此时删除任何一个结点都会导致 下次remove 链表化。 untreeify 前红黑树结点数为3。 第一次操作,判断树结构如上图,不满足条件无需链表化。并删除一个结点,剩余结点数为3 第二次操作,判断树结构满足链表化要求,执行链表化。并删除一个结点
3.3 最大临界情况
如上图所示,如果此时 005 结点被删除,树不会发生旋转。
下次remove 时因为满足 root.left.left == null 从而引发链表化。 untreeify 前红黑树结点数为10。 第一次操作,判断树结构(图3.3.1)不满足条件无需链表化。并删除05结点(图3.3.2),剩余结点数为10 第二次操作,判断树结构满足链表化要求,执行链表化。并删除一个结点
4、结论
HashMap中红黑树转换成链表的条件是: 扩容时,红黑树被拆分后,结点数小于等于6个,则链表化 remove 时,检查红黑树的结构满足一定条件则链表化,与结点数无关(结点数受限于红黑树本身) 扩容计算时是否包含插入的结点,取决于它们的执行顺序,不同方法有不同的实现,如 put (先插入后扩容), putAll (先扩容后插入) remove 链表化判断则是确定的:通过移除前的红黑树状态判断
1. 进入lua官网
https://www.lua.org
2. 点击download
3. 点击 get a binary
4. 选择lua版本 (此处选择最新的)
5. 进入下载页, 直接点击 Tools Executables 无需安装
6. 选择win64 / 32版本 (根据自己的系统选择)
7. 下载完成,解压到D:/Lua (目录地址自定义)
8. 将lua所在目录加入环境变量
8. 验证安装
此处输入lua53 , 如果觉得麻烦可以将lua53.exe修改为lua.exe(如果没有其它版本的lua)
9. Lua编辑器
1. 命令行cmd
直接输入 lua53回车,进入lua运行环境中
2. sublime text
点击 new ....
复制一下json内容到文件中,替换原有内容。 { "cmd": ["D:/Lua/lua53.exe", "$file"], "file_regex": "^(?:lua:)?[t ](...*?):([0-9]*):?([0-9]*)", "selector": "source.lua" }
保存为 lua53(名字自定义)
然后tool => build system 选择lua53。
运行文件: ctrl + B运行
3. vscode
安装插件 lua debug
ctrl + F5 非调试模式运行
F5 调试模式运行
4. IDEA
安装插件 EmmyLua
配置lua环境
1. 编辑
选择templates
点击确定就可以了。
右键,Run 就ok了。
其他编辑器,就不推荐了,可以根据自己的喜好选择适合自己的。
Set集合
最大的特点就是不允许保存重复的元素。其有两个常用的子类,HashSet,TreeSet。
HashSet HashSet是Set接口中使用最多的一个子类,最大的特点是保存的数据是无序的。 特点: 保存的数据是无序的 不保存重复的数据
TreeSet
特点 保存的数据不重复 按照数据的升序进行排序
关于重复元素的说明 HashSet并不是利用 Comparable 接口判读数据重复,他利用的是Object类中的方法实现的。 hashCode 编码, equals 比较。eclipse能自动生成 hashCode , equals 函数 对象编码: hashCode 对象比较: equals 在Java中,对于重复元素的判断处理,就是利用的 hashCode , equals 函数完成的,只有在排序(TreeSet)时,才会使用 Comparable 接口实现
存入自定义的类型 TreeSet本质上是使用的TreeMap实现的数据存储,而TreeMap就需要 Comparable 接口确定大小关系 自定义的类型要实现 Comparable 接口,只有实现这个接口才能知道大小关系 TreeSet 在实现比较的时候,一定要把所有的属性都要进行比较,否则,部分属性相同,也会认为是重复数据。因此,TreeSet是利用 Comparable 接口确认重复数据的 由于TreeSet需要将所有的属性进行比对,实现难度太高,因此开发的时候一般使用HashSet进行储存
0. 前言
为什么我们需要异常处理?什么是异常?
在汉语中,异常指非正常的;不同于平常的。翻译到程序中,就是指会导致程序无法按照既定逻辑运行的意外,或者说是错误。可能会有小伙伴好奇了,我们的程序不是正常的吗,为什么还会出错呢?
我来举几个例子: 程序需要访问一个文件,但这个文件不存在,当程序尝试打开一个读该文件的流时就会出错 成绩管理系统中,成绩需要一个浮点型的数字,但是输入的人错误的输入了其他符号或者用中文输入了成绩 程序需要通过网络与其他服务器进行交互,但是程序所在计算机没有网了 程序在计算一个数除以另一个数的时候,除数错误的设置为0了
等等,以上都是出现异常的情景。
那么为什么需要异常处理机制呢?这是因为我们需要我们的程序不能是一个精美的易碎品,所以必须有一定程度的容错性,或者叫强壮性。这时候就要求程序员在开发过程中,对一些可能出现的场景进行预估,然后预先处理这些错误。而异常处理机制使得程序员更加简单方便的处理这些错误。
1. 异常类
C#中,所有异常都继承自System.Exception类,Exception类定义了C#异常应该具有的信息和方法。值得注意的属性有: public virtual string Message { get; }// 错误的信息,文字描述 public virtual string StackTrace { get; }// 发生异常的调用堆栈信息 public System.Reflection.MethodBase TargetSite { get; }//引发这个错误的方法 public Exception InnerException { get; }// 子异常
解释一下,调用堆栈指的是调用方法的列表。因为在实际开发中,方法的调用大多是一层套一层的形式调用的,而调用堆栈指的就是引发异常的方法到最外层的调用层次。(描述不太准确,大家意会即可)
而子异常或者内部异常,是因为在处理异常的时候,经常会对底层异常做处理然后将底层的异常进行封装和包装然后传递给上一级,使得越接近客服异常的信息越简单明了。
1.1 如何处理异常
之前说了一堆,但是如何处理异常呢?
在C#中,处理异常是一套通用的流程,涉及到三个关键字:try/catch/finally。先看一下写法: try { //可能会抛出异常 } catch (System.Exception e) { // 处理异常 }
简单介绍一下,try块里写的是可能会出现异常的代码。这是因为C#的机制,并不强制性声明方法会抛出异常。也就是说,C#的异常可以在合适的地方处理也可以不处理。
catch块用来声明捕获的异常,catch有三种写法: try { // } catch (System.Exception e)// 1 { // } catch(System.Exception)//2 { // } catch//3 { } 声明捕获一个异常,并获取这个异常实例 e 声明捕获一个异常,但不使用这个实例 声明捕获所有异常,不指定捕获的异常,也不获取异常实例
catch多次使用,意思是多次捕获不同的异常。如示例中的写法,但是示例中的写法存在一定问题。这是因为C#的异常捕获机制引起的,C#的异常捕获要求先捕获特殊的异常,再捕获一般的异常。换句话就是,在异常类继承树中,越是靠近Exception的异常类越是最后catch,在所有可能的异常处理中,Exception最后处理。所以catch可以是不在一个继承树上的异常类并列处理,也可以先子类再父类这种方式处理,但不论如何都不能对同一个异常多次catch。而且,一旦上一个catch了Exception,则之后的catch全都不会起作用。
finally块在异常处理中并不一定需要出现,但是这个块在异常处理中有着特殊的意义。finally块表示最后执行的块,用finally包裹的代码必然会执行。通常finally用来处理一些托管资源的释放和流的关闭等类型。
1.2 如何抛出一个异常
在上一节我们简单介绍了一下如何处理异常,这一节我们演示一下如何抛出一个异常。
使用throw就可以了,简单演示一下如何抛出异常: static void Main(string[] args) { throw new Exception(); }
这是最简单的写法,在方法中引发一个异常然后抛出。
这时候回过头来看一下Exception有哪些构造方法: public Exception (); public Exception (string message); public Exception (string message, Exception innerException);
所以我们在抛出异常的时候,可以指定异常的信息(message),其中堆栈信息和调用方法等内容由C#底层代码自动填写。
1.3 如何创建一个自定义异常
在简单演示了如何处理异常和如何抛出异常之后,我们来看看如何自定义一个异常类。根据类继承原则和异常处理原则,我们可以使用以下方式来自定义一个类: public class CustomException : Exception { }
这样我们就能获取一个异常类,我们可以根据自己的需要定制这个异常类,然后在使用的时候使用throw抛出。
2. 演示异常处理 class Program { static void Main(string[] args) { try { ThrowAnExcetption(); } catch(CustomException e) { Console.WriteLine(e.StackTrace); } finally { Console.WriteLine("执行了finally方法"); } } public static void ThrowAnExcetption() { throw new CustomException(); } } public class CustomException : Exception { }
以上示例简单演示了如何抛出异常,处理异常。
3. 总结
异常处理很简单,但是也很难。简单是指使用起来很简单,很难说的是在项目中如何合理优雅的处理异常和抛出异常。
这里是我自己总结的一个异常处理的哲学: 不是必须的场景,不要抛出异常 底层异常不要直接抛给上层方法 在程序编写的期间,预估一些场景,并对这些场景做数据校验和提示,而不是使用异常 在捕获异常时,最好编写相应的处理逻辑,而不是为了程序不报错直接写一个空的catch块 不要把异常当做额外的返回值处理
当然,最重要的一点就是结合实际业务需要进行异常处理。
C#的异常对于程序员来说,不是强制的,但是程序员必须在开发过程中对异常足够的重视才行。 更多内容烦请关注 我的博客《高先生小屋》
只有不断的学习,才能在成功的道路上畅通无阻
人们常说“活到老,学到老。”这在今天来说已经是实实在在的社会需求。
在知识加速更新的今天,昨天的知识可能很快就会变得陈旧,昔日的人才,如果不去通过不断的学习来充实自己,那么迟早都会落伍。
说到李嘉诚的大名,几乎无人不晓。他有着许多值得我们学习和借鉴的地方。
但是,我们最应该从他身上学到的,应该是他对知识的那种尊重。
李嘉诚曾经说过:不会学习的人就不会成功 。
凡是能成事之人,都有一个共同点,那就是对知识的尊重 。
你可以鄙视一切事物,但唯独不能鄙视的就是知识。知识,是我们的精神食粮。
我们知道,一个人如果想生存,就必须摄取足够的水分以及营养,从而保持我们整个肌体的健康。
但是,一个人只有强健的四肢还不够。如果你想在这个世界上生存,更要有一个发达的大脑。
大脑是我们前进的助推器。就像一辆车,无论外表多么华丽,如果没有一个优良的发动机,还是不能正常工作。
只有不断学习,才能让自己不停地进步,才能使自己立于不败之地,才会成为通往成功道路上的领跑者。
为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!
1.性能优化
深入内核,直击故障,拒绝蒙圈
2.应用框架 源码解
站在巨人的肩膀上,收货不一样的视野
3.分布式架构设计
透彻理解高性能架构的好处和优点
必然性,适应市场需求,能够去找一些更大的平台发展,提升自己的综合技术能力和薪资。
了解从传统架构到分布式架构演变过程所带来的技术变革,将理论和实战相结合,透彻理解分布式架构及其解决方案。
从分布式架构原理,到分布式架构策略,再到分布式架构中间件,最后在加上分布式架构实战,让程序员可以在技术深度和技术广度上得到飞跃的提升,成为互联网行业所需要的T型人才。
4.微服务架构设计
随着业务的发展,代码量的膨胀和团队成员的增加,传统单体式架构的弊端越来越凸显,严重制约了业务的快速创新和敏捷交付。
为了解决传统单体架构面临的挑战,先后演进出了SOA服务化架构、RPC框架、分布式服务框架,最后就是当今非常流行的微服务架构。
微服务化架构并非银弹,它的实施本身就会面临很多陷阱和挑战,涉及到设计、开发、测试、部署、运行和运维等各个方面,一旦使用不当,则会导致整个微服务架构改造的效果大打折扣,甚至失败。
5.团队协作
让你的团队开发效率提升十倍的秘密都在这里
6.大型商城项目实战
要想立足于互联网公司,且能在互联网浪潮中不被淹没,对于项目的开发实战演练是不必可少的技能,也是对自身能力的一个衡量,有多少的量对等于获得多少的回报。
看似简单的一个项目需求图谱,其中的底层原理,实现原理又能知道多少?
你搭建一个完整的B2C项目平台到底需要多少知识?这一切都是需要我们考量的。
7.全面知识点(部分):
spring源码解读、mybaits、spring boot、设计模式等.........
上面的这些面试题都整理成了PDF文档,希望能帮助到你面试前的复习并找到一个好的工作,相对来说也节省了你在网上搜索资料的时间来学习!!!
附欢迎关注我的公种号:it资源之家 ,扫描下面二维码即可领取更多一线大厂Java面试题资料!
最后
欢迎大家评论区一起交流,相互提升;整理资料不易,如果喜欢文章记得点个赞哈,感谢大家支持!!!
对,你没看错。我不推荐大家去做安全测试工程师。
为什么不推荐大家去做安全测试?
今天,很多软件并没有经过专门的安全测试便运行在互联网上,它们携带着各类安全漏洞直接暴露在公众面前,其中一些漏洞甚至直指软件所承载的核心敏感信息或业务逻辑。这些漏洞一旦被不怀好意者利用,很可能会给企业造成经济损失,带来负面声誉影响的同时,还可能被起诉遭到罚款等等,细思极恐。其中的一部分原因是企业本身安全意识不强,但是很多时候虽然软件企业已经开始意识到这些问题,却苦于缺少专业的安全测试人员,他们不得不冒着极大的风险先上线赌一把运气再说。
看到这里你可能会说:正是由于这样的情况,所以我们才要去做安全测试啊!
我在这里先不评价这个想法对或不对。要想知道为什么,请继续往下看。
我先来介绍一下神秘的安全测试:安全测试在软件测试里面是一个很特别的科目(“工种”),每次一碰到这个科目,很多人都觉得这个科目应该全权交给神秘的安全测试人员来管。这一个观念导致很多测试人员徘徊在安全测试的门口却迟迟不进去。
安全测试是非常复杂的,相信大家都没有异议。一个专业的安全测试专家在某种程度上来说是一个全栈工程师。所以,想要在安全测试上一夜成才很难。虽然,作为测试人员的我们却有得天独厚的优势,使我们能够在安全测试上快速起步,帮助团队尽快展开预防并检测安全漏洞的工作。
作为一个安全测试人员,你需要具备什么专业素养呢?
要使用别具一格的视角来审视需要测试的软件
这种能力说难不难,说简单却绝不简单。举个栗子:假设你正在测试一个web应用用户登录界面,你输入错误的用户名时提示“该用户名不存在”,你输入正确的用户名但是错误的密码时提示“密码输入错误”。这时你没有任何想法,一掠而过。但是作为一个安全测试员他会说:敏感信息暴露了,提示信息需要修改!为什么?!!因为通过我们的提示信息,恶意用户可以推测出哪些用户名已经存在于系统中,然后利用这些用户名再进行密码的暴力破解!
要改变测试中模拟的对象
软件测试人员通常会模拟普通用户的操作来测试软件,而安全测试员则需要模拟hacker来测试软件。这里说一些题外话,零几年软件测试岗位就开始出现了,大家有没有想过那批最早从事软件测试的人都去哪儿,干什么去了呢?
“黑帽子”和“白帽子”
那时候软件测试还没有这么专业,测试人员大多什么都需要会一点儿,做这一行的都是真心喜欢测试的,钻研技术的人也比较多。由于经常找BUG,对软件的漏洞测试人员也一清二楚,早期的软件测试人员有的做了“黑帽子”,有的做了“白帽子”。而安全测试员就是由白客发展而来的。
使用专用的测试工具
在具体做安全测试的时候我们会发现并不是那么容易去模拟恶意用户的行为。毕竟系统的前端会给我们很多的屏障。而且恶意用户可不总都是从系统前门进去的。这时候,使用一些工具,比如OWASP Zap、Burp suite等是非常有帮助的。我们可以在系统界面上执行功能测试的用例,用这些工具来获取http请求,篡改后发送给后台服务器。有了这些实用又比较容易上手的工具,就可以执行很多恶意用户的操作场景了。
Burp Suite
OWASP
一般来说,具备以上三点你就可以开始进行初步安全测试了。
现在我们可以回到文章开始的地方。文章开头的那个想法在读完上面的内容之后你会发现,它是对又不对的,很矛盾。确实由于很多公司不注重安全或者由于成本原因舍弃了安全保障,往后会需要更多安全测试员去进行安全测试。但是一个没有安全测试思考力的安全测试员他真的能胜任这份工作吗?
然而现实是:
一个安全测试工程师曾告诉我:他们公司安全测试岗位和其他岗位的比例为1:500。为什么?因为大公司的程序都是由很牛逼的开发做出来的,本身就已经有一定的防御能力,漏洞也不多,安全测试员的工作就是找出可能存在安全隐患的地方然后再由开发补上。一定程度上来说软件已经算是固如金汤了,一个hacker真想找出漏洞,花费的时间和精力绝对是要翻倍的。你觉得一个公司需要这么多安全测试工程师吗?
现在的情况是大公司才需要安全测试保障程序安全。小公司的想法是只要实现了用户需求就万事大吉了,何必花费大成本去保障安全呢?
作为恶意用户,即hacker,他会花时间去“黑”一个小公司?不会,因为那对于他来说根本没挑战,也没意义。
说到这里,我终于可以说一句:不推荐大家去做安全测试。这是建立在上文的分析之后的出的结论。很多人可能觉得安全测试工程师很高大上,薪资也非常可观。但是你也要看清行业的现实状况,不要一头雾水就扎进安全测试的圈子,到头来芝麻和西瓜都丢了,不值得。
最后也要声明一下,我绝不是反对大家做安全测试。如果你对安全测试很感兴趣,也有安全测试工程师需要有的独特的思考视角,能够沉下心来认真学习。那我非常推荐你去做安全测试,因为那才是适合你的工作!那才能证明你的价值!那才Cool!
还有,对测试感兴趣的小伙伴,可以加**718897738**一起交流,有免费的自动化、性能测试资料提供学习,希望能帮助到需要的你~
那么,你还会选择去做安全测试工程师吗?
所以说,你还会向安全测试工程师靠拢吗?
转载WeTester
cat /proc/cpuinfo 查看简略的信息 lscpu 简略快速 lshw sudo lshw -class processor
通过选项查看某一个硬件信息。 nproc 查看核心数量 dmidecode 稍微详细的
1.强引用
直接用new构建的对象的引用,jvm就是抛出OOM也不会回收;当不需要该对象时,可以通过弱化该引用去让 GC回收。
如: String str=new String("abc") //强引用;
str = null //当不需要时显示地赋值null,这样GC就会认为不存在引用,可以回收这个对象 或者在方法中新建一个强引用,引用保存在栈中,对象则在堆中,当方法调用完毕后,栈空间自动释放,这个对象的引用数为0,那么就可以回收了。
2.软引用
当一个对象仅具有软引用时,如果内存充足,那么就不会回收这个对象;而如果内存不足,就会将它回收。
软引用可以和 引用队列 联合使用,当对象被回收前,会将这个对象的软引用加到关联的引用队列中, 程序可以通过判断引用队列中是否加入了该引用来了解其对象是否已经被垃圾回收了。
常用于网页缓存、图片缓存。
3.弱引用
和软引用有点相似,但弱引用是不管内存是否充足,只有发现了弱引用就会回收该对象。
同样可以和引用队列联合使用。
4.虚引用
它不会对对象的生命周期有任何影响。虚引用主要用来跟踪对象被垃圾回收的活动。
它 必须和引用队列一起使用 。当对象被回收之前,GC会先将虚引用放入引用队列中。
如果虚引用被放入队列中了,表示该对象要被回收了,可以在被回收之前做些操作。
理解Java的强引用、软引用、弱引用和虚引用
Java 如何有效地避免OOM:善于利用软引用和弱引用
所谓的“线程八锁” 其实就是考察 synchronized 锁住的是哪个对象
情况1:12 或 21 @Slf4j(topic = "c.Number") class Number{ public synchronized void a() { log.debug("1"); } public synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n1.b(); }).start(); }
情况2:1s后12,或 2 1s后 1 @Slf4j(topic = "c.Number") class Number{ public synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n1.b(); }).start(); }
情况3:3 1s 12 或 23 1s 1 或 32 1s 1 @Slf4j(topic = "c.Number") class Number{ public synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } public void c() { log.debug("3"); } } public static void main(String[] args) { Number n1 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n1.b(); }).start(); new Thread(()->{ n1.c(); }).start(); }
情况4:2 1s 后 1 @Slf4j(topic = "c.Number") class Number{ public synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n2.b(); }).start(); }
情况5:2 1s 后 1 @Slf4j(topic = "c.Number") class Number{ public static synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n1.b(); }).start(); }
情况6:1s 后12, 或 2 1s后 1 @Slf4j(topic = "c.Number") class Number{ public static synchronized void a() { sleep(1); log.debug("1"); } public static synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n1.b(); }).start(); }
情况7:2 1s 后 1
@Slf4j(topic = "c.Number") class Number{ public static synchronized void a() { sleep(1); log.debug("1"); } public synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n2.b(); }).start(); }
情况8:1s 后12, 或 2 1s后 1 @Slf4j(topic = "c.Number") class Number{ public static synchronized void a() { sleep(1); log.debug("1"); } public static synchronized void b() { log.debug("2"); } } public static void main(String[] args) { Number n1 = new Number(); Number n2 = new Number(); new Thread(()->{ n1.a(); }).start(); new Thread(()->{ n2.b(); }).start(); }
来自黑马程序员的并发编程教程附件资料中,没有超链接无法贴出。
centos7 以上版本php7.2安装redis扩展,使php能和redis进行通信。
一、使用oneinstack安装php7.2
二、下载php7的phpredis扩展库 php官网下载redis扩展页面:http://pecl.php.net/package/redis wget http://pecl.php.net/get/redis-4.0.2.tgz
三、解压phpredis扩展库 tar -xzvf redis-4.0.2.tgz cd redis-4.0.2
四、通过phpize生成编译configure配置文件 [root@zixuephp redis-4.0.2]# /mnt/php7/bin/phpize Configuring for: PHP Api Version: 20170718 Zend Module Api No: 20170718 Zend Extension Api No: 320170718
五、进行编译前的配置 ./configure --with-php-config=/mnt/php7/bin/php-config
六、编译和安装 make && make install
七、修改配置文件 在php.ini增加扩展extension=redis.so vim php.ini #文件尾增加下面代码 extension=redis.so
八、指定配置文件重新启动 /mnt/php7/sbin/php-fpm -y /mnt/php7/etc/php-fpm.conf -c /mnt/php7/etc/php.ini
ps:
fpm 重启的时候 出现127.0.0.1:9000已经被占用
ERROR: unable to bind listening socket for address '127.0.0.1:9000': Address already in use (98)
ERROR: FPM initialization failed
解决方法:
netstat -lntup | grep 9000
kill all php-fpm
[root @www ~]# service php-fpm start
Starting php-fpm done 启动成功
或者使用平滑重启
# service php-fpm reload
Java并没有衰落.大家对它的认识才刚刚开始
很高兴能在此给大家分享Java8的新特性。这篇文章将一步一步带你了解Java8的所有新特性。我将通过简单的实例代码向大家展示接口中默认方法,lambda 表达式,方法引用,重复注解的使用. 看过这篇文章之后你将了解流、函数、接口、map扩展、日期中的新变化.不废话,放码过来!
Default Methods for Interfaces(接口中的默认方法)
Java 8准许我们在接口中增加一个通过default关键字修饰的非抽象的方法. 这个特性被我们称为扩展方法. 下面举例说明: interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
接口Formula 中除了抽象方法calculate 其中还定义了非抽象方法sqrt. 实现类里面继承了抽象方法calculate. 而默认方法可以直接调用. Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0
formula是一个继承Formula 接口的匿名对象. 实例代码十分详细: 仅仅6行代码实现了一个简单的 sqrt(a * 100)计算. 在接下来的章节, 将给大家介绍更加完善的方案实现单个方法对象在Java8中的使用.
Lambda expressions(Lambda表达式)
通过下面实例向大家展示怎么在之前版本中对List中的String排序: List
names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator() { @Override public int compare(String a, String b) { return b.compareTo(a); } });
通过一个静态 Collections.sort 方法接收一个list和compare来对每个List中的元素排序.你经常发现你自己创建一个匿名Comparator类并通过它来实现排序.
为了摆脱通过创建匿名对象的方式来实现排序, Java 8带来了更加简洁的方式实现方式,lambda 表达式: Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
你会发现代码更加简洁可读性更强. 甚至它还可以更加简洁: Collections.sort(names, (String a, String b) -> b.compareTo(a));
对于单行的方法体你可以直接省略{}和return关键字. 甚至可以使它更加简短: Collections.sort(names, (a, b) -> b.compareTo(a));
Java编译器能够自动识别参数类型所以你能够省略它们. 让我们更深入的了解lambda 表达式在java中更广泛的应用.
Functional Interfaces(函数化接口)
lambda表达式怎么去匹配Java类型呢? 每一个lambda表达式相当于指定一个接口的类型. 一个必须定义一个抽线的方法的函数接口. 每一个lambda表达式类型都将和这个抽象方法匹配.由于默认方法并不是抽象方法,所以你可以自由的添加默认方法到你的函数接口中.
我们能够使用任意接口作为lambda表达式,这个接口中只需要包含一个抽象的方法就可以了. 为了确保你的接口满足需求,你需要在你接口上添加一个@FunctionalInterface注解.当你使用这个注解之后,这个接口只能定义一个抽象方法,否者编译器会报错 . Example: @FunctionalInterface interface Converter { T convert(F from); } Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
注意@FunctionalInterface也是有效代码.
Method and Constructor References(方法和构造器的引用)
上面的代码可以通过静态方法应用可以更加简洁: Converter converter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123
Java 8能够传递一个方法或者构造器引用通过 ::关键字. 上面的实例向你们展示了怎么去引用一个静态方法. 我们也能够引用一个普通对象方法: class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
让我们来看看::是怎么引用构造器的的. 首先我们定义两个构造器在一个javabean中: class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
下一步我们创建一个工厂接口,用它来创建一个Person对象: interface PersonFactory { P create(String firstName, String lastName); }
手动实现这个工厂接口, 我们通过构造器和每件事情关联在一起: PersonFactory personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");
我们通过 Person::new来创建一个对Person构造器的引用. Java编译器自动的创建一个对象通过PersonFactory.create参数匹配合适的构造器.
Lambda Scopes(Lambda 的作用域)
从 lambda表达式访问局部变量和匿名对象是相似的.你可以访问final修饰的局部变量也能够访问实例化局部属性和静态变量.
Accessing local variables(访问本地变量)
lambda 表达式能够从局部读取一个的final修饰的局部变量: final int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
和匿名对象不同的是,在匿名对象中变量num并不需要用 final修饰. 也能正常调用: int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
然而num必须隐式使用final修饰才能编译. 下面代码就不会编译: int num = 1; Converter stringConverter = (from) -> String.valueOf(from + num); num = 3;
把num 写到lambda表达式内也是被禁止的.
Accessing fields and static variables(访问属性和静态变量)
与局部变量相比我们在lambda表达式中能够读和写一个全局属性和静态变量. 这和匿名对象中是一样的. class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
Accessing Default Interface Methods(访问默认的接口方法)
还记得第一个 formula 的实例嘛? 接口Formula定义一个默认的方法 sqrt,它能够被每个formula实例包括匿名对象访问. 它却不适合lambda 表达式.
lambda表达式不能访问接口的默认方法. 下面代码不能编译通过: Formula formula = (a) -> sqrt( a * 100);
Built-in Functional Interfaces(内置函数)
DK 1.8 API包含了许多内置函数. 它们中很多在老版本中是大家很熟悉的,像 Comparator 和Runnable. 这些接口通过 添加@FunctionalInterface注解来支持Lambda表达式.
但是Java 8 API 也添加了很多新接口使编程变的更加容易 . 这些新的函数很多是借鉴被大家所熟知的 Google Guava 函数库.即使你很熟悉这些函数库,你也需要关注这些方法是怎么在接口中扩展的以及它们怎么使用.
Predicates( 判断) 判断是传递一个参数返回一个布尔值. 这个接口包含各种默认的方法组成复杂的逻辑判断单元 (and, or, negate) Predicate predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate nonNull = Objects::nonNull; Predicate isNull = Objects::isNull; Predicate isEmpty = String::isEmpty; Predicate isNotEmpty = isEmpty.negate();
Functions(函数)
函数接收一个参数返回一个结果. 默认方法能够链式调用 (compose, andThen). Function toInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Suppliers(生产者)
Suppliers返回一个给定的泛型类型的结果. 不像函数, Suppliers不需要传递参数. Supplier personSupplier = Person::new; personSupplier.get(); // new Person
Consumers(消费者) Consumers代表在一个输入参数上执行一项操作. Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Comparators(比较) Comparators在老版本中大家都比较熟悉. Java 8 为这个接口增加了几种默认的方法. Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0
Optionals(选项) Optionals 并不是一个函数接口, 相反的它的作用就是避免出现空指针异常.它是一个很重要的概念对以一部分,然我们来快速了解它.
Optional是一个简单的容器里面包含有空或者非空的值.想象一下如果有一个方法能够返回一个空或者一个非空的值 . 在Java8中你可以通过返回一个Optional来替代空. Optional optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Streams java.util.Stream代表一个元素序列,在它上面能够进行一种或多种操作. Stream操作可以包括对中间部分或者终端部分进行操作. 终端操作能够返回一个确定的类型, 中间操作能返回流的本身你能够通过多种方法调用某一行. Streams 可以通过一个源来创建, 例如. 像 java.util.Collection中的lists 或 sets (maps 不支持)都可以创建流. Stream 能够串行操作也能并行操作.
首先看下流怎么进行序列操作. 我们先通过一个包含字符串List创建源: List stringCollection = new ArrayList<>(); stringCollection.add("ddd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ddd1");
在Java8中Collections已经继承了streams ,所以你能够通过in Java 8 are extended so you can simply create 调用Collection.stream() 和 Collection.parallelStream()来创建流.下面将给你介绍大部分流的操作 .
Filter(过滤) Filter接收一个判断用来过滤流中的所有元素. 这个操作是中间操作,它能够使我们对结果进行另一个流操作(forEach) . ForEach接受一个consumer操作对每一个过滤的流元素中. ForEach是一个终端操作. 它返回值void,所以我们不能调用另一个函数操作. stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
Sorted(排序) Sorted是一个中间操作,它能够返回一个排序的流.这个元素按照自然排序除非你传递一个Comparator . stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2"
注意 sorted 仅仅是创建一个排序后的视图操作,并没有操作排序返回的集合. 排序的 stringCollection并没有受到影响: System.out.println(stringCollection); // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map(映射) 中间操作 map转换每个元素到另一个元素中通过给定的函数.下面的实例是把把每一个字符串转换成大写.你也可以使用map转换每个对象的类型. 泛型流依赖你传递给map的泛型函数. stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match(匹配) 可以使用各种匹配操作来确定是否是确定的流. 所有这些操作是终端操作并返回一个布尔值. boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // true
Count(统计) Count是一个终端操作返回流中元素的个数. long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3 Reduce(合并)
这个终端操作完成一个流中元素合并操作通过给定的函数.返回的结果通过 Optional来保存值. Optional reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2" Parallel Streams(并行流)
上文提到过流可以是串行的也可以是并行的. 串行的序列操作是单线程的,而并行流是在多个线程上进行操作.
下面的实例向你展示通过并行流增加性能.
首先创建一个元素出现次数唯一的大的List: int max = 1000000; List values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
现在测试它的排序所消耗的时间. Sequential Sort(串行排序) long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // sequential sort took: 899 ms Parallel Sort(并行排序) long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // parallel sort took: 472 ms
你能看到上述代码基本相同的,但是并行大约快了50%.你可以测试着用 parallelStream()操作代替stream().
Map(映射) 上文提到map并不支持流操作. 但是新的map支持各种新方法和常见的任务操作. Map map = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val));
上面的代码简洁易懂: putIfAbsent方法对传统的非空检验 ; forEach 方法遍历map中每一个值进行consumer操作.
这个实例展示了怎么利用函数在map上进行操作: map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33
下一步,我们学习怎么给定一个key怎么移除对象操作, 现在加入给一个键值对: map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null
另一个有用的方法: map.getOrDefault(42, "not found"); // not found
map中合并对象操作很容易: map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); map.get(9); // val9 map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); map.get(9); // val9concat
合并操作如果键值对不存在就添加到里面, 合并操作也可以用来改变存在的值.
Date API(日期API)
Java 8 包含一个全新的日期和时间操作API在 java.time包里面. 新的Date API 可以和 Joda-Time 包相媲美, 不过,它们并不一样. 下面给你实例一下新日期里面主要新的API.
Clock(时钟)
时钟提供了访问现在的日期和时间的方法. 时钟意思到时区将可能替代 System.currentTimeMillis() 来获取现在的毫秒数. 这样一个瞬时点在时间线上用类 Instant来代表.代替能够被使用的java.util.Date 传统对象. Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date
Timezones(时区) 时区被ZoneId代替. 你可以很方便的访问通过一个静态的工厂方法. 时区定义一个弥补了一个重要在瞬时和本地时间和日期上的转换. System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00] LocalTime(本地时间)
本地时间代表一个不包含时区的时间, 例如. 10pm 或者 17:30:15. 下面两个实例创建两个本地时间包含上面定义的时区. 然后我们比较两个时间和日历在小时和分钟的不同之处. LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239
LocalTime可以从各种工厂方法简单的创建新实例, 包含格式化的字符串. LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37
LocalDate(本地日期) 本地日期代表了清晰的日期 , 例如. 2014-03-11. 它是不可更改的完全模拟LocalTime. 这个简单的实例展示怎么计算新的日期通过增加或者减去天、月、或者年. 注意每个操纵返回一个新的实例. LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY
通过一个字符串解析LocalDate from 和解析LocalTime一样简单: DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24
LocalDateTime(本地日期时间) LocalDateTime代表一个日期时间. 它是上例时间和日期的一个组合. LocalDateTime也是不可变的,它工作原理和 LocalTime、LocalDate工作原理一样.我们能够利用方法获取一个属性从date-time: LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439
传统的 timezone 能够转换成一个 instant. Instants 也能够很轻松转换成一个传统的java.util.Date. Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式化date-times 就是格式化dates 和 times一样.我们也可以使用预先定义的格式才创建定制的格式 . DateTimeFormatter formatter = DateTimeFormatter .ofPattern("MMM dd, yyyy - HH:mm"); LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 - 07:13
不像java.text.NumberFormat 新的 DateTimeFormatter是不可修改的且线程安全的.
Annotations(注解)
注解在Java 8中可重复使用.让我们通过实例来深入了解.
首先, 我们定义一个包装注解,它包含一个实际的注解数组: @interface Hints { Hint[] value(); } @Repeatable(Hints.class) @interface Hint { String value(); }
Java 8使我们能够使用一样的注解通过@Repeatable注解定义. Variant 1: 使用容器注解(old school) @Hints({@Hint("hint1"), @Hint("hint2")}) class Person {} Variant 2: 使用重复注解(new school) @Hint("hint1") @Hint("hint2") class Person {}
使用variant 2java编译器会隐式的设置 一个@Hints注解. 这个是很重要的通过反射读取注解信息. Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2
虽然我们从来没有定义 @Hints注解在 Person 类上,它一直能获取到通过 getAnnotation(Hints.class). 然而,这里有更方便的方法getAnnotationsByType它能够直接访问所有的 @Hint 注解.
此外使用Java8注解是扩展的两个新的目标: @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) @interface MyAnnotation {}
我的Java 8新特征实例到此结束. 如果你想学习 JDK 8 API所有新特性和类, 可以关注公众号:程序零世界 它帮你了解所有的新类和一些隐藏的新特性在 JDK 8,像 Arrays.parallelSort, StampedLock 和CompletableFuture等等.
我希望我的这篇文章能够帮助你,同时也希望你读后有所收获.你也可以通过公众号:程序零世界 向我反馈意见.
Hi,大家好,我是明哥。
在自己学习 Golang 的这段时间里,我写了详细的学习笔记放在我的个人微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,因此写的东西应该会比较适合刚接触的同学,如果你也是刚学习 Go 语言,不防关注一下,一起学习,一起成长。 我的在线博客: http://golang.iswbm.com 我的 Github:github.com/iswbm/GolangCodingTime
通常之前的学习,我们知道了在 Go 的项目中,可以 import 一个托管在远程仓库的模块,这个模块在我们使用 go get 的时候,会下载到本地。
既然是放在远程仓库上,意味着所有人都可以发布,并且所以人也都可以使用。
今天就来学习一下,如何发布一个开源的模块,并且使用它。
1. 新建仓库
先在你的 Github 上新建一个仓库,记得选 Public(默认)
然后你会得到一个仓库地址,在你的电脑上 使用 git clone 命令克隆下来
2. 编写模块代码
使用前面学过的 go mod init 命令进行初始化,注意这里的模块名,填写我们的git仓库地址(但是要去掉 .git 哈) $ git clone https://github.com/BingmingWong/goutils.git $ go mod init github.com/BingmingWong/goutils
然后新建一个 hash 文件夹,存放编写的一个计算 md5 值工具包,编辑 md5.go package hash import ( "crypto/md5" "encoding/hex" "errors" "fmt" "io" "os" ) // get file md5 func FileMd5(filename string) (string, error) { file, err := os.Open(filename) if err != nil { return "", errors.New( fmt.Sprintf("md5.go hash.FileMd5 os open error %v", err)) } h := md5.New() _, err = io.Copy(h, file) if err != nil { return "", errors.New(fmt.Sprintf("md5.go hash.FileMd5 io copy error %v", err)) } return hex.EncodeToString(h.Sum(nil)), nil } // get string md5 func StringMd5(s string) string { md5 := md5.New() md5.Write([]byte(s)) return hex.EncodeToString(md5.Sum(nil)) }
由于我们使用的都是内置包,没有引入第三方的包,所以接下来可以把你刚刚那些新增的文件,全部 push 到 git 仓库。 $ git add -A $ git commit -m "Add a md5 function" $ git push
3. 发布版本
一切完成后,刷新我们的仓库,就可以看到我们的刚刚上传的项目代码了,点击 release 发布一个版本
然后像下图一样,添加一些版本说明
最后点击一个 Publish release ,就发布了一个版本
4. 如何使用?
使用 go get 命令下载我们的发布的模块 $ go get github.com/BingmingWong/goutils
再使用 tree 命令,查看一下我们下载的包已经放入了 $GOPATH/pkg/mod 下。
有一点很有趣的是,我的 Github 用户名(BingmingWong)是有大写字母的,下载下来后,在目录中 大写字母 会对应变成 !小写字母 ,如下所示
这个用户名看起来有点非主流,你要想改的话,也是可以的。如果你有其他的开源项目,github 并不会为你做重定向,你需要自己评估这个风险。
回过头来,我还是继续讲如何使用吧。
下载下来后,我们试着去调用一下他的函数,有一点需要注意的是,在这个示例里,你不能使用 github.com/BingmingWong/goutils 去导入,因为在这个目录下并没有 package ,所以你必须导入 github.com/BingmingWong/goutils/hash 。
整个过程如下所示,供你参考:
本文参考学习自: https://studygolang.com/articles/22851
系列导读
01. 开发环境的搭建(Goland & VS Code)
02. 学习五种变量创建的方法
**03. 详解数据类型:** 整形与浮点型
04. 详解数据类型:byte、rune与string
05. 详解数据类型:数组与切片
06. 详解数据类型:字典与布尔类型
07. 详解数据类型:指针
08. 面向对象编程:结构体与继承
09. 一篇文章理解 Go 里的函数
10. Go语言流程控制:if-else 条件语句
11. Go语言流程控制:switch-case 选择语句
12. Go语言流程控制:for 循环语句
13. Go语言流程控制:goto 无条件跳转
14. Go语言流程控制:defer 延迟调用
15. 面向对象编程:接口与多态
16. 关键字:make 和 new 的区别?
17. 一篇文章理解 Go 里的语句块与作用域
18. 学习 Go 协程:goroutine
19. 学习 Go 协程:详解信道/通道
20. 几个信道死锁经典错误案例详解
21. 学习 Go 协程:WaitGroup
22. 学习 Go 协程:互斥锁和读写锁
23. Go 里的异常处理:panic 和 recover
24. 超详细解读 Go Modules 前世今生及入门使用
25. Go 语言中关于包导入必学的 8 个知识点
26. 如何开源自己写的模块给别人用?
27. 说说 Go 语言中的类型断言?
28. 这五点带你理解Go语言的select用法
查询快捷键
CTRL+N 查找类
CTRL+SHIFT+N 查找文件,打开工程中的文件(类似于eclipse中的ctrl+shift+R),目的是打开当前工程下任意目录的文件
CTRL+SHIFT+ALT+N 查 找类中的方法或变量(JS)
CIRL+B 找变量的来源,跳到变量申明处
CTRL+ALT+B 找所有的子类
CTRL+SHIFT+B 找变量的 类
CTRL+G 定位行,跳转行
CTRL+F 在当前窗口查找文本
CTRL+SHIFT+F 在指定路径查找文本
CTRL+R 当前窗口替换文本
CTRL+SHIFT+R 在指定路径替换文本
ALT+SHIFT+C 查找修改的文件,最近变更历史
CTRL+E 最近打开的文件
F3 查找下一个
SHIFT+F3 查找上一个
F4 查找变量来源
CTRL+ALT+F7 选 中的字符 查找工程出现的地方
ALT+F7 直接查询选中的字符
Ctrl+F7 文件中查询选中字符
自动代码
ALT+回车 导入包,自动修正
CTRL+ALT+L 格式化代码
CTRL+ALT+I 自动缩进
CTRL+ALT+O 优化导入的类和包
CTRL+E 最近更改的文件/代码
CTRL+SHIFT+SPACE 切换窗口
CTRL+SPACE空格 代码自动完成,代码提示,一般与输入法冲突
CTRL+ALT+SPACE 类 名或接口名提示(与系统冲突)
CTRL+P 方法参数提示,显示默认参数
CTRL+J 自动代码提示,自动补全
CTRL+ALT+T 把选中的代码放在 TRY{} IF{} ELSE{} 里
ALT+INSERT 生成代码(如GET,SET方法,构造函数等)
复制快捷方式
F5 复制文件/文件夹
CTRL+C 复制
CTRL+V 粘贴
CTRL+X 剪 切,删除行
CTRL+D 复制行
Ctrl + Y 删除行插入符号
CTRL+SHIFT+V 可以复制多个文本
高亮
CTRL+F 选中的文字,高亮显示 上下跳到下一个或者上一个
F2(Shift+F2) 高亮错误或警告快速定位
SHIFT+F2 高亮错误或警告快速定位
CTRL+SHIFT+F7 高亮显示多个关键字.
本地历史VCS/SVN
Alt +反引号(') 快速弹出VCS菜单
Ctrl + K 提交项目VCS
Ctrl + T 更新项目从VCS
Alt + Shift + C 查看最近发生的变化
其他快捷方式
CTRL+Z 倒退(代码后悔)
CTRL+SHIFT+Z 向前
CTRL+H 显 示类结构图
Ctrl +F12 文件结构弹出
Ctrl+Shift+H 方法的层次结构
Ctrl+Alt+H 呼叫层次
CTRL+Q 显示代码注释
CTRL+W 选中代码,连续按会 有其他效果
Ctrl+Shift+W 减少当前选择到以前的状态
CTRL+B 转到声明,快速打开光标处的类或方法说明注释(CTRL + 鼠标单击 也可以)
CTRL+O 魔术方法
CTRL+/ 注释//取消注释
CTRL+SHIFT+/ 注释/*...*/
CTRL+ [] 光标移动到 {}[]开头或结尾位置
CTRL+SHIFT+[] 选中块代码,可以快速复制
ctrl + '-/+': 可以折叠项目中的任何代码块,包括htm中的任意nodetype=3的元素,function,或对象直接量等等。它不是选中折叠,而是自动识别折叠。
ctrl + '.': 折叠选中的代码的代码
Ctrl+Shift+U 选中的字符大小写转换
ctrl+shift+i 快速查看变量或方法定义源
CTRL+ALT+F12 资源管理器打开文件夹,跳转至当前文件在磁盘上的位置
ALT+F1 选择当前文件或菜单中的任何视图工具栏
SHIFT+ALT+INSERT 竖编辑模式
CTRL+ALT ←/→ 返回上次编辑的位置
ALT+ ←/→ 切换代码视图,标签切换
ALT+ ↑/↓ 在方法间快速移动定位
alt + '7': 显示当前的类/函数结构。类似于eclipse中的outline的效果。试验了一下,要比aptana的给力一些,但还是不能完全显示prototype下面的方法名。
SHIFT+F6 重命名,重构 当前区域内变量重命名/重构
不但可以重命名文件名,而且可以命名函数名,函数名可以搜索引用的文件,还可以重命名局部变量。还可以重命名标签名。在sublime text中有个类似的快捷键:ctrl+shift+d。
ctrl+shift+enter(智能完善代码 如 if())
ctrl+shift+up/down(移动行、合并选中行,代码选中区域 向上/下移动)
CTRL+UP/DOWN 光标跳转到编辑器显示区第一行或最后一行下
ESC 光标返回编辑框
SHIFT+ESC 光 标返回编辑框,关闭无用的窗口
CTRL+F4 关闭当前的编辑器或选项卡
Ctrl + Alt + V引入变量
Ctrl + Alt + F 类似引入变量
Ctrl + Alt + C引入常量
Ctrl + Tab 键切换选项卡和工具窗口
Ctrl + Shift + A 查找快捷键
Alt + #[0-9] 打开相应的工具窗口
Ctrl + Shift + F12 切换最大化编辑器
Alt + Shift + F 添加到收藏夹
Alt + Shift + I 检查当前文件与当前的配置文件
Ctrl +反引号(`) 快速切换目前的配色/代码方案/快捷键方案/界面方案
Ctrl + Alt + S 打开设置对话框(与QQ冲突)
运行
Alt + Shift + F10 选择的配置和运行
Alt + Shift + F9 选择配置和调试
Shift + F10 运行
Shift + F9调试
Ctrl + Shift + F10运行范围内配置编辑器
Ctrl + Shift + X运行命令行
调试
F8步过
F7步入
Shift + F7智能进入
Shift + F8步骤
ALT + F9运行到光标
Alt + F8计算表达式
F9恢复程序
Ctrl + F8切换断点
Ctrl + Shift + F8查看断点
导航
Shift + Esc键隐藏活动或最后一个激活的窗口
Ctrl + Shift + F4关闭活动运行/消息/ / ...选项卡
Ctrl + Shift + Backspace键导航到最后编辑的位置
Ctrl + Alt+B 到实施(S)
Ctrl + Shift+I 打开快速定义查询
Ctrl + U 转到super-method/super-class
Alt + Home 组合显示导航栏
书签
Ctrl + F11切换书签助记符
Ctrl +#[0-9]转到编号书签
Shift + F11显示书签
编辑
Ctrl + Q 快速文档查询
ALT + INSERT 生成的代码...器(getter,setter方法,构造函数)
Ctrl + O 覆盖方法
Ctrl + I 实现方法
Alt + Enter 显示意图的行动和快速修复
Shift + Tab 键缩进/取消缩进选中的行
Ctrl + Shift + J 智能线连接(仅适用于HTML和JavaScript)
Ctrl + Enter 智能线分割(HTML和JavaScript)
Shift + Enter 开始新的生产线
Ctrl + Delete 删除字(word)
Ctrl + Backspace删除字开始
Ctrl +小键盘+ / - 展开/折叠代码块
Ctrl + Shift +小键盘+展开全部
Ctrl + Shift +数字键盘关闭全部
os,语义操作系统,所以该模块就是操作系统相关的功能了,用于处理文件和目录这些我们日常手动需要做的操作。
比如新建文件夹、获取文件列表、删除某个文件、获取文件大小、重命名文件、获取文件修改时间等,该模块就包含了大量的操作系统操作函数,精选常用的进行解析,希望对大家有所帮助。
os下面有很多函数,调用方式一般为os.name,其中比较特殊的是Path,Path模块是OS的一个子模块,下面又有很多的函数,调用方式一般为os.path.isfile。刚接触的可能不大明白,需要注意下。 #加载 import os #查看os下的函数 print(dir(os)) #查看os.path下的函数 print(dir(os.path))
1、os.name()
描述:显示当前使用的平台,'nt'表示Windows,'posix' 表示Linux
语法:os.name os.name 'nt'
2、os.getcwd()
描述:返回当前进程的工作目录。
语法:os.getcwd() os.getcwd() 'C:
0. 前言
这是一个新的系列,来源于工作中的一个需求,领导准备新开一个项目线路,要求使用Java,项目符合现有主流技术,并要求对并发量有一定的承受能力 ,支持扩展。我和公司的几个小伙伴一起沟通了一下,这不就是标准的Spring Cloud微服务的系统架构吗。
之前读过小高之前发的文章的小伙伴也清楚我是C#开发,不过想当年我也系统学过Java,多年下来虽然手生,但也好歹没有落下技术。于是就揽下了这个活。毕竟学习是终身的。
不怎么简明的介绍了这个系列成立的原因,让我们言归正传,这个系列是我在搭建该项目过程的一个总结,如果后续开发中对框架有调整,也会在这个系列发布后续的更新。这也是为什么叫实战系列,而不是实战教程的原因。
那么,有兴趣的小伙伴,跟我一起来吧~
1. 项目结构
项目采用maven作为软件包管理工具,Spring boot+Spring Cloud作为项目基础架构,设有配置中心、服务发现中心、网关中心和链路追踪中心以及服务集群,其中服务集群之间添加链路熔断和负载均衡机制。
当然,目前参照了几个系统都按照这种逻辑搭建的框架,所以我们大致上也参考了这个模型。具体如下图:
2. 主要技术组件使用
这里大概介绍一下,我在实践中选用的技术组件,选用这些技术没多少原因,很大程度上考虑到团队喜好以及后续维护的方便,还有就是官方团队的维护上考虑。
2.1 Maven
Maven 翻译为"专家"、"内行",是 Apache 下的一个纯 Java 开发的开源项目,我主要用来进行包管理。老开发大多都知道如果一个项目使用了大量的三方包,如果没有统一的三方包管理就会出现三个人可能会引用四个版本的同一个包,这对于项目协同来说简直就是噩梦。所以Maven诞生了,从此Javaer有了一个进行包管理的神器。再次声明一下哈,maven不只是用来做包管理的,只是我使用的基本想法。当然还有编译、批量测试等功能。
2.2 Spring Boot
Spring Boot被誉为是Spring的一个跨时代发明,一度被认为是“不能更好了”的技术。Spring Boot解决了开发一个Web网站必须启动一个Tomcat或者其他的服务器才能进行调试的问题;又解决了Spring之前需要大量xml文件配置;Spring Boot集成了很多常见的Bean组件,在开发过程中只需要设置对应的参数值就可以了。
Spring Boot创建了Spring Boot maven的pom文件,这个文件里设置了大量java三方库的版本,依赖于一个Spring Boot的pom文件就可以统一的使用对应版本的java三方库。
2.3 Spring Cloud
Spring Cloud,嗯,怎么说呢。Spring Cloud可以说是一系列框架的有序集合,利用Spring Boot简化了基础设施的开发。Spring Cloud是实现微服务的一个有力的武器,我们利用Spring Cloud搭建服务发现注册、配置中心、消息总线、负载均衡、断路器和数据监控等。
总而言之,这是一个很神奇的项目。
2.4 Nacos
Nacos,阿里巴巴又一个“KPI”产品。最开始的时候,很多人都说又是阿里的一个KPI产品。但实际上Nacos现在成长为业内一个很强的技术框架,它集成了服务发现、配置管理等功能。
以下是我从nacos官方扒的图,有兴趣的小伙伴可以看看。
2.5 Feign 服务接口化
Feign的英文是“伪装、变形”等意思,是一种HTTP调用框架。它使Http调用变成了一种接口式的调用,而不用写一堆Http请求的代码。在Spring Cloud中,对Feign进行了加强,使得在各个服务之间可以通过定义Interface来调用其他服务的代码。与此同时,Spring Cloud Feign 集成了Ribbon作为负载均衡组件,Hystrix 作为链路熔断组件。
具体为什么要使用负载均衡和链路熔断可以参考该系列的Feign配置篇。
2.6 Zipkin
之所以使用链路追踪系统,主要是为了跟踪各服务之间的调用信息。跟踪之后,可以通过链路信息确认各服务之间的调用关系;可以在发生错误的时候,通过链路追踪定位具体的错误;可以根据链路信息确认服务之间的负载是否均衡,是否满负荷是否超负荷等。
而Zipkin正是链路追踪系统中的佼佼者,项目中选型选中了Zipkin,让我们欢呼吧。
2.7 ElasticSearch
在使用了链路追踪之后,每次都会启动大量的链路数据,所以就需要一个数据库,为了方便查找然后选中了ElasticSearch。那么什么是ElasticSearch呢?
暂且抛开官网中的Elastic Stack,因为我也不知道它说的是啥。简单来讲,ElasticSearch是一种文档型的数据引擎或者数据库。我们可以通过ElasticSearch实现全文检索功能。
2.8 其他
项目还集成了Spring Boot Admin,计划集成ELK、jwt这些框架。不过业务暂时没有其他方面的需求,所以暂时没有加入其他组件。后续如有变更会更新到该系列中。
3. 其他
这一篇简单介绍了一下项目的技术选型,从下一篇开始正式说明我是如何从零开始搭建项目框架的。真的是从零开始,很多技术框架都是新接触的。 更多内容烦请关注 我的博客《高先生小屋》
在 JavaScript 语言中,运算符主要用于执行程序代码运算,针对一个以上的操作数项目来进行运算。运算符大家应该不陌生,因为我们在数学中都学过,例如加减乘除等。JavaScript 中的运算符可以分为 算术运算符、赋值运算符、字符串运算符、比较运算符、逻辑运算符、类型运算符、按位运算符等。
算术运算符
JavaScript 中算术运算符主要用于对数字执行算术,以数值作为其操作数,并返回一个单个数值。
算术运算符我们可以分为7种:
运算符 描述
+ | 加,加法运算符的作用是数值求和,或者字符串拼接。 - | 减,减法运算符使两个操作数相减,结果是它们的差值。 |
---|
* | 乘,乘法运算符的结果是操作数的乘积。 | / | 除,除法运算符的结果是操作数的商 ,左操作数是被除数,右操作数是除数。 | % | 求余,求余运算符返回第一个操作数对第二个操作数的模。 ++ | -- 递增,递增运算符为其操作数增加1,返回一个数值。 | 递减,递减运算符将其操作数减去1,并返回一个数值。 | 赋值运算符 赋值运算符基于右边的值,给左边的值赋值。最基本的赋值运算符是等号 = ,也是我们最常用到的。 赋值运算符可以分为下面6种: 运算符 描述 | = | x = y,将 y 的值赋给 x += | x += y, 相当于 x = x + y |
---|
-= | x -= y ,相当于 x = x - y | *= | x *= y ,相当于 x = x * y /= | %= x /= y ,相当于 x = x / y | x %= ,y相当于 x = x % y | 比较运算符 JavaScript 中的比较运算符可以用于比较两个值。关于比较运算符我们可以分为下面8种: 运算符 描述 | == | 等于 === | 等值等类型 |
---|
!= | 不等于 | !== | 不等于值或不等于类型 | > | 大于 | < | 小于 >= | <= 大于等于 | 小于等于 | 逻辑运算符 逻辑运算符又被叫做条件运算符,运算的结果只有两种,真(true)或假(false)。关于逻辑运算符可以分为三种,如下所示: 运算符 描述 | && | 逻辑与 类型运算符 类型运算符主要有两种,分别是: typeof :用于查找JavaScript变量的数据类型,返回变量的类型。 instanceof :用来判断某个构造函数的prototype属性是否存在在另一个要检测对象的原型链上,返回一个Boolean值,指出对象是否是特定类的一个实例。 示例: 我们可以使用 typeof 操作符来检测变量的数据类型,如下所示,定义了一些不同类型的变量,然后在控制台输出这些变量的类型: var name = "xkd"; var age = 18; var msg = true; var lst = [1, 5, 8]; var obj = {a: 1, b:2 }; console.log(typeof(name)); // string console.log(typeof(age)); // number console.log(typeof(msg)); // boolean console.log(typeof(lst)); // object console.log(typeof(obj)); // object 操作符 instanceof 可以用于检测函数是否存在于实例对象的原型链中: function Person() {} function Student() {} Student.prototype = new Person(); const student = new Student() if (student instanceof Student) { console.log("student是Student的实例"); } if (student instanceof Student) { console.log("student是Person的实例"); } 输出: student是Student的实例 student是Person的实例 按位运算符 处理32位数字,就是它在操作中的任何数字都将转换为32位数字,结果返回一个JavaScript编号。 关于按位运算符我们有一个大概的相关表格如下: 运算符 描述 示例 等同于 结果 十进制的 | & | and 和 | 5 & 1 | 0101 & 0001 | 0001 | 1 | | or 或者 | 5 | 1 | 0101 ~ | ^ | << | >> | >>> not 不 | xor 异或 | 零填充左移 | 有符号右移 | 零填充右移 ~ 5 | 5 ^ 1 | 5 << 1 | 5 >> 1 | 5 >>> 1 ~ 0101 | 0101 ^ 0001 | 0101 << 1 | 0101 >> 1 | 0101 >>> 1 1010 | 0100 | 1010 | 0010 | 0010 10 | 4 | 10 | 2 | 2 |
---|
上面我们简单的介绍了一下 JavaScript 语言中的几种运算符,后面我们会详细讲一下常用运算符的使用。 熟练识记 运算符的分类可以有哪些。 认识和知道运算符的各个基本概念。 定义两个字符,然后通过 + 运算符连接起来,输出结果值。 同样定义两个字符串,通过 += 运算符连接起来并将结果赋值给第一个后,在浏览器输出结果。 typeof 类型运算符(在任意类型运行后)的返回值可以有哪些。 new 一个对象,然后通过instanceof 类型运算符进行代码的不同类型计算判断。 一、java基础语句规范 1.1 switch得default语句 必须带有default语句,除非是枚举类型。 1.2 switch的break语句 分支必须包含break语句,否则不满足条件会发生全部输出语句,不会结束。 1.3 建议 if-else-if类型条件判断 建议,最后加一个else分支。 用来解决不符合规则的输入。类似default语句。 1.4 不能用浮点类型作为循环变量 精度问题会产生意想不到的问题 浮点数由三部分组成:符号位S,指数部分E,尾数部分M float: S----E----M 1位 8位 23位 供32位 double: S----E----M 1 11 52 尾数部分为有效数字。 1.5 明确的进行类型转换 不要依赖隐士类型转换【系统自动完成,存储范围晓得类型自动向打的类型进行的转换】 强制类型转换:存储大的-->小的 进度损失 精确计算?BigDecimal来进行计算 二、类的使用规范 2.1 类名调用静态方法 不要使用实例化的方式进行调用方法 注意:使用实例化来调用静态方法时候,调用的静态方法是声明类型的静态方法,与实例实际类型无关。 当父类与子类有同名的静态方法时候,声明父类变量引用子类实例,调用静态方法时候调用的就是父类的静态方法。 父类子类有同名非静态:重写 父类和子类有同名静太:隐藏,调用的是父类的方法 使用类名来调用!!! 2.2 避免隐藏,遮蔽和遮掩 正确: 复写-->存在于子类父类之间 重载-->存在于类的内部【一组相似的行为用相同的命名】 隐藏-->子类与父类之间相同成员名,相同的方法名 遮蔽-->发生子啊类内部:一个变量方法或者类分别遮蔽在类内部具有相同名字的变量方法或者类,无法简单名引用到。 遮掩-->一个变量可以遮掩具有相同名字的一个类,只要他们都在同一个范围内:唯一一种两个名字位于不同名字空间的名字重用形式。 包括变量,包,方法或者类型。 2.3 子类复写父类方法要加Override注解 编译期发现是否已经复写父类方法 2.4 哈希集合存储对象的相关规则 HashSet是Set接口的实现,不允许重复【当有对象存储于这个类型之中,不允许修改hascode,否则内存泄漏等问题】 元素不重复是基于HashMap实现的 非线程安全 对于HashSet和HashMap,对象的hashcode至关重要,在hashcode集合内找到该对象完全依赖此值。 如果一个对象存入hash集合后,修改了参与计算的hashcode有关的字段,那么修改后的hashcode值就与最初存储进来的hashcode不同了 结果就是在集合内部找不到该对象,进而不能删除对象,造成内存泄漏。 2.5 向下转型前使用instanceof判断 自动类型转换:向上类型转换,小类型--->大类型的转换 强制类型转换:向下类型转换,大类型--->小类型的转换 老虎-->动物 没问题 动物-->老虎 有风险 需要判断instanceof 2.6 使用集合的toArray()方法转成数组 2.7 使用System.arraycopy()进行数组复制 如:for循环 复制到新数组 简单实现:System.arraycopy(),从制定位置开始,到目标函数指定位置结束 函数原型: public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length); src:源数组 srcpos:源数组的指定开始位置 dest:目标数组 desPos:目标数组起始位置 length:长度 native:是原生方法,由C、C++实现 优势:数组大小 小数组:for循环适合几百大小 大数组:arraycopy适合成千上万 三、异常处理规范 3.1 在异常条件下,保证释放已有的锁 ReentrantLock 可重入锁 互斥锁 ReentrantLock是一种递归同步机制=====等同于synchronized使用 但是ReentrantLock提供了比synchronized更强大的,灵活的锁机制,可以减少死锁的发生 ReentrantLock类能够实现线程之间的同步互斥 ReentrantLock完全互斥排他的效果,同一时间只有一个线程在执行 ReentrantLock.lock()方法后面的任务 释放锁放到finally语句块中。 这样就会解决没有异常的线程正常执行! 3.2 防止通过异常泄露敏感信息 信息泄露,如FileNotFoundException 就会发现异常,从而推测系统挖掘结构。 会打印文件的路径!!!!!黑客得知。 比进行过滤。 解决:放大IOException,catch异常进行处理! 3.3 拒绝在finally块中非正常结束 异常处理机制,最佳补充。 不要使用return,break,continue使其非正常结束! 否则会导致无法抛出异常。 四、认证考试试题 /** * @author 纸浅 * @date 2020/5/29 9:34 */ public class TestC { public static void main(String[] args) { List nameList2 = new ArrayList<>(); nameList2.add("jim"); nameList2.add("jim"); String[] array2 = (String[]) nameList2.toArray(); for (int i = 0; i < array2.length; i++) { System.out.println("array->"+array2[i]); } } } 编译通过,执行异常 ------------------------------------------------------ /** * @author 纸浅 * @date 2020/5/29 9:34 */ public class TestC { public static void main(String[] args) { char data = '5'; int test = 4; switch (data + 1){ case '5': test = test+1; case '6': test = test+3; case '7': test = test+5; break; default: test = test+7; } System.out.println(test); } } 12 ---------------------------------------------- 设置 线程名字 /** * @author 纸浅 * @date 2020/5/29 14:36 */ public class ThreadName extends Thread { public ThreadName(String threadName){ super(threadName); } @Override public void run(){ while (true){ System.out.println("running"); } } public static void main(String[] args) { ThreadName threadName = new ThreadName("testName"); threadName.start(); } } ---------------------------------------- /** * @author 纸浅 * @date 2020/5/29 14:36 */ public class ThreadName extends Thread { public void start(){ for (int i = 0; i < 10; i++) { System.out.println(i); } } public static void main(String[] args) { ThreadName threadName = new ThreadName(); threadName.start(); } } 编译通过 -------------------------------------------- /** * @author 纸浅 * @date 2020/5/29 9:34 */ public class TestC { public static void main(String[] args) { //int a = 3; int a = 1; switch (a){ default: System.out.println(5); break; case 0: System.out.println(0); case 1: System.out.println(1); case 2: System.out.println(2); break; } } } 5 1 2 --------------------------------------------- /** * @author 纸浅 * @date 2020/5/29 9:34 */ public class TestC extends Thread{ public static void main(String[] args) { switch (3){ default: System.out.println(3); case 0: System.out.println(0); break; case 1: System.out.println(1); case 2: System.out.println(2); break; } } } 3 0 ------------------------------------------ 添加一个配置类,完事clean项目,重启,清理浏览器缓存,清理之前生成的流程图片 FlowableConfig import org.flowable.spring.SpringProcessEngineConfiguration; import org.flowable.spring.boot.EngineConfigurationConfigurer; import org.springframework.context.annotation.Configuration; //www.1b23.com @Configuration public class FlowableConfig implements EngineConfigurationConfigurer { @Override public void configure(SpringProcessEngineConfiguration engineConfiguration) { engineConfiguration.setActivityFontName("宋体"); engineConfiguration.setLabelFontName("宋体"); engineConfiguration.setAnnotationFontName("宋体"); } } |