Quantcast
Channel: CSDN博客推荐文章
Viewing all 35570 articles
Browse latest View live

设计模式六大原则之----接口隔离原则

$
0
0

一、定义

建立单一的接口,功能尽量细化,
不要建立臃肿的接口。

  • 不依赖不需要的接口,剔除不需要的接口
  • 最小接口,对接口进行细化,方法数最少

友情提醒:xmind导出的图片有点模糊,请放大查看
这里写图片描述

二、 问题的由来

2.1 问题

如下图,类C和类D分别实现接口I,I中有5个方法,在子类Class C中只需要用到1、2、3,但确需要实现所有方法;在子类Class D中,本来只需要用到4、5,也需要实现所有方法。
这里写图片描述

2.2 解决方案

遵循接口隔离原则。细化接口I1,内部只包含方法1、2、3,让Class C实现;创建接口I2,内部只包含4、5,让ClassD实现它。

三、 单一职责和接口隔离原则的区别

  • 单一原则:注重职责,业务逻辑的划分
  • 接口隔离原则:重点在接口的方法数更少

四、 特点

  • 接口尽量小
    • 拆分接口,接口隔离的核心定义,不出现臃肿的接口
      • 不违反单一原则把一个业务拆分成两个接口
      • 遵循单一原则,避免臃肿
  • 接口高内聚
    • 提高类、接口、模块的处理能力,减少与外界交互
    • 具体:public越少越好,后期也越好维护
  • 定制服务
    • 起源 : 系统模块间的耦合需要有相互访问的接口, 这里就需要为各个 客户端 的访问提供定制的服务接口;
    • 只提供需要的接口,不需要就不提供
  • 接口隔离限度
    • 粒度越小,接口越灵活,但同时使系统变复杂,维护难度增加
    • 粒度越大,灵活性越低,无法提供定制服务,增大项目风险

五、 原子接口划分原则

  • 接口模块对应关系:一个接口只服务于一个子模块或业务逻辑
  • 方法压缩:通过业务逻辑压缩接口中的public方法,减少接口的方法数量
  • 修改适配:尽量去修改已经被污染的接口,如果变更过大,使用适配器模式处理
作者:xwh_1230 发表于2017/11/20 20:10:02 原文链接
阅读:0 评论:0 查看评论

CHROME开发者工具的小技巧

$
0
0

本文转自:https://coolshell.cn/articles/17634.html

Chrome的开发者工具是个很强大的东西,相信程序员们都不会陌生,不过有些小功能可能并不为大众所知,所以,写下这篇文章罗列一下可能你所不知道的功能,有的功能可能会比较实用,有的则不一定,也欢迎大家补充交流。

话不多话,我们开始。

代码格式化

有很多css/js的代码都会被 minify 掉,你可以点击代码窗口左下角的那个 { }  标签,chrome会帮你给格式化掉。

强制DOM状态

有些HTML的DOM是有状态的,比如<a> 标签,其会有 active,hover, focus,visited这些状态,有时候,我们的CSS会来定关不同状态的样式,在分析网页查看网页上DOM的CSS样式时,我们可以点击CSS样式上的 :hov 这个小按钮来强制这个DOM的状态。

 

 

动画

现在的网页上都会有一些动画效果。在Chrome的开发者工具中,通过右上角的菜单中的 More Tools => Animations 呼出相关的选项卡。于是你就可以慢动作播放动画了(可以点选 25% 或 10%),然后,Chrome还可以帮你把动画录下来,你可以拉动动再画的过程,甚至可以做一些简单的修改。

 

直接编辑网页

在你的 console 里 输入下面的命令:

document.designMode = "on" 

于是你就可以直接修改网页上的内容了。

P.S. 下面这个抓屏中还演示了一个如何清空console的示例。你可以输入 clear() 或是 按 Ctrl+L(Windows下),CMD + K (Mac下)

 

网络限速

你可以设置你的网络的访问速度来模拟一个网络很慢的情况。

 

复制HTTP请求

这个是我很喜欢 的一个功能,你可以在 network选项卡里,点击 XHR 过滤相关的Ajax请求,然后在相关的请求上点鼠标右键,在菜单中选择: Copy => Copy as cURL,然后就可以到你的命令行下去 执行 curl 的命令了。这个可以很容易做一些自动化的测试。

 

友情提示:这个操作有可能会把你的个人隐私信息复制出去,比如你个人登录后的cookie。

抓个带手机的图

这个可能有点无聊了,不过我觉得挺有意思的。

在device显示中,先选择一个手机,然后在右上角选 Show Device Frame,然后你就看到手机的样子了,然后再到那个菜中中选 Capture snapshot,就可以抓下一个有手机样子的截图了。

我抓的图如下(当然,不是所有的手机都有frame的)

 

设置断点

除了给Javascript的源代码上设置断点调试,你还可以:

给DOM设置断点

选中一个DOM,然后在右键菜单中选 Break on … 你可以看到如下三个选项:

给XHR和Event Lisener设置断点

在 Sources 面页中,你可以看到右边的那堆break points中,除了上面我们说的给DOM设置断点,你还可以给XHR和Event Listener设置断点,载图如下:

关于Console中的技巧

DOM操作
  • chrome会帮你buffer 5个你查看过的DOM对象,你可以直接在Console中用 $0, $1, $2, $3, $4来访问。
  • 你还可以使用像jQuery那样的语法来获得DOM对象,如:$("#mydiv")
  • 你还可使用 $$(".class") 来选择所有满足条件的DOM对象。
  • 你可以使用 getEventListeners($("selector")) 来查看某个DOM对象上的事件(如下图所示)。

  • 你还可以使用 monitorEvents($("selector")) 来监控相关的事件。比如:
monitorEvents(document.body, "click");

Console中的一些函数

1)monitor函数

使用 monitor函数来监控一函数,如下面的示例

2)copy函数

copy函数可以把一个变量的值copy到剪贴板上。

3)inspect函数

inspect函数可以让你控制台跳到你需要查看的对象上。如:

更多的函数请参数官方文档 – Using the Console / Command Line Reference

Console的输出

我们知道,除了console.log之外,还有console.debugconsole.infoconsole.warnconsole.error这些不同级别的输出。另外一个鲜为人知的功能是,console.log中,你还可以对输出的文本加上css的样式,如下所示:

console.log("%c左耳朵", "font-size:90px;color:#888")

于是,你可以定义一些相关的log函数,如:

console.todo = function( msg){
  console.log( '%c%s %s %s', 'font-size:20px; color:yellow; background-color: blue;', '--', msg, '--');
}
console.important = function( msg){
  console.log( '%c%s %s %s', 'font-size:20px; color:brown; font-weight: bold; text-decoration: underline;', '--', msg, '--');
}

关于console.log中的格式化,你可以参看如下表格:

指示符 输出
%s 格式化输出一个字符串变量。
%i or %d 格式化输出一个整型变量的值。
%f 格式化输出一个浮点数变量的值。
%o 格式化输出一个DOM对象。
%O 格式化输出一个Javascript对象。
%c 为后面的字符串加上CSS样式

 

除了console.log打印js的数组,你还可以使用console.table来打印,如下所示:

var pets = [
  { animal: 'Horse', name: 'Pony', age: 23 },
  { animal: 'Dog', name: 'Snoopy', age: 13 },
  { animal: 'Cat', name: 'Tom', age: 18 },
  { animal: 'Mouse', name: 'Jerry', age: 12}
];
console.table(pets)

 

关于console对象

  • console对象除了上面的打日志的功能,其还有很多功能,比如:
  • console.trace() 可以打出js的函数调用栈
  • console.time() 和 console.timeEnd() 可以帮你计算一段代码间消耗的时间。
  • console.profile() 和 console.profileEnd() 可以让你查看CPU的消耗。
  • console.count() 可以让你看到相同的日志当前被打印的次数。
  • console.assert(expression, object) 可以让你assert一个表达式

这些东西都可以看看Google的Console API的文档

其实,还有很多东西,你可以参看Google的官方文档 – Chrome DevTools

关于快捷键

点击在 DevTools的右上角的那三个坚排的小点,你会看到一个菜单,点选 Shortcuts,你就可以看到所有的快捷键了

如果你知道更多,也欢迎补充!

(全文完)

作者:ardo_pass 发表于2017/11/20 20:23:17 原文链接
阅读:0 评论:0 查看评论

725. Split Linked List in Parts。

$
0
0

Given a (singly) linked list with head node root, write a function to split the linked list into k consecutive linked list “parts”.

The length of each part should be as equal as possible: no two parts should have a size differing by more than 1. This may lead to some parts being null.

The parts should be in order of occurrence in the input list, and parts occurring earlier should always have a size greater than or equal parts occurring later.

Return a List of ListNode’s representing the linked list parts that are formed.

Examples 1->2->3->4, k = 5 // 5 equal parts [ [1], [2], [3], [4], null ]

Example 1:

Input:
root = [1, 2, 3], k = 5
Output: [[1],[2],[3],[],[]]
Explanation:
The input and each element of the output are ListNodes, not arrays.
For example, the input root has root.val = 1, root.next.val = 2, \root.next.next.val = 3, and root.next.next.next = null.
The first element output[0] has output[0].val = 1, output[0].next = null.
The last element output[4] is null, but it’s string representation as a ListNode is [].

Example 2:

Input:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
Explanation:
The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts.


这题很长花了很久看题,但是看懂题目之后就好做了。题中的主要意思是给定一个链表和一个整数k,然后将这个链表分为k段,并且有限定条件:

1.如果链表的长度比k小就用null来补充。
就如给的例1一样,链表长度为3但是k的值为5,所以将链表分为5段时,节点不够,所以后面的都用null来表示。

2.如果链表不能平均分的时候,各段相差的节点数不能超过1,并且前面分段的要比后面的长。
就如给的例2一样,链表长度为10,分为3段,此时不能平均分配,各段相差的不能超过1并且前面分段的要比后面的长,所以就分为三段:[1,2,3,4]、[5,6,7]、[8,9,10]。

3.最后将每段组成一个链表,然后全部装进数组中返回。

按照这个算,如果给的链表长度是11然后k为3的话,分开之后就是:[1,2,3,4]、[5,6,7,8]、[9,10,11]。

看明白题之后就简单了,我们先对给定的链表求长度,然后除以k,会得到一个商和余数,商的数值代表平均分为k段之后每段有多少个节点,余数的数值代表前多少段需要多加一个节点,商和余数总共有以下几个情况:

(1)商为0,余数为0。

此时说明链表长度就是0,也不需要做什么处理,直接返回一个空数组就行。

(2)商为0,余数不为0。

说明此时的链表长度是小于k的,就如例1一样,商为0,余数为3。说明平均分为5段之后,平均每段有0个节点,然后前3段需要多加一个节点,那么正好就是:[1]、[2]、[3]、[]、[]。

(3)商不为0,余数为0。

说明此时正好能够将链表平均分为k段,每段的长度就是商的数值了。

(4)商不为0,余数不为0。

此时说明能将链表分为k段,但是还有多余的节点。而题中规定了各个分段的长度相差不能大于1,那么我们就只能让这多出的节点再次分给每段,而且每段只能分一个。并且题中还规定了前面的长度要比后面的常,所以我们就应该按照段的顺序再给分配一个了。需要注意这里的节点顺序不能乱。

比如题中的例2,长度为10,然后除以k之后得到商为3,余数为1。那么我们就知道平均分为3段之后,每段有3个节点,但是还多了一个节点,按照前面的要比后面的长的规则,只能将多出的节点放在第一段上。而且还要按照原来的顺序,所以我们先给第一段分了3个节点之后发现余数为1,我们需要将这1个节点再分给第一段,最后第一段就是[1,2,3,4],并且将余数减1说明没有多余的节点了。然后后面每段分配完3个节点之后发现余数为0,就代表没有多余的节点了再给他们了。

就好像万圣节发糖一样,我有10个糖,有3个小孩来要糖,我算了一下,每人能分3个糖,还多出一个糖,然后将这个糖奖励给了第一个小孩。

唯一不同的是我们需要注意原链表的顺序!


C++:

class Solution {
public:
    vector<ListNode*> splitListToParts(ListNode* root, int k) {
        vector<ListNode*> nodes;
        int counts = 0;//用来统计链表共有多少个节点
        ListNode* p = root;

        while(p) {//循环计算出总共的节点数
            p = p->next;
            counts++;
        }

        int num,rem,i,j;
        num = counts/k;//计算出每节有多少个固定节点
        rem = counts%k;//计算出余数,也就是前多少个段需要多一个节点

        for(i=0;i<k;i++) {
            ListNode* head = new ListNode(0);//新建一个新的头结点
            p = head;
            for(j=0;j<num;j++) {//每个节点的固定个数进行添加
                ListNode* node = new ListNode(root->val);
                p->next = node;
                p = p->next;
                if(root) {
                    root = root->next;
                }
            }
            if(rem-- > 0 && root) {//需要多加一个节点
                ListNode* rmnode = new ListNode(root->val);
                p->next = rmnode;
                if(root) {
                    root = root->next;
                }
            }

            nodes.push_back(head->next);//添加到数组中
        }

        return nodes;
    }
};
作者:Leafage_M 发表于2017/11/20 20:53:47 原文链接
阅读:0 评论:0 查看评论

安卓使用videoview进行音频、视频播放,及播放控制

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

使用MediaPlayer播放视频,需要制作视频界面,渲染数据的surfaceView(屏幕缓冲区)、滚动条SeekBar和播放前准备。而videoview类封装了surface的创建以及media player中视频内容的分配和准备。所以使用起来更加方便,不过可定制程序也就不高了。

使用videoView播放本地和在线视频,在线视频是边加载边播放。

 //使用videoview控件进行视频播放
 private void configureVideoView() {

     VideoView videoView = (VideoView)findViewById(R.id.activity1_video1);
    //配置videoview并分配一个视频来源
    videoView.setKeepScreenOn(true);               //应用屏幕唤醒锁,播放时防止屏幕变暗。
    //videoView.setVideoPath("/sdcard/test.mp4");  //分配一个本地资源

    String videoUrl2 = "http://www.525heart.com/test.mp4";
    Uri uri = Uri.parse( videoUrl2 );
    videoView.setVideoURI(uri);                    //分配一个网络资源,加载需要一些时间,不过是边加载边播放

    //添加一个mediacontroller媒体控制器
    MediaController mediaController = new MediaController(this);
    videoView.setMediaController(mediaController); 

    //播放完成回调函数
    videoView.setOnCompletionListener( new MyPlayerOnCompletionListener());


    //下面的操作你可以在videoview中通过控件来操作
    videoView.seekTo(10);        //跳转到多少毫秒
    videoView.stopPlayback();    //停止返回
    videoView.pause();           //暂停
    videoView.start();           //开始
 }


 //视频播放完成的回调函数
  class MyPlayerOnCompletionListener implements MediaPlayer.OnCompletionListener
  {
        @Override
        public void onCompletion(MediaPlayer mp) {
          Log.v("媒体播放", "视频播放完成");
        }

  }
作者:luanpeng825485697 发表于2017/11/20 20:55:57 原文链接
阅读:0 评论:0 查看评论

【拜小白opencv】38-形态学滤波3——开运算

$
0
0

常言道“温故而知新”,写此文章就是对自己目前学习内容的小小的总结与记录。

本文力求用最简洁的语言,详细的代码将此部分内容讲解清楚,但由于博主同样是刚刚接触OpenCV,或许表达上有些瑕疵,还望读者能够指教探讨,大家共同进步。

博主机器配置为:VS2013+opencv2.4.13+Win-64bit。

若本文能给读者带来一点点启示与帮助,我就很开心了。

====================分割线====================


1-开运算

形态学开运算操作能去除噪声及平滑目标边缘等功能,其数学表达式如下:




  • 开运算(Opening Operation),其实就是先腐蚀后膨胀的过程。
  • 结构元素各向同性的开运算操作主要用于消除图像中小于结构元素的细节部分,物体的局部形状不变。
  • 物体较背景明亮时能够排除小区域物体,消除高于邻近点的孤立点,到达去噪的用作。
  • 可以平滑物体轮廓,断开狭窄的连接,去掉了细小的突出部分。
======================分割线=====================

2-morphologyEx()函数

作用:该函数可以进行形态学滤波的操作,里面包含了开运算、闭运算、形态学梯度、顶帽、黑帽、腐蚀、膨胀等。
void morphologyEx( InputArray src, OutputArray dst,
                                int op, InputArray kernel,
                                Point anchor=Point(-1,-1), int iterations=1,
                                int borderType=BORDER_CONSTANT,
                                const Scalar& borderValue=morphologyDefaultBorderValue() );
参数解释:
  • 参数1:输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
  • 参数2:OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
  • 参数3:int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
————MORPH_OPEN – 开运算(Opening operation)
————MORPH_CLOSE – 闭运算(Closing operation)
————MORPH_GRADIENT -形态学梯度(Morphological gradient)
————MORPH_TOPHAT - “顶帽”(“Top hat”)
————MORPH_BLACKHAT - “黑帽”(“Black hat“)
————MORPH_ERODE - “腐蚀”
————MORPH_DILATE - “膨胀”
————另有CV版本的标识符也可选择,如CV_MOP_CLOSE,CV_MOP_GRADIENT,CV_MOP_TOPHAT,CV_MOP_BLACKHAT等,这应该是OpenCV1.0系列版本遗留下来的标识符,和上面的“MORPH_OPEN”一样的效果。 
  • 参数4:InputArray类型的kernel,形态学运算的内核。若为NULL时,表示的是使用参考点位于中心3x3的核。我们一般使用函数 getStructuringElement()配合这个参数的使用。getStructuringElement()函数会返回指定形状和尺寸的结构元素(内核矩阵)。关于getStructuringElement()函数,请见文章里有相关讲解:

【拜小白opencv】36-形态学滤波1——腐蚀

  • 参数5:Point类型的anchor,锚的位置,其有默认值(-1,-1),表示锚位于中心。
  • 参数6:int类型的iterations,迭代使用函数的次数,默认值为1。
  • 参数7:int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
  • 参数8:const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档中的createMorphologyFilter()函数得到更详细的解释。

使用morphologyEx()函数,一般我们只需要填前面的四个参数,后面的四个参数都有默认值。
========================分割线====================

3-代码演示

/*
功能:形态学滤波:开运算。 本质就是先腐蚀后膨胀的过程。
*/

#include <opencv2/core/core.hpp>                
#include <opencv2/highgui/highgui.hpp>                
#include <opencv2/imgproc/imgproc.hpp>               
#include <iostream>              
using namespace std;
using namespace cv;

int main()
{
	Mat srcImage, dstImage; //源图像,输出图像
	//---------【1】读取源图像并检查图像是否读取成功---------      
	srcImage = imread("D:\\OutPutResult\\ImageTest\\xing.jpg");
	if (!srcImage.data)
	{
		cout << "读取图片错误,请重新输入正确路径!\n";
		system("pause");
		return -1;
	}
	imshow("【源图像】", srcImage);
	//---------【2】获取自定义核---------
	Mat element = getStructuringElement(MORPH_RECT, Size(9, 9));
	//---------【3】进行开运算操作---------
	morphologyEx(srcImage, dstImage, MORPH_OPEN, element);
	//---------【4】显示效果图---------
	imshow("【效果图--开运算操作】", dstImage);
	waitKey(0);
	return 0;
}

====================分割线==============

4-显示结果


====================分割线=================

5-代码解释

为了观察效果,本例中核的设置为9*9,该参数可以根据自己需要进行调节。
例子中,morphologyEx()函数的第三个参数,同学可以右键进行”查看定义“,看看都有什么,自己换个参数,看看会不会有什么效果。



=================END=======================

作者:sinat_36264666 发表于2017/11/20 20:54:13 原文链接
阅读:24 评论:0 查看评论

设计模式六大原则之----迪米特、开闭原则(六大原则简单总结)

$
0
0

由于迪米特和开闭原则不具体写代码逻辑,同时,这两个原则部分思想是和前面的4个原则有重合部分,所以就在一起来回顾。

一、 迪米特原则

1. 定义

也叫最少知识原则,一个对象应该对其他对象有最少的了解,即一个类对自己需要耦合或者调用的类知道的最少。

2. 生活实例看代码

  • 生活实例:有些朋友间距离太近,无话不说,无所不知,类A和类B两个人配合非常默契,如果某一天其中一个人无法和另一个人配合,而用第三个人类C跟他一起工作协同的话,有可能会引起很多问题。
  • 保持距离的方法:将类B暴露给A的方法进行封装,暴露的方法越少越好,这样的话类B和类A实现低耦合,将类B替换为类C的时候就非常容易
  • 设计方法:一个类的public方法越多,修改时设计的范围也就越大,变更引起的风险也就越大,在系统设计时需要注意,能用private就用private,能用protected就用protected,能少用public就少用public,能加上fiinal就加上final。

3. 优缺点

  • 优点:类间解耦,弱耦合,耦合降低,复用率高
  • 缺点:类间的耦合性太低,会产生大量的中转或者跳转类,会倒置系统的复杂性提高,同时加大系统维护难度。

4. 例子

在Activity中需要保存一部分网络请求的数据。

  • 不遵循迪米特原则的方式

    • 在Actvity中调用数据存储的接口
    • 告知存储路径
    • 告知存储文件名
    • 告知存储到数据库中
    • 告知加密
  • 遵循迪米特原则的方式

    • 调用加密存储数据到数据库中的方法(调用者不需要知道接口内部的细节)

二、 开闭原则

1. 定义

一个软件的实体,如类、模块和函数等应该对扩展开放,对修改关闭。

2. 问题由来

2.1 问题

在软件的生命周期内,因为变化、升级和维护等原因需要对原有代码进行修改时,可能会给旧代码引入错误,更严重的还可能需要进行整个功能重构,原有逻辑需要重新测试,费时费力。

2.2 解决方案

通过扩展实现变化而不是通过修改实现变化。

3. 总结

开闭原则是面向对象设计中的最基础的设计原则:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,就可以保持架构的稳定。而软件中的易变细节,我们从抽象类派生的实现类进行扩展即可实现。
前提是,抽象要合理,具有前瞻性和预见性。

三、六大设计原则的核心

用抽象构建架构,用实现扩展细节:
1. 单一职责原则:类职责要单一
2. 里氏替换原则:不要破坏继承体系
3. 依赖倒置原则:面向接口编程
4. 接口隔离原则:设计接口要精简单一
5. 迪米特原则:降低耦合度
6. 开闭原则;对扩展开放,对修改关闭


最后说明一下如何去遵守这六个原则。对这六个原则的遵守并不是是和否的问题,而是多和少的问题,也就是说,我们一般不会说有没有遵守,而是说遵守程度的多少。任何事都是过犹不及,设计模式的六个设计原则也是一样,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对他们的遵守程度只要在一个合理的范围内,就算是良好的设计。

作者:xwh_1230 发表于2017/11/20 21:06:15 原文链接
阅读:9 评论:0 查看评论

Java并发编程札记-(一)基础-05线程安全问题

$
0
0

在多线程编程中,可能会出现多个线程访问一个资源的情况,资源可以是同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件等等。如果不对这样的访问做控制,就可能出现不可预知的结果。这就是线程安全问题,常见的情况是“丢失修改”、“不可重复读”、“读‘脏’数据”等等。

目录

  1. 线程安全问题
  2. 线程安全的实现

线程安全问题

上面简单介绍了什么是线程安全问题,下面具体说下什么是“丢失修改”,其他的问题有兴趣可以自己去了解。

丢失修改

两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。

拿火车票订票系统举例:

  1. 一号窗口读出某班次的火车票余票A,设A=1;
  2. 二号窗口读出同一班次的火车票余票B,当然也为1;
  3. 一号窗口判断出余票A=1>0,卖出一张火车票,修改余票A←A-1,A为0,把A写回数据库;
  4. 二号窗口判断出余票B=1>0,也卖出一张火车票,修改余票B←B-1,B为-1;

余票只有一张,但最后卖出了两张火车票。在程序中,没有对两个窗口对余票的访问做控制,所以造成了这个错误。
例1:火车票订票系统-线程不安全版

public class SellTickets {

    public static void main(String[] args) {
        TicketsWindow tw = new TicketsWindow();
        Thread t1 = new Thread(tw, "一号窗口");
        Thread t2 = new Thread(tw, "二号窗口");
        t1.start();
        t2.start();
    }
}

class TicketsWindow implements Runnable {
    private int tickets = 1;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "还剩余票:" + tickets + "张");
                tickets--;
                System.out.println(Thread.currentThread().getName() + "卖出一张火车票,还剩" + tickets + "张");
            } else {
                System.out.println(Thread.currentThread().getName() + "余票不足,暂停出售!");
                try {
                    Thread.sleep(1000 * 60 * 5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果为

一号窗口还剩余票:1张
二号窗口还剩余票:1张
一号窗口卖出一张火车票,还剩0张
二号窗口卖出一张火车票,还剩-1张
一号窗口余票不足,暂停出售!
二号窗口余票不足,暂停出售!

这明显不是我们想要的结果。

线程安全问题解决方法

上面的问题归根结底是由于两个线程访问相同的资源造成的。对于并发编程,需要采取措施防止两个线程来访问相同的资源。

一种措施是当资源被一个线程访问时,为其加锁。第一个访问资源的线程必须锁定该资源,是其他任务在资源被解锁前不能访问该资源。

基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案。即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。
 
在Java多线程编程当中,提供了以下几种方式来实现线程安全:

  • 内部锁(Synchronization)和显式锁(Lock对象)。这两种方式是重量级的多线程同步机制,可能会引起上下文切换和线程调度,它同时提供内存可见性和原子性。
  • CAS原子指令:轻量级多线程同步机制,不会引起上下文切换和线程调度。它同时提供内存可见性和原子化更新保证。
  • volatile变量:轻量级多线程同步机制,不会引起上下文切换和线程调度。仅提供内存可见性保证,不提供原子性。

本文就讲到这里,想了解更多内容请参考:

END.

作者:panweiwei1994 发表于2017/11/20 21:10:43 原文链接
阅读:19 评论:0 查看评论

安卓使用MediaPlayer自定义音频视频播放器

$
0
0

全栈工程师开发手册 (作者:栾鹏)

安卓教程全解

安卓使用MediaPlayer,一般还要配置一个播放画面SurfaceView,和一个进度条SeekBar。

视频的播放会更改进度条的进度,也可以手动更改进度条来调节视频时间。MediaPlayer不想videoview以及集成了SurfaceView和MediaPlayer的相关准备,所以需要手动实现的功能还有很多。这里先实现一个播放器类。完成视频控制的功能。

下面的代码存储为Player.java类,内容中先

package com.lp.app.media;

import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.SeekBar;


//自定义视频播放器,使用MediaPlayer播放视频,需要制作视频界面,渲染数据的surfaceView(屏幕缓冲区)、滚动条SeekBar和播放前准备,

public class Player implements OnBufferingUpdateListener,OnCompletionListener, MediaPlayer.OnPreparedListener,SurfaceHolder.Callback{

  private int videoWidth;
  private int videoHeight;
  public MediaPlayer mediaPlayer;       //媒体播放器
  private SurfaceHolder surfaceHolder;  //渲染画面
  private SeekBar skbProgress;          //播放进度

  private Timer mTimer=new Timer();
  public Player(SurfaceView surfaceView,SeekBar skbProgress)
  {
      this.skbProgress=skbProgress;
      this.skbProgress.setOnSeekBarChangeListener(new SeekBarChangeEvent());
      surfaceHolder=surfaceView.getHolder();
      surfaceHolder.addCallback(this);
      surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
      mTimer.schedule(mTimerTask, 0, 1000);
  }




   //通过定时器和Handler来更新进度条
   TimerTask mTimerTask = new TimerTask() {
       @Override
       public void run() 
       {
            if(mediaPlayer==null)
                return;
            if (mediaPlayer.isPlaying() && skbProgress.isPressed() == false) {
                handleProgress.sendEmptyMessage(0);
            }
        }
    };
    //主界面接收消息更新进度条ui
    Handler handleProgress = new Handler() {
       public void handleMessage(Message msg) {
            int position = mediaPlayer.getCurrentPosition();
            int duration = mediaPlayer.getDuration();

            if (duration > 0) {
                long pos = skbProgress.getMax() * position / duration;
                skbProgress.setProgress((int) pos);
            }
       };
   };

  //启动播放
  public void play()
  {
      mediaPlayer.start();
  }
  //播放在线视频
  public void playUrl(String videoUrl)
  {
      try {
           mediaPlayer.reset();
           mediaPlayer.setDataSource(videoUrl);
           mediaPlayer.prepare();//prepare之后自动播放
           //mediaPlayer.start();
           Log.v("mediaPlayer", "在线视频开始播放");
      } catch (IllegalArgumentException e) {
          e.printStackTrace();
      } catch (IllegalStateException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      }
  }
  //暂停
  public void pause()
  {
      mediaPlayer.pause();
  }
  //停止播放
  public void stop()
  {
      if (mediaPlayer != null) 
      { 
          mediaPlayer.stop();
          mediaPlayer.release(); 
          mediaPlayer = null; 
      } 
  }
  //跳转到多少毫秒
  public void seekTo(int ms)
  {
      mediaPlayer.seekTo(ms);   //加载到多少毫秒
  }



//SurfaceView创建时调用
@Override
public void surfaceCreated(SurfaceHolder holder) {
    try {
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setDisplay(surfaceHolder);
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setOnBufferingUpdateListener(this);
        mediaPlayer.setOnPreparedListener(this);   //视频准备就绪事件
        Log.v("mediaPlayer", "视频准备就绪");
     } catch (Exception e) {
      Log.e("mediaPlayer", "error", e);
     }

}


@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
     Log.v("mediaPlayer", "surface变化");
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
     Log.v("mediaPlayer", "surface销毁");
}


//视频准备就绪事件,启动播放
@Override
public void onPrepared(MediaPlayer mp) {
    videoWidth = mediaPlayer.getVideoWidth();
    videoHeight = mediaPlayer.getVideoHeight();
     if (videoHeight != 0 && videoWidth != 0) {
         mp.start();
     }
     Log.v("mediaPlayer", "视频启动");
}


@Override
public void onCompletion(MediaPlayer mp) {
    Log.v("mediaPlayer", "视频完成");
}


//缓冲进度变化事件(不是播放进度),其中percent是0~100的整数,代表已经缓冲好的多媒体数据的百分比
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
     skbProgress.setSecondaryProgress(percent);
     int currentProgress=skbProgress.getMax()*mediaPlayer.getCurrentPosition()/mediaPlayer.getDuration();
     Log.v("播放进度"+currentProgress+"% play", "缓冲进度"+percent + "% buffer");
}

//进度条监控,进度条变化要更改进度显示和视频时间。
class SeekBarChangeEvent implements SeekBar.OnSeekBarChangeListener {
      int progress;
      @Override
      public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
          // 原本是(progress/seekBar.getMax())*player.mediaPlayer.getDuration()
          this.progress = progress * mediaPlayer.getDuration()/seekBar.getMax();
      }
      @Override
      public void onStartTrackingTouch(SeekBar seekBar) {

      }
      @Override
      public void onStopTrackingTouch(SeekBar seekBar) {
          // seekTo()的参数是相对与影片时间的数字,而不是与seekBar.getMax()相对的数字
          seekTo(progress);
      }
    }

}

然后我要自定义实现一个视频播放画面。mediaplay.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:layout_alignParentTop="true"
        android:id="@+id/mediaplay_linear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <Button
        android:id="@+id/mediaplay_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="启动" />

    <Button
        android:id="@+id/mediaplay_pause"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="暂停" />

    <Button
        android:id="@+id/mediaplay_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止" />

    </LinearLayout>

    <SurfaceView
        android:layout_below="@id/mediaplay_linear"
        android:id="@+id/mediaplay_surfaceView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <SeekBar
        android:layout_below="@id/mediaplay_linear"
        android:id="@+id/mediaplay_seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

最后再activity中实现播放器类的调用和播放基本控制。
其中用户点击进度条调整观看的时间点,可以写在播放类player中,也可以写在activity中,而且最好写在activity中,因为除了点击进度条除了调整视频时间,可能还会调整其他UI。

package com.lp.app.media;

import com.lp.app.R;
import android.app.Activity;
import android.content.Context;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.SeekBar;

//使用MediaPlayer播放视频,需要将视频界面,渲染surface和播放前准备,

//影片的时间变化,进度条变化,时间的提示变化要同步。用户可以通过进度条控制视频时间跳转.
public class MediaplayActivity extends Activity {

  Context context;
  SurfaceView surfaceView;
  public MediaPlayer mediaPlayer;   //媒体播放器
  private SeekBar skbProgress;
  private Player player;
  private Button btnPause, btnStart, btnStop;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.mediaplay);
    context=this;
    surfaceView = (SurfaceView)findViewById(R.id.mediaplay_surfaceView);

    btnStart = (Button)findViewById(R.id.mediaplay_start);
    btnPause = (Button)findViewById(R.id.mediaplay_pause);
    btnStop = (Button)findViewById(R.id.mediaplay_stop);

    btnStart.setOnClickListener(new ClickEvent());
    btnPause.setOnClickListener(new ClickEvent());
    btnStop.setOnClickListener(new ClickEvent());

    skbProgress = (SeekBar) this.findViewById(R.id.mediaplay_seekbar);

    player = new Player(surfaceView, skbProgress);

  }

  //点击事件
  class ClickEvent implements OnClickListener 
  {
      @Override
      public void onClick(View view) {
           if (view == btnPause) {
               player.pause();
           } else if (view == btnStart) {
               String url="http://www.525heart.com/test.mp4";
               player.playUrl(url);
           } else if (view == btnStop) {
               player.stop();
           }
      }
  }


作者:luanpeng825485697 发表于2017/11/20 21:22:05 原文链接
阅读:25 评论:0 查看评论

刷题笔记:C/C++专项练习1

$
0
0

题目:
下面有关C++的类和C里面的struct的描述,正确的有?
A.在C++中,来自class的继承默认按照private继承处理,来自struct的继承默认按照public继承处理
B.class的成员默认是private权限,struct默认是public权限
C.c里面的struct只是变量的聚合体,struct不能有函数
D.c++的struct可有构造和析构函数

答案:ABCD
知识点:c\c++中的class和struct。

解析:
感谢网友分享的解析,很清楚。
1. c++中,class和struct的区别:
a.成员访问权限——class的成员访问权限为private,而struct的成员访问权限为public。
b.默认的继承方式——class的默认继承方式为private,而struct的默认继承方式为public。
2. struct在C和C++之间的区别:
a.c中,struct是用户自定义数据类型,而c++中,struct是抽象数据类型,支持成员定义函数。
b.c中的struct是没有权限设置的,但是在c++中,给strcut添加了权限设置,增加了访问权限。
c.c中的struct只是变量的聚合体,可以封装数据,但是不可以隐藏,不可以定义函数成员;但是C++中的struct可以定义函数成员。

作者:lin453701006 发表于2017/11/20 21:22:47 原文链接
阅读:11 评论:0 查看评论

OpenCV:视频/图像背景减除方法

$
0
0

  背景减除法(Background subtraction)常用于通过静态摄像头生成一个前景掩码,即场景中移动物体的二进制图像。


代码示例

#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/video.hpp"

#include <iostream>
#include <sstream>

using namespace std;
using namespace cv;

Mat frame;                              // 当前帧
Mat fgMaskMOG2;                         // 前景模板,通过MOG2方法生成
Ptr<BackgroundSubtractor> pMOG2;        // MOG2 背景消除
char keyboard;                          // 按键响应
const char* filename;

void processVideo(const char* videoFilename);
void processImages(const char* firstFrameFilename);

int main()
{
    pMOG2 = createBackgroundSubtractorMOG2();                      // MOG2方法,背景消除

    char video_or_image;
    string s;
    cout << "Pless choose Video or Image: ( V / I )\t";
    cin >> video_or_image ;

    if (video_or_image == 'V' || video_or_image == 'v')             // 视频
    {
        cout << "\n\nPlease input the address of the video:\t";
        cin >> s;
        filename = s.c_str();

        namedWindow("Frame");
        namedWindow("FG Mask MOG2");

        processVideo(filename);
    }
    else if (video_or_image == 'I' || video_or_image == 'i')        // 图像
    {
        cout << "\n\nPlease input the address of the images:\t";
        cin >> s;
        filename = s.c_str();

        namedWindow("Frame");
        namedWindow("FG Mask MOG2");

        processImages(filename);
    }
    else
    {
        cerr << "Wrong Input !" << endl;
        return EXIT_FAILURE;
    }

    destroyAllWindows();
    return EXIT_SUCCESS;
}

void processVideo(const char* videoFilename)
{
    VideoCapture capture(videoFilename);                            // 捕获视频
    if (!capture.isOpened()) { exit(EXIT_FAILURE); }

    keyboard = 0;
    while (keyboard != 'q'&&keyboard != 27)
    {
        if (!capture.read(frame)) { exit(EXIT_FAILURE); }           // 读取当前帧

        pMOG2->apply(frame, fgMaskMOG2);                            // 获取前景掩模

        stringstream ss;
        ss << capture.get(CAP_PROP_POS_FRAMES);                     // 获取当前帧数

        string frameNumberString = ss.str();
        rectangle(frame, cv::Point(10, 2), cv::Point(100, 20), cv::Scalar(255, 255, 255), -1);
        putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
            FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));

        imshow("Frame", frame);
        imshow("FG Mask MOG2", fgMaskMOG2);
        keyboard = (char)waitKey(30);
    }

    capture.release();
}

void processImages(const char* fistFrameFilename) 
{
    frame = imread(fistFrameFilename);
    if (frame.empty()) { exit(EXIT_FAILURE); }

    string fn(fistFrameFilename);
    keyboard = 0;
    while (keyboard != 'q' && keyboard != 27) 
    {
        pMOG2->apply(frame, fgMaskMOG2);                    // 获取前景掩模

        size_t index = fn.find_last_of("/");
        if (index == string::npos)                          // 如果'/'不存在则搜寻'\\'
            index = fn.find_last_of("\\");
        size_t index2 = fn.find_last_of(".");

        string prefix = fn.substr(0, index + 1);            // 图像路径前部分,前为起始位置,后为长度
        string suffix = fn.substr(index2);                  // 图像路径后部分,图像格式
        string frameNumberString = fn.substr(index + 1, index2 - index - 1);

        istringstream iss(frameNumberString);
        int frameNumber = 0;
        iss >> frameNumber;

        rectangle(frame, cv::Point(10, 2), cv::Point(100, 20),
            cv::Scalar(255, 255, 255), -1);
        putText(frame, frameNumberString.c_str(), cv::Point(15, 15),
            FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0));

        imshow("Frame", frame);
        imshow("FG Mask MOG 2", fgMaskMOG2);

        keyboard = (char)waitKey(30);

        ostringstream oss;
        oss << (frameNumber + 1);
        string nextFrameNumberString = oss.str();
        string nextFrameFilename = prefix + nextFrameNumberString + suffix;

        frame = imread(nextFrameFilename);
        if (frame.empty()) 
            exit(EXIT_FAILURE);

        fn.assign(nextFrameFilename);                       // 更新图片路径
    }
}

运行结果

作者:u013165921 发表于2017/11/20 20:09:39 原文链接
阅读:12 评论:0 查看评论

数据结构与算法分析(Java语言描述)(27)—— 深度优先遍历与连通分量

$
0
0
package com.dataStructure.graph;

// 求无权图的联通分量

public class Components {

    private Graph graph;    // 存放输入的数组
    private boolean[] visited;  // 存放节点被访问状态
    private int componentCount; // 连通分量的数量
    private int[] mark; // 存储节点所属联通分量的标记

    // 构造函数,初始化私有属性
    public Components(Graph graph) {
        this.graph = graph;
        componentCount = 0; // 连通分量初始数量为 0
        visited = new boolean[graph.V()];
        mark = new int[graph.V()];
        for (int i = 0; i < graph.V(); i++) {
            visited[i] = false; // 节点初始访问状态为 false
            mark[i] = -1;   // 节点初始连通分量标记为 -1
        }

        for (int i = 0; i < graph.V(); i++) {
            // 对于未被访问的节点进行 dfs深度优先遍历
            if (!visited[i]) {
                dfs(i);
                componentCount++;   // 对一个节点进行dfs 到底后,一个连通分量结束,数量+1
            }
        }
    }

    private void dfs(int i) {
        visited[i] = true;  // 节点 i 已被访问
        mark[i] = componentCount;   // 节点 i 属于当前连通分量的数量(标记)
        for (int node : graph.adjacentNode(i)) {    // 遍历图中节点 i 的邻接节点
            if (!visited[node]) // 对未被访问的邻接节点进行 dfs
                dfs(node);
        }
    }

    public boolean isConnected(int v, int w) {
        return mark[v] == mark[w];  // 根据两节点所属连通分量的标记判断两节点是否相连
    }

    public int getComponentCount() {
        return componentCount;  // 返回 graph 中连通分量的数量
    }
}

//public class Components {
//
//    private Graph G;                    // 图的引用
//    private boolean[] visited;  // 记录dfs的过程中节点是否被访问
//    private int ccount;         // 记录联通分量个数
//    private int[] id;           // 每个节点所对应的联通分量标记
//
//    // 图的深度优先遍历
//    private void dfs(int v) {
//
//        visited[v] = true;  // 节点 v 的访问状态置为 true
//        id[v] = ccount; // 节点 v 对应的联通标记设置为 ccount
//
//        // 遍历节点 v 的邻接点 i
//        for (int i : G.adjacentNode(v)) {
//            // 如果邻接点 i 尚未被访问
//            if (!visited[i])
//                // 对邻接点 i 进行深度优先遍历
//                dfs(i);
//        }
//    }
//
//    // 构造函数, 求出无权图的联通分量
//    public Components(Graph graph) {
//
//        // 算法初始化
//        G = graph;
//
//        // visited 数组存储 图G 中 节点的被访问状态
//        visited = new boolean[G.V()];
//
//        // id 数组存储 图G 中 节点所属连通分量的标记
//        id = new int[G.V()];
//
//        // 连通分量数量初始化为 0
//        ccount = 0;
//
//        // 将 visited 数组全部置为 false; id 数组全部置为 -1
//        for (int i = 0; i < G.V(); i++) {
//            visited[i] = false;
//            id[i] = -1;
//        }
//
//        // 求图的联通分量
//        for (int i = 0; i < G.V(); i++)
//            // 访问一个未曾被访问的节点
//            if (!visited[i]) {
//                // 对其进行深度优先遍历
//                dfs(i);
//                ccount++;
//            }
//    }
//
//    // 返回图的联通分量个数
//    int count() {
//        return ccount;
//    }
//
//    // 查询点v和点w是否联通(节点v 和 w 的联通分量的标记是否相同
//    boolean isConnected(int v, int w) {
//        assert v >= 0 && v < G.V();
//        assert w >= 0 && w < G.V();
//        return id[v] == id[w];
//    }
//}

这里写图片描述

这里写图片描述

连通分量数量为 3

作者:HeatDeath 发表于2017/11/20 22:54:45 原文链接
阅读:8 评论:0 查看评论

数据结构与算法分析(Java语言描述)(28)—— 使用 dfs 求两节点间的路径

$
0
0
package com.dataStructure.graph;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

// 使用 dfs 获取两节点之间的路径

public class Path {
    private Graph graph;    // 输入的图
    private boolean[] visited;  // 存储访问状态
    private int[] from; // 储存访问路径
    private int startNode;  // 起始节点

    // 构造函数,初始化私有属性
    public Path(Graph graph, int startNode) {
        this.graph = graph;
        this.startNode = startNode;
        visited = new boolean[graph.V()];
        from = new int[graph.V()];

        // 初始化 visited 和 from 
        for (int i = 0; i < graph.V(); i++) {
            visited[i] = false;
            from[i] = -1;
        }

        dfs(startNode); // 对起始节点进行深度优先遍历
    }

    private void dfs(int i) {
        visited[i] = true;  // 节点 i 的访问状态变为 true
        for (int node : graph.adjacentNode(i)) {    // 遍历 i 的邻接节点
            if (!visited[node]) {   // 对于未被访问的节点 node
                from[node] = i; // 设置 from[] 中 node 的前一个节点为 i
                dfs(node);  // 继续对 node 进行 dfs
            }
        }
    }

    public boolean hasPath(int w) {
        return visited[w];
    }

    // 获取从 startNode 到 w 的路径
    public List<Integer> getPath(int w) {
        Stack<Integer> stack = new Stack<>();   // stack 存储路径
        int p = w;  // p 指向 w 节点

        while (p != -1) {   // 从 w 逆向遍历 from[] 到 startNode 处
                            // from[startNode] = -1
            stack.push(p);  // 将路径上的节点入栈
            p = from[p];    // 将 p 替换为其之前一个节点
        }

        List<Integer> pathList = new ArrayList<>(); // pathList 正向存储路径
        while (!stack.isEmpty()) {  // 当栈非空
            pathList.add(stack.pop());  // pathList add stack 中的出栈元素
        }

        return pathList;    // 返回正向的路径
    }

    // 打印出从s点到w点的路径
    void showPath(int w) {
        List<Integer> path = getPath(w);
        for (int i = 0; i < path.size(); i++) {
            System.out.print(path.get(i));
            if (i == path.size() - 1)
                System.out.println();
            else
                System.out.print(" -> ");
        }
        System.out.println();
    }

}

//public class Path {
//
//    private Graph G;   // 图的引用
//    //    private int v;     // 起始点
//    private boolean[] visited;  // 记录dfs的过程中节点是否被访问
//    private int[] from;         // 记录路径, from[i]表示查找的路径上i的上一个节点
//
//    // 图的深度优先遍历
//    private void dfs(int v) {
//        visited[v] = true;  // 节点v 的访问状态置为 true
//
//        // 遍历节点 v 的邻接点
//        for (int i : G.adjacentNode(v))
//            if (!visited[i]) {  // 将节点 v 未被访问的邻接点 i
//                from[i] = v;    // 邻接点 i from(来自) v
//                dfs(i); // 对 i 进行深度优先遍历
//            }
//    }
//
//    // 构造函数, 寻路算法, 寻找图graph从v点到其他点的路径
//    public Path(Graph graph, int v) {
//
//        // 算法初始化
//        G = graph;
////        assert v >= 0 && v < G.V();
//
//        visited = new boolean[G.V()];   // visited 数组存储图中节点的访问状态
//        from = new int[G.V()];  // from 数组存储路径上的节点
//
//        for (int i = 0; i < G.V(); i++) {
//            visited[i] = false;
//            from[i] = -1;
//        }
//
//        // 寻路算法
//        // 从起始节点 v 开始深度优先遍历 图G
//        dfs(v);
//    }
//
//    // 查询从s点到w点是否有路径
//    private boolean hasPath(int w) {
//        assert w >= 0 && w < G.V();
//        return visited[w];  // 如果访问到 节点w ,则 起始节点 v 到 节点w 有路径
//    }
//
//    // 查询从 v 点到w点的路径, 存放在 res 中
//    private List<Integer> path(int w) {
//
//        assert hasPath(w);
//
//        // 栈 s 用于
//        Stack<Integer> s = new Stack<>();
//
//        // 通过from数组逆向查找到从 v 到 w 的路径, 存放到栈中
//
//        int p = w;  // p 指向 w
//        while (p != -1) {
//            s.push(p);  // 将 p 压入栈中
//            p = from[p];    // 使用 from[] 数组逆向查找
//        }
//
//        // 从栈中依次取出元素, 获得顺序的从s到w的路径
//        List<Integer> res = new ArrayList<>();
//        while (!s.isEmpty())
//            res.add(s.pop());
//
//        return res;
//    }
//
//    // 打印出从s点到w点的路径
//    void showPath(int w) {
//
//        assert hasPath(w);
//
//        List<Integer> vec = path(w);
//        for (Integer node : vec) {
//            System.out.print(node);
////            if (i == vec.size() - 1)
////                System.out.println();
////            else
//            System.out.print(" -> ");
//        }
//        System.out.println();
//    }
//}

这里写图片描述

作者:HeatDeath 发表于2017/11/20 23:22:46 原文链接
阅读:13 评论:0 查看评论

What Causes Poor Availability?

$
0
0

What Causes Poor Availability?


What causes an application that previously performed well to begin exhibiting poor
availability? There are many causes:

Resource exhaustion

Increase the number of users or increase the amount of data in use in a system
and your application may fall victim to resource exhaustion, resulting in a slower
and unresponsive application.

Unplanned load-based changes

Increases in the popularity of your application might require code and application changes to

 handle the increased load. These changes, often implemented
quickly and at the last minute with little or no forethought or planning, increase
the likelihood of problems occurring

Increased number of moving parts

As an application gains popularity, it is often necessary to assign more and more
developers, designers, testers, and other individuals to work on and maintain it.
This larger number of individuals working on the application creates a large
number of moving parts, whether those moving parts are new features, changed
features, or just general application maintenance. The more individuals working
on the application, the more moving parts within the application and the greater
the chance for bad interactions to occur in it.

Outside dependencies

The more dependencies your application has on external resources, the more it is
exposed to availability problems caused by those resources.

Technical debt

Increases in the applications complexity typically increases technical debt (i.e.,
the accumulation of desired software changes and pending bug fixes that typi‐
cally build up over time as an application grows and matures). Technical debt
increases the likelihood of a problem occurring.
All fast-growing applications have one, some, or all of these problems. As such,
potential availability problems can begin occurring in applications that previously
performed flawlessly. Often the problems will creep up on you; often they will start
suddenly.
But most growing applications have the same problem. They eventually will begin
having availability problems.
Availability problems cost you money, they cost your customer’s money, and they cost
you your customer’s trust and loyalty. Your company cannot survive for long if you
constantly have availability problems.
Building applications designed to scale means building applications designed for high
availability.



读书笔记:

Architecting for Scale

High Availability for Your Growing Applications




作者:doctor_who2004 发表于2017/11/20 23:50:13 原文链接
阅读:13 评论:0 查看评论

数据结构与算法分析(Java语言描述)(29)—— 广度优先遍历与最短路径

$
0
0
package com.dataStructure.graph;

// 使用 广度优先遍历 查找节点之间的最短路径

import java.util.*;

public class ShortestPath {
    private Graph graph;
    private int startNode;
    private boolean[] visited;
    private int[] from;
    private int[] order;

    public ShortestPath(Graph graph, int startNode) {
        this.graph = graph;
        this.startNode = startNode;
        visited = new boolean[graph.V()];
        from = new int[graph.V()];
        order = new int[graph.V()];

        for (int i = 0; i < graph.V(); i++) {
            visited[i] = false;
            from[i] = -1;
            order[i] = -1;
        }

        Queue<Integer> cacheQueue = new LinkedList<>(); // 用于缓存节点的队列

        visited[startNode] = true;  // startNode 访问状态变为 true
        order[startNode] = 0;   // startNode 的访问次序为 0

        cacheQueue.offer(startNode);    // 将 startNode 入队

        while (!cacheQueue.isEmpty()) {  // 当缓存队列不为空
            int frontNode = cacheQueue.poll();   // frontNode 存放从缓存队列中出队的元素
            for (int adjNode : graph.adjacentNode(frontNode)) { // 遍历 frontNode 的未被访问的邻接节点
                if (!visited[adjNode]) {
                    visited[adjNode] = true;    // 邻接节点 adjNode 的访问状态为 true
                    from[adjNode] = frontNode;   // adjNode 的前一个节点为 frontNode
                    order[adjNode] = order[frontNode] + 1;  // ajdNode 的访问次序在 frontNode 上+1
                    cacheQueue.offer(adjNode);  // adjNode 入队
                }
            }
        }
    }

    public List<Integer> getPath(int w) {
        Stack<Integer> stack = new Stack<>();
        int p = w;
        while (p != -1) {
            stack.push(p);
            p = from[p];
        }
        List<Integer> list = new ArrayList<>();
        while (!stack.isEmpty())
            list.add(stack.pop());

        return list;
    }

    public boolean hasPath(int w) {
        return visited[w];
    }

    public int pathLength(int w) {
        return order[w];
    }

    // 打印出从 startNode 到w点的路径
    void showPath(int w) {
        List<Integer> path = getPath(w);
        for (int i = 0; i < path.size(); i++) {
            System.out.print(path.get(i));
            if (i == path.size() - 1)
                System.out.println();
            else
                System.out.print(" -> ");
        }
        System.out.println();
    }
}


//public class ShortestPath {
//
//    private Graph G;   // 图的引用
//    private int s;     // 起始点
//    private boolean[] visited;  // 记录dfs的过程中节点是否被访问
//    private int[] from;         // 记录路径, from[i]表示查找的路径上i的上一个节点
//    private int[] ord;          // 记录路径中节点的次序。ord[i]表示i节点在路径中的次序。
//
//
//    // 构造函数, 寻路算法, 寻找图graph从s点到其他点的路径
//    public ShortestPath(Graph graph, int s) {
//
//        // 算法初始化
//        G = graph;
//        assert s >= 0 && s < G.V();
//
//        visited = new boolean[G.V()];
//        from = new int[G.V()];
//        ord = new int[G.V()];
//        for (int i = 0; i < G.V(); i++) {
//            visited[i] = false;
//            from[i] = -1;
//            ord[i] = -1;
//        }
//        this.s = s;
//
//        // 无向图最短路径算法, 从s开始广度优先遍历整张图
//        LinkedList<Integer> q = new LinkedList<>();
//
//        q.push(s);
//        visited[s] = true;
//        ord[s] = 0;
//        while (!q.isEmpty()) {
//            int v = q.pop();
//            for (int i : G.adjacentNode(v))
//                if (!visited[i]) {
//                    q.push(i);
//                    visited[i] = true;
//                    from[i] = v;
//                    ord[i] = ord[v] + 1;
//                }
//        }
//    }
//
//    // 查询从s点到w点是否有路径
//    public boolean hasPath(int w) {
//        assert w >= 0 && w < G.V();
//        return visited[w];
//    }
//
//    // 查询从s点到w点的路径, 存放在vec中
//    public List<Integer> path(int w) {
//
//        assert hasPath(w);
//
//        Stack<Integer> s = new Stack<Integer>();
//        // 通过from数组逆向查找到从s到w的路径, 存放到栈中
//        int p = w;
//        while (p != -1) {
//            s.push(p);
//            p = from[p];
//        }
//
//        // 从栈中依次取出元素, 获得顺序的从s到w的路径
//        List<Integer> res = new ArrayList<>();
//        while (!s.empty())
//            res.add(s.pop());
//
//        return res;
//    }
//
//    // 打印出从s点到w点的路径
//    public void showPath(int w) {
//
//        assert hasPath(w);
//
//        List<Integer> vec = path(w);
//        for (int i = 0; i < vec.size(); i++) {
//            System.out.print(vec.get(i));
//            if (i == vec.size() - 1)
//                System.out.println();
//            else
//                System.out.print(" -> ");
//        }
//    }
//
//    // 查看从s点到w点的最短路径长度
//    // 若从s到w不可达,返回-1
//    public int length(int w) {
//        assert w >= 0 && w < G.V();
//        return ord[w];
//    }
//}

这里写图片描述

作者:HeatDeath 发表于2017/11/20 23:52:00 原文链接
阅读:11 评论:0 查看评论

SpringMVC之分析HandlerMethodReturnValueHandler(二)

$
0
0

我们在之前的文章中对HandlerMethodReturnValueHandler进行了简单的分析(SpringMVC之分析HandlerMethodReturnValueHandler(一)), 在
这篇文章中我们继续分析一下HandlerMethodReturnValueHandler这个类。有时候我们的请求映射处理方
法的返回值是View对象,当返回值是View对象时,会被ViewMethodReturnValueHandler这个类进行处
理;有时候我们方法的返回值是ModelAndView,返回值是ModelAndView时,会被ModelAndViewMethodReturnValueHandler类进行处理,返回值的类型有很多种,下面我用表格的形式列举一下:

返回值类型 处理类
String(方法上无ResponseBody注解) ViewNameMethodReturnValueHandler
View ViewMethodReturnValueHandler
ModelAndView ModelAndViewMethodReturnValueHandle
Model ModelMethodProcessor
Map MapMethodProcessor
HttpHeaders HttpHeadersReturnValueHandler
ModelAttribute(或者是自定义对象) ModelAttributeMethodProcessor
HttpEntity HttpEntityMethodProcessor
ResponseEntity ResponseBodyEmitterReturnValueHandler
ResponseBody注解 RequestResponseBodyMethodProcessor

还有其他的几个异步的,不是很了解,这里就先不说了。这里我们需要重点说一下的是RequestResponseBodyMethodProcessor和ModelAttributeMethodProcessor这两个类,其他的看一下代码应该都能看的差不多。这两个类都是Processor结尾的类,不是ReturnValueHandler结尾的类,以Processor结尾的类有什么特殊的地方呢?特殊之处在于它同时实现了HandlerMethodArgumentResolver、HandlerMethodReturnValueHandler这两个接口。

作者:zknxx 发表于2017/11/20 23:58:08 原文链接
阅读:16 评论:0 查看评论

【小技巧解决大问题】使用 frp 突破阿里云主机无弹性公网 IP 不能用作 Web 服务器的限制

$
0
0

背景

order.png

今年 8 月份左右,打折价买了一个阿里云主机,比平常便宜了 2000 多块。买了之后,本想作为一个博客网站的,毕竟国内的服务器访问肯定快一些。满心欢喜的下单之后,却发现 http 服务,外网怎么也无法访问。各种搜,最终在文档中,终于看见:必须要买弹性公网 IP,并且绑定到阿里云主机上,才可以用作web服务器。而且要求,阿里云主机必须是未绑定过 IP 的。不过很不幸,我当时下单时,已经勾选了使用公网IP。本来想着解绑然后重新绑定下,应该就可以了。然而,已经绑定过公网IP的,是不允许再改绑弹性公网IP的。欲哭无泪,这个服务器,也就闲置了几个月。

config.png

最近接触了些内网穿透的知识,我突然想到,是不是借助内网穿透,也可以把我的阿里云主机给暴露出来?毕竟内网穿透,使用的是 Linux 一些基础知识,算不上很极客的技术,应该是具有通用性的。经验证,竟然真的可行! 特记录下来,献给有同样遭遇的有缘人。

使用 revel 搭建一个简单的 Web 服务器

需要先搭建一个 Web 服务器,以供测试用。如果你选择其他方式搭建 web 服务器,可直接跳过这一节。

下载最新版本 go

可以在 Go 下载页,查看最新的稳定版本,来替换 go1.9.2.linux-amd64.tar.gz :

wget https://redirector.gvt1.com/edgedl/go/go1.9.2.linux-amd64.tar.gz 
tar -C /usr/local -xzf go1.9.2.linux-amd64.tar.gz

如果已经安装过 Go,可能需要先移除:

apt remove golang-go

在 ~/.profile 中,添加:

export PATH=$PATH:/usr/local/go/bin

在 ~/.bash_profile 中,添加:

export GOPATH=$HOME/go

HOME/goGOPATH 的文件夹。

设置完后,最好重启下 shell 终端,以使信息生效。

直接从 github 下载 golang.org/x/sys 和 golang.org/x/net 源码

golang.org下的包,直接安装,在无法科学上网时,有极大概率会失败。作为一种替代手段,我们可以直接从 github 下载对应的源码到 $GOPATH 对应路径。

# 创建包存储路径
mkdir $GOPATH/src/golang.org/x

#安装git
apt install git

#下载 revel 依赖的包源码。
git clone https://github.com/golang/net $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/sys $GOPATH/src/golang.org/x/sys

# 安装 revel
go get -u github.com/revel/cmd/revel

# revel 命令,需要在 $GOPATH 中执行
cd $GOPATH/src

# 创建并运行一个web应用
revel new hello-go-web
revel run hello-go-web

web.png

frp 配置

有关 frp 配置的细节,请参考 借助 frp 随时随地访问自己的树莓派。此处只贴出关键配置文件。

在公网能访问的服务器上配置 frps 服务器端

[common]
bind_port = 7000
vhost_http_port =80
dashboard_port = dashboard_port_number
dashboard_user = dashboard_user_name
dashboard_pwd = dashboard_pwd_value
privilege_token = privilege_token_value
subdomain_host = example.com

注意: example.com 要换为自己的域名。

域名泛解析

dns.png

将泛域名 *.example.com 解析到 frps 所在服务器的 IP 地址。这样,你不需要频繁修改 DNS 配置了。此处我们是打算把某个子域名解析到我们的阿里云主机上。如果你想直接把根域名解析到服务器上,参考:通过自定义域名访问部署于内网的 web 服务

在只能内网访问的阿里云主机上配置 fprc 客户端

[common]
server_addr = your_server_IP
server_port = 7000
privilege_token = privilege_token_value
login_fail_exit = false

[ssh-aliyun]
type = tcp
local_IP = 127.0.0.1
local_port = 22
remote_port = remote_port_number
use_encryption = true
use_compression = true

[web-show]
type = http
local_port = 9000
subdomain = show

注意:9000 表示web服务器的本地端口,请根据需要替换;show,表示子域名,配置成功后,可以通过 show.example.com 访问自己的 web 网页了。

使用 Systemd 实现自动启动 revel

使用 Systemd 实现自动启动 frp,可以直接看 借助 frp 随时随地访问自己的树莓派 相关部分,不再赘述。此处着重说下 revel 自启动的配置:


# 编写 frp service 文件,以 centos7 为例,适用于 debian
mkdir /usr/lib/systemd/system/
vim /usr/lib/systemd/system/revel-hello-go-web.service

# 内容如下

变更内容:
[Unit]
DescrIPtion=/revel-hello-go-web
After=network.target

[Service]
Environment=PATH=$PATH:/usr/local/go/bin
Environment=GOPATH=/root/go
TimeoutStartSec=30
ExecStart=/root/go/bin/revel run hello-go-web
ExecStop=/bin/kill $MAINPID
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

# 启动 revel-hello-go-web 并设置开机启动
systemctl enable revel-hello-go-web
systemctl start revel-hello-go-web
systemctl status revel-hello-go-web

# 部分服务器上,可能需要加 .service 后缀来操作,即:
systemctl enable revel-hello-go-web.service
systemctl start revel-hello-go-web.service
systemctl status revel-hello-go-web.service

# 重新加载: 
systemctl daemon-reload

注意:

  • /root/go 要替换为自己电脑 $GOPATH 的真实路径。
  • 此处的 sytemd 的配置中, Environment 环境变量,必须设置,否则报错

参考文章

作者:sinat_30800357 发表于2017/11/21 0:57:17 原文链接
阅读:9 评论:0 查看评论

OpenCV:目标检测

$
0
0

  OpenCV支持的目标检测的方法是利用样本的Haar特征进行的分类器训练,得到的级联boosted分类器(Cascade Classification)。haar支持的目标有人脸、眼、嘴、鼻、身体,这里给出的是脸部和眼部的示例。

  参考链接:https://docs.opencv.org/master/db/d28/tutorial_cascade_classifier.html


代码示例

#include "opencv2/objdetect.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

void detectAndDisplay(Mat frame);                       // 检测并显示

String face_cascade_name, eyes_cascade_name;
CascadeClassifier face_cascade;                         // 脸部识别
CascadeClassifier eyes_cascade;                         // 眼部识别

String window_name = "Capture - Face detection";

int main(int argc, const char** argv)
{
    CommandLineParser parser(argc, argv,
        "{help h||}"
        "{face_cascade|../data/haarcascade_frontalface_alt.xml|}"
        "{eyes_cascade|../data/haarcascade_eye_tree_eyeglasses.xml|}");
    cout << "\nThis program demonstrates using the cv::CascadeClassifier class "
        "to detect objects (Face + eyes) in a video stream.\n"
        "You can use Haar or LBP features.\n\n";
    parser.printMessage();

    face_cascade_name = parser.get<string>("face_cascade");
    eyes_cascade_name = parser.get<string>("eyes_cascade");

    VideoCapture capture;
    Mat frame;

    // 加载cascades级联分类器
    if (!face_cascade.load(face_cascade_name)) { printf("--(!)Error loading face cascade\n"); return -1; };
    if (!eyes_cascade.load(eyes_cascade_name)) { printf("--(!)Error loading eyes cascade\n"); return -1; };

    // 读取视频
    capture.open(0);
    if (!capture.isOpened())    { printf(" --(!)Error opening video capture\n"); return -1; }

    while (capture.read(frame))
    {
        if (frame.empty())      { printf(" --(!) No captured frame -- Break!"); break;  }

        // 检测并显示
        detectAndDisplay(frame);                        

        char c = (char)waitKey(10);
        if (c == 27) { break; }
    }
    return 0;
}

void detectAndDisplay(Mat frame)
{
    std::vector<Rect> faces;
    Mat frame_gray;

    cvtColor(frame, frame_gray, COLOR_BGR2GRAY);        // 转换灰度图像
    equalizeHist(frame_gray, frame_gray);               // 直方图均衡化

    // 检测脸部
    face_cascade.detectMultiScale(frame_gray, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));

    for (size_t i = 0; i < faces.size(); i++)
    {
        Point center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);                        // faces矩形的中点

        ellipse(frame, center, Size(faces[i].width / 2, faces[i].height / 2), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0);// 绘制椭圆

        Mat faceROI = frame_gray(faces[i]);             // ROI

        std::vector<Rect> eyes;

        eyes_cascade.detectMultiScale(faceROI, eyes, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));            // 眼部检测

        for (size_t j = 0; j < eyes.size(); j++)
        {
            Point eye_center(faces[i].x + eyes[j].x + eyes[j].width / 2, faces[i].y + eyes[j].y + eyes[j].height / 2);
            int radius = cvRound((eyes[j].width + eyes[j].height)*0.25);
            circle(frame, eye_center, radius, Scalar(255, 0, 0), 4, 8, 0);
        }
    }

    imshow(window_name, frame);
}

  运行结果就不贴出来了,只是在原有基础上加了注释,亲测可行(⊙o⊙)!


作者:u013165921 发表于2017/11/21 1:06:43 原文链接
阅读:13 评论:0 查看评论

leetcode-169. Majority Element

$
0
0

leetcode-169. Majority Element


Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.
You may assume that the array is non-empty and the majority element always exist in the array.

题目大意:
给定一个大小为n的数组,找到大多数元素。大多数元素是出现超过⌊n / 2⌋次的元素。 您可能会认为该数组是非空的,而且大多数元素始终存在于数组中。

题目本身还是容易的,主要是记录一下各种解法:


解法一:HashMap

我们可以使用一个HashMap来将元素映射到value上,以便通过在数字上循环来计算线性时间的出现次数。然后,我们只需返回最大值的key。

class Solution {
    private Map<Integer, Integer> countNums(int[] nums) {
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        for (int num : nums) {
            if (!counts.containsKey(num)) {
                counts.put(num, 1);
            }
            else {
                counts.put(num, counts.get(num)+1);
            }
        }
        return counts;
    }

    public int majorityElement(int[] nums) {
        Map<Integer, Integer> counts = countNums(nums);

        Map.Entry<Integer, Integer> majorityEntry = null;
        for (Map.Entry<Integer, Integer> entry : counts.entrySet()) {
            if (majorityEntry == null || entry.getValue() > majorityEntry.getValue()) {
                majorityEntry = entry;
            }
        }

        return majorityEntry.getKey();
    }
}

方法二:排序

这个是比较好的方法,将数组排序后,出现超过半数的元素,肯定是length/2位置的元素

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length/2];
    }
}

方法三:随机

这种方法也不是很难理解,但是时间复杂度的证明是比较困难的,也就是为什么没有超时。。
该方法的思路是既然所求的元素在数组中出现的次数超过半数,那么我们随机取数的话,取到的元素也很有可能就是所求的元素。
每次随机取一个数,然后遍历整个数组,计算出现的次数是否超过半数,如果不超过半数就继续取值,直到取到结果为止。

class Solution {
    private int randRange(Random rand, int min, int max) {
        return rand.nextInt(max - min) + min;
    }

    private int countOccurences(int[] nums, int num) {
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == num) {
                count++;
            }
        }
        return count;
    }

    public int majorityElement(int[] nums) {
        Random rand = new Random();

        int majorityCount = nums.length/2;

        while (true) {
            int candidate = nums[randRange(rand, 0, nums.length)];
            if (countOccurences(nums, candidate) > majorityCount) {
                return candidate;
            }
        }
    }
}

方法四:分治法

将数组分成两部分,寻找第一个部分中出现次数超过一半的元素为A,第二个部分出现次数超过一半的元素为B,如果A==B,那么A就是这个数组中出现次数超过一半的元素,如果A!=B,那么A和B都可能是这个数组中出现次数超过一半的元素,那么重新遍历这个数组,记录A和B出现的次数,返回出现次数多的元素,时间复杂度T(n)=2T(n/2)+2n=O(nlogn)

T(n)=2T(n2)+2n

根据 主定理,循环满足条件2,所以得:
T(n)=Θ(n logbalogn)=Θ(n logn)
public static void main(String[] args) {
    int majorityElement(vector<int>& nums) {

        return helper(nums,0,nums.size()-1);
    }

    helper(vector<int> nums,int begin,int end){
        if(begin == end)
            return nums[begin];
        else{
            int mid = begin + (end-begin)/2;
            int left = helper(nums,begin,mid);
            int right = helper(nums,mid+1,end);
            if(left == right) return left;
            else{
                int leftCount = 0;
                int rightCount = 0;
                for (int i = begin;i<=end ;i++ ) {
                    if (nums[i] == left) 
                        leftCount++;
                    else if(nums[i] == right)
                        rightCount++;
                }

                if (leftCount<rightCount) 
                    return right;
                else return left;
            }   
        }       
    }
}

方法五:Boyer-Moore Voting Algorithm

建议! 读过思路后,阅读一下solution 代码,再回想一下思路 !,就比较容易理解。
这种方法的思路是:每次从数组中找出一对不同的数字,将他们从数组中删除,当最后肯定至少剩下一个元素(3个元素的情况下)(!保证数组中一定存在出现次数超过半数的数字)。
那怎么找到一对不同的数字?怎么删除呢?
在算法的执行过程中,使用一个常量空间来记录候选元素 c 以及他出现的次数 f(c) , c 即为当前阶段出现超过半次的元素(当前阶段指在遍历数组的过程中的 前i个元素)。
在开始之前,c 为第一个元素, f(c)0,然后开始遍历数组:
 如果数组A[i] == c,即元素相同,则f(c)+=1
 如果数组A[i] !== c,即元素不相同,则 f(c)-=1,即删除这个元素的计数。
 如果元素相同或者计数为0,则应该更新元素并将计数加1。
对于两个不同元素,在遍历过后就会使计数器重新变回0,就相当于删除了两个不同元素!
 在遍历的过程中,如果 f(c) = 0,表示当前并没有候选的元素存在;如果f(c)!=0就是在当前阶段存在候选元素(元素在当前阶段出现超过半数),那么他在剩余的字符串中出现的次数也用超过半数,所以方法是可行的。

int majorityElement(vector<int>& nums) {
    int count=0;
    int result=nums[0];
    for(int i=0;i<nums.size();i++)
    {
        if(count==0||nums[i]==result)
        {
            count++;
            result=nums[i];
        }
        else
            count--;
    }
    return result;
}

本来最后f(c) > 0的元素可能并不是出现半次以上的元素,比如{1,2,3},需要遍历数组确认是否出现超过半次,但是题目保证存在出现半次以上的元素,就不用考虑了。

作者:qq_32832023 发表于2017/11/21 9:00:11 原文链接
阅读:0 评论:0 查看评论

前端框架-11-jQuery文档&对象

$
0
0

前端框架-jQuery文档&对象

1.scroll滚动属性

滚动条属性:
 scrollTop
 scrollLeft

#获取 滚动高度/宽度
$(document).click(function () {
    console.log("滚动高度" + $(this).scrollTop());
    console.log("滚动宽度" + $(this).scrollLeft());
})

#设置滚动高度/宽度
$(document).click(function () {
    $(this).scrollTop(750); #滚动的垂直位置
    $(this).scrollLeft(750);#滚动的水平位置
})

2.append,prepend

添加子元素 可以是标签、文本、js对象、jq对象
#append 追加在该元素里面的最后面
 $("#box").append("<a>我是新增的a标签</a>");
#appendTo  同append功能
$("<a>我是新增的a标签</a>").appendTo($("#box"));

# prepend 追加在该元素里面最前面
$("#box").prepend("<a>我是新增的a标签</a>");
#prependTo同 prepend
$("<a>我是新增的a标签</a>").prependTo($("#box")

#特殊用法append
<div id="box">
    我是div
    <p>我是已经存在的p标签</p>
</div>
<b>我是b标签</b>
 <b>我是b标签2</b>

var b = document.querySelector("b");
  $("#box").append(b);#会把相邻的第一个b标签添加到div里面

var $b = $("b");
$("#box").append($b);#会把相邻的所有b标签添加到div里面

3.before,after

#添加兄弟元素
after ,添加在后面
$("#box").after("<b>我是新增的</b>");
#insertAfter 同after
$("<b>我是新增的</b>").insertAfter("#box");

#before,添加该元素前面
$("#box").before("<b>我是新增的</b>");

#insertBefore 同before
$("<b>我是新增的</b>").insertBefore("#box");

4.wrap

#wrap  给每个元素添加一个父元素
$("p").wrap("<div>123</div>");

#unwrap 删除元素的父元素
$("p").unwrap();

#wrapAll 给子元素添加一个父元素都包起来
$("p").wrapAll("<div></div>");

wrapInner
$("p").wrapInner("<em></em>");//所有段落内的每个子内容加粗

5.empty

empty 清空子节点
$("#box").empty();

remove 移除自己(不保留数据和事件)
$("#box").remove();//不传参 移除自己和子元素
$("p").remove(".box");//删除classbox的那个p

detach 移除自己(保留数据和事件)
$("#box").detach();

clone  复制自己(根据参数不同,,决定是否保留事件和子元素)
$(".box").clone().appendTo($("p"));

6.筛选

#html演示
<div id="box" class="show">
    <p>1</p>
    <p class="box">2</p>
    <div>
        <p>3</p>
        <p>4</p>
    </div>
</div>

eq
#hasClass 检查当前的元素是否含有某个特定的类,如果有,则返回truealert($("p").hasClass("box"));

#children 找儿子 可以不传参数
console.log($("#box").children(".box"));

#find  不传参,默认不找
console.log($("#box").find("p"));

#parent 不需要参数
console.log($(".box").parent());

#parents找到所有
console.log($(".box").parents()); #找到所有的
console.log($(".box").parents(".show"));

#siblings 不传参 所有兄弟 传参 所有兄弟按照参数筛选出合格的
 $("p").siblings(".box").css("color","red");

7.事件

jquery里面的事件
        都是函数形式的,去掉on的那种
        原理上事件都是事件绑定的形式而不是赋值的形式
jquery事件绑定、解绑
        所有事件的添加都是绑定的形式
        可以通过on来添加事件

#html
<div id="box">
    <ul>
        <li>11</li>
        <li>22</li>
        <li>33</li>
        <li>44</li>
    </ul>
</div>
#绑定点击事件,事件可以绑定多次,会执行多次点击事件
var $box = $("#box");
$box.click(function () {
    alert(1);
})
$box.click(function () {
    alert(2);
})

#每个li添加点击事件
$("#box ul li").click(function () {
    alert($(this).index());
});
#on绑定单个事件
$("#box").on("click","li",function () {
     alert($(this).index());
})

#on绑定多个事件
$("#box").on({
    "click": function () {
        console.log("我被点击了");
    },
    "mouseenter": function () {
        console.log("我被鼠标移入");
    },
    "mouseleave": function () {
         console.log("我被鼠标移出");
    }
});

#移除事件
$("#box").off("click");

#鼠标移入移除,mouseenter和 mouseleave结合功能
$("#box").hover(function () {
    console.log(1);//移入
},function () {
    console.log(2);//移除
});

8.动画

#show  不传参 瞬间显示隐藏
hide  传一个数字参数,代表毫秒,改变宽、高、透明度
toggle
var off = false;
var $box = $("#box");
$(document).click(function () {
    if(off){
        $box.show(2000);
    }else{
        $box.hide(2000);
    }
    off = !off;
})


#fadeIn
fadeOut 默认300 改变透明度
fadeTo  可以把透明度设置一个值,时间参数不能省略
var off = false;
var $box = $("#box");
$(document).click(function () {
    if(off){
        $box.fadeIn(2000);
    }else{
        $box.fadeOut(2000);
    }
    off = !off;
})

var $box = $("#box");
  $(document).click(function () {
      $box.fadeTo(500,0.2); #指定透明度
})


#slideDown 默认300 改变高度
slideUp

var off = false;
var $box = $("#box");
$(document).click(function () {
    if(off){
        $box.slideDown(2000);
    }else{
        $box.slideUp(2000);
    }
    off = !off;
})
#slideToggle 改变高度,有就隐藏,没有就显示
var $box = $("#box");
$(document).click(function () {
    $box.slideToggle();
})


这三组动画属性,不仅仅可以接受一个数字参数,能接受的参数有:
* number / string  代表动画时间(可缺省)   毫秒数 / ("fast" "normal" "slow")
* string   代表运动曲线(可缺省)
* function   代表回调函数

9.animate

animate
传参:
    * obj  必传  json格式代表的变化的属性和目标值  数值变化
    * number/string 可缺省 代表毫秒数 或者 三个预设好的值 默认300
    * string 可缺省,代表运动曲线,只能是三个预设好的运动缺陷 默认easing
    * function 可缺省,代表动画结束后的回调函数

$("#box").animate({
    "width": 500,
    "height": 200,
    "marginLeft": 200
},5000)
--------------------------------------------------------------------
#stop动画
清空动画队列,可以接受两个布尔值参数
第一个不用管
第二个决定是否瞬间到达,true到底,false没到

#css
<style>
    * {
        margin: 0;
        padding: 0;
    }
    li{
        list-style: none;
    }
    #box ul li{
        float: left;
        width: 150px;
        height: 30px;
        text-align: center;
        overflow: hidden;
        border-right: 1px solid #fff;
        background: red;
    }
    #box ul li p{
        height: 200px;
        text-align: center;
        line-height: 30px;
        font-size: 14px;
        font-weight: bold;
        color: #fff;
    }
</style>

#html
<div id="box">
    <ul>
        <li>
            <p>苹果</p>
        </li>
        <li>
            <p>香蕉</p>
        </li>
        <li>
            <p>梨子</p>
        </li>
        <li>
            <p>橘子</p>
        </li>
        <li>
            <p>黄瓜</p>
        </li>
    </ul>
</div>
#动画开启
$("#box ul li").hover(function () {
    $(this).stop(true,false).animate({"height":200},1000);
},function () {
    $(this).stop(true,false).animate({"height":30},1000);
})
--------------------------------------------------------------------
#延迟属性
delay 只对动画有用
$(document).click(function () {
//   $("#box").delay(1000).fadeOut(500);#延迟操作
//            $("#box").delay(5000).css("background","pink");//delay无用
    $("#box").delay(2000).queue(function () {
        $(this).css({
            "background":"pink",
            "width": 300
        });
    })
})
作者:lianjiaokeji 发表于2017/11/21 9:13:53 原文链接
阅读:19 评论:0 查看评论

剑指offer 有环链表

$
0
0

链表带环的情况下就需要活用指针:

  • 两个指针的组合可以很好的遍历结点
  • 两个指针的行走速度有差异即可进行环的相关判断(一步、两步、先走后走)
  • 链表每走一步都要注意下一个节点为nullptr时,是否会影响到程序的鲁棒性,造成程序的崩溃,走两步的指针更需要注意这个问题,需要判断两次!(否则为nullptr也就不会再走下去)
#include <iostream>  
#include <string>  
#include <vector>  
#include <stack>
using namespace std;  

typedef int datatype;

struct Node
{
	datatype value;
	Node* Next_Node;
};

struct Node_circle
{
	unsigned int Number;
	Node *entrace;
};

//判断链表中是否有环
Node* Exist_circle(Node *first)
{

	if (first == nullptr || first->Next_Node == nullptr)
	{
		return nullptr;
	}

	Node* Node_temp1 = first->Next_Node;
	Node* Node_temp2 = Node_temp1->Next_Node;

	while (Node_temp2 != nullptr && Node_temp1 != nullptr)
	{

		Node_temp1 = Node_temp1->Next_Node;
		Node_temp2 = Node_temp2->Next_Node;
		//在链表中,每一个节点为nullptr时是否影响到程序的鲁棒性,造成程序崩溃,都必须考虑在内
		if (Node_temp2->Next_Node != nullptr)
		{
			Node_temp2 = Node_temp2->Next_Node;
		}
		
		if (Node_temp1 == Node_temp2)
		{
			return Node_temp1;
			break;
		}
	}
	return nullptr;
}

//输出环中结点的个数以及环的入口
Node_circle* Find_circle(Node *first)
{
	Node* Node_temp5 = Exist_circle(first);
	if (Node_temp5 == nullptr)
	{
		return nullptr;
	}

	int number = 1;
	//已存在环,无需判断下一个节点是否为nullptr了
	Node* Node_temp3 = Node_temp5->Next_Node;
	while (Node_temp3 != Node_temp5)
	{
		Node_temp3 = Node_temp3->Next_Node;
		number++;
	}

	Node* entrace = first;
	for (int i = 0;i<number;i++)
	{
		entrace = entrace->Next_Node;
	}

	Node* Node_temp4 = first;
	while (Node_temp4 != entrace)
	{
		Node_temp4 = Node_temp4->Next_Node;
		entrace = entrace->Next_Node;
	}

	Node_circle* circle_Node;
	circle_Node->Number = number;
	circle_Node->entrace = entrace;

	return circle_Node;
}

void main()  
{     

	system("pause");
}  


作者:misayaaaaa 发表于2017/11/21 9:21:42 原文链接
阅读:35 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>