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

C++从零实现深度神经网络之三——神经网络的训练和测试

$
0
0

本文由@星沉阁冰不语出品,转载请注明作者和出处。

文章链接:http://blog.csdn.net/xingchenbingbuyu/article/details/53698627

微博:http://weibo.com/xingchenbing 



在之前的博客中我们已经实现了Net类的设计和前向传播和反向传播的过程。可以说神经网络的核心的部分已经完成。接下来就是应用层面了。要想利用神经网络解决实际的问题,比如说进行手写数字的识别,需要用神经网络对样本进行迭代训练,训练完成之后,训练得到的模型是好是坏,我们需要对之进行测试。这正是我们现在需要实现的部分的内容。


需要知道的是现在的Net类已经相对完善了,为了实现接下来的功能,不论是成员变量还是成员函数都变得更加的丰富。现在的Net类看起来是下面的样子:

	class Net
	{
	public:
        //Integer vector specifying the number of neurons in each layer including the input and output layers.
		std::vector<int> layer_neuron_num;
		std::string activation_function = "sigmoid";
		double learning_rate; 
		double accuracy = 0.;
		std::vector<double> loss_vec;
		float fine_tune_factor = 1.01;
	protected:
		std::vector<cv::Mat> layer;
		std::vector<cv::Mat> weights;
		std::vector<cv::Mat> bias;
		std::vector<cv::Mat> delta_err;

		cv::Mat output_error;
		cv::Mat target;
		float loss;

	public:
		Net() {};
		~Net() {};

		//Initialize net:genetate weights matrices、layer matrices and bias matrices
		// bias default all zero
		void initNet(std::vector<int> layer_neuron_num_);

		//Initialise the weights matrices.
		void initWeights(int type = 0, double a = 0., double b = 0.1);

		//Initialise the bias matrices.
		void initBias(cv::Scalar& bias);

		//Forward
		void farward();

		//Forward
		void backward();

		//Train,use loss_threshold
		void Net::train(cv::Mat input, cv::Mat target_, float loss_threshold, bool draw_loss_curve = false);

		//Test
		void test(cv::Mat &input, cv::Mat &target_);

		//Predict,just one sample
		int predict_one(cv::Mat &input);

		//Predict,more  than one samples
		std::vector<int> predict(cv::Mat &input);

		//Save model;
		void save(std::string filename);

		//Load model;
		void load(std::string filename);

	protected:
		//initialise the weight matrix.if type =0,Gaussian.else uniform.
		void initWeight(cv::Mat &dst, int type, double a, double b);

		//Activation function
		cv::Mat activationFunction(cv::Mat &x, std::string func_type);

		//Compute delta error
		void deltaError();

		//Update weights
		void updateWeights();
	};

可以看到已经有了训练的函数train()、测试的函数test(),还有实际应用训练好的模型的predict()函数,以及保存和加载模型的函数save()和load()。大部分成员变量和成员函数应该还是能够通过名字就能够知道其功能的。


本文重点说的是训练函数train()和测试函数test()。这两个函数接受输入(input)和标签(或称为目标值target)作为输入参数。其中训练函数还要接受一个阈值作为迭代终止条件,最后一个函数可以暂时忽略不计,那是选择要不要把loss值实时画出来的标识。


训练的过程如下:

1.接受一个样本(即一个单列矩阵)作为输入,也即神经网络的第一层;

2.进行前向传播,也即forward()函数做的事情。然后计算loss;

3.如果loss值小于设定的阈值loss_threshold,则进行反向传播更新阈值;

4.重复以上过程直到loss小于等于设定的阈值。


train函数的实现如下:

	//Train,use loss_threshold
	void Net::train(cv::Mat input, cv::Mat target_, float loss_threshold, bool draw_loss_curve)
	{
		if (input.empty())
		{
			std::cout << "Input is empty!" << std::endl;
			return;
		}

		std::cout << "Train,begain!" << std::endl;

		cv::Mat sample;
		if (input.rows == (layer[0].rows) && input.cols == 1)
		{
			target = target_;
			sample = input;
			layer[0] = sample;
			farward();
			//backward();
			int num_of_train = 0;
			while (loss > loss_threshold)
			{
				backward();
				farward();
				num_of_train++;
				if (num_of_train % 500 == 0)
				{
					std::cout << "Train " << num_of_train << " times" << std::endl;
					std::cout << "Loss: " << loss << std::endl;
				}
			}
			std::cout << std::endl << "Train " << num_of_train << " times" << std::endl;
			std::cout << "Loss: " << loss << std::endl;
			std::cout << "Train sucessfully!" << std::endl;
		}
		else if (input.rows == (layer[0].rows) && input.cols > 1)
		{
			double batch_loss = loss_threshold + 0.01;
			int epoch = 0;
			while (batch_loss > loss_threshold)
			{
				batch_loss = 0.;
				for (int i = 0; i < input.cols; ++i)
				{
					target = target_.col(i);
					sample = input.col(i);
					layer[0] = sample;

					farward();
					backward();

					batch_loss += loss;
				}

				loss_vec.push_back(batch_loss);

				if (loss_vec.size() >= 2 && draw_loss_curve)
				{
					draw_curve(board, loss_vec);
				}
				epoch++;
				if (epoch % output_interval == 0)
				{
					std::cout << "Number of epoch: " << epoch << std::endl;
					std::cout << "Loss sum: " << batch_loss << std::endl;
				}
				if (epoch % 100 == 0)
				{
					learning_rate *= fine_tune_factor;
				}
			}
			std::cout << std::endl << "Number of epoch: " << epoch << std::endl;
			std::cout << "Loss sum: " << batch_loss << std::endl;
			std::cout << "Train sucessfully!" << std::endl;
		}
		else
		{
			std::cout << "Rows of input don't cv::Match the number of input!" << std::endl;
		}
	}

这里考虑到了用单个样本和多个样本迭代训练两种情况。而且还有另一种不用loss阈值作为迭代终止条件,而是用正确率的train()函数,内容大致相同,此处略去不表。


在经过train()函数的训练之后,就可以得到一个模型了。所谓模型,可以简单的认为就是权值矩阵。简单的说,可以把神经网络当成一个超级函数组合,我们姑且认为这个超级函数就是y = f(x) = ax +b。那么权值就是a和b。反向传播的过程是把a和b当成自变量来处理的,不断调整以得到最优值或逼近最优值。在完成反向传播之后,训练得到了参数a和b的最优值,是一个固定值了。这时自变量又变回了x。我们希望a、b最优值作为已知参数的情况下,对于我们的输入样本x,通过神经网络计算得到的结果y,与实际结果相符合是大概率事件。


test()函数的作用就是用一组训练时没用到的样本,对训练得到的模型进行测试,把通过这个模型得到的结果与实际想要的结果进行比较,看正确来说到底是多少,我们希望正确率越多越好。


test()的步骤大致如下几步:

1.用一组样本逐个输入神经网络;

2.通过前向传播得到一个输出值;

3.比较实际输出与理想输出,计算正确率。


test()函数的实现如下:

	//Test
	void Net::test(cv::Mat &input, cv::Mat &target_)
	{
		if (input.empty())
		{
			std::cout << "Input is empty!" << std::endl;
			return;
		}
		std::cout << std::endl << "Predict,begain!" << std::endl;

		if (input.rows == (layer[0].rows) && input.cols == 1)
		{
			int predict_number = predict_one(input);

			cv::Point target_maxLoc;
			minMaxLoc(target_, NULL, NULL, NULL, &target_maxLoc, cv::noArray());		
			int target_number = target_maxLoc.y;

			std::cout << "Predict: " << predict_number << std::endl;
			std::cout << "Target:  " << target_number << std::endl;
			std::cout << "Loss: " << loss << std::endl;
		}
		else if (input.rows == (layer[0].rows) && input.cols > 1)
		{
			double loss_sum = 0;
			int right_num = 0;
			cv::Mat sample;
			for (int i = 0; i < input.cols; ++i)
			{
				sample = input.col(i);
				int predict_number = predict_one(sample);
				loss_sum += loss;

				target = target_.col(i);
				cv::Point target_maxLoc;
				minMaxLoc(target, NULL, NULL, NULL, &target_maxLoc, cv::noArray());
				int target_number = target_maxLoc.y;

				std::cout << "Test sample: " << i << "   " << "Predict: " << predict_number << std::endl;
				std::cout << "Test sample: " << i << "   " << "Target:  " << target_number << std::endl << std::endl;
				if (predict_number == target_number)
				{
					right_num++;
				}
			}
			accuracy = (double)right_num / input.cols;
			std::cout << "Loss sum: " << loss_sum << std::endl;
			std::cout << "accuracy: " << accuracy << std::endl;
		}
		else
		{
			std::cout << "Rows of input don't cv::Match the number of input!" << std::endl;
			return;
		}
	}

这里在进行前向传播的时候不是直接调用forward()函数,而是调用了predict_one()函数,predict函数的作用是给定一个输入,给出想要的输出值。其中包含了对forward()函数的调用。还有就是对于神经网络的输出进行解析,转换成看起来比较方便的数值。


这一篇的内容已经够多了,我决定把对于predict部分的解释放到下一篇。

未完待续。。。

作者:xingchenbingbuyu 发表于2016/12/16 21:57:20 原文链接
阅读:17 评论:0 查看评论

谈谈数据库的ACID

$
0
0

谈谈数据库的ACID

                                                                                                                                                                  帅宏军

一.事务

       定义:所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。

       准备工作:为了说明事务的ACID原理,我们使用银行账户及资金管理的案例进行分析。

 

       

  1. // 创建数据库  
  2. create table account(  
  3.    idint primary key not null,  
  4.    namevarchar(40),  
  5.    moneydouble  
  6. );  
  7.   
  8. // 有两个人开户并存钱  
  9. insert into account values(1,'A',1000);  
  10. insert into account values(2,'B',1000);  


二.ACID

       ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability.这是可靠数据库所应具备的几个特性.下面针对这几个特性进行逐个讲解.


三.原子性

       原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生。

       1.案例

              AB转帐100元钱

 

       

  1. begin transaction  
  2. update account set money= money - 100where name='A';  
  3. update account set money= money +100where name='B';  
  4. if Error then  
  5.        rollback  
  6. else  
  7.        commit  

 

       2.分析 

       在事务中的扣款和加款两条语句,要么都执行,要么就都不执行。否则如果只执行了扣款语句,就提交了,此时如果突然断电,A账号已经发生了扣款,B账号却没收到加款,在生活中就会引起纠纷。

 

       3.解决方法

       在数据库管理系统(DBMS)中,默认情况下一条SQL就是一个单独事务,事务是自动提交的。只有显式的使用start transaction开启一个事务,才能将一个代码块放在事务中执行。保障事务的原子性是数据库管理系统的责任,为此许多数据源采用日志机制。例如,SQL Server使用一个预写事务日志,在将数据提交到实际数据页面前,先写在事务日志上。


四.一致性

       一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。

       1.案例

       对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNT表中aaabbb的存款总额为2000元。

 

       2.解决方法

保障事务的一致性,可以从以下两个层面入手

       2.1数据库机制层面

       数据库层面的一致性是,在一个事务执行之前和之后,数据会符合你设置的约束(唯一约束,外键约束,Check约束等)和触发器设置。这一点是由SQL SERVER进行保证的。比如转账,则可以使用CHECK约束两个账户之和等于2000来达到一致性目的

       2.2业务层面

   对于业务层面来说,一致性是保持业务的一致性。这个业务一致性需要由开发人员进行保证。当然,很多业务方面的一致性,也可以通过转移到数据库机制层面进行保证。


五.隔离性

       多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。

       这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

       Windows中,如果多个进程对同一个文件进行修改是不允许的,Windows通过这种方式来保证不同进程的隔离性:

   

       企业开发中,事务最复杂问题都是由事务隔离性引起的。当多个事务并发时,SQL Server利用加锁和阻塞来保证事务之间不同等级的隔离性。一般情况下,完全的隔离性是不现实的,完全的隔离性要求数据库同一时间只执行一条事务,这样会严重影响性能。想要理解SQL Server中对于隔离性的保障,首先要了解并发事务之间是如何干扰的.

       1.事务之间的相互影响

       事务之间的相互影响分为几种,分别为:脏读,不可重复读,幻读,丢失更新

 

       1.1脏读

       脏读意味着一个事务读取了另一个事务未提交的数据,而这个数据是有可能回滚的;如下案例,此时如果事务1回滚,则B账户必将有损失。

     

 

       1.2不可重复读

    不可重复读意味着,在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。如下案例,事务1必然会变得糊涂,不知道发生了什么。

 

       1.3幻读(虚读)

    幻读,是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样.

  

 

       1.4丢失更新

       两个事务同时读取同一条记录,A先修改记录,B也修改记录(B是不知道A修改过),B提交数据后B的修改结果覆盖了A的修改结果。

 

       2.理解SQL SERVER中的隔离级别

       数据库的事务隔离级别(TRANSACTION ISOLATION LEVEL)是一个数据库上很基本的一个概念。为什么会有事务隔离级别,SQL Server上实现了哪些事务隔离级别?事务隔离级别的前提是一个多用户、多进程、多线程的并发系统,在这个系统中为了保证数据的一致性和完整性,我们引入了事务隔离级别这个概念,对一个单用户、单线程的应用来说则不存在这个问题。

    为了避免上述几种事务之间的影响,SQL Server通过设置不同的隔离级别来进行不同程度的避免。因为高的隔离等级意味着更多的锁,从而牺牲性能。所以这个选项开放给了用户根据具体的需求进行设置。不过默认的隔离级别Read Commited符合了多数的实际需求.

 

隔离级别

脏读

丢失更新

不可重复读

幻读

并发模型

更新冲突检测

未提交读:Read Uncommited

悲观

已提交读:Read commited

悲观

可重复读:Repeatable Read

悲观

可串行读:Serializable

悲观

    

       SQL Server隔离事务之间的影响是通过锁来实现的,通过阻塞来阻止上述影响。不同的隔离级别是通过加不同的锁,造成阻塞来实现的,所以会以付出性能作为代价;安全级别越高,处理效率越低;安全级别越低,效率高。

 

       使用方法:SET TRANSACTIONISOLATION LEVEL REPEATABLE READ

 

       未提交读: 在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。  

       已提交读:只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。已提交读是SQL Server的默认隔离级别。 

       可重复读: 像已提交读级别那样读数据,但会保持共享锁直到事务结束。  

       可串行读:工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围。这就阻止了新数据插入查询所涉及的范围。


六.持久性

       持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

       即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。

       SQL SERVER通过write-ahead transaction log来保证持久性。write-ahead transaction log的意思是,事务中对数据库的改变在写入到数据库之前,首先写入到事务日志中。而事务日志是按照顺序排号的(LSN)。当数据库崩溃或者服务器断点时,重启动SQL SERVERSQLSERVER首先会检查日志顺序号,将本应对数据库做更改而未做的部分持久化到数据库,从而保证了持久性。


七.总结

       事务的(ACID)特性是由关系数据库管理系统(RDBMS,数据库系统)来实现的。数据库管理系统采用日志来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。

  数据库管理系统采用锁机制来实现事务的隔离性。当多个事务同时更新数据库中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。


转载请注明出处:http://blog.csdn.net/shuaihj/article/details/14163713



作者:erlian1992 发表于2016/12/16 22:05:41 原文链接
阅读:15 评论:0 查看评论

STM32F030低温下RTC不工作

$
0
0

1 前言

客户反馈在批量生产阶段,发现部分产品的MCU的RTC在低温(0℃)下工作不正常,但是在常温下又是正常的,且其他正常的MCU的RTC在常温与低温下都是正常的。

2 问题跟进与分析

通过与客户邮件沟通,了解到客户使用的MCU型号是:STM32F030C6T6TR。在产品的主从结构中主要用作电源管理和时钟管理。通过客户的描述,似乎相同型号不同片子都存在较大的差异。

由于时间紧急,在了解到初步信息后立即拜访客户,针对客户认为有问题的MCU芯片做针对性试验。通过STM32CubMx生成测试工程,分别使用LSI(40K),LSE(32.768K),RTC工作时每秒通过LED1(PB5)取反一次(通过LED1灯是否闪烁来指示RTC是否工作正常),然后分别测量OSC管脚与PA8脚(输出LSI或LSE),并对比ST官方的NUCLEO-F030板,最终测试结果如下:

Test item Temperature Low-speed clock type LED1(use PB5 to indicate the RTC status) OSC pin PA8 output clock
Use Customer board without any modify Indoor temperature(25℃) Use LSI(40KHz) OK(Flash every second) N/A OK(Output 40K waveform)
Use Customer board without any modify Under the low temperature(0℃) Use LSI(40KHz) OK(Flash every second) N/A OK(Output 40K waveform)
Use Customer board without any modify Indoor temperature(25℃) Use LSE(32.768KHz) OK(Flash every second) 32.768K waveform OK(Output 32.768K waveform)
Use Customer board without any modify Under the low temperature(0℃) Use LSE(32.768KHz) Failed(no flash) 32.768K waveform detected Failed(no output waveform)
Use the Customer board and modify the LSE load capacitance value to 6.8pF Indoor temperature(25℃) Use LSE(32.768KHz) OK(Flash every second) 32.768K waveform OK(Output 32.768K waveform)
Use the Customer board and modify the LSE load capacitance value to 6.8pF Under the low temperature(0℃) Use LSE(32.768KHz) OK(Flash every second) 32.768K waveform OK(Output 32.768K waveform)
Use ST Nucleo-F030 board Indoor temperature(25℃) Use LSE(32.768KHz) OK(Flash every second) 32.768K waveform OK(Output 32.768K waveform)
Use ST Nucleo-F030 board Under the low temperature(0℃) Use LSE(32.768KHz) OK(Flash every second) 32.768K waveform OK(Output 32.768K waveform)

表1 测试内容

通过测试结果,我们得到如下信息:

  • 当使用LSI时,无论常温还是低温下都能正常工作。
  • 当使用LSE时,常温下能正常工作,但在低温(0℃)时,RTC不再工作(LED1停止闪烁),且PA8管脚无输出,但保持为高电平,且此时OSC管脚此时是存在32.768K的波形的。
  • 通过修改负载电容C1&C2的电容值从5.1pF修改到6.8pF时,原本低温下不工作的RTC又能恢复正常工作。
  • 对比ST官方的NUCLEO-F030板子,在常温与低温下均能正常工作。
    OSC脚在低温下的波形

    图1 OSC脚在低温下的波形

    PA8管脚在低温下的波形

    图2 PA8管脚在低温下的波形

    从测试结果来看,通过修改负载电容的方式能让原本不能正常工作的RTC恢复正常工作,这个似乎为客户的负载电容不能精准的匹配系统的原因所致。

但客户对于这种解释是不接受的,理由是现在设计的负载电容5.1pF是通过测试后的值,精度可以达到6.5ppm,但如果改为6.8pF,那么精度将会变到大约30ppm,这个会严重影响到MCU的RTC的时间精准度,系统在长时间运行后,时间必然会偏差很大,超出设计合理范围,这个是不允许的。

3 问题分析

既然客户不接受修改负载电容,那么首先我们重新梳理下客户的晶振设计各种参数是否准确,客户的LSE电路设计如下所示:

 LSE电路设计

图3 LSE电路设计

如上图,图中的MR10 10Mohm这个反馈电阻在实际电路中是没有加的,晶振使用的是TXC的,从晶振厂商提供的数据手册中得到相关参数如下:

index parameters Sym Typical Unit
1 Nominal Frequency F0 32.768 KHz
2 Load Capacitance CL 7.0 pF
3 Equivalent Series Resistance ESR 70 KΩ
4 Shunt Capacitance C0 1.0 pF

表2 晶振参数

再者,由于客户代码中使用的LSE drive配置的是最高等级,从下图芯片对应的数据手册中可以找到对应的gm值为25uA/V,此时的驱动电流为1.6uA:
LSE驱动能力与跨导对应的关系(理论值)

图4 LSE驱动能力与跨导对应的关系(理论值)

上图有提到AN2867这个文档,于是我们打开这个文档,在3.4节,发现有这个要求:
LSE对gain margin的要求

图5 LSE对gain margin的要求

也就是要求gain margin的值要求大于5,这样晶振才能正常起振,那么gain margin又是如何计算的呢?接下来找到gainmargin 的计算公式,如下:
gain margin的计算公式

图6 gain margin的计算公式

其中gm就是图4中从数据手册中提到的跨导值,STM32F030 LSE的不同驱动等级对应着不同的gm值,由于我们的测试代码使用的是CubeMx自动生成的代码,其默认使用的是最高等级,且客户使用的也是最高等级,因此,这个得出的gm值为25 uA/V, gm有了,那么上面公式中的gmcrit又该如何计算,我们接下来找到它的计算公式,如:
g_mcrit的计算公式

图7 g_mcrit的计算公式

通过晶振对应参数,我们可以得出:
ESR =70KΩ, C0 =1.0pF, CL =7.0pF, 而F就是LSE的频率,为32.768KHz.
于是:
g_mcrit =4 * 7E4 * POWER(2*PI()32768,2) POWER ((1.0E-12 + 7.0E-12),2) =7.6E-07
最终得到:
gain_magin =gm/g_mcrit =2.5E-05/7.6E-07 =32.89
这个值是远大于5,因此,理论上不会存在晶振不起振是的问题,实际上当在低温下,之前在测试中也有发现晶振也是有起振,有波形输出的,只不过PA8脚没有波形输出,那个又是什么问题呢?

提交给division,最终定位到LSE的驱动等级过高,在AN2867这个文档中,有这样的描述:

使用高驱动模式的注意事项

图8 使用高驱动模式的注意事项

也就是说,在STM32F0和STM32F3中,当使用最高驱动模式(gm_crit_max=5uA/V)时,对应地应该只使用在CL=12.5pF的晶振上,以此避免振荡回路饱和,从而导致启动失败。若此时使用了一个较小的CL(如CL=6pF),那么会导致振荡频率不稳定和工作周期可能被扭曲。

AN2867随后给出了一张表,列出了驱动等级与gm_min、gm_crit_max的关系,如下:

STM32各系列的gm_min与gm_crit_max关系

图9 STM32各系列的gm_min与gm_crit_max关系

如上图,对于STM32F0,当使用最高驱动模式High时,此时的gm_min=25 uA/V,这个与数据手册中是一致的,另外gm_crit_max =5uA/V,正是上面所描述的。

也就是说,在使用最高驱动模式下,此时与之对应的CL应该使用12.5pF,而客户所使用的CL是7pF,这个与手册建议的内容是不相符的。从图4可以看出,在最高驱动等级模式下,此时驱动电流最大(1.6uA),但这里使用了一个比较小的负载电容(CL=7pF),按AN2867所述,此时有可能导致振荡回路饱和,振荡不稳定,工作周期扭曲。

此时,应该对应地下调这个LSE驱动等级,减小驱动电流,这里按比例估算的话,使用Medium High相对合适。

打开STM32F030的参考手册,在7.4.9节中:

RCC_BDCR寄存器

图10 RCC_BDCR寄存器

如上图,将LSEDRV[1:0]这两个为修改为10即可,将原先低温下RTC有问题的MCU芯片修改后再次放到低温下进行验证,测试结果为正常。由于此问题是部分芯片有可能会出现的问题,客户需要对修改后的芯片进行持续跟踪,至今没有再反馈出现过此问题,由此,此问题基本算是解决。

另外,从图1中所作的测试结果来看,实际上,在低温条件下,RTC出现问题的时候,OSC pin还是能正常捕捉到波形,只不过,PA8脚这个MCO上没有波形,只是维持在高电平。于是,对于驱动电流过大所导致的振荡回路饱和,振荡不稳定,工作周期扭曲,这里理解为MCO脚与MCU内部振荡回路的连接点,也就是MCO所表现的波形。

3 总结

AN2867这个文档总结了关于STM32晶振匹配方面的信息。里边有提到,负载电容CL值越大,所需的驱动电流也就越大,但牵引度越小。这也就解释了表1中通过增大C1&C2的电容值,原本出现问题的RTC能恢复正常的现象,这是由于C1&C2的电容值变大将导致负载电容CL变大,进而对应所需的驱动电流也就跟着增加,这反而减少了在高驱动模式情况下振荡回路出现饱和的机会。

在一般情况下,关于晶振这方便我们往往比较关注的是gain margin的计算,它的值太小的话会导致不起振,但同时,我们也应该适当关注由驱动电流过大导致振荡回路饱和的情况。

作者:flydream0 发表于2016/12/16 22:28:35 原文链接
阅读:0 评论:0 查看评论

Android--Alarm机制

$
0
0
Android中的定时任务一般有两种实现方式,一种是使用 Java API 里提供的 Timer 类,
一种是使用 Android的 Alarm机制。 这两种方式在多数情况下都能实现类似的效果, 但 Timer
有一个明显的短板,它并不太适用于那些需要长期在后台运行的定时任务。我们都知道,为
了能让电池更加耐用,每种手机都会有自己的休眠策略,Android手机就会在长时间不操作
的情况下自动让 CPU 进入到睡眠状态,这就有可能导致 Timer中的定时任务无法正常运行。
而 Alarm 机制则不存在这种情况,它具有唤醒 CPU 的功能,即可以保证每次需要执行定时
任务的时候 CPU都能正常工作。 需要注意, 这里唤醒 CPU和唤醒屏幕完全不是同一个概念,
千万不要产生混淆。
那么首先我们来看一下 Alarm 机制的用法吧,其实并不复杂,主要就是借助了
AlarmManager类来实现的。这个类和 NotificationManager有点类似,都是通过调用 Context的
getSystemService()方法来获取实例的, 只是这里需要传入的参数是 Context.ALARM_SERVICE。
因此,获取一个 AlarmManager的实例就可以写成:
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
接下来调用 AlarmManager 的 set()方法就可以设置一个定时任务了,比如说想要设定一
个任务在 10秒钟后执行,就可以写成:
long triggerAtTime = SystemClock.elapsedRealtime() + 10 * 1000;
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pendingIntent);
上面的两行代码你不一定能看得明白, 因为 set()方法中需要传入的三个参数稍微有点复
杂,下面我们就来仔细地分析一下。第一个参数是一个整型参数,用于指定 AlarmManager的
工作类型,有四种值可选,分别是 ELAPSED_REALTIME、ELAPSED_REALTIME_WAKEUP、
RTC 和 RTC_WAKEUP。其中 ELAPSED_REALTIME 表示让定时任务的触发时间从系统开
机开始算起,但不会唤醒 CPU。ELAPSED_REALTIME_WAKEUP 同样表示让定时任务的触
发时间从系统开机开始算起,但会唤醒 CPU。RTC表示让定时任务的触发时间从 1970 年 1
月 1 日 0点开始算起,但不会唤醒 CPU。RTC_WAKEUP 同样表示让定时任务的触发时间从
1970 年 1 月 1 日 0 点开始算起,但会唤醒 CPU。使用 SystemClock.elapsedRealtime()方法可
以获取到系统开机至今所经历时间的毫秒数,使用 System.currentTimeMillis()方法可以获取
到 1970年 1 月 1日 0点至今所经历时间的毫秒数。
然后看一下第二个参数,这个参数就好理解多了,就是定时任务触发的时间,以毫秒为
单位。如果第一个参数使用的是 ELAPSED_REALTIME或 ELAPSED_REALTIME_WAKEUP,
则这里传入开机至今的时间再加上延迟执行的时间。如果第一个参数使用的是 RTC 或
RTC_WAKEUP,则这里传入 1970年 1月 1日 0点至今的时间再加上延迟执行的时间。

第三个参数是一个 PendingIntent,对于它你应该已经不会陌生了吧。这里我们一般会调
用 getBroadcast()方法来获取一个能够执行广播的 PendingIntent。 这样当定时任务被触发的时
候,广播接收器的 onReceive()方法就可以得到执行。
了解了 set()方法的每个参数之后,你应该能想到,设定一个任务在 10 秒钟后执行还可
以写成:
long triggerAtTime = System.currentTimeMillis() + 10 * 1000;
manager.set(AlarmManager.RTC_WAKEUP, triggerAtTime, pendingIntent);
好了,现在你已经掌握 Alarm机制的基本用法,下面我们就来创建一个可以长期在后台
执行定时任务的服务。 创建一个 ServiceBestPractice 项目, 然后新增一个 LongRunningService
类,代码如下所示:
public class LongRunningService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d("LongRunningService", "executed at " + new Date().
toString());
}
}).start();
AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);
int anHour = 60 * 60 * 1000; // 这是一小时的毫秒数
long triggerAtTime = SystemClock.elapsedRealtime() + anHour;
Intent i = new Intent(this, AlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtTime, pi);
return super.onStartCommand(intent, flags, startId);
}
}
我们在 onStartCommand()方法里开启了一个子线程, 然后在子线程里就可以执行具体的

逻辑操作了。这里简单起见,只是打印了一下当前的时间。
创建线程之后的代码就是我们刚刚讲解的 Alarm 机制的用法了,先是获取到了
AlarmManager 的实例,然后定义任务的触发时间为一小时后,再使用 PendingIntent 指定处
理定时任务的广播接收器为 AlarmReceiver,最后调用 set()方法完成设定。
显然,AlarmReceiver目前还不存在呢,所以下一步就是要新建一个 AlarmReceiver类,
并让它继承自 BroadcastReceiver,代码如下所示:
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, LongRunningService.class);
context.startService(i);
}
}
onReceive()方法里的代码非常简单,就是构建出了一个 Intent 对象,然后去启动
LongRunningService 这个服务。那么这里为什么要这样写呢?其实在不知不觉中,这就已经
将一个长期在后台定时运行的服务完成了。因为一旦启动 LongRunningService,就会在
onStartCommand()方法里设定一个定时任务,这样一小时后 AlarmReceiver 的 onReceive()方
法就将得到执行,然后我们在这里再次启动 LongRunningService,这样就形成了一个永久的
循环,保证 LongRunningService 可以每隔一小时就会启动一次,一个长期在后台定时运行的
服务自然也就完成了。
接下来的任务也很明确了,就是我们需要在打开程序的时候启动一次LongRunningService,
之后 LongRunningService 就可以一直运行了。修改 MainActivity中的代码,如下所示:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, LongRunningService.class);
startService(intent);
}
}
最后别忘了,我们所用到的服务和广播接收器都要在 AndroidManifest.xml中注册才行,
代码如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.servicebestpractice"
android:versionCode="1"
android:versionName="1.0" >
……
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.servicebestpractice.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".LongRunningService" >
</service>
<receiver android:name=".AlarmReceiver" >
</receiver>
</application>
</manifest>
现在就可以来运行一下程序了。虽然你不会在界面上看到任何有用的信息,但实际上
LongRunningService 已经在后台悄悄地运行起来了。为了能够验证一下运行结果,我将手机
闲置了几个小时,然后观察 LogCat中的打印日志.

可以看到,LongRunningService 果然如我们所愿地运行着,每隔一小时都会打印一条日
志。这样,当你真正需要去执行某个定时任务的时候,只需要将打印日志替换成具体的任务
逻辑就行了。
另外需要注意的是,从 Android 4.4 版本开始,Alarm 任务的触发时间将会变得不准确,
有可能会延迟一段时间后任务才能得到执行。这并不是个 bug,而是系统在耗电性方面进行
的优化。系统会自动检测目前有多少 Alarm任务存在,然后将触发时间将近的几个任务放在
一起执行,这就可以大幅度地减少 CPU被唤醒的次数,从而有效延长电池的使用时间。
当然,如果你要求 Alarm任务的执行时间必须准备无误,Android仍然提供了解决方案。
使用 AlarmManager的 setExact()方法来替代 set()方法,就可以保证任务准时执行了。
作者:chaoyu168 发表于2016/12/19 15:36:20 原文链接
阅读:137 评论:0 查看评论

结构型模式——适配器模式

$
0
0

1.由来

客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。

看图吧,能跟好的理解:

      

      


2.定义

适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。


3.代码的实现

这里又分为两种实现方式:

     (1)类的适配器模式(采用继承实现)

// 已存在的、具有特殊功能、但不符合我们既有的标准接口的类  
class Adaptee {  
    public void specificRequest() {  
        System.out.println("被适配类具有 特殊功能...");  
    }  
}  
  
// 目标接口,或称为标准接口  
interface Target {  
    public void request();  
}  
  
// 具体目标类,只提供普通功能  
class ConcreteTarget implements Target {  
    public void request() {  
        System.out.println("普通类 具有 普通功能...");  
    }  
}  
   
// 适配器类,继承了被适配类,同时实现标准接口  
class Adapter extends Adaptee implements Target{  
    public void request() {  
        super.specificRequest();  
    }  
}  
   
// 测试类public class Client {  
    public static void main(String[] args) {  
        // 使用普通功能类  
        Target concreteTarget = new ConcreteTarget();  
        concreteTarget.request();  
          
        // 使用特殊功能类,即适配类  
        Target adapter = new Adapter();  
        adapter.request();  
    }  
}  


     (2)对象适配器(采用对象组合方式实现)

// 适配器类,直接关联被适配类,同时实现标准接口  
class Adapter implements Target{  
    // 直接关联被适配类  
    private Adaptee adaptee;  
      
    // 可以通过构造函数传入具体需要适配的被适配类对象  
    public Adapter (Adaptee adaptee) {  
        this.adaptee = adaptee;  
    }  
      
    public void request() {  
        // 这里是使用委托的方式完成特殊功能  
        this.adaptee.specificRequest();  
    }  
}  
  
// 测试类  
public class Client {  
    public static void main(String[] args) {  
        // 使用普通功能类  
        Target concreteTarget = new ConcreteTarget();  
        concreteTarget.request();  
          
        // 使用特殊功能类,即适配类,  
        // 需要先创建一个被适配类的对象作为参数  
        Target adapter = new Adapter(new Adaptee());  
        adapter.request();  
    }  
}  

个人感觉两种方式 还是差不多。

4.模式的优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
类适配器模式还具有如下优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
对象适配器模式还具有如下优点:
一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

5.模式的缺点


类适配器模式的缺点如下:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
对象适配器模式的缺点如下:
与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

6.应用场景

  • 系统需要使用现有的类,而这些类的接口不符合系统的需要。
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作


作者:u013424496 发表于2016/12/19 16:03:42 原文链接
阅读:95 评论:0 查看评论

javascript学习之日期 字符串(14)—— 时间和日期 Date类型

$
0
0

js提供了Date类型来处理日期和时间

获取系统当前日期时间

        var date=new Date();
        alert(date);

效果图:
这里写图片描述

自定义不同的时间格式:

1000毫秒:

        var d1=new Date(1000);//从1970.1.1 08:00开始 1000毫秒
        alert(d1);

效果图:
这里写图片描述

指定2016年6月20号:

        var d2=new Date("2016/6/20");//内部使用了Date.parse()方法
        alert(d2);

效果图:
这里写图片描述

如果输入无效数值:

        var d3=new Date("sszvgz 2016");
        alert(d3);

效果图:
谷歌浏览器下:
这里写图片描述
火狐浏览器下:
这里写图片描述
这说明存在浏览器兼容性问题,在不同浏览器显示效果不同。

个人微信公众号:

这里写图片描述

如果我的文章对您有帮助,微信支付宝打赏:

这里写图片描述
这里写图片描述

作者:SundayAaron 发表于2016/12/19 16:26:47 原文链接
阅读:95 评论:0 查看评论

Android安全开发之安全使用HTTPS全面透析

$
0
0

1、HTTPS简介

阿里聚安全的应用漏洞扫描器中有证书弱校验、主机名弱校验、webview未校验证书的检测项,这些检测项是针对APP采用HTTPS通信时容易出现风险的地方而设。接下来介绍一下安全使用HTTPS的相关内容。

1.1 为何需要HTTPS

HTTP协议是没有加密的明文传输协议,如果APP采用HTTP传输数据,则会泄露传输内容,可能被中间人劫持,修改传输的内容。如下图所示就是典型的APP HTTP通信被运营商劫持修改,插入广告:

上图是在我的住处,用WiFi打开某APP,页面底部出现了一个拆红包的广告,点开以后是一个安装APP的页面,如果我用联通的4G网络打开,就不会出现这种情况,说明小区运营商劫持了HTTP通信,往APP的通信中加入了自己的推广内容,还有一些低俗的推广广告,这很影响用户体验。一些别有用心的人通过搭建公共WiFi,进行流量劫持、嗅探,可以获得通过HTTP传输的敏感信息。

为了保护用户的信息安全、保护自己的商业利益,减少攻击面,我们需要保障通信信道的安全,采用开发方便的HTTPS是比较好的方式,比用私有协议要好,省时省力。但是如果HTTPS使用不当,就很难起到应有的保护效果。乌云上有很多Android HTTPS使用不当导致产生风险的例子,如 wooyun-2010-079358、wooyun-2010-081966、wooyun-2010-080117,有兴趣的话可以去找找看看。

1.2 HTTPS通信原理

HTTPS是HTTP over SSL/TLS,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL/TLS:

SSL/TLS层负责客户端和服务器之间的加解密算法协商、密钥交换、通信连接的建立,安全连接的建立过程如下所示:

HPPTS握手协议有很丰富的内容,建议读者使用wireshark抓包进行分析,由于篇幅所限,这里不再进一步深入。

2、如何使用HTTPS

2.1 数字证书、CA与HTTPS

信息安全的基础依赖密码学,密码学涉及算法和密钥,算法一般是公开的,而密钥需要得到妥善的保护,密钥如何产生、分配、使用和回收,这涉及公钥基础设施。

公钥基础设施(PKI)是一组由硬件、软件、参与者、管理政策与流程组成的基础架构,其目的在于创造、管理、分配、使用、存储以及撤销数字证书。公钥存储在数字证书中,标准的数字证书一般由可信数字证书认证机构(CA,根证书颁发机构)签发,此证书将用户的身份跟公钥链接在一起。CA必须保证其签发的每个证书的用户身份是唯一的。

链接关系(证书链)通过注册和发布过程创建,取决于担保级别,链接关系可能由CA的各种软件或在人为监督下完成。PKI的确定链接关系的这一角色称为注册管理中心(RA,也称中级证书颁发机构或者中间机构)。RA确保公钥和个人身份链接,可以防抵赖。如果没有RA,CA的Root 证书遭到破坏或者泄露,由此CA颁发的其他证书就全部失去了安全性,所以现在主流的商业数字证书机构CA一般都是提供三级证书,Root 证书签发中级RA证书,由RA证书签发用户使用的证书。

X509证书链,左边的是CA根证书,中间的是RA中间机构,右边的是用户:

www.google.com.hk 网站的证书链如下,CA证书机构是 GeoTrust Global CA,RA机构是 Google Internet Authority G2,网站的证书为 *.google.com.hk:

HTTPS通信所用到的证书由CA提供,需要在服务器中进行相应的设置才能生效。另外在我们的客户端设备中,只要访问的HTTPS的网站所用的证书是可信CA根证书签发的,如果这些CA又在浏览器或者操作系统的根信任列表中,就可以直接访问,而如12306.cn网站,它的证书是非可信CA提供的,是自己签发的,所以在用谷歌浏览器打开时,会提示“您的连接不是私密连接”,证书是非可信CA颁发的:

所以在12306.cn的网站首页会提示为了我们的购票顺利,请下载安装它的根证书,操作系统安装后,就不会再有上图的提示了。

2.2 自有数字证书的生成

HTTPS网站所用的证书可向可信CA机构申请,不过这一类基本上都是商业机构,申请证书需要缴费,一般是按年缴费,费用因为CA机构的不同而不同。如果只是APP与后台服务器进行HTTPS通信,可以使用openssl工具生成自签发的数字证书,可以节约费用,不过得妥善保护好证书私钥,不能泄露或者丢失。HTTPS通信所用的数字证书格式为X.509。

自签发数字证书步骤如下:

Step1 生成自己的CA根证书

生成CA私钥文件ca.key:
openssl genrsa -out ca.key 1024

生成X.509证书签名请求文件ca.csr:
openssl req -new -key ca_private.key -out ca.csr

在生成ca.csr的过程中,会让输入一些组织信息等。

生成X.509格式的CA根证书ca_public.crt(公钥证书):
openssl x509 -req -in ca.csr -signkey ca_private.key -out ca_public.crt

Step2 生成服务端证书

先生成服务器私钥文件server_private.key:
openssl genrsa -out server_private.key 1024

根据服务器私钥生成服务器公钥文件server_public.pem:
openssl rsa -in server_private.key -pubout -out server_public.pem

服务器端需要向CA机构申请签名证书,在申请签名证书之前依然是创建自己的证书签名请求文件server.csr:
openssl req -new -key server_prviate.key -out server.csr

对于用于HTTPS的CSR,Common Name必须和网站域名一致,以便之后进行Host Name校验。

服务器端用server.csr文件向CA申请证书,签名过程需要CA的公钥证书和私钥参与,最终颁发一个带有CA签名的服务器端证书server.crt:
openssl x509 -req -CA ca_public.crt -CAkey ca_private.key -CAcreateserial -in server.csr -out server.crt

如果服务器端还想校验客户端的证书,可以按生成服务器端证书的形式来生成客户端证书。

使用openssl查看证书信息:
openssl x509 -in server.crt -text -noout

用web.py搭建一个简单的服务器测试生成的server.crt,文件webpytest.py为:

在本地运行web服务器程序:
python webpytest.py 1234

在safari浏览器中输入 https://0.0.0.0:1234 ,提示此证书无效(主机名不相符),因为在生成服务器端证书签名请求文件server.csr时,在Common Name中输入的是localhost,与0.0.0.0不符:

在safari浏览器中输入 https://localhost:1234 ,不再提示主机名不相符了,而是提示此证书是由未知颁发机构签名的,因为是私有CA签发的证书,私有CA不在浏览器或者操作系统的的根信任列表中:

还可用以下命令查看网站证书信息:
openssl s_client -connect localhost:1234

服务器端搭建成功,接下来讲Android客户端怎么和服务端进行HTTPS通信。

2.3 使用HttpsURLConnection进行HTTPS通信

Android官网给出了使用HttpsURLConnection API访问HTTPS的网站示例:

此方法的特点:

  • 由Android系统校验服务端数字证书的合法性,用可信CA签发的数字证书的网站才可以正常访问,私有CA签发的数字证书的网站无法访问。

  • 不能抵御在用户设备上安装证书(将中间人服务器的证书放到设备的信任列表中)进行中间人攻击,做此类攻击的一般是为了分析应用和服务器的交互协议,找应用和服务器的其他漏洞。

  • 如果网站没有启用SSL site wide(use HTTPS only)或HSTS(HTTP Strict Transport Security)则无法抵御SSL Strip(HTTPS降级为HTTP)攻击,局域网攻击,如针对免费WiFi。

如果要使用私有CA签发的证书,必须重写校验证书链TrustManager中的方法,否则的话会出现javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found。但是在重写TrustManger中的checkServerTrusted()很多开发者什么也没有做,会导致证书弱校验(没有真正校验证书)。

如下是错误的写法:

正确的写法是真正实现TrustManger的checkServerTrusted(),对服务器证书域名进行强校验或者真正实现HostnameVerifier的verify()方法。

真正实现TrustManger的checkServerTrusted()代码如下:

其中serverCert是APP中预埋的服务器端公钥证书,如果是以文件形式,其获取为如下形式:

对服务器证书域名进行强校验:

真正实现HostnameVerifier的verify()方法:

另外一种写法证书锁定,直接用预埋的证书来生成TrustManger,过程如下:

参数certStream是证书文件的InputSteam流:

另外可以用以下命令查看服务器证书的公钥:

keytool -printcert -rfc -file uwca.crt

直接复制粘贴可以将公钥信息硬编码在代码中:

可以用以下形式获取此公钥对应的X.509证书:

2.4 使用OKHttp3.0进行HTTPS通信

除了使用Android系统提供的HttpsURLconnection进行https通信,还有其他的第三方库可以使用,以OKhttp3.0为例,先看未校验服务器端证书链、未校验服务端证书域名的错误写法:

这些错误的发生其实和HttpsURLConnection的其实相同,都涉及SSLContext和HostnameVerifier,聚安全应用扫描器都能扫出来这些潜在风险点,解决办法也和2.3 节相同使用HttpsURLConnection都是真正实现TrustManager和HostnameVerifier中的方法。

2.5 Webview的HTTPS安全

目前很多应用都用webview加载H5页面,如果服务端采用的是可信CA颁发的证书,在 webView.setWebViewClient(webviewClient) 时重载 WebViewClient的onReceivedSslError() ,如果出现证书错误,直接调用handler.proceed()会忽略错误继续加载证书有问题的页面,如果调用handler.cancel()可以终止加载证书有问题的页面,证书出现问题了,可以提示用户风险,让用户选择加载与否,如果是需要安全级别比较高,可以直接终止页面加载,提示用户网络环境有风险:

不建议直接用handler.proceed(),聚安全的应用安全扫描器会扫出来直接调用handler.proceed()的情况。

如果webview加载https需要强校验服务端证书,可以在 onPageStarted() 中用 HttpsURLConnection 强校验证书的方式来校验服务端证书,如果校验不通过停止加载网页。当然这样会拖慢网页的加载速度,需要进一步优化,具体优化的办法不在本次讨论范围,这里也不详细讲解了。

3、阿里聚安全对开发者建议

阿里聚安全的漏洞扫描器发现,很多APP都存在HTTPS使用不当的风险。正确使用HTTPS能有效抵御在用户设备上安装证书进行中间人攻击和SSL Strip攻击。

但是上述方法都需要在客户端中预埋证书文件,或者将证书硬编码写在代码中,如果服务器端证书到期或者因为泄露等其他原因需要更换证书,也就必须强制用户进行客户端升级,体验效果不好。阿里聚安全推出了一个能完美解决这个问题的安全组件。APP开发者只需要将公钥放在安全组件中,安全组件的动态密钥功能可以实现公钥的动态升级。

另外正确使用HTTPS并非完全能够防住客户端的Hook分析修改,要想保证通信安全,也需要依靠其他方法,比如重要信息在交给HTTPS传输之前进行加密,另外实现客户端请求的签名处理,保证客户端与服务端通信请求不被伪造。目前阿里聚安全的安全组件已经具备以上所有功能,此外还有安全存储、模拟器检测,人机识别等功能。安全组件还具有实时更新客户端模块的功能,保证攻防对抗强度。

4、参考

[1] Survival guides - TLS/SSL and SSL (X.509) Certificates,http://www.zytrax.com/tech/survival/ssl.html

[2] Public key infrastructure,https://en.wikipedia.org/wiki/Public_key_infrastructure

[3] http://www.barretlee.com/blog/2015/10/05/how-to-build-a-https-server/

[4] Security with HTTPS and SSL,https://developer.android.com/training/articles/security-ssl.html

[5] 窃听风暴:Android平台https嗅探劫持漏洞,http://www.freebuf.com/articles/terminal/26840.html

[6] Android HTTPS中间人劫持漏洞浅析,https://jaq.alibaba.com/blog.htm?id=60

[7] 浅析HTTPS中间人攻击与证书校验,http://www.evil0x.com/posts/26569.html

[8] https://github.com/menjoo/Android-SSL-Pinning-WebViews

[9] https://github.com/square/okhttp/wiki/HTTPS

[10] https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java

5、Android安全开发系列

目录

Android安全开发之通用签名风险

Android安全开发之ZIP文件目录遍历

Android安全开发之Provider组件安全

Android安全开发之浅谈密钥硬编码

Android安全开发之浅谈网页打开APP

Android应用安全开发之浅谈加密算法的坑

本文转自:http://www.cnblogs.com/alisecurity/p/5939336.html
作者:X_i_a_o_H_a_i 发表于2016/12/19 16:35:25 原文链接
阅读:101 评论:0 查看评论

Android开发-自定义View-AndroidStudio(六)ViewPager再体验

$
0
0
转载请注明出处:http://blog.csdn.net/iwanghang/
绝对博文有用,请点赞,请留言,谢谢!~

直接看GIF效果和代码(相对于ViewPager初体验,添加了当前页面的点点选中变色效果,以及对应文字的设置):
注意一下,point_selector.xml、point_normal.xml、point_press.xml需要放在drawable下面:


MainActivity.java:
package com.iwanghang.viewpager;

import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private ViewPager viewpager;
    private TextView tv_title;
    private LinearLayout ll_point_group;

    // 因为大家都了解ListView,先说一下ListView的使用,然后我们对比着,看一下ViewPager的使用
    // ListView的使用
    // 1、在布局文件中定义ListView
    // 2、在代码中实例化ListView
    // 3、准备数据
    // 4、设置适配器(BaseAdapter)-item布局-绑定数据

    // ArrayList不安全但是效率高
    // 这里是用于准备数据,这个就是图片数据的集合
    private ArrayList<ImageView> imageViews;
    // 图片资源ID集合
    private final int[] imageIds = {R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d,R.drawable.e};
    // 图片标题集合
    private final String[] imageDescriptions = {"元旦好","新年好","大家好","哈哈哈","嘻嘻嘻"};

    int prePosition = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewpager = (ViewPager) findViewById(R.id.viewpager);
        tv_title = (TextView) findViewById(R.id.tv_title);
        ll_point_group = (LinearLayout) findViewById(R.id.ll_point_group);

        // ViewPager的使用
        // 1、在布局文件中定义ViewPager
        // 2、在代码中实例化ViewPager
        // 3、准备数据
        // 4、设置适配器(PagerAdapter)-item布局-绑定数据
        imageViews = new ArrayList<>();
        for (int i = 0; i < imageIds.length;i++) {
            ImageView imageView = new ImageView(this);
            // 设置src会按比例填充,但是设置background会拉伸填充
            // 我们要的效果是拉伸填充,所以这里使用setBackgroundResource
            imageView.setBackgroundResource(imageIds[i]);
            // 添加到集合中
            imageViews.add(imageView);
            // 添加点点
            ImageView point = new ImageView(this);
            point.setBackgroundResource(R.drawable.point_selector);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(8,8);
            if (i==0){
                point.setEnabled(true); // 显示红色
            }else {
                point.setEnabled(false); // 显示灰色
                params.leftMargin = 8;
            }
            point.setLayoutParams(params);
            ll_point_group.addView(point);
        }
        viewpager.setAdapter(new MyPagerAdapter());
        viewpager.addOnPageChangeListener(new MyOnPageChangeListener());
        tv_title.setText(imageDescriptions[0]);
    }

    class MyOnPageChangeListener implements ViewPager.OnPageChangeListener{

        /**
         * 当页面滚动了的时候回调这个方法
         * @param position 当前页面的位置
         * @param positionOffset 滑动页面的百分比
         * @param positionOffsetPixels 在屏幕上滑动的像素
         */
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        }

        /**
         * 当某个页面被选中
         * @param position 被选中的页面的位置
         */
        @Override
        public void onPageSelected(int position) {
            // 设置对应页面的文本信息
            tv_title.setText(imageDescriptions[position]);
            // 设置点点的颜色
            ll_point_group.getChildAt(prePosition).setEnabled(false); // 上一页页面对应点点设置为灰色
            ll_point_group.getChildAt(position).setEnabled(true); // 当前页面对应点点设置为红色
            prePosition = position; // 记录当前点点
        }

        /**
         * 当页面滚动 状态的变化 回调这个方法
         * 静止 -> 滑动
         * 滑动 -> 静止
         * 静止 -> 拖拽
         * @param state
         */
        @Override
        public void onPageScrollStateChanged(int state) {

        }
    }

    class MyPagerAdapter extends PagerAdapter{

        /**
         * @return 图片的总数
         */
        @Override
        public int getCount() {
            return imageViews.size();
        }

        /**
         * 相当于getView方法
         * @param container ViewPager自身
         * @param position 当前实例化页面的位置
         * @return
         */
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView = imageViews.get(position);
            container.addView(imageView); // 把图片添加到ViewPager容器中
            return imageView;
        }

        /**
         * 比较view和object是否是同一个实力
         * @param view 页面
         * @param object instantiateItem返回的结果
         * @return
         */
        @Override
        public boolean isViewFromObject(View view, Object object) {
            if (view == object) {
                return true;
            }else {
                return false;
            }
        }

        /**
         * 释放资源
         * @param container viewpager
         * @param position 要释放的位置
         * @param object 要释放的页面
         */
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView((View) object);
        }

    }
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.iwanghang.viewpager.MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>
    <LinearLayout
        android:background="#44000000"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_alignBottom="@+id/viewpager"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:id="@+id/linearLayout">

        <TextView
            android:id="@+id/tv_title"
            android:padding="2dp"
            android:text="iwanghang"
            android:textStyle="bold"
            android:textColor="#ffffff"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal" />

    </LinearLayout>

    <LinearLayout
        android:id="@+id/ll_point_group"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_above="@+id/linearLayout"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true">


    </LinearLayout>
</RelativeLayout>
point_selector.xml:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="false" android:drawable="@drawable/point_normal"/>
    <item android:state_enabled="true" android:drawable="@drawable/point_press"/>
</selector>
point_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <size android:width="8dp"
        android:height="8dp"/>
    <solid android:color="#44000000"/>
</shape>
point_press.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <size
        android:width="8dp"
        android:height="8dp"/>
    <solid android:color="#ff0000"/>
</shape>



转载请注明出处:http://blog.csdn.net/iwanghang/



欢迎移动开发爱好者交流
沈阳或周边城市公司有意开发Android,请与我联系
联系方式

微信:iwanghang
QQ:413711276
邮箱:iwanghang@qq.com



绝对博文有用,请点赞,请留言,谢谢!~

作者:iwanghang 发表于2016/12/19 16:41:10 原文链接
阅读:102 评论:0 查看评论

写一个垃圾桶开关盖子的动画其实很简单

$
0
0

转载请注明出处:王亟亟的大牛之路

上周5因为要出去玩所以礼拜4基本没干活然后写了个垃圾桶的demo觉得蛮好玩的,准备做的深一点,但是文章还是一篇一篇发,省的有些小伙伴搞不清楚实现

还是先安利,地址如下:https://github.com/ddwhan0123/Useful-Open-Source-Android 最近把Rx和下拉刷新做了细分,方便大家查”裤”

先看下运行效果

这里写图片描述

就是一个垃圾桶然后用户点击按钮之后 盖子盖上去(可以用到项目里那种拖动到垃圾桶删除的效果)


首先讲下实现思路

首先我在https://icomoon.io/app/#/select里找了个垃圾桶的icon然后照着那个样子考虑我该怎么画(这网站还是不错的,不过现在都用iconfont了吧?)

因为 垃圾桶的桶身部分是不需要动画的,所以就先画桶身再画桶盖,桶盖用ValueAnimator解决旋转问题就好了


知识点

要画自定义控件,首先要知道view的绘画流程,具体理论知识就不说了以前说过,这里再点下
要自定义View 你要先实现onMeasure再是onDraw

onMeasure决定尺寸 onDraw决定具体ui展现

如果你是viewgroup的话还需要再实现onLayout来操作子视图间的位置关系


安卓的坐标系,跟我们数学的数轴不太一样,数轴有4个象限,而安卓只有一个。

屏幕左上角为(0,0)点,那右下角必然是最大点了。

越向右 x越大,越向下y越大。

ok,简单的道理讲完了,就开始贴代码看吧


代码实现

public class GabageCan extends View {

    private Paint paint;
    private Path path;
    private int viewWidth, viewHeight, picWidth, picHeight;
    private Float canBAnimProgress;
    private ValueAnimator animator;


    public GabageCan(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public GabageCan(Context context) {
        super(context);
        init();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //设置画笔
        paint.setStrokeWidth(5f);
//        canvas.drawColor(Color.WHITE);
        //创建下半部分的路径和三条线
        createCanBPath();
        //画路径和线
        canvas.drawPath(path, paint);
        //动画判断是否刷新视图
        if (animator != null && animator.isRunning()) {
            //动画执行过程中具体帧值
            canBAnimProgress = (Float) animator.getAnimatedValue();
            drawCan(0, canvas);
            drawCan(1, canvas);
            drawCan(2, canvas);
            invalidate();
        } else if (animator != null && !animator.isRunning()) {
            drawCan(1, canvas);
            drawCan(2, canvas);
        }
    }

    public void startAnimator() {
        animator = ValueAnimator.ofFloat(1f, 0f);
        animator.setDuration(2000);
        animator.start();
        invalidate();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);

        if (width < height) {
            height = width;
        } else {
            width = height;
        }

        setMeasuredDimension(width, height);
        Log.d("--->", "width " + width + " height " + height);
        viewWidth = width > 0 ? width : 0;
        viewHeight = height > 0 ? height : 0;
        if (viewWidth > 0) {
            picWidth = viewWidth / 3;
        }
        if (viewHeight > 0) {
            picHeight = viewHeight / 3;
        }
    }
    //初始化画笔
    private void init() {
        paint = new Paint();
        path = new Path();
        paint.setStyle(Paint.Style.STROKE);
    }

    private void drawCan(int type, Canvas canvas) {
        switch (type) {
            case 0:
                canvas.rotate(canBAnimProgress * 30, viewWidth / 2 + (picHeight / 2), viewHeight / 2 - (picWidth / 2));
                break;
            case 1:
                canvas.drawLine(viewWidth / 2 - (picWidth / 2) - 20, viewHeight / 2 - (picWidth / 2) - (picHeight / 8),
                        viewWidth / 2 + (picHeight / 2) + 20, viewHeight / 2 - (picWidth / 2) - (picHeight / 8), paint);
                break;
            case 2:
                canvas.drawRect(viewWidth / 2 - (picWidth / 9), viewHeight / 2 - (picWidth / 2) - (picHeight / 4), viewWidth / 2 + (picHeight / 9),
                        viewHeight / 2 - (picHeight / 2) - (picHeight / 8), paint);
                break;
            case 3:
                break;
        }
    }


    private void createCanBPath() {
        if (path == null) {
            path = new Path();
        }
        //画轮廓
        path.moveTo(viewWidth / 2 - (picWidth / 2), viewHeight / 2 - (picWidth / 2));
        path.lineTo(viewWidth / 2 - (picWidth / 3), viewHeight / 2 + (picHeight / 2));
        path.lineTo(viewWidth / 2 + (picHeight / 3), viewHeight / 2 + (picHeight / 2));
        path.lineTo(viewWidth / 2 + (picHeight / 3), viewHeight / 2 + (picHeight / 2));
        path.lineTo(viewWidth / 2 + (picHeight / 2), viewHeight / 2 - (picWidth / 2));
        //画里面的竖线
        path.moveTo(viewWidth / 2 - (picWidth / 5), viewHeight / 2 - (picWidth / 3));
        path.lineTo(viewWidth / 2 - (picWidth / 5), viewHeight / 2 + (picHeight / 3));
        path.moveTo(viewWidth / 2 + (picWidth / 5), viewHeight / 2 - (picWidth / 3));
        path.lineTo(viewWidth / 2 + (picWidth / 5), viewHeight / 2 + (picHeight / 3));
        path.moveTo(viewWidth / 2, viewHeight / 2 - (picWidth / 3));
        path.lineTo(viewWidth / 2, viewHeight / 2 + (picHeight / 3));
    }


    private int getMySize(int defaultSize, int measureSpec) {
        int mySize = defaultSize;

        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
                //我们将大小取最大值,你也可以取其他值
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
                mySize = size;
                break;
            }
        }
        return mySize;
    }
}

整体实现不难,只是提供个思路吧,写着玩,哈哈哈哈

源码地址:https://github.com/ddwhan0123/BlogSample/blob/master/GabageCan.class

之后可能写个拽入垃圾桶的动画,到时候再说吧。。。

作者:ddwhan0123 发表于2016/12/19 17:57:04 原文链接
阅读:297 评论:1 查看评论

HDP学习--HDFS Storage(上)

$
0
0

学习HDFS

1、了解分布式文件系统(HDFS)的属性
2、了解HDFS的访问方法
3、知道所有者及组用户的角色, 以及文件和目录的访问权限
4、使用HDFS命令或Ambari Files View 管理文件和目录
5、使用REST API 访问HDFS
6、使用 HDFS Access Control Lists (ACLs)保护数据

一、Hadoop and Storage

   Hadoop是可扩展, 为不同的文件系统所设计,用户或程序可以通过特殊的URI前缀在命令行货代码中访问特殊的文件系统。
例如:hdfs dfs -ls 用来查看目录下的内容
这个命令可以使用不同的前缀来访问不同的文件系统:
1、显示本地文件系统的/bin的内容:

hdfs dfs –ls file:///bin 

2、显示HDFS的/root的内容:

hdfs dfs –ls hdfs:///root 

3、默认存储类型是HDFS,由core-site.xml中的fs.defaultFS的属性决定:

  Set in core-site.xml
  fs.defaultFS=hdfs://<NameNode_host>:8020
  HDFS客户端尝试使用RPCl连接HDFS的NameNode在8020端口,除非另有明确说明。

注意: 即使默认存储文件系统指定为HDFS,

hdfs dfs -ls file:///bin 
仍然是显示本地文件系统的/bin的内容

   这里写图片描述

二、Accessing HDFS

Hadoop提供许多方法来访问HDFS,包括: HDFS Shell, WebHDFS, the HDFS NFS Gateway, the Java API, the Ambari Files View, and HUE (Hadoop User Experience).

这里写图片描述

“`

三、 NameNode and DataNode Introduction

HDFS服务中有两种组件:
NameNode(主)—> Master node
DataNode(从)—> Worker node(slave node)
NameNode管理System namespace 和元信息:
 文件和目录名称;
 文件系统的层次
 权限和所有者
 最后一次修改时间
 access control lists(ACLs)
DataNode只包含数据块的信息:
   默认情况下,一个块(data block) 最小是128M, 每一个block都分配一个唯一的ID,映射到文件名, 维护在NameNode , 只有NameNode知道那个block对应那个文件。
下图是NameNode和DataNode的结构图:
这里写图片描述

四、 File and Directory Attributes

  HDFS中的文件和目录的属性和Linux 基本一致,不做过多介绍。使用 hdfs fs -ls 命令来查看一个目录下的内容,及文件文件、目录的属性, 如下图:
这里写图片描述

五、HDFS Home Directories

  Home Directories 是用与调整HDFS的权限来控制数据访问, 一个HDFS Administrator可以创建一个home directories供所有用户来会用,但是一些应用, 例如Hive也可以创建home directories 。
  如下图Saad对它的主目录有read-write访问权限, 这意味只有HDFS superuser and Saad 在它的主目录创建文件和子目录。
  一旦Saad有它自己的主目录, 它可以创建文件和子目录,设置不同的权限, 来控制其他用户的访问。
这里写图片描述

六、HDFS Superuser

  许多Hadoop服务都有超级用用户的概念, 和Linux中的root, Windows中的Adminsrator类似, 是Hadoop为特殊服务给予特殊的权利,常见的超级用户时hdfs,能够执行任何的HDFS操作。
HDFS超级用户账户由hdfs-site.xml中的dfs.cluster.administrators属性决定,
使用su - hdfs 获取HDFS超级用户的特权
exit– 退出超级用户权限。
这里写图片描述

七、HDFS Shell Operations

7.1、帮助:

这里写图片描述

7.2、 目录创建及查看:

这里写图片描述

7.3、主目录和当前目录:

这里写图片描述

7.4 、 创建文件:

这里写图片描述

7.5、查看文件内容:

这里写图片描述

7.6 、文件拷贝和移动:

这里写图片描述

7.7 、 文件及目录的删除:

这里写图片描述

作者:wuxintdrh 发表于2016/12/19 18:05:21 原文链接
阅读:42 评论:0 查看评论

IOS 之 UIPickerView 学习总结

$
0
0

1. UIPickerView 什么时候使用?

通常在注册模块,当用户需要选择一些东西的时候使用,比如说城市,往往弹出一个 PickerView 给用户选择。

2. UIPickerView 常见用法

(1)独立的,没有任何关系 —>菜单系统;
(2)相关联的,下一列和第一了有联系 —>省会城市选择;
(3)图文并茂,—>国旗选择。

3. UIPickerView 数据源方法和代理方法简述

首先进入 UIPickerView 控件描述,发现其有 dataSource 和 delegate 。
这里写图片描述

为了监听控件发生的行为,我们需要对控件设置代理,而UIPickerView不仅要设置代理,也要设置数据源。是因为之后要通过数据来确定 UIPickerView 有多少行,多少列。

代理,其实就是众多方法声明的集合,那么这么多的方法。设置完代理,还需遵守代理协议,实现代理方法。

这里写图片描述

实现协议方法

数据源协议中有必须要实现的两个方法,需要去实现,不然会报错。代理协议中没有必须要实现的方法,有一些可选的实现方法。

两个必须实现的方法简述:

// returns the number of 'columns' to display.
// 返回有多少列(即,有几个picker)
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 3;
}
// returns the # of rows in each component..
// 返回第component列有多少行
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    return 5;
}

代理协议中的可选方法简述:

可选方法较多,这里不一一解释,下面对其中几个比较重要的方法进行说明。

// 返回第 component 列第 row 行的标题
- (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    if (component == 1 && row == 1) {
        return @"BCD";
    }
    return @"ABC";
}
// NSAttributedString:给文本添加一些属性,富文本,设置文本颜色,字体,空心,阴影,图文混排等
- (nullable NSAttributedString *)pickerView:(UIPickerView *)pickerView attributedTitleForRow:(NSInteger)row forComponent:(NSInteger)component
{

}
// 返回第 component 列第 row 行的控件
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(nullable UIView *)view
{

}
// 监听 UIPickerView 选中哪行哪列,只要一滑动UIPickerView,就会调用这个方法;
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    NSLog(@"%ld %ld",component,row);
}

到此,我们可以加载数据了,之前说过,数据源协议里面的方法需要用到数据来实现,所以接下来我们开始导入数据。

用懒加载的方法加载数据,这样可以避免重复导入,提高效率,如果这个数组中没有值,就进行赋值;如果里面有值,就直接返回这个数组。

加载 .plist 文件,来进行数据的导入:
这里写图片描述
注意: 这里带下划线的变量是声明的 property 后自动生成的实例变量,不包含 set 和 get 方法,也可以理解成是一种编程习惯。

将数据导入之后,改变一些方法内容,写成通用形式。

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return self.foods.count;
}

// returns the # of rows in each component..
// 返回第component列有多少行
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    NSArray *arr = self.foods[component];

    return arr.count;
}

// 返回第 component 列第 row 行的标题
- (nullable NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    NSArray *arr = self.foods[component];

    return arr[row];
}

// 监听 UIPickerView 选中
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    NSString *title = self.foods[component][row];

    self.label.text = title;
}

self.xxx 与 属性用下划线的区别:

(1)通过 self.xxx 访问的方法的引用,包含了set和get方法。而通过属性下划线是获取自己的实例变量,不包含set和get的方法;

(2)self.xxx 是对属性的访问;而属性 _xxx 是对局部变量的访问,所有被声明为属性的成员;

(3)使用 self.xxx 是更好的选择,因为这样可以兼容懒加载,同时也避免了使用下滑线的时候忽略了 self 这个指针,后者容易在Block中造成循环引用。同时,使用属性下划线是获取不到父类的属性的,因为它只是对局部变量的访问。
因此,self 方法实际上是用了 get 和 set 方法间接调用,属性下划线方法是直接对变量操作。

最后,附一张自定义的 UIPickerView 实例代码和运行结果图:
这里写图片描述

作者:huangfei711 发表于2016/12/19 18:16:29 原文链接
阅读:71 评论:0 查看评论

Android 性能优化——TypedArray 调用recycle()回收对象

$
0
0

我们知道,在 Android 自定义 View 的时候,需要使用 TypedArray 来获取 XML layout 中的属性值,使用完之后,需要调用 recycle() 方法将 TypedArray 回收。

当这么用的时候感觉是理所当然,可是仔细一想,TypedArray并没有占用IO,线程,它仅仅是一个变量而已,为什么需要 recycle?让GC自己回收不就好了吗?

官方文档解释

以下是来自TypedArray类的官方文档解释。

Container for an array of values that were retrieved with obtainStyledAttributes(AttributeSet, int[], int, int) or obtainAttributes(AttributeSet, int[]). Be sure to call recycle() when done with them. The indices used to retrieve values from this structure correspond to the positions of the attributes given to obtainStyledAttributes.

以下是对recycle()的方法描述。

 /**
     * Recycles the TypedArray, to be re-used by a later caller. After calling
     * this function you must not ever touch the typed array again.
     *
     * @throws RuntimeException if the TypedArray has already been recycled.
     */
    public void recycle() {
        if (mRecycled) {
            throw new RuntimeException(toString() + " recycled twice!");
        }

        mRecycled = true;

        // These may have been set by the client.
        mXml = null;
        mTheme = null;

        mResources.mTypedArrayPool.release(this);
    }

简单翻译下来,就是说:TypedArray是一个存储属性值的数组,使用完之后应该调用recycle()回收,用于后续调用时可复用之。当调用该方法后,不能再操作该变量。至于深层次的原因,文档没说。这就需要我们自己去研究源码了。

使用方法

首先,是 TypedArray 的常规使用方法:

TypedArray array = context.getTheme().obtainStyledAttributes(attrs,
                R.styleable.PieChart,0,0);
try {
    mShowText = array.getBoolean(R.styleable.PieChart_showText,false);
    mTextPos = array.getInteger(R.styleable.PieChart_labelPosition,0);
}finally {
    array.recycle();
}

可见,TypedArray不是我们new出来的,而是调用了 obtainStyledAttributes 方法得到的对象,该方法实现如下:

public TypedArray obtainStyledAttributes(AttributeSet set,
                int[] attrs, int defStyleAttr, int defStyleRes) {
    final int len = attrs.length;
    final TypedArray array = TypedArray.obtain(Resources.this, len);
    // other code .....
    return array;
}

从上面的代码片段得知,TypedArray也不是它实例化的,而是调用了TypedArray的一个静态方法,得到一个实例,再做一些处理,最后返回这个实例。


public class TypedArray {

    static TypedArray obtain(Resources res, int len) {
        final TypedArray attrs = res.mTypedArrayPool.acquire();
        if (attrs != null) {
            attrs.mLength = len;
            attrs.mRecycled = false;

            final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
            if (attrs.mData.length >= fullLen) {
                return attrs;
            }

            attrs.mData = new int[fullLen];
            attrs.mIndices = new int[1 + len];
            return attrs;
        }

        return new TypedArray(res,
                new int[len*AssetManager.STYLE_NUM_ENTRIES],
                new int[1+len], len);
    }
    // Other members ......
}

仔细看一下这个方法的实现,该类没有公共的构造函数,只提供静态方法获取实例。在代码片段的第 5 行,很清晰的表达了这个 array 是从一个 array pool的池中获取的。继续追踪,可以看到这是来自Resources的一个成员变量。

 // Pool of TypedArrays targeted to this Resources object.
    final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);

从名字上可以看出这是一个同步对象池,数量为5.

具体实现类在 android.support.v4包中。

package android.support.v4.util;


public final class Pools {

    //管理对象池的一个接口
    public static interface Pool<T> {

       //从对象池中取出一个对象
        public T acquire();

      //释放一个对象并重新进入线程池
        public boolean release(T instance);
    }
    //私有构造函数
    private Pools() {
        /* do nothing - hiding constructor */
    }

    //一个简单的非同步对象池实现了对象池管理接口
    public static class SimplePool<T> implements Pool<T> {
        private final Object[] mPool;

        private int mPoolSize;

       //创建一个大小为maxPoolSize的对象池实例
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mPool = new Object[maxPoolSize];
        }
    //从对象池中取得一个对象,出栈操作
        @Override
        @SuppressWarnings("unchecked")
        public T acquire() {
            if (mPoolSize > 0) {
                final int lastPooledIndex = mPoolSize - 1;
                T instance = (T) mPool[lastPooledIndex];
                mPool[lastPooledIndex] = null;
                mPoolSize--;
                return instance;
            }
            return null;
        }
       //释放对象,相当于入栈操作
        @Override
        public boolean release(T instance) {
            if (isInPool(instance)) {
                throw new IllegalStateException("Already in the pool!");
            }
            if (mPoolSize < mPool.length) {
                mPool[mPoolSize] = instance;
                mPoolSize++;
                return true;
            }
            return false;
        }
    //判断一个对象实例是否在对象池中
        private boolean isInPool(T instance) {
            for (int i = 0; i < mPoolSize; i++) {
                if (mPool[i] == instance) {
                    return true;
                }
            }
            return false;
        }
    }

      //利用对象锁为SimplePool加了同步访问,使之线程安全
    public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock = new Object();


        public SynchronizedPool(int maxPoolSize) {
            super(maxPoolSize);
        }

        @Override
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }

        @Override
        public boolean release(T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
    }
}

从Pool的实现当中可以看到,Pool是一个final类,本身不可变,不允许继承。而内部类SynchronizedPool通过继承SimplePool,并添加对象锁,实现了一个同步的对象池,保证了线程安全,同时也继承了对象池的优点,避免对象重复创建和销毁,减少了系统开销。

结论

  从上述源码可以看到,framework层维护了一个同步栈结构的对象池,从而避免在程序运行期间频繁创建属性值TypedArray对象,维护TypedArray对象池的大小默认为5,使用时记得调用recyle()方法将不用的对象返回至对象池来达到重用的目的。

更确切来讲,TypedArray的使用场景之一,就是上述的自定义View,会随着 Activity的每一次Create而Create,因此,需要系统频繁的创建array,对内存和性能是一个不小的开销,如果不使用池模式,每次都让GC来回收,很可能就会造成OutOfMemory。不得不让人Google程序猿大神们的构思和精妙的设计,也为我们平常编写程序优化性能提供了依据和参考。

作者:ylyg050518 发表于2016/12/19 19:18:48 原文链接
阅读:92 评论:0 查看评论

HTML5—浏览器支持问题

$
0
0

最新版本的 Safari、Chrome、Firefox 以及 Opera 支持某些 HTML5 特性。Internet Explorer 9 将支持某些 HTML5 特性。

为了能让旧版本的浏览器正确显示这些元素,可以设置 CSS 的 display 属性值为 block:

header, section, footer, aside, nav, main, article, figure {
    display: block; 
}

对于ie浏览器,为了能让ie9以下的浏览器兼容html5元素,可以使用shiv解决方案:

<!--[if lt IE 9]>
  <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
国内用户请使用runboob静态资源库(Google 资源库在国内不稳定):

<!--[if lt IE 9]>
  <script src="http://cdn.static.runoob.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
<![endif]-->

html5shiv.js 引用代码必须放在 <head> 元素中,因为 IE 浏览器在解析 HTML5 新元素时需要先加载该文件。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>渲染 HTML5</title>
  <!--[if lt IE 9]>
  <script src="http://cdn.static.runoob.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
  <![endif]-->
</head>

 本文参考资料来自:http://www.runoob.com/html/html5-browsers.html

作者:nongweiyilady 发表于2016/12/19 19:56:53 原文链接
阅读:62 评论:0 查看评论

Codeforces Round #384 (Div. 2)D. Chloe and pleasant prizes(树DP)

$
0
0

题目链接:点击打开链接

思路:

比较简单的树DP, 用dp[u][id]表示当前以u为根的子树还已经找到几个子树的最大值。  转移比较多, 一方面可以转移到某一个儿子, 表示问题在以后解决, 一方面如果id==1说明还要找1个子树,可以直接用val[u]更新, val[u]表示该子树的和。   如果id == 0说明还要找两个子树, 我们用两个最大的儿子值更新即可。

细节参见代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <ctime>
#include <bitset>
#include <cstdlib>
#include <cmath>
#include <set>
#include <list>
#include <deque>
#include <map>
#include <queue>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
typedef long double ld;
const double eps = 1e-6;
const double PI = acos(-1);
const int mod = 1000000000 + 7;
const int seed = 131;
const ll INF = ll(1e16);
const int maxn = 2e5 + 10;
int n, vis[maxn][3], kase = 0, par[maxn];
ll a[maxn], d[maxn][3], val[maxn];
vector<int> g[maxn];
ll dfs(int u, int fa) {
    int len = g[u].size();
    val[u] = a[u];
    par[u] = fa;
    for(int i = 0; i < len; i++) {
        int v = g[u][i];
        if(v == fa) continue;
        val[u] += dfs(v, u);
    }
    return val[u];
}
ll dp(int u, int id) {
    ll& ans = d[u][id];
    if(vis[u][id] == kase) return ans;
    vis[u][id] = kase;
    ans = -INF;
    int len = g[u].size();
    vector<ll> cur;
    for(int i = 0; i < len; i++) {
        int v = g[u][i];
        if(v == par[u]) continue;
        ll di = dp(v, id);
        if(di > -INF) ans = max(ans, di);
        if(id == 0) cur.push_back(dp(v, id+1));
    }
    sort(cur.begin(), cur.end());
    len = cur.size();
    if(id == 1) ans = max(ans, val[u]);
    if(id == 0 && len > 1) ans = max(ans, cur[len-1]+cur[len-2]);
    return ans;
}
int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%I64d", &a[i]);
    for(int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    ++kase;
    dfs(1, 1);
    ll ans = dp(1, 0);
    if(ans == -INF) printf("Impossible\n");
    else printf("%I64d\n", ans);
    return 0;
}


作者:weizhuwyzc000 发表于2016/12/19 20:20:09 原文链接
阅读:56 评论:0 查看评论

HDP学习--HDFS Storage(中)

$
0
0

承接HDP学习–HDFS Storage(上)

八、 HDFS Trash

Trash相当于回收站,暂时的将删除的文件和目录移动到/.Trash/Current, 当文件被其他用户删除, 方便恢复。删除的文件保存在Trash directory
例如:
删除 /user/steve/dir1/fileA
重建在Trash directory:

/user/steve/.Trash/Current/user/steve/dir1/fileA

但也是有限制:
 如果你使用 HDFS Shell or the Ambari Files View删除文件, 是会保护的;
 如果使用Java API, WebHDFS, the HDFS NFS Gateway, or HUE删除的文件,是不被保护的。
Ttrash有两个属性决定:

Set In core-default.xml 
property:
    fs.trash.checkpoint.interval
    Determines how often the NameNode should checkpoint the .Trash directory.
    0 means use the value set in fs.trash.interval

Set In core-site.xml
property:
    fs.trash.interval
    Determines how often checkpoints in the .Trash directory should be removed. 
    A value of 0 disables trash. 
    The HDP default value is 360 minutes. 

HDFS Shell -rm 命令包含一个 参数:

   -skip Trash  相当于Windows中的永久删除, 步移动回收站

九、 HDFS Trash Operation

下图是Trash的流程:
这里写图片描述
解释:
The fs.trash.checkpoint.interval determines the number of minutes between trash checkpoints. If zero, the value is set to the value of fs.trash.interval. Zero is the HDP default. The number for fs.trash.checkpoint.interval should be smaller than or equal to fs.trash.interval.

Every time the checkpointer runs, it renames the .Trash/Current directory to a new numeric name. For example, .Trash/Current could be renamed to .Trash/150518175000. When new files or directories are deleted, HDFS creates a new .Trash/Current directory to hold them.

How long the older and now renamed checkpoint directory—with its deleted files and directories—is retained is determined by the fs.trash.interval property in core-site.xml. It determines the number of minutes after which the checkpoint directory gets deleted. If zero, the trash feature is disabled. The HDP default is 360 minutes. It is important to note that it is not the individual files and directories that are older that the fs.trash.interval that are deleted, but it is the checkpoint directory that is older than the fs.trash.interval that is deleted.

The fs.trash.interval may be configured both on the server and the client. If trash is disabled on the server side then the client side configuration is checked. If trash is enabled on the server side then the value configured on the server is used and the client configuration value is ignored.

十、 Overriding HDFS Default Properties

这里写图片描述

十一、 Changing File and Directory Ownership

这里写图片描述

十二、Changing File and Directory Permissions

这里写图片描述

作者:wuxintdrh 发表于2016/12/19 20:34:11 原文链接
阅读:26 评论:0 查看评论

HDP学习--HDFS Stroge(下)

$
0
0

承接HDP–HDFS Stroge(中)

一、Ambari Files View 中HDFS Storage操作

1、文件及目录操作

这里写图片描述

2、 配置文件的设置

这里写图片描述

3、File View

3.1、Creating a View Instance
创建View Instance 供其他用户使用。

这里写图片描述

3.2 、 Filling Out the Create Instance Form

这里写图片描述

3.3、Adding Permissions to Use the View Instance

这里写图片描述

二、使用浏览器查看Name UI

2.1、访问Name UI

这里写图片描述

2.2、查看内容

这里写图片描述

2.3、文件信息及下载

这里写图片描述

三、Knowledge Check

这里写图片描述

四、总结

这里写图片描述

作者:wuxintdrh 发表于2016/12/19 20:37:36 原文链接
阅读:28 评论:0 查看评论

一起talk C栗子吧(第一百九十四回:C语言实例--DIY less命令三 )

$
0
0

各位看官们,大家好,上一回中咱们说的是DIY less命令的例子,这一回咱们继续说该例子。闲话休提,言归正转。让我们一起talk C栗子吧!


看官们,我们在上一回中介绍了响应用户命令的整体框架,这回我们详细介绍响应用户命令的具体内容。因为我们只响应了q,j,k三个命令,所以我们分别介绍如何响应这三个命令:

1.响应q命令:

我们在接受到用户输入该命令后会使用break跳出读取文件内容的while循环。然后关闭打开的文件并且结束less命令,回到我们DIY的shell中。这里的核心代码就是break语句,为了更加友好一些,我们还输出了一个提示,告诉用户less命令已经退出了,请大家参考以下代码:

            if(cmd == 'q')
            {
                current_line = 0;
                printf("Exit less cmd. \n");

                break;
            }

2.响应j命令:

我们在接受到用户输入该命令后,会向当前终端中输出文件中下一行的内容,屏幕会向下滚动一行,然后继续回到while循环中读取文件中下一行的内容。在输出内容的时候,我们修改了两个变量的值。一个是用来统计当前行数的变量:current_line;另外一个是统计j命令运行的次数:back_countcurrent_line变量在显示“一个屏幕大小的内容”时使用,back_count是留给k命令使用的,看官莫急,我们稍后就会介绍他。下面是程序的主要代码,请大家参考:

            else if(cmd == 'j')
            {
                printf("line: %-4d:",current_line);
                printf("%s",buf);

                back_count += 1; // add the count of j cmd
                current_line += 1;
            }

3.响应k命令:

我们在接受到用户输入该命令后,会向当前终端中显示文件中前一行的内容,屏幕会向上滚动一行。

大家都知道,我们操作文件流的时候文件流会自动向前移动,因此输出文件下一行的内容比较容易。这时我们想输出文件中前一行的内容,相当于文件流向后退,目前还没有这样的功能。就像水往低处流是一种自然现象现象一样,想要让水往高处流怎么办?那就需要使用水泵。那么让文件流回退是不是可以使用文件流泵呢?这位看官可真会想呀,文件流泵都能想出来,哈哈。虽然文件流不能回退,但是我们可以想办法让它达到回退的效果。至于使用什么办法呢?我们暂且不表,下一回中再给大家详细介绍。

各位看官,关于DIY less命令的例子咱们就说到这里。欲知后面还有什么例子,且听下回分解 。


作者:talk_8 发表于2016/12/19 20:51:09 原文链接
阅读:50 评论:0 查看评论

全排列问题算法及实现(Permutation)

$
0
0

前言

做项目遇到数据采集系统中ADC拼合问题,如果顺序不对,波形就是错误的(题外话),为了找到正确的顺序,涉及到排列问题。

什么是排列组合

定义

一般地,从n个不同元素中取出m(m≤n)个元素,按照一定的顺序排成一列,叫做从n个元素中取出m个元素的一个排列(Arrangement)。特别地,当m=n时,这个排列被称作全排列(Permutation)。 
Ex:考虑三个数字1,2,3,这个序列有6个可能的排列组合 
123 
132 
213 
231 
312 
321 
这些排列组合根据less-than操作符做字典顺序的排序。 
字典顺序顾名思义是就是将1-n的一个排列看成一个数,然后按照字典的顺序从小到达的输出

全排列及序号

所谓的全排列,就是说将数字进行不重复的排列,所有得到的序列,就是全排列 
给定数字1 , 2 , 3 , 4,其全排列是: 
{1,2,3,4}, {1,2,4,3}, {1,3,2,4}, {1,3,4,2}, {1,4,2,3}, {1,4,3,2}

{2,1,3,4}, {2,1,4,3}, {2,3,1,4}, {2,3,4,1}, {2,4,1,3}, {2,4,3,1}

{3,1,2,4}, {3,1,4,2}, {3,2,1,4}, {3,2,4,1}, {3,4,1,2}, {3,4,2,1}

{4,1,2,3}, {4,1,3,2}, {4,2,1,3}, {4,2,3,1}, {4,3,1,2}, {4,3,2,1} 
全排列如上所示,那么什么是全排列的序号?这里我们通常将全排列按照字典序进行编排,就如上面从左到右看,就是按照字典序排列的。 
我们说,对于1,2,3,4的全排列,第20号序列是{4,1,3,2},因为其在这个按照字典序排列的全排列中处在第20的位置。

排列组合涉及的问题

  • 下一个全排列
  • 上一个全排列
  • 给定一个排列的序号以及排列中数字的个数,那么这个排列是什么
  • 给定一个排列,求这个排列的序号是多少

下一个全排列(next_permutation)

在STL中,有next_permutation的算法实现。 
next_permutation()会取得[first,last) 所标之序列的下一个排列组合。如果没有下一个排列组合,便返回false,否则返回true。 
算法: 
首先,从最尾端开始往前寻找两个相邻元素,令第一个元素为*i,第二个元素为*ii,且满足*i < *ii。找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个大于*i 的元素,令为*j ,将i,j元素对调,再将ii之后的所有元素颠倒排序。 
如下图:方框为i和ii 
 
Code:

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        vector<int>::iterator first=nums.begin();
        vector<int>::iterator last=nums.end();
        if(first==last)      //empty
            return;
        vector<int>::iterator i=first;
        i++;
        if(i==last)    //only one element
            return;
        i=last;        //i指向尾端
        i--;           
        for(;;)
        {
            vector<int>::iterator ii=i;
            --i;   //锁定一组(两个)相邻元素
            if(*i<*ii)   //如果前一个元素小于后一个元素
            {
                vector<int>::iterator j=last;    //j指向尾端
                while(!(*i < *--j));             //从尾端往前找,直到比*i大的元素
                iter_swap(i,j);
                reverse(ii,last);
                return;
            }
            if(i==first)
            {
                reverse(first,last);
                return;
            }
        }
    }
};

上一个全排列(prev_permutation)

首先,从最尾端开始往前寻找两个相邻元素,令第一个元素为*i,第二个元素为*ii,且满足*i > *ii。找到这样一组相邻元素后,再从最尾端开始往前检验,找出第一个小于*i 的元素,令为*j ,将i,j元素对调,再将ii之后的所有元素颠倒排序。 
代码和next_permutation类似

已知排列求序号

康托展开式实现了由1到n组成的全排列序列到其编号之间的一种映射 
公式: 
X=an*(n-1)!+an-1*(n-2)!+…+ai*(i-1)!+…+a2*1!+a1*0!

由1到n这n个数组成的全排列,共n!个,按每个全排列组成的数从小到大进行排列,并对每个序列进行编号(从0开始),并记为X;比如说1到4组成的全排列中,1234对应编号0,1243对应编号1。

对于ai(系数)的解释需要用例子来说明:

对1到4的全排列中,我们来考察3214,则

  1. a4={3在集合(3,2,1,4)中是第几大的元素,有多少个逆序对}=2

  2. a3={2在集合(2,1,4)中是第几大的元素,有多少个逆序对}=1

  3. a2={1在集合(1,4)中是第几大的元素,有多少个逆序对}=0

  4. a1=0(最后只剩下一项)

也就是说康托公式中的每一项依次对应全排列序列中的每个元素,并按上述规则映射;

则X=2*3!+1*2!+0*1!+0*0!=14,即3214对应的编号为14。

Code:

//已知排列求序号
long long getIndex(short dim, short *rankBuf)
{
    int i, j;
    long long index = 0;
    long long k = 1;
    for (i = dim - 2; i >= 0; k *= dim - (i--))//每一轮后k=(n-i)!,注意下标从0开始
        for (j = i + 1; j<dim; j++)
            if (rankBuf[j]<rankBuf[i])
                index += k;//是否有逆序,如有,统计,即计算系数
    return index;
}

已知序号求排列

康托公式可以根据排列的序号来求出该排列,即通过X的值求出ai(i大于等于1小于等于n)的值,运用辗转相除的方法即可实现,现在已知一个编号14(注意该编号是从0开始的,如果是从1开始,此处要减1),求其对应的全排列序列: 
14 / (3!) = 2 余 2 
2 / (2!) = 1 余 0 
0 / (1!) = 0 余 0 
0 / (0!) = 0 余 0 
故得到:a4=2,a3=1,a2=0,a1=0,由ai的定义即可确定14对应的全排列为3214。 
Code:

//已知序号求排列
void getPermutation(int dim, short *rankBuf, long long index){
    short i, j; 
    //求逆序数数组
    for (i = dim - 1; i >= 0; i--)
    {
        rankBuf[i] = index % (dim - i);
        index /= dim - i;
    }
    for (i = dim - 1; i; i--)
        for (j = i - 1; j >= 0; j--)
            if (rankBuf[j] <= rankBuf[i])
                rankBuf[i]++;       //根据逆序数数组进行调整   
}
作者:u011391629 发表于2016/12/19 21:23:00 原文链接
阅读:33 评论:0 查看评论

JuheNews For aNdroid (改进版)

$
0
0

简介

上一篇介绍的是最开始自己制作的一个采用聚合数据免费接口制作的一个头条类新闻应用,最近对其在界面上做了很大的改动,结合之前介绍的TabHost作为底部仿微信菜单,然后新闻内容丰富到10种分类,搜索功能放在ToolBar上,整体效果个人感觉还是比较OK。

代码开源:
https://github.com/onlyloveyd/JuheNews

觉得不错的话,给我一个小红星吧, 有意见的话可以在博文下留言,会及时改正

使用到的开源内容

主要用到的开源库有以下一些

    compile 'com.squareup.okhttp3:okhttp:3.5.0'
    compile 'com.android.support:recyclerview-v7:25.0.1'
    compile 'com.google.code.gson:gson:2.8.0'
    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'com.android.support:design:25.0.1'
    compile 'com.github.bumptech.glide:glide:3.7.0'
    compile 'com.android.support:cardview-v7:25.0.1'
    compile 'com.jakewharton:butterknife:8.4.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
    compile 'com.astuetz:pagerslidingtabstrip:1.0.1'

主要涉及到网络通信,响应式编程,注解,RecyclerView,CardView和PageIndicator等方面,站在巨人的肩膀上,减低了不少开发的难度


使用到的聚合数据接口

  • 新闻头条
    主要分为头条,社会,国内,娱乐,体育,军事,科技,财经,时尚等新闻信息

  • 新闻
    这个和上面的新闻头条不太一样,这个是用来搜索热点信息的

  • 笑话大全

  • 趣图
    趣图和上面的笑话大全是一个接口,只是请求数据的内容不同而已

  • 历史上的今天

具体的使用方式和接口介绍,有兴趣的朋友可以去聚合数据官网上去了解,这里不做赘述。


具体效果

这里写图片描述

这里写图片描述


由于模拟器上没有一些中文输入法,为了方便引用下Github上的Gif

这里写图片描述

这里写图片描述

备注

具体代码内容请移步Github,谢谢!
一直想借这个机会写一个万能的RecyclerView.Adapter,但是目前还没完成,后面接着看下怎么写比较合适。

作者:poorkick 发表于2016/12/19 21:24:55 原文链接
阅读:33 评论:0 查看评论

HDP学习--Using WebHDFS

$
0
0

一、 Java Native API VS WebHDFS Access

这里写图片描述

二、 WebHDFS 特点

这里写图片描述

三、WebHDFS的启用

WebHDFS默认是启用的, 可以在hdfs-site.xml或Ambari中设置
这里写图片描述

四、 WebHDFS的操作

WebHDFS中包含许多内嵌的操作, 如下表,这些操作配合适当的URIs,就能访问和管理HDFS file
这里写图片描述

4.1 Examples

这里写图片描述

这里写图片描述

五、WebHDFS and HttpFS

这里写图片描述

六、Knowledge Check

这里写图片描述

七、总结

这里写图片描述

作者:wuxintdrh 发表于2016/12/19 21:26:12 原文链接
阅读:21 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


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