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

c++及算法实现文件压缩小项目

$
0
0

背景:为了锻炼自己的代码能力,以及数据结构算法掌握的能力,做此项目来锻炼自己提高自己的能力,本项目运用了C++中的知识,比如模板类,仿函数等等,还用到了数据结构中的算法知识,比如建堆调堆、哈夫曼编码,还用到了文件操作的知识。总是试一次很好的训练。

下面列出代码:

建堆调堆头文件:Heap.h

#ifndef __HEAP_H__
#define __HEAP_H__

#include <iostream>
#include <assert.h>
#include <vector>
#include "FileCompress.h"
#include "HuffmanTree.h"

template <typename T>
struct Less
{
	bool operator()(const T& left, const T& right)const
	{
		return left < right;
	}
};

template<typename K> //模板的偏特化
struct Less<HuffmanNode<K>*>
{
	bool operator()(const HuffmanNode<K>* left, const HuffmanNode<K>* right)const
	{
		return left->_weight < right->_weight;
	}
};

//template <>
//struct Less<CharInfo>
//{
//	bool operator()(const CharInfo& left, const CharInfo& right)const
//	{
//		return left < right;
//	}
//};

template <typename T>
struct Greater
{
	bool operator()(const T& left, const T& right)const
	{
		return left > right;
	}
};

template <typename T,template<class> class Compare = Greater>
class Heap
{
	friend std::ostream& operator<<<T,Compare>(std::ostream& out, const Heap<T,Compare>& heap);
public:
	Heap();
	Heap(const T* array, size_t size);
	Heap(const Heap<T,Compare>& heap);
	Heap<T,Compare>& operator=(const Heap<T, Compare>& heap);
	~Heap();

	void Push(const T& x);
	void Pop();
	T Top()const;
	bool Empty()const;
	size_t Size()const;

protected:
	void _AdjustDown(int parent);
	void _AdjustUp(int pos);

protected:
	std::vector<T> _array;
};

//堆排序
template <typename T, template<class> class Compare = Greater>
void HeapSort(T* array, size_t size, const Compare<T>& com = Compare<T>());
//建初堆
template <typename T, template<class> class Compare = Greater>
void CrtHeap(T* array, size_t size, const Compare<T>& com = Compare<T>());
//下调
template <typename T, template<class> class Compare = Greater>
void AdjustDown(T* array, size_t size, int parent = 0, const Compare<T>& com = Compare<T>());

template <typename T, template<class> class Compare = Greater>
std::ostream& operator<<(std::ostream& out, const Heap<T, Compare>& heap);

template <typename T, template<class> class Compare = Greater>
void GetTopK(T* array, const vector<T>& money, const size_t& k, const size_t& n, const Compare<T>& com = Compare<T>());

#endif /*__HEAP_H__*/


建堆调堆实现文件:Heap.hpp

#define _CRT_SECURE_NO_WARNINGS 1

#include "Heap.h"

template <typename T,template<class> class Compare>
Heap<T,Compare>::Heap()
{}

template <typename T,template<class> class Compare>
Heap<T,Compare>::~Heap()
{}

template <typename T,template<class> class Compare>
Heap<T,Compare>::Heap(const T* array, size_t size)
{
	this->_array.resize(size);
	for (size_t i = 0; i < size; ++i)
	{
		this->_array[i] = array[i];
	}
	for (int i = (size - 2) / 2; i >= 0; --i)
	{
		this->_AdjustDown(i);
	}
}

template <typename T,template<class> class Compare>
Heap<T,Compare>::Heap(const Heap<T,Compare>& heap)
{
	size_t size = heap.size();
	this->_array.resize(size);
	for (size_t i = 0; i < size; ++i)
	{
		this->_array[i] = heap._array[i];
	}
}

template <typename T,template<class> class Compare>
Heap<T,Compare>& Heap<T,Compare>::operator=(const Heap<T,Compare>& heap)
{
	if (this != &heap)
	{
		this->_array = heap._array;
	}
	return *this;
}

template <typename T,template<class> class Compare>
void Heap<T,Compare>::Push(const T& x)
{
	size_t size = this->_array.size();
	this->_array.push_back(x);
	this->_AdjustUp(size);
}

//Compare
template <typename T,template<class> class Compare>
void Heap<T,Compare>::_AdjustUp(int pos)
{
	assert(pos<this->_array.size());
	Compare<T> com;
	int parent = (pos - 1) / 2;
	int child = pos;
	while (parent >= 0 && com(this->_array[child], this->_array[parent]))
	{
		swap(this->_array[child], this->_array[parent]);
		child = parent;
		parent = (child - 1) / 2;
	}
}

template <typename T,template<class> class Compare>
void Heap<T,Compare>::Pop()
{
	assert(!this->_array.empty());
	size_t size = this->_array.size();
	swap(this->_array[0], this->_array[size - 1]);
	this->_array.pop_back();
	this->_AdjustDown(0);
}

//Compare
template <typename T,template<class> class Compare>
void Heap<T,Compare>::_AdjustDown(int parent)
{
	size_t child = parent * 2 + 1;
	size_t size = this->_array.size();
	Compare<T> com;
	while (child < size)
	{
		if (child + 1 < size && com(this->_array[child + 1], this->_array[child]))
		{
			++child;
		}
		if (com(this->_array[child], this->_array[parent]))
		{
			swap(this->_array[parent], this->_array[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

template <typename T,template<class> class Compare>
T Heap<T,Compare>::Top()const
{
	assert(!this->_array.empty());
	return this->_array[0];
}

template <typename T,template<class> class Compare>
bool Heap<T,Compare>::Empty()const
{
	return this->_array.empty();
}

template <typename T,template<class> class Compare>
size_t Heap<T,Compare>::Size()const
{
	return this->_array.size();
}

template <typename T,template<class> class Compare>
std::ostream& operator<<(std::ostream& out, const Heap<T,Compare>& heap)
{
	size_t size = heap._array.size();
	for (size_t i = 0; i < size; ++i)
	{
		out << heap._array[i] << " ";
	}
	out << endl;
	return out;
}

//堆排序
//template <typename T>
//void HeapSort(T* array, size_t size)
//{
//	//建初堆
//	Heap<T,Compare> heap(array, size);
//	for (int i = size - 1; i >= 0; --i)
//	{
//		array[i] = heap.GetTop();
//		heap.Pop();
//	}
//}

template <typename T,template<class> class Compare>
void HeapSort(T* array, size_t size, const Compare<T>& com)
{
	CrtHeap(array, size, com);//建初堆
	for (int i = size - 1; i > 0; --i)
	{
		swap(array[0], array[i]); //交换头和尾
		AdjustDown(array, i, 0, com); //使得0...i-1也为堆
	}
}

//建初堆
template <typename T, template<class> class Compare>
void CrtHeap(T* array, size_t size, const Compare<T>& com)
{
	int parent = (size - 2) / 2;
	for (int i = parent; i >= 0; --i)
	{
		AdjustDown(array, size, i, com);
	}
}
//下调 //Compare
template <typename T,template<class> class Compare>
void AdjustDown(T* array, size_t size, int parent, const Compare<T>& com)
{
	int child = parent * 2 + 1;
	while (child < (int)size)
	{
		if (child + 1 < size && com(array[child + 1], array[child]))
		{
			++child;
		}
		if (com(array[child], array[parent]))
		{
			swap(array[parent], array[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

template <typename T, template<class> class Compare>
void GetTopK(T* array, const vector<T>& money, const size_t& k, const size_t& n, const Compare<T>& com)
{
	assert(array);
	assert(k < n);
	for (size_t i = 0; i < k; ++i)
	{
		array[i] = money[i];
	}
	//建堆
	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		AdjustDown(array, k, i, com);
	}
	for (int i = k; i < n; ++i)
	{
		if (com(array[0],money[i]))
		{
			array[0] = money[i];
			AdjustDown(array, k, 0, com);
		}	
	}
}


建立哈夫曼数头文件:HuffmanTree.h

#pragma once
#ifndef __HUFFMAN_TREE_H__
#define __HUFFMAN_TREE_H__

#include <iostream>

template <typename T>
struct HuffmanNode
{
	T _weight;
	HuffmanNode* _left;
	HuffmanNode* _right;
	HuffmanNode(T weight = 0) :_weight(weight), _left(NULL), _right(NULL)
	{}
	//建堆时要用
	//bool operator<(const HuffmanNode* right)
	//{
	//	return this->_weight < right->_weight;
	//}
};

template <typename T>
class HuffmanTree
{
	typedef HuffmanNode<T> Node;

public:
	HuffmanTree();
	HuffmanTree(const T* array, size_t size, int vailed = 0);
	Node* GetHuffmanNode();
	~HuffmanTree();
protected:
	static Node* _CreateHuffmanTree(const T* array, size_t size, int vailed = 0);
	static void _Clear(Node* root);
protected:
	Node* _root;
};

#endif /*__HUFFMAN_TREE_H__*/

建立哈夫曼数实现文件:HuffmanTree.hpp

#pragma once
#ifndef __HUFFMAN_TREE_HPP__
#define __HUFFMAN_TREE_HPP__

#include "Heap.hpp"
#include "HuffmanTree.h"

template <typename T>
HuffmanTree<T>::HuffmanTree() :_root(NULL)
{}

template <typename T>
HuffmanTree<T>::HuffmanTree(const T* array, size_t size, int vailed) : _root(NULL)
{
	this->_root = _CreateHuffmanTree(array, size, vailed);
}

template <typename T>
HuffmanNode<T>* HuffmanTree<T>::_CreateHuffmanTree(const T* array, size_t size, int valid)
{
	//选最小,减小堆
	Heap<Node*, Less> heap;
	Node* parent = NULL;
	for (size_t i = 0; i < size; ++i)//往堆里入结点
	{
		if (array[i] != valid)
		{
			heap.Push(new Node(array[i]));
		}
	}
	while (heap.Size() > 1)
	{
		Node* minFirst = heap.Top();
		heap.Pop();
		Node* minSecond = heap.Top();
		heap.Pop();
		parent = new Node(minFirst->_weight + minSecond->_weight);
		parent->_left = minFirst;
		parent->_right = minSecond;
		heap.Push(parent);
	}
	return parent;
}

template <typename T>
HuffmanTree<T>::~HuffmanTree()
{
	this->_Clear(this->_root);
}

template <typename T>
void HuffmanTree<T>::_Clear(Node* root)
{
	if (root)
	{
		_Clear(root->_left);
		_Clear(root->_right);
		delete root;
	}
}

template <typename T>
HuffmanNode<T>* HuffmanTree<T>::GetHuffmanNode()
{
	return this->_root;
}

#endif /*__HUFFMAN_TREE_HPP__*/

文件压缩头文件:FileCompress.h

#pragma once 
#ifndef __HUFFMAN_CODE_H__
#define __HUFFMAN_CODE_H__

#include <iostream>
#include <vector>
#include <string>
#include "HuffmanTree.h"

using namespace std;

typedef unsigned long long TypeLong;

struct CharInfo
{
	unsigned char _ch; //字符
	TypeLong _count;//出现次数
	string _code;//Huffman编码
	CharInfo(TypeLong count = 0) :_ch(0), _count(count)
	{}
	bool operator!=(const CharInfo& info)const
	{
		return this->_count != info._count;
	}
	bool operator<(const CharInfo& info)const 
	{
		return this->_count < info._count;
	}
	CharInfo operator+(const CharInfo& info)const
	{
		return CharInfo(this->_count + info._count);
	}
};

class FileCompress
{
public:
	FileCompress();
	void CompressHuffCode(const char* filename);
	void UnCompressHuffCode(const char* filename);
	void PrintCode()const;

protected:
	static void GenerateHuffmanCode(HuffmanNode<CharInfo>* root, FileCompress& file, string& code);
	
protected:
	CharInfo _info[256];

};

#endif /*__HUFFMAN_CODE_H__*/

文件压缩实现文件:FileCompreess.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include <assert.h>
#include "FileCompress.h"
#include <io.h>
#include <direct.h>

#define __DEBUG_COUT__

FileCompress::FileCompress()
{
	size_t size = sizeof(this->_info) / sizeof(this->_info[0]);
	for (size_t i = 0; i < size; ++i)
	{
		this->_info[i]._ch = i;
		this->_info[i]._count = 0;
	}
}

void FileCompress::CompressHuffCode(const char* filename)
{
	assert(filename);
	FILE* fOut = fopen(filename, "rb");
	assert(fOut);
	//统计字符出现的次数
	char ch = fgetc(fOut);
	while (!feof(fOut))
	{
		++this->_info[(unsigned char)ch]._count;
		ch = fgetc(fOut);
	}

	//建立哈夫曼树
	HuffmanTree<CharInfo> huffTree(this->_info, sizeof(this->_info) / sizeof(this->_info[0]), 0);

	//生成哈夫曼编码
	string code;
	HuffmanNode<CharInfo>* root = huffTree.GetHuffmanNode();
	GenerateHuffmanCode(root, *this, code);

	//生成压缩文件名及配置配置文件名
	string fileNewName = (string)filename; 

	size_t last_ = fileNewName.find_last_of('.');
	size_t name_ = fileNewName.find_last_of('\\');//文件名
	if (last_ < fileNewName.size())
	{
		fileNewName.erase(last_);
	}

	char file[10];
	strcpy(file, filename + name_+1);

	fileNewName += "yasuo\\";

	char *fileName = (char *)fileNewName.c_str(), *tag, path[1000];
	strcpy(path, fileName);
	int a = 0;
	for (tag = fileName; *tag; tag++)
	{
		if (*tag == '\\')
		{
			a = strlen(fileName) - strlen(tag);
		}
	}
	path[a] = NULL;
	char filePath[1000];

	sprintf(filePath, "md %s", path);
	system(filePath);
	fileNewName += file;

	string fileInName = fileNewName;//压缩文件名
	string fileConfig = fileInName;//配置文件名

	last_ = fileNewName.find_last_of('.');
	if (last_ < fileNewName.size())
	{
		fileInName.erase(last_);
		fileConfig.erase(last_);
	}
	
	fileInName += ".huff";
	fileConfig += ".config";

	string tmp;

	//生成压缩配置文件
	FILE* fConfig = fopen(fileConfig.c_str(), "wb");
	char buff[20] = { 0 };
	for (size_t i = 0; i < sizeof(this->_info) / sizeof(this->_info[0]); ++i)
	{
		if (this->_info[i]._count != 0)
		{
			tmp += this->_info[i]._ch;
			tmp += ':';
			tmp += (string)_itoa((this->_info[i]._count), buff, 10);
			tmp += '\n';
			fputs(tmp.c_str(), fConfig);		
			tmp.clear();
		}
	}

	//对文件进行压缩
	FILE* fIn = fopen(fileInName.c_str(), "wb");
	assert(fIn);
	fseek(fOut, 0, SEEK_SET);
	int pos = 0;
	unsigned char putch = 0;
	ch = fgetc(fOut);
	while (!feof(fOut))
	{
		tmp = this->_info[(unsigned char)ch]._code;
		for (size_t i = 0; i < tmp.size(); ++i)
		{
			putch <<= 1;
			putch |= (tmp[i] - '0');
			if (++pos == 8)
			{
				fputc(putch, fIn);
				pos = 0;
				putch = 0;
			}
		}
		ch = fgetc(fOut);
	}
	if (pos > 0)
	{
		putch <<= (8 - pos);
		fputc(putch, fIn);
	}

	fclose(fOut);
	fclose(fIn);
	fclose(fConfig);
}

void FileCompress::GenerateHuffmanCode(HuffmanNode<CharInfo>* root, FileCompress& file, string& code)
{
	if (root == NULL)
	{
		return;
	}
	if (root->_left == NULL && root->_right == NULL)
	{
		file._info[root->_weight._ch]._code = code;
		return;
	}
	code.push_back('0'); 
	GenerateHuffmanCode(root->_left, file, code);
	code.pop_back(); 
	code.push_back('1');
	GenerateHuffmanCode(root->_right, file, code);
	code.pop_back();
}

void FileCompress::UnCompressHuffCode(const char* filename)
{
	assert(filename);
	FILE* fOut = fopen(filename, "rb");
	assert(fOut);
	//读取文件,
	string fileConfig = (string)filename;
	string fileNewName = (string)filename;
	string fileInName = fileConfig;
	size_t last_ = fileInName.find_last_of('.');
	size_t name_ = fileNewName.find_last_of('\\');
	if (name_ < fileNewName.size())
	{
		fileConfig.erase(last_);
		fileNewName.erase(name_);
	}

	char file[10];
	strcpy(file, fileConfig.c_str() + name_ + 1);//文件名

	name_ = fileNewName.find_last_of('\\');
	if (name_ < fileInName.size())
	{
		fileInName.erase(name_+1);
	}

	fileInName += file;  //弥补文件名

	fileConfig += ".config";
	fileInName += "_Com.txt";

	FILE* fIn = fopen(fileInName.c_str(), "wb");
	assert(fIn);
	FILE* fConfig = fopen(fileConfig.c_str(), "rb");
	assert(fConfig);

	//修改_count,注意\n,有可能代表字符,有可能是行结束标志
	char buff[20] = { 0 };
	unsigned char ch = fgetc(fConfig);
	while (!feof(fConfig))
	{
		fgetc(fConfig);//读取冒号
		fgets(buff, 20, fConfig);
		this->_info[ch]._count = (TypeLong)atoi((buff));
		ch = fgetc(fConfig);
	}

	//重建哈夫曼树
	HuffmanTree<CharInfo> tree(this->_info, sizeof(this->_info) / sizeof(this->_info[0]), 0);
	HuffmanNode<CharInfo>* root = tree.GetHuffmanNode();
	HuffmanNode<CharInfo>* cur = root;
	TypeLong countSum = root->_weight._count; //记录字符的总个数控制结束
	ch = fgetc(fOut);
	int pos = 7;
	while (countSum > 0)
	{
		while (pos >= 0)
		{
			if ((ch & (1 << pos)) == 0) //向左走
			{
				cur = cur->_left;
			}
			else
			{
				cur = cur->_right;
			}
			if (cur->_left == NULL && cur->_right == NULL)
			{
				fputc(cur->_weight._ch, fIn);

#ifndef __DEBUG_COUT__
				cout << cur->_weight._ch;
#endif /*__DEBUG_COUT__*/

				if (--countSum == 0)//将没有写的字符的次数减1
					break;
				cur = root;
			}
			--pos;
		}
		pos = 7;
		ch = fgetc(fOut);
	}
	fclose(fIn);
	fclose(fOut);
	fclose(fConfig);
}

void FileCompress::PrintCode()const
{
	for (int i = 0; i < 256; ++i)
	{
		if (this->_info[i]._count != 0)
		{
			cout << this->_info[i]._ch << ":>" << this->_info[i]._code << endl;
		}
	}
}


压缩和解压时间检查代码:TimeChick.h

#pragma once
#ifndef __TIME_CHECK_H__
#define __TIME_CHECK_H__

#include <windows.h>

class MyTimer
{
public:
	MyTimer()
	{
		QueryPerformanceFrequency(&_freq);
		costTime = 0.0;
	}
	void Start()
	{
		for (int i = 0; i<EN_NUMER; ++i)
		{
			QueryPerformanceCounter(&_array[i]._begin);
		}
	}
	void Stop()
	{
		for (int i = 0; i<EN_NUMER; ++i)
		{
			QueryPerformanceCounter(&_array[i]._end);
		}
	}
	void Reset()
	{
		costTime = 0.0;
	}
	void showTime()
	{
		double allTime = 0.0;
		for (int i = 0; i<EN_NUMER; ++i)
		{
			allTime += (((double)_array[i]._end.QuadPart - (double)_array[i]._begin.QuadPart) / (double)_freq.QuadPart);
		}
		costTime = allTime / EN_NUMER;
		costTime *= 1000000;

		if ((((int)costTime) / 1000000) > 0)
		{
			cout << costTime / 1000000 << " s" << endl;
		}
		else if (((int)costTime) / 1000 > 0)
		{
			cout << costTime / 1000 << " ms" << endl;
		}
		else
		{
			cout << costTime << " us" << endl;
		}
	}
private:
	class Array
	{
	public:
		LARGE_INTEGER _begin;
		LARGE_INTEGER _end;
	};
	enum{ EN_NUMER = 5 };
	LARGE_INTEGER _freq;
	double costTime;
	Array _array[EN_NUMER];
};
#endif

测试代码:压缩测试函数,放在主函数里

void test()
{
	string filename = "C:\\Users\\小明\\Desktop\\input.txt";
	cout << "压缩时间";
	MyTimer timer;
	timer.Start();
	////////////////////////////////

	FileCompress fc;
	fc.CompressHuffCode(filename.c_str());

	/////////////////////////////////
	timer.Stop();
	timer.showTime();

}


测试代码:解压缩测试函数,放在主函数里
void untest()
{
	string filename = "C:\\Users\\小明\\Desktop\\inputyasuo\\input.huff";
	cout << "解压时间";
	MyTimer timer;
	timer.Start();
	////////////////////////////////

	FileCompress unfc;
	unfc.UnCompressHuffCode(filename.c_str());

	/////////////////////////////////
	timer.Stop();
	timer.showTime();
	
}


主函数:test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

#include "HuffmanTree.hpp"
#include "FileCompress.h"
#include "FileCompress.cpp"

#include "TimeCheck.h"


void test()
{
	string filename = "C:\\Users\\小明\\Desktop\\input.txt";   //需要压缩的文件
	cout << "压缩时间";
	MyTimer timer;
	timer.Start();
	////////////////////////////////

	FileCompress fc;
	fc.CompressHuffCode(filename.c_str());

	/////////////////////////////////
	timer.Stop();
	timer.showTime();

}

void untest()
{
	string filename = "C:\\Users\\小明\\Desktop\\inputyasuo\\input.huff";  //需要解压缩的文件名
	cout << "解压时间";
	MyTimer timer;
	timer.Start();
	////////////////////////////////

	FileCompress unfc;
	unfc.UnCompressHuffCode(filename.c_str());

	/////////////////////////////////
	timer.Stop();
	timer.showTime();
	
}

int main()
{ 

	test();
	untest();

	system("pause");
	return 0;
}

现在测试的是我桌面上的一个文件:文件名为:input.txt


压缩过程


压缩和解压缩之后桌面显示:


解压缩文件:input_Com.txt


本项目特点:将压缩后的文件,以及配置文件新建立了一个文件夹,放在文件夹中,这样美观,易识别。


代码中还有很多不足,有些问题也未解决,欢迎各位批评指正!!!  我也会子啊后续努力中继续添加功能!






作者:no_sying_nothing 发表于2016/7/30 0:35:19 原文链接
阅读:4 评论:0 查看评论

AppBarLayout控件 & CoordinatorLayout 控件 详解

$
0
0

先认识几个英文单词:

CoordinatorLayout 可协调的Layout—--Coordinator:协调者

CollapsingToolbarLayout :可折叠的ToolBar—–Collapsing :折叠

parallax : 视差

pin : 钉住

官方文档: https://developer.android.com/reference/android/support/design/widget/AppBarLayout.html

参考链接; http://blog.csdn.net/caihongdao123/article/details/51654948

参考链接: http://blog.csdn.net/xyz_lmn/article/details/48055919

参考链接: http://www.jianshu.com/p/f418bf95db2d

先看效果图:

这里写图片描述

下面是xml布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/coordinator_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/main.appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/main.collapsing"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginEnd="64dp"
            app:expandedTitleMarginStart="48dp"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <ImageView
                android:id="@+id/main.backdrop"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:fitsSystemWindows="true"
                android:scaleType="centerCrop"
                android:src="@drawable/girl"
                app:layout_collapseMode="parallax" 
                app:layout_collapseParallaxMultiplier="0.1"
            />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolBar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:layout_scrollFlags="scroll|enterAlways"
                app:logo="@mipmap/ic_launcher"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:title="CoordinatorLayout" />
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>
    <!--主页面布局-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:scrollbars="none"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <android.support.design.widget.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#30469b"
            app:tabGravity="fill"
            app:tabMode="fixed"
            app:tabSelectedTextColor="#ff0000"
            app:tabTextColor="#ffffff" />

        <android.support.v4.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:lineSpacingExtra="8dp"
                android:padding="@dimen/activity_horizontal_margin"
                android:text="@string/lorem"
                android:textSize="20sp" />
        </android.support.v4.widget.NestedScrollView>



    </LinearLayout>
</android.support.design.widget.CoordinatorLayout>

java代码很简单

public class MainActivity extends AppCompatActivity {

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

        TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
        tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
        tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));

    }
}

使用到的控件详解:

  • CoordinatorLayout 该控件也是Design包下的一个控件,然而这个控件可以被称为Design包中最复杂、功能最强大的控件:CoordinatorLayout。为什么这样说呢?原因是:它是组织它众多子view之间互相协作的一个ViewGroup。
    CoordinatorLayout 的神奇之处就在于 Behavior 对象。怎么理解呢?CoordinatorLayout使得子view之间知道了彼此的存在,一个子view的变化可以通知到另一个子view,CoordinatorLayout 所做的事情就是当成一个通信的桥梁,连接不同的view,使用 Behavior 对象进行通信。
    比如:在CoordinatorLayout中使用AppBarLayout,如果AppBarLayout的子View(如ToolBar、TabLayout)标记了app:layout_scrollFlags滚动事件,那么在CoordinatorLayout布局里其它标记了app:layout_behavior的子View(LinearLayout、RecyclerView、NestedScrollView等)就能够响应(如ToolBar、TabLayout)控件被标记的滚动事件。如

  • AppBarLayout 是继承LinerLayout实现的一个ViewGroup容器组件,它是为了Material Design设计的App Bar,支持手势滑动操作(需要跟CoordinatorLayout配合使用,下面会介绍如何配合CoordinatorLayout组件)。默认的AppBarLayout是垂直方向的,它的作用是把AppBarLayout包裹的内容都作为AppBar。

  • CollapsingToolbarLayout CollapsingToolbarLayout作用是提供了一个可以折叠的Toolbar,它继承至FrameLayout,给它设置layout_scrollFlags,它可以控制包含在CollapsingToolbarLayout中的控件(如:ImageView、Toolbar)在响应layout_behavior事件时作出相应的scrollFlags滚动事件(移除屏幕或固定在屏幕顶端)。

1、在CollapsingToolbarLayout(可折叠的ToolBar)中:

参考官方API: https://developer.android.com/reference/android/support/design/widget/CollapsingToolbarLayout.html

我们设置了layout_scrollFlags:关于它的值我这里再说一下:

scroll 所有想滚动出屏幕的view都需要设置这个flag, 没有设置这个flag的view将被固定在屏幕顶部.例如,TabLayout 没有设置这个值,将会停留在屏幕顶部。CollapsingToolbarLayout 设置了 app:layout_scrollFlags="scroll|exitUntilCollapsed" 就会滚出屏幕

enterAlways 实现quick return效果, 当向下移动时,立即显示View(比如Toolbar)。
设置这个flag时,向下的滚动都会导致该view变为可见,启用快速“返回模式”。

exitUntilCollapsed 向上滚动时收缩View,但可以固定Toolbar一直在上面。

enterAlwaysCollapsed - 当你的View已经设置minHeight属性又使用此标志时,你的View只能以最小高度进入,只有当滚动视图到达顶部时才扩大到完整高度。

其中还设置了一些属性,简要说明一下:

contentScrim - 设置当完全CollapsingToolbarLayout折叠(收缩)后的背景颜色。

expandedTitleMarginStart - 设置扩张时候(还没有收缩时)title向左填充的距离。

  • **为了ToolBar可以滚动,CoordinatorLayout里面,放一个带有可滚动的View.如上的例子,NestedScrollView,即是可以滚动的View。CoordinatorLayout包含的子视图中带有滚动属性的View需要设置app:layout_behavior属性。例如,示例中 NestedScrollViewr 设置了此属性。
    **

2、在ImageView控件中:

我们设置了:

layout_collapseMode (折叠模式) - 有两个值:

pin - 设置为这个模式时,当CollapsingToolbarLayout完全收缩后,Toolbar还可以保留在屏幕上。

parallax - 设置为这个模式时,在内容滚动时,CollapsingToolbarLayout中的View(比如ImageView)也可以同时滚动,实现视差滚动效果,通常和layout_collapseParallaxMultiplier(设置视差因子)搭配使用。

layout_collapseParallaxMultiplier(视差因子) - 设置视差滚动因子,值为:0~1。

3、在Toolbar控件中:

我们设置了layout_collapseMode(折叠模式):为pin。

综上分析:当设置了layout_behavior的控件响应起了CollapsingToolbarLayout中的layout_scrollFlags事件时,ImageView会有视差效果的向上滚动移除屏幕,当开始折叠时CollapsingToolbarLayout的背景色(也就是Toolbar的背景色)就会变为我们设置好的背景色,Toolbar也一直会固定在最顶端。

关于锚点 ancher

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/background_light"
    android:fitsSystemWindows="true"
    >

    <android.support.design.widget.AppBarLayout
        android:id="@+id/main.appbar"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:fitsSystemWindows="true"
        >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/main.collapsing"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp"
            >

            <ImageView
                android:id="@+id/main.backdrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                android:src="@drawable/material_flat"
                app:layout_collapseMode="parallax"
                />

            <android.support.v7.widget.Toolbar
                android:id="@+id/main.toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_collapseMode="pin"
                />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="20sp"
            android:lineSpacingExtra="8dp"
            android:text="@string/lorem"
            android:padding="@dimen/activity_horizontal_margin"
            />
    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:layout_margin="@dimen/activity_horizontal_margin"
        android:src="@drawable/ic_comment_24dp"
        app:layout_anchor="@id/main.appbar"
        app:layout_anchorGravity="bottom|right|end"
        />
</android.support.design.widget.CoordinatorLayout>

效果图

这里写图片描述

所谓锚点 anchor ,就是绑定到一个 Layout 作为自己的父容器,个人理解,有错请指正.

扩展阅读:

TabLayout 遇到那些坑

分分钟教你学会 ToolBar 的使用

作者:itguangit 发表于2016/7/30 0:35:59 原文链接
阅读:3 评论:0 查看评论

点线表示及其计算

$
0
0
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
struct CVector{  //向量 
	double x,y;
	CVector(double x,double y):x(x),y(y) {}
};
struct CPoint{  //点 
	double x,y;
	CPoint(double x,double y):x(x),y(y) {}
};
#define CPoint CVector  //两者表示是等价的 
struct CLine{   //线 
	CPoint a,b;
	CLine(CPoint a,CPoint b):a(a),b(b) {}
};
double PI = acos(-1);
double INF = 1e20;
double EPS = 1e-6;
bool IsZero(double x){  //判断x是否为零 
	return -EPS<x&&x<EPS;
}
bool FLarger(double a,double b){
	return a-b>EPS; 
}
bool FLess(double a,double b){
	return b-a>EPS;
}


CVector operator - (CPoint b,CPoint a){ // c= a-b,b指向a的向量 
	return CVector(b.x-a.x,b.y-a.y);
}
CVector operator + (CPoint a,CPoint b){
	return CVector(a.x+b.x,a.y+b.y);
}
double operator * (CVector p,CVector q){  //p*q=|p|*|q|*cos<p,q>
	return p.x*q.x+p.y+q.y;
} 
double operator ^ (CVector p,CVector q){  //c=a x b 叉积 
	return p.x*q.y-q.x*p.y;
}
CVector operator * (double k,CVector p){  //c=f*|a|;
	return CVector(k*p.x,k*p.y);
}
bool operator == (CPoint a,CPoint b){
	return a.x==b.x&&a.y==b.y;
}


double length(CVector p){  //求|p| 
	return sqrt(p*p);
}
CVector unit(CVector p){ //p的单位向量 
	return 1/length(p)*p;
} 
double dot(CVector p,CVector q){  //点积 
	return p.x*q.x+p.y*q.y;
}
double project(CVector p,CVector n){ //p在n上射影 
	return dot(p,unit(n));
} 
double area(CVector p,CVector q){ //两个向量围成的面积 
	return (p^q)/2;
}


double dist(CPoint p,CPoint q){ //两点距离 
	return length(p-q);
}
double dist(CPoint p,CLine l){  //点线距离 
	return fabs((p-l.a)^(l.b-l.a))/length(l.b-l.a); //面积除以高 
}
CPoint rotate(CPoint b,CPoint a,double alpha){
	CVector p=b-a;
	return CPoint(a.x+(p.x*cos(alpha)-p.y*sin(alpha)),a.y+(p.x*sin(alpha)+p.y*cos(alpha)));
}  //将ab向量绕a点逆时针转alpha角度
int sideOfLine(CPoint p,CPoint a,CPoint b){  //点p与直线a->b的位置关系 
	double result=(b-a)^(p-a);
	if(IsZero(result)) return 0;  //p在a->b上
	else if(result>0)  return 1;  //p在a->b左侧
	else               return -1; //p在a->b右侧 
}
CLine Vertical(CPoint p,CLine l){  //过点做线的垂线 
	return CLine( p,p + (rotate(l.b,l.a,PI / 2)-l.a) );
}
CPoint foot(CPoint p,CLine l){  //点到线的垂足的向量 
	return l.a+project(p-l.a,l.b-l.a)*unit(l.b-l.a);
}
CPoint intersect(CLine l,CLine m,string msg){ //判断直线交点 
	double x=area(m.a-l.a,l.b-l.a);
	double y=area(l.b-l.a,m.b-l.a);
	if(IsZero(x+y)) {
		if(IsZero(dist(l.a,m))) msg="重合";
		else msg="平行";
		return CPoint(0,0); 
	}
	return m.a+x/(x+y)*(m.b-m.a);
}
bool IsFormalCross(CPoint p1,CPoint p2,CPoint p3,CPoint p4){
	return ((p2-p1)^(p3-p1)) * ((p2-p1)^(p4-p1)) < -EPS
		&& ((p4-p3)^(p1-p3)) * ((p4-p3)^(p2-p3)) < -EPS ;
}

//两条线段相交的各种情况//
struct Seg{
	CPoint a,b;
	Seg(const CPoint& a,const CPoint& b):a(a),b(b){}
	double getX(double y){  //给定y坐标求x坐标  
		return (y-a.y)/(b.y-a.y)*(b.x-a.x) + a.x;
	}  //直线两点式方程 (y-y1)/(y2-y2) = (x-x1)/(x2-x1) 
	double getY(double x){
		return (x-a.x)/(b.x-a.x)*(b.y-a.y) + a.y;
	} 
}; 
//两条线段相交的各种情况
//结果保存在pair<int,CPoint> 中 
//返回值 result.first:
//0 规范相交,
//1 端点重合,但不平行,不共线
//2 一个端点在另一个内部 s1端点在 s2内部 (不平行,不共线)
//3 一个端点在另一个内部 s2端点在 s1内部 (不平行,不共线)
//4 无交点,不平行,不共线,两直线交点是result.second
//5 平行
//6 共线且有公共点
//7 共线且无公共点
//8 s1,s2无交点,但是s2所在直线和s1有交点,即交点在s1上
//9 s1,s2无交点,但是s1所在直线和s2有交点,即交点在s2上
bool FLessEq(double a,double b){ //b不小于a 
	return b-a>EPS || IsZero(b-a);
} 
double dist(CPoint p,Seg s){
	return dist(p,CLine(s.a,s.b));
}
bool PointInSeg(CPoint p,Seg L){  //p点在线段l内 
	double tmp = (L.a-p)^(L.a-L.b);
	if(!IsZero(tmp)) return false;
	if(FLessEq(min(L.a.x,L.b.x),p.x) &&
	   FLessEq(p.x,max(L.a.x,L.b.x)) &&
	   FLessEq(min(L.a.y,L.b.y),p.y) &&
	   FLessEq(p.y,max(L.a.y,L.b.y)) )
	   return true;
	return false;
} 
pair<int,CPoint> CrossPoint(Seg s1,Seg s2){
	CPoint p1=s1.a;
	CPoint p2=s1.b;
	CPoint p3=s2.a;
	CPoint p4=s2.b;
	
	double a1 = area(p3-p1,p4-p1); 
	double a2 = area(p4-p2,p3-p2);
	if(((p2-p1)^(p3-p1))*((p2-p1)^(p4-p1)) < -EPS
	   && ((p4-p3)^(p1-p3))*((p4-p3)^(p2-p3)) < -EPS)
	   return make_pair(0,p1+(a1/(a1+a2))*(p2-p1)); //规范相交
	if(!(IsZero((p2-p1)^(p3-p4)))){  //不平行不共线
		if(p1==p3||p1==p4) return make_pair(1,p1);
		if(p2==p3||p2==p4) return make_pair(1,p2);
		if(PointInSeg(p1,s2)) return make_pair(2,p1);
		if(PointInSeg(p2,s2)) return make_pair(2,p2);
		if(PointInSeg(p3,s1)) return make_pair(3,p3);
		if(PointInSeg(p4,s1)) return make_pair(3,p4);
		
		CPoint crossPoint = p1+(a1/(a1+a2))*(p2-p1); //交点 
		if(PointInSeg(crossPoint,s1)) return make_pair(8,crossPoint);
		if(PointInSeg(crossPoint,s2)) return make_pair(9,crossPoint);
		
		//直线和线段也无交点 不平行 不共线 两直线交点是second 
		return make_pair(4,crossPoint);  	
	} 
	if(!IsZero(dist(p1,s2))) return make_pair(5,CPoint(0,0));  //平行
	
	//共线  且有公共点 
	if(PointInSeg(p1,s2))   return make_pair(6,p1);
	if(PointInSeg(p2,s2))   return make_pair(6,p2);
	if(PointInSeg(p3,s1))   return make_pair(6,p3);
	if(PointInSeg(p4,s1))   return make_pair(6,p4);
	return make_pair(7,CPoint(0,0));//共线 且无公共点
}
double angle(CLine l,CLine m){  //两线段之间的夹角:射影/线段长 
	return acos(fabs(project(l.b-l.a,m.b-m.a)/length(l.b-l.a)));
}

作者:qq_34446253 发表于2016/7/30 0:36:13 原文链接
阅读:6 评论:0 查看评论

【leetcode】经典算法题-Counting Bits

$
0
0

题目描述:

给定一个数字n,统计0~n之间的数字二进制的1的个数,并用数组输出

例子:

For num = 5 you should return [0,1,1,2,1,2].

要求:

  • 算法复杂复o(n)
  • 空间复杂度o(n)

原文描述:

Given a non negative integer number num. For every numbers i in the range 0 ≤ i ≤ num calculate the number of 1’s in their binary representation and return them as an array.

Example:

For num = 5 you should return [0,1,1,2,1,2].

Follow up:

It is very easy to come up with a solution with run time O(n*sizeof(integer)). But can you do it in linear time O(n) /possibly in a single pass?

Space complexity should be O(n).

Can you do it like a boss? Do it without using any builtin function like __builtin_popcount in c++ or in any other language.

思路分析:

  • 根据题目的要求,时间和空间复杂度,明显是要动态规划的方法
  • 得出推到公式:f(n) = 不大于f(n)的最大的2的次方+f(k),k一定是在前面出现的,用数组记录,直接查询
  • 举例f(5) = f(4)+ f(1),注意2de次方都是一个1,而且是最高位,f(5) = 1+f(1),f(6) = 1+f(2)直到f(8) = 1

代码:

public class Solution {
    public int[] countBits(int num) {
        int[] res = new int[num+1];
        int pow2 = 1,before =1;
        for(int i=1;i<=num;i++){
            if (i == pow2){
                before = res[i] = 1;
                pow2 <<= 1;
            }
            else{
                res[i] = res[before] + 1;
                before += 1;
            }
        }
        return res;
    }
}

我的微信二维码如下,欢迎交流讨论

这里写图片描述

欢迎关注《IT面试题汇总》微信订阅号。每天推送经典面试题和面试心得技巧,都是干货!

微信订阅号二维码如下:

这里写图片描述

作者:u010321471 发表于2016/7/30 0:37:56 原文链接
阅读:57 评论:0 查看评论

2016HDU多校赛第4场(hdu5765 hdu5769 hdu5772)

$
0
0

hdu5765 Bonds

题意

给你一个连通图,问每个边在多少个极小割边集上。

解法

官方题解:

极小割边集的定义下割边集恰好会将原图分成两块。枚举左右两块是否独立连通。块连通必然可以删掉一个点,这个点与剩下的点有边且剩下的点连通,可以先状压每个点的边, DP转移即可。
接下来要处理的是将两个块之间的边加一。这部分可以处理成将左块中的边减一,右块中的边减一,整体加一。操作变成了把一个块的边加权值的操作。考虑一条边表达成形如 0..010..010..00..010..010..0的形式,它在所有块中的权值总和,便是求一遍把这个形式作为子集的集合的权值和,求一遍高维前缀和就可以了。

大神们总是说随便写写就过,但是并不能看懂题解说的是什么,看了好久标程(只想吐槽能或的地方为何要异或,很影响理解)

先预处理出来每种点集周围的可达的点的集合,然后做一次bfs处理出所有可以构成连通图的点集。

枚举一个点集i,令j = U - i,看i和j是否是连通的,若是,则记录sum[i]++;sum[j]++,ans++

但是i和j求的是以当前割集分割的情况下,i和j的极大的情况的sum值,子连通图其实可以由i和j转移过来,但是为了避免记重,要用到一个高维前缀和

最后答案就是:

(连通的点集总数 - 包含第i条边(u,v)的连通点集数目) / 2

高维前缀和

就是要枚举转移第几位,每次按位从父状态转移,这样可以保证在父状态指向相同祖先状态时不会重复计数。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int SIZE = 22;
int rch[1<<SIZE], sum[1<<SIZE], q[1<<SIZE], u[405], v[405];
bool show[1<<SIZE];
inline int lb(int x) {return x&(-x);}

int main() {
    int T;
    scanf("%d",&T);
    for(int cs = 1; cs <= T; cs++) {
        int n, m;
        scanf("%d%d",&n,&m);
        fill(rch , rch + (1<<n) , 0);
        fill(show , show + (1<<n) , 0);
        fill(sum , sum + (1<<n) , 0);
        for(int i = 0; i < m; i++) {
            int u, v;
            scanf("%d%d",&u,&v);
            ::u[i] = u;
            ::v[i] = v;
            rch[1<<u] |= 1<<v;
            rch[1<<v] |= 1<<u;
        }
        for(int i = 1; i < 1<<n; i++) {
            rch[i] |= rch[i-lb(i)] | rch[lb(i)]; //处理出当前点集临接的点
        }
        int l = 0, r = 0;
        for(int i = 0; i < n; i++) show[q[r++] = 1<<i] = true;
        //标程上学来的神奇bfs
        while(l < r) {
            int c = q[l++]; //当前选的点集
            int left = rch[c] ^ (rch[c] & c); //剩下可达的没去的点集
            while(left) {
                int now = lb(left) | c;
                if(!show[now]) show[q[r++] = now] = true;
                left -= lb(left);
            }
        } 
        int all = 0;
        for(int i = 0; i < 1 << n; i++) {
            int j = (1 << n) - 1;
            j ^= i;
            if(show[i] && show[j]) {
                sum[i]++;
                all++; 
                sum[j]++;
            }
        }
        //all >>= 1; 
        for(int j = 0; j < n; j++) {
            for(int i =(1<<n)-1;i>=0;--i) if(!(i>>j&1)) sum[i] += sum[i^(1<<j)]; 
            //高维前缀和,sum[i]代表点i超集数目,即包含i的连通的点集数目
        }
        printf("Case #%d: ",cs);
        for(int i = 0; i < m; i++) {
            printf("%d%c",(all - sum[(1<<u[i]) | (1<<v[i])])/2, " \n"[i+1==m]);
            //all是所有连通点集数目,减去同时包含u和v的连通点集个数,剩下就是以(u,v)为割的点集数目的两倍
        }
    }

}

hdu5769 Substring

题意

求包含字符x的不同子串个数

解法

后缀数组板题。
不同子串个数是 ∑length−(sa[i]+height[i])
带限制字符x时只要判断对于后缀sa[i]最近的x位置(没有时设为n)和sa[i]+height的最大值就好了

代码

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
typedef long long ll;
const int MAXN=100010;
int t1[MAXN],t2[MAXN],c[MAXN];//求SA数组需要的中间变量,不需要赋值
//待排序的字符串放在s数组中,从s[0]到s[n-1],长度为n,且最大值小于m,
//除s[n-1]外的所有s[i]都大于0,r[n-1]=0
//函数结束以后结果放在sa数组中
//sa[1~n]->[0,N] rank[0~n-1]->[1,N]  height[1~n]
bool cmp(int *r,int a,int b,int l)
{
    return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int Rank[],int height[],int n,int m)
{
    n++;
    int i, j, p, *x = t1, *y = t2;
    //第一轮基数排序,如果s的最大值很大,可改为快速排序
    for(i = 0;i < m;i++)c[i] = 0;
    for(i = 0;i < n;i++)c[x[i] = str[i]]++;
    for(i = 1;i < m;i++)c[i] += c[i-1];
    for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1)
    {
        p = 0;
        //直接利用sa数组排序第二关键字
        for(i = n-j; i < n; i++)y[p++] = i;//后面的j个数第二关键字为空的最小
        for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;
        //这样数组y保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for(i = 0; i < m; i++)c[i] = 0;
        for(i = 0; i < n; i++)c[x[y[i]]]++;
        for(i = 1; i < m;i++)c[i] += c[i-1];
        for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];
        //根据sa和x数组计算新的x数组
        swap(x,y);
        p = 1; x[sa[0]] = 0;
        for(i = 1;i < n;i++)
            x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p >= n)break;
        m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i = 0;i <= n;i++)Rank[sa[i]] = i;
    for(i = 0;i < n;i++)
    {
        if(k)k--;
        j = sa[Rank[i]-1];
        while(str[i+k] == str[j+k])k++;
        height[Rank[i]] = k;
    }
}
int Rank[MAXN],height[MAXN];

char str[MAXN];
char X[3];
int r[MAXN];
int sa[MAXN];
int nxt[MAXN];

int main(){
    int T;
    scanf("%d",&T);
    for(int cs = 1; cs <= T; cs++) {
        scanf("%s%s",X,str);
        int n = strlen(str);
        int temp = n;
        for(int i = n - 1; i >= 0; i--) {
            if(str[i] == X[0]) temp = i;
            nxt[i] = temp;

        }
        for(int i = 0; i < n; i++) r[i] = str[i];
        r[n] = 0;
        da(r,sa,Rank,height,n,128);
        ll ans = 0;
        for(int i = 1; i <= n; i++) {
            //printf("%d %d %d\n",nxt[sa[i]], sa[i] , height[i]);
            ans += n - max(nxt[sa[i]], sa[i] + height[i]);
        }
        printf("Case #%d: %I64d\n",cs,ans);
    }
    return 0;
}

hdu5772

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5772

解法

最大权闭合子图问题。(正点权之和 - 最大流)
难点在建图,首先将点分为3类

第一类:Pij 表示第i个点和第j个点组合的点,那么Pij的权值等于w[i][j]+w[j][i](表示得分)

第二类:原串中的n个点每个点拆出一个点,第i个点权值为 –a[s[i]] (表示要花费)

第三类:对于10种字符拆出10个点,每个点的权值为 -(b[x]-a[x])

然后跑一个dinic就行了

代码

/*
最大流模板
dinic算法
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int INF=0x3f3f3f3f;
const int MAXN=15000;//点数的最大值
const int MAXM=1050000;//边数的最大值

struct Node
{
    int from,to,next;
    int cap;
}edge[MAXM];
int tol;

int dep[MAXN];//dep为点的层次
int head[MAXN];

int n;
void init()
{
    tol=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)//第一条变下标必须为偶数
{
    //printf("u=%d v=%d w=%d\n",u,v,w);
    edge[tol].from=u;
    edge[tol].to=v;
    edge[tol].cap=w;
    edge[tol].next=head[u];
    head[u]=tol++;
    edge[tol].from=v;
    edge[tol].to=u;
    edge[tol].cap=0;
    edge[tol].next=head[v];
    head[v]=tol++;
}

int BFS(int start,int end)
{
    int que[MAXN];
    int front,rear;
    front=rear=0;
    memset(dep,-1,sizeof(dep));
    que[rear++]=start;
    dep[start]=0;
    while(front!=rear)
    {
        int u=que[front++];
        if(front==MAXN)front=0;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(edge[i].cap>0&&dep[v]==-1)
            {
                dep[v]=dep[u]+1;
                que[rear++]=v;
                if(rear>=MAXN)rear=0;
                if(v==end)return 1;
            }
        }
    }
    return 0;
}
int dinic(int start,int end)
{
    int res=0;
    int top;
    int stack[MAXN];//stack为栈,存储当前增广路
    int cur[MAXN];//存储当前点的后继
    while(BFS(start,end))
    {
        memcpy(cur,head,sizeof(head));
        int u=start;
        top=0;
        while(1)
        {
            if(u==end)
            {
                int min=INF;
                int loc;
                for(int i=0;i<top;i++)
                  if(min>edge[stack[i]].cap)
                  {
                      min=edge[stack[i]].cap;
                      loc=i;
                  }
                for(int i=0;i<top;i++)
                {
                    edge[stack[i]].cap-=min;
                    edge[stack[i]^1].cap+=min;
                }
                res+=min;
                top=loc;
                u=edge[stack[top]].from;
            }
            for(int i=cur[u];i!=-1;cur[u]=i=edge[i].next)
              if(edge[i].cap!=0&&dep[u]+1==dep[edge[i].to])
                 break;
            if(cur[u]!=-1)
            {
                stack[top++]=cur[u];
                u=edge[cur[u]].to;
            }
            else
            {
                if(top==0)break;
                dep[u]=-1;
                u=edge[stack[--top]].from;
            }
        }
    }
    return res;
}

int a[15], b[15], w[105][105];
char s[105];

int main()//多源多汇点,在前面加个源点,后面加个汇点,转成单源单汇点
{
    int start,end;
    int u,v,z;
    int T;
    scanf("%d",&T);
    for(int cs = 1; cs <= T; cs++)
    {
        init();
        scanf("%d",&n);
        scanf("%s",s);
        end = n*n + n + 10 + 1;
        start = 0;
        int res = 0;
        for(int i = 0; i <= 9; i++) {
            scanf("%d %d",&a[i],&b[i]);
        }
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                scanf("%d",&w[i][j]);
            }
        }
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < n; j++) {
                if(i == j) continue;
                int u = i * n + j + 1;
                res += w[i][j];
                addedge(0,u,w[i][j]);
                addedge(u,n*n+i+1,INF);
                addedge(u,n*n+j+1,INF);
            }
        }
        //printf("-----%d\n",res);
        for(int i = 0; i < n; i++) {
            addedge(n*n+i+1, end, a[s[i] - '0']);
            addedge(n*n+i+1, s[i] - '0' + 1 + n*n + n, INF);
        }
        for(int i = 0; i < 10; i++) {
            addedge(i + 1 + n*n +n, end, b[i] - a[i]);
        }
        int ans=dinic(start,end);
        printf("Case #%d: %d\n",cs,res - ans);
    }
    return 0;
}
作者:CZWin32768 发表于2016/7/30 0:47:49 原文链接
阅读:4 评论:0 查看评论

LDA算法调研报告

$
0
0

LDA算法调研报告

 

 

1LDA算法概述

    本文所阐述的LDA算法全称为Latent Dirichlet Allocation(网上没有标准的中文名称,我称之为潜在狄利克雷分配算法),不是线性判别分析算法(Linear Discriminant Analysis)。LDA算法由加州伯克利大学计算机系的David M. Blei2003年发表。目前LDA算法的应用挺广泛,2012北京举办的SIGKDD国际会议上,在个性化推荐、社交网络、广告预测等各个领域的workshop上都提到LDA模型LDA算法是以同样出自加州伯克利大学计算机系的Thomas Hufmann1999年发表的PLSAProbabilistic Latent Semantic Analysis)为基础,PLSA算法则是对芝加哥大学Scott Deerwester, Susan T. Dumais等人在1990发表的潜在语义分析(Latent Semantic Analysis,简称LSA)算法思想的吸收。本报告稍后会对PLSALSALDA三种方法的背景和关联作介绍。

 由于LDA算法是一个用于研究语料库(corpus)的生成模型(generation model),其理论基础是一元语义模型(unigram model),也就是说,LDA算法的前提是,文档(document)可以被认为是词(word)的集合,忽略任何语法或者出现顺序关系,这个特性也被称为可交换性(Exchangeability)。本文所涉及的PLSALSALDA三种方法的分析都是在一元语义模型下进行。

本文立足于细节阐释,即在假定读者没有机器学习算法基础的前提下,通过它能迅速了解到LDA算法的思想和实现原理。虽然PLSA目前并没有在工作中被使用,但是它和LDA有很多通用的思想,而且比LDA容易理解,因而也占据较大篇幅。

 

2、三种文本分析方法

    首先统一一下名称。表1列举了LSA算法、PLSA算法、LDA算法各自的简称、全称和别称。为了方便,下文中,我们统一采用简称。

 

  表1三种算法的全称和别名

 

 2.1 LSA算法

在文档的空间向量模型(VSM)中,文档被表示成由特征词出现概率组成的多维向量。这种方法的好处是可以将query和文档转化成同一空间下的向量计算相似度,可以对不同词项赋予不同的权重,在文本检索、分类、聚类问题中都得到了广泛应用。但是空间向量模型的缺点是无法处理一词多义和同义词问题,例如同义词也分别被表示成独立的一维,计算向量的余弦相似度时会低估用户期望的相似度;而某个词项有多个词义时,始终对应同一维度,因此计算的结果会高估用户期望的相似度。

潜在语义分析(LSA)是对传统的空间向量模型(VSMvector space model)的改进。通过降维,LSA将词和文档映射到潜在语义空间,从而去除了原始向量空间中的一些噪音(比如词的误用或者不相关的词偶尔出现在一起),解决了同义词的问题。

    降维是LSA分析的核心。具体做法,是对词项文档矩阵(也叫Term-Document矩阵,以词项(terms)为行, 文档(documents)为列,这里记为C)做SVD分解

设C一共有t行d列, 矩阵的元素为词项的TF-IDF词频term frequency–反文档频率inverse document frequency值。然后把的r个对角元素的前k个保留(最大的k个保留), 后面最小的r-k个奇异值置0, 得到;最后计算一个近似的分解矩阵

   

则是在最小二乘意义下C的最佳逼近,从而得到原始词项文档矩阵的一个低秩逼近矩阵。

    通过在SVD分解近似,我们将原始的向量转化成一个低维隐含语义空间中,起到了特征降维的作用。每个奇异值对应的是每个“语义”维度的权重,将不太重要的权重置为0,只保留最重要的维度信息,去掉一些“噪音”,因而可以得到文档的一种更优表示形式。

 

2.2 PLSA算法

   基于SVD的LSA相比于空间向量模型解决了同义词问题,但是其缺乏严谨的数理统计基础,而且SVD分解非常耗时。另外,LSA没能解决一词多义的问题。

Hofmann于1999在SIGIR'99提出了基于概率统计的PLSA模型,并且用EM算法学习模型参数。在传统文本分类或聚类模型中,一篇文章(document)只有一个topic(姑且翻译成主题),Hofmann提出,一篇文章可以有多个topic,这就是自然语言处理领域颇具开创性的Topic Model的思想。之所以说LDA算法继承了PLSA的衣钵,一个重要的原因就是LDA在文本建模中继承并且发展了Topic ModelTopic Model目前在自然语言理解和生物信息学方面都有应用。通过增加Topic Model,一个词语可以从属于多个Topic,一个Topic下也有多个词(word),这样就保证了一词多义和同义词的问题都可以在Topic Model下得到解决。按照维基百科的解释,主题模型(Topic Model)就是一类从大量的文档中发掘抽象的主题的统计模型。至于Topic的具体理解,有些文章将之称之为主题,有的将之解释成一个概念,一个方面

另外,与LSA依赖于矩阵运算得到结果不同,PLSA算法采用的是机器学习中以概率统计为基础的生成模型(generation model)来做文本建模和理解。这里对生成模型做一下简单的介绍。生成模型,在机器学习中的定义是显示或者隐式地对输入输出的分布建模,参考自Bishop的《Pattern Recognition and Machine Learning》。在决策论中,有三种决策方式,分别是对联合概率分布建模、对后验概率建模、和找判别函数。判别模型属于第二种,对后验概率建模,将每个观察值划分到一个类。判别函数法是思路最简单的,直接把观察值通过函数映射到分类。而生成模型,是对先验概率和类条件密度函数建模,然后标准化,进而得到后验概率估计Bishop的《Pattern Recognition and Machine Learning》提到,决策论中,对于分类问题采用的决策准则是,如果决策目标是使观测值被误分类的概率最小,那么应该选择相应的后验概率最大的分类作为决策。

 

Hofmann提出的PLSA的概率图模型如下

 

 其中D代表文档,Z代表隐含类别或者主题,W为观察到的单词,表示单词出现在文档的概率,表示文档di中出现主题zk下的单词的概率,给定主题Zk出现单词Wj的概率。并且每个主题在所有词项上服从Multinomial (多项式)分布,每个文档在所有主题上服从Multinomial (多项式)分布。

 

    关于这里的两个关于多项式分布的选择,是有其理论依据的。以每个主题在所有词项上服从Multinomial (多项式)分布为例,主要是基于:

1)在同一个主题下,每个词项的产生都可以看成一次从该主题下的所有候选词项中随机挑选出一个词项的试验。不难发现,试验之间是相互独立的。在一元语义模型下,词项之间的顺序不影响其联合分布概率,也就是如果在一个主题下选择了V个词项,那么。举一个例子,如果先挑选词项w1,后挑选词项w2,其概率为,反过来,先挑选词项w2,后挑选词项w1,其概率为,不难看到

2)每个词项都需只能从从属于该主题的词项中选择。这一条在Topic Model下是很自然的。

3)在不同的试验中,同一个词项被选中的概率是相同的。这一条是在使用多项式分布的时候需要注意的,如果不能保证,多项式分布满足不了。但是这句话不要被错误理解成在同一次的试验中,不同词项被选中的概率是相同的”

 

整个文档的生成过程是这样的:

(1) 以的概率选中文档;

(2) 以的概率选中主题;

(3) 以的概率产生一个单词。

我们可以观察到的数据就是对,而zk是隐含变量。的联合分布是


分别对应两组Multinomial 分布,我们需要估计这两组分布的参数。PLSA采用EM算法来做估计。

   需要说明的是,PLSA有时会出现过拟合的现象。所谓过拟合(Overfit),是这样一种现象:一个假设在训练数据上能够获得比其他假设更好的拟合,但是在训练数据外的数据集上却不能很好的拟合数据。此时我们就叫这个假设出现了Overfit的现象。出现这种现象的主要原因是训练数据中存在噪音或者训练数据太少。PLSA通过采用tempering heuristic来平滑参数,但是实际应用时,即使采用此方法,过拟合现象仍可能发生。

这里补充一个小插曲,Hofmann在他2004年的论文Latent semantic models for collaborative filtering中指出,他自然知道弄成LDA这种fully bayesian model更漂亮,可是为了避免高时间复杂度,他使用了tempered EM

PLSALSA的概率学延伸,这样说的依据是,LSA的核心思想是降维,只是基于SVD分解的LSA方法是借助于矩阵分解降维,而PLSA是借助于引进Topic层降维

PLSA中,求解两个多项式分布的参数,是求解的关键。注意到,对应每个文档在所有主题上所服从的多项式分布的参数,而di就表示第i文档,所以我们可以看到这个参数的规模是随着文档数的增加而增加的。在下文中,我们会看到LDA算法的参数是和文档数无关的,只和Topic个数以及以及字典中term个数有关。

 

表2   LSAPLSA的相同点和不同点



 
 

2.3 LDA算法与PLSA的关联和区别

   PLSA在文档级别没有提供一个概率模型,PLSA中每篇文档表示成一系列的主题数目,对于这些数目没有生成概率模型,将导致两个问题:

1) 模型的参数将随着语料库(corpus)的大小线性增长,继而导致过拟合的严重问题。

2) 不清楚如何对于训练集外的文档进行主题分配。

   关于“参数线性增长是如何导致过拟合”的问题,现有的论文和资料都没有做过详细解释,其实这里只举一个小例子就可以阐明。在基于最小二乘法,采用多项式拟合数据点的时候,可能使用的多项式次数高以后,最小二乘误差会最小的,但是,多项式的次数选取的过高,会出现严重的过拟合问题,在对后续数据点做预测的时候完全不可用。而这里的多项式的次数过高,会直接造成拟合时采用的参数过多(拟合一元N次多项式需要N+1个参数),实际上也是参数过多造成数据拟合时出现过拟合的一种情形。

   鉴于PLSA的局限性,LDA算法应运而生。前面提到,LDA算法继承了PLSA中采用生成模型的思路,延续了Topic Model的思想,自然LDA也具有解决一词多义和同义词问题的能力。此外,LDA也延续了PLSA中文档、主题、词项的生成过程,认为每个主题在所有词项上服从Multinomial 分布,每个文档在所有主题上服从Multinomial 分布,采用多项式分布的合理性在2.2中已经详尽说明。   

LDA的参数规模只取决于Topic个数和字典中term总数。LDA在document到topic一层引入了Dirichlet分布,这是它优于PLSA的地方,使得模型参数的数量不会随着语料库的扩大而增多。

这里取Dirichlet分布的依据是什么?首先需要说明,Dirichlet分布是多项分布的共轭先验(Conjugate priors),也就是说,先验分布为Dirichlet分布,似然函数为多项分布,那么后验分布仍为Dirichlet分布(《模式识别与机器学习》117页有证明)。但是,共轭先验又有什么好处呢?David M. Blei在文章里面说,这样的好处是fancy,或者说,好算或许,这个好算也不够准确,准确的说法是能算,也就是在共轭的前提下,这个问题可以通过编程实现求解。无论是Variational inference方法还是Gibbs Sampling都是在前面共轭的条件保证下的计算方法。所以,如果没有Dirichlet分布,后面这些推导可能都不知道怎么算。LDA把一个几乎没法求解的东西解出来了,而且极大的推动了变分推断这个东西。因此,在很多LDA的资料里都会不加解释地说,选择Dirichlet分布是因为其共轭特性大大减小了计算量”

PLSA的主题是有观察到的数据,通过最大似然估计的方法得到的,也就是从经验获取的。而LDA是假设主题服从Dirichilet分布,然后从观察数据中,优化参数,得到后验参数,使用的思想是最大后验概率。前者是频率学派的思想,后者是贝叶斯学派的思想。

 



 

参考文献

[1]Patter Recognition and Machine Learning ,Christopher M. Bishop

[2]Latent Dirichlet Allocation,David M. Blei,Journal of Machine Learning Research 3 (2003) 993-1022

[3]Parameter estimation for text analysis,Gregor Heinrich

作者:ldily110 发表于2016/7/30 0:52:54 原文链接
阅读:10 评论:0 查看评论

安卓图片轮播Banner的实现

$
0
0

图片轮播Banner现在算是比较常见的功能了,可以用来循环播放广告或者是热门内容

在这里我利用ViewPager模拟实现了Banner的功能,效果图如下:

这里写图片描述

可以看到,在刚打开应用时,显示的只有一张动漫黄昏图,且标示图片数量的圆点也只有一个,这是程序的初始状态。
然后我模拟了从服务器获取要显示的标题以及图片链接的过程,当数据获取成功后,就可以看到图片开始变换轮播了,且标示图片数量的圆点也变成了五个

新建一个工程,修改默认布局,将根布局改为FrameLayout,然后添加一个ViewPager控件,用来显示图片,TextView用来显示标题,View组件用来显示小圆点,默认可见性为Gone

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    tools:context="com.czy.banner.MainActivity">

    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:id="@+id/articleTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:gravity="left"
        android:maxLines="2"
        android:padding="20dp"
        android:textSize="20sp"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dip"
        android:gravity="center">

        <View
            android:id="@+id/v_dot0"
            style="@style/dot_style"
            android:background="@drawable/dot_focus"
            android:visibility="gone" />

        <View
            android:id="@+id/v_dot1"
            style="@style/dot_style"
            android:visibility="gone" />

        <View
            android:id="@+id/v_dot2"
            style="@style/dot_style"
            android:visibility="gone" />

        <View
            android:id="@+id/v_dot3"
            style="@style/dot_style"
            android:visibility="gone" />

        <View
            android:id="@+id/v_dot4"
            style="@style/dot_style"
            android:visibility="gone" />
    </LinearLayout>

</FrameLayout>

View用到了Style属性,所以需要在styles.xml文件下添加自定义样式dot_style,定义了圆点的大小、背景图、边距等属性

    <style name="dot_style">
        <item name="android:layout_width">7dp</item>
        <item name="android:layout_height">7dp</item>
        <item name="android:background">@drawable/dot_blur</item>
        <item name="android:layout_marginLeft">1.5dp</item>
        <item name="android:layout_marginRight">1.5dp</item>
    </style>

工程中我也用到了universal-image-loader来下载网络图片,所以需要在自定义MyApplication中配置universal-image-loader属性

/**
 * Created by ZY on 2016/7/29.
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        initImageLoader(this);
    }

    private void initImageLoader(Context context) {
        File cacheDir = StorageUtils.getOwnCacheDirectory(getApplicationContext(), "imageloader/Cache");
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
                // 缓存路径
                .diskCache(new UnlimitedDiskCache(cacheDir)).writeDebugLogs()
                // connectTimeout (10 s), readTimeout (10 s)  超时时间
                .imageDownloader(new BaseImageDownloader(context, 10 * 1000, 10 * 1000))
                // 缓存的文件数量
                .diskCacheFileCount(100)
                .build();
        ImageLoader.getInstance().init(config);
    }
}

然后在AndroidManifest.xml文件中为Application标签添加

android:name=".MyApplication"

然后再自定义一个常量类Constant,用来获取ImageLoader实例与DisplayImageOptions对象

/**
 * Created by ZY on 2016/7/29.
 * 常量
 */
public class Constant {

    public static ImageLoader getImageLoader() {
        return ImageLoader.getInstance();
    }

    public static DisplayImageOptions getDisplayImageOptions() {
        return new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.i) //设置图片在下载期间显示的图片
                .showImageForEmptyUri(R.drawable.i)//设置图片Uri为空或是错误的时候显示的图片
                .showImageOnFail(R.drawable.i)  //设置图片加载/解码过程中错误时候显示的图片
                .displayer(new FadeInBitmapDisplayer(500))//是否图片加载好后渐入的动画时间
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .build();
    }

}

再定义一个Banner实体,用来承载从网络获取到的数据

public class Banner {

    private String title;

    private String imgUrl;

    private String id;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(String imgUrl) {
        this.imgUrl = imgUrl;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

因为是利用ViewPager来实现Banner,所以还需要自定义一个Adapter继承于PagerAdapter ,作为ViewPager的适配器

/**
 * Created by ZY on 2016/7/29.
 */
public class MyAdapter extends PagerAdapter {

    private Context context;

    private List<ImageView> imageViews;

    public MyAdapter(Context context,List<ImageView> imageViews) {
        this.context = context;
        this.imageViews = imageViews;
    }

    @Override
    public int getCount() {
        return imageViews.size();
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        ImageView iv = imageViews.get(position);
        container.addView(iv);
        iv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context, position + "", Toast.LENGTH_SHORT).show();
            }
        });
        return iv;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView((View) object);
    }

    @Override
    public boolean isViewFromObject(View arg0, Object arg1) {
        return arg0 == arg1;
    }
}

在MainActivity当中,声明的变量有:

    private ViewPager viewPager;

    private TextView articleTitle;

    private List<ImageView> imageViews;

    private List<Banner> bannerList;

    private List<View> dotList;
    //当前显示的图片索引
    private int currentItem = 0;

    private View dot0;
    private View dot1;
    private View dot2;
    private View dot3;
    private View dot4;

    private boolean flag = true;

    private Handler handler = new Handler();

    private MyAdapter adapter;

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            viewPager.setCurrentItem(currentItem);
            currentItem = (currentItem + 1) % imageViews.size();
            if (flag) {
                bannerList = getNewBannerList();
                reset();
                adapter.notifyDataSetChanged();
                articleTitle.setText(bannerList.get(0).getTitle());
                flag = false;
            }
            handler.postDelayed(this, 2000);
        }
    };

初始化函数与重置函数,均会在onCreate(Bundle savedInstanceState)函数中调用,将两者分开调用的原因是bannerList 会在程序刚打开时获得一个初始值,即那一张动漫黄昏图,之后会去网络获取新数据,如果获取成功,则需要再次调用重置函数

    //初始化
    public void init() {
        bannerList = getDefaultBannerList();
        dotList = new ArrayList<>();
        viewPager = (ViewPager) findViewById(R.id.viewPager);
        articleTitle = (TextView) findViewById(R.id.articleTitle);
        dot0 = findViewById(R.id.v_dot0);
        dot1 = findViewById(R.id.v_dot1);
        dot2 = findViewById(R.id.v_dot2);
        dot3 = findViewById(R.id.v_dot3);
        dot4 = findViewById(R.id.v_dot4);
        dotList.add(dot0);
        dotList.add(dot1);
        dotList.add(dot2);
        dotList.add(dot3);
        dotList.add(dot4);
    }

    //重置
    private void reset() {
        imageViews = new ArrayList<>();
        for (int i = 0; i < bannerList.size(); i++) {
            ImageView imageView = new ImageView(this);
            imageView.setImageResource(R.drawable.i);
            imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
            Constant.getImageLoader().displayImage(bannerList.get(i).getImgUrl(),
                    imageView, Constant.getDisplayImageOptions());
            imageViews.add(imageView);
            dotList.get(i).setVisibility(View.VISIBLE);
        }
        adapter = new MyAdapter(MainActivity.this, imageViews);
        viewPager.setAdapter(adapter);
        viewPager.addOnPageChangeListener(new MyPageChangeListener());
    }

bannerList 获取默认值

//获取默认值
    private List<Banner> getDefaultBannerList() {
        List<Banner> bannerList = new ArrayList<>();
        Banner banner = new Banner();
        banner.setTitle("享受阅读的乐趣");
        banner.setId("0");
        banner.setImgUrl("");
        bannerList.add(banner);
        return bannerList;
    }

此外还需要实现ViewPager.OnPageChangeListener接口,用来在当图片切换后更新标题与红色圆点的位置

private class MyPageChangeListener implements ViewPager.OnPageChangeListener {

        private int oldPosition = 0;

        @Override
        public void onPageScrollStateChanged(int arg0) {

        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {

        }

        @Override
        public void onPageSelected(int position) {
            currentItem = position;
            Banner banner = bannerList.get(position);
            articleTitle.setText(banner.getTitle());
            dotList.get(oldPosition).setBackgroundResource(R.drawable.dot_blur);
            dotList.get(position).setBackgroundResource(R.drawable.dot_focus);
            oldPosition = position;
        }
    }

因为还需要实现自动轮播图片的功能,所以需要设置一个定时任务,这里采用handler对象的postDelayed方法

首先需要声明一个Handler 与Runnable 对象,在Runnable 对象中设置要执行的任务,即利用currentItem的值设置当前要显示的图片索引,然后利用flag设置在首次执行定时任务时为bannerList设置新值,重置Banner,而需要执行

articleTitle.setText(bannerList.get(0).getTitle());

的原因是设置标题的触发点在监听函数中,当重置了Banner后并不会触发监听函数,所以需要手动设置一次

    private Handler handler = new Handler();

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            viewPager.setCurrentItem(currentItem);
            currentItem = (currentItem + 1) % imageViews.size();
            if (flag) {
                bannerList = getNewBannerList();
                reset();
                adapter.notifyDataSetChanged();
               articleTitle.setText(bannerList.get(0).getTitle());
                flag = false;
            }
            handler.postDelayed(this, 2000);
        }
    };

为bannerList设置新值的函数,模拟从网络获取数据的过程

private List<Banner> getNewBannerList() {
        List<Banner> bannerList = new ArrayList<>();
        Banner banner1 = new Banner();
        banner1.setTitle("Snackbar的使用");
        banner1.setImgUrl("http://pic3.zhimg.com/f665508fc07c122a7d79670600ca6c9e.jpg");
        bannerList.add(banner1);
        Banner banner2 = new Banner();
        banner2.setTitle(" 实现扫码登录");
        banner2.setImgUrl("http://pic3.zhimg.com//144edd4fa57e8b0b9c70bfea5c6b5dee.jpg");
        bannerList.add(banner2);
        Banner banner3 = new Banner();
        banner3.setTitle("Android简易版天气预报app的实现(改进版)");
        banner3.setImgUrl("http://pic4.zhimg.com/ea2e46e40b74da68960775b1cbcfd3bb.jpg");
        bannerList.add(banner3);
        Banner banner4 = new Banner();
        banner4.setTitle(" 自定义抽象化标题栏");
        banner4.setImgUrl("http://pic1.zhimg.com/9213def521eb908e37c15016c9d0ed24.jpg");
        bannerList.add(banner4);
        Banner banner5 = new Banner();
        banner5.setTitle("围住神经猫(2)");
        banner5.setImgUrl("http://pic3.zhimg.com/32e1aaa65945ec773d3ffdf614c0b07e.jpg");
        bannerList.add(banner5);
        return bannerList;
    }

当中包含了要显示的标题内容与图片链接地址,依靠该图片链接就可以在重置函数reset()中为ImageView设置图片URL了

此外,定时任务的建立与取消分别在onResume()与onStop()函数当中,因为当Activuty要退出时会触发onStop()函数,所以可以在这里取消定时任务,而当暂停或停止后,想要恢复Activity均要调用onResume()函数,所以可以在在当中建立定时任务

 @Override
    protected void onResume() {
        super.onResume();
        handler.postDelayed(runnable, 2000);
    }

    @Override
    protected void onStop() {
        super.onStop();
        handler.removeCallbacks(runnable);
    }

代码下载地址:安卓图片轮播Banner的实现

作者:new_one_object 发表于2016/7/30 1:05:54 原文链接
阅读:2 评论:0 查看评论

Apache Spark入门攻略

$
0
0

摘要:本文聚焦Apache Spark入门,了解其在大数据领域的地位,覆盖Apache Spark的安装及应用程序的建立,并解释一些常见的行为和操作。

【编者按】时至今日,Spark已成为大数据领域最火的一个开源项目,具备高性能、易于使用等特性。然而作为一个年轻的开源项目,其使用上存在的挑战亦不可为不大,这里为大家分享SciSpike软件架构师Ashwini Kuntamukkala在Dzone上进行的Spark入门总结(虽然有些地方基于的是Spark 1.0版本,但仍然值得阅读)—— Apache Spark:An Engine for Large-Scale Data Processing,由OneAPM工程师翻译。


本文聚焦Apache Spark入门,了解其在大数据领域的地位,覆盖Apache Spark的安装及应用程序的建立,并解释一些常见的行为和操作。

一、 为什么要使用Apache Spark

时下,我们正处在一个“大数据”的时代,每时每刻,都有各种类型的数据被生产。而在此紫外,数据增幅的速度也在显著增加。从广义上看,这些数据包含交易数据、社交媒体内容(比如文本、图像和视频)以及传感器数据。那么,为什么要在这些内容上投入如此多精力,其原因无非就是从海量数据中提取洞见可以对生活和生产实践进行很好的指导。

在几年前,只有少部分公司拥有足够的技术力量和资金去储存和挖掘大量数据,并对其挖掘从而获得洞见。然而,被雅虎2009年开源的Apache Hadoop对这一状况产生了颠覆性的冲击——通过使用商用服务器组成的集群大幅度地降低了海量数据处理的门槛。因此,许多行业(比如Health care、Infrastructure、Finance、Insurance、Telematics、Consumer、Retail、Marketing、E-commerce、Media、 Manufacturing和Entertainment)开始了Hadoop的征程,走上了海量数据提取价值的道路。着眼Hadoop,其主要提供了两个方面的功能:

  • 通过水平扩展商用主机,HDFS提供了一个廉价的方式对海量数据进行容错存储。
  • MapReduce计算范例,提供了一个简单的编程模型来挖掘数据并获得洞见。

下图展示了MapReduce的数据处理流程,其中一个Map-Reduce step的输出将作为下一个典型Hadoop job的输入结果。

 

在整个过程中,中间结果会借助磁盘传递,因此对比计算,大量的Map-Reduced作业都受限于IO。然而对于ETL、数据整合和清理这样的用例来说,IO约束并不会产生很大的影响,因为这些场景对数据处理时间往往不会有较高的需求。然而,在现实世界中,同样存在许多对延时要求较为苛刻的用例,比如:

  1. 对流数据进行处理来做近实时分析。举个例子,通过分析点击流数据做视频推荐,从而提高用户的参与度。在这个用例中,开发者必须在精度和延时之间做平衡。
  2. 在大型数据集上进行交互式分析,数据科学家可以在数据集上做ad-hoc查询。

 

毫无疑问,历经数年发展,Hadoop生态圈中的丰富工具已深受用户喜爱,然而这里仍然存在众多问题给使用带来了挑战:

1.每个用例都需要多个不同的技术堆栈来支撑,在不同使用场景下,大量的解决方案往往捉襟见肘。

2.在生产环境中机构往往需要精通数门技术。

3.许多技术存在版本兼容性问题。

4.无法在并行job中更快地共享数据。

而通过Apache Spark,上述问题迎刃而解!Apache Spark是一个轻量级的内存集群计算平台,通过不同的组件来支撑批、流和交互式用例,如下图。

二、 关于Apache Spark

Apache Spark是个开源和兼容Hadoop的集群计算平台。由加州大学伯克利分校的AMPLabs开发,作为Berkeley Data Analytics Stack(BDAS)的一部分,当下由大数据公司Databricks保驾护航,更是Apache旗下的顶级项目,下图显示了Apache Spark堆栈中的不同组件。

 

Apache Spark的5大优势:

1.更高的性能,因为数据被加载到集群主机的分布式内存中。数据可以被快速的转换迭代,并缓存用以后续的频繁访问需求。很多对Spark感兴趣的朋友可能也会听过这样一句话——在数据全部加载到内存的情况下,Spark可以比Hadoop快100倍,在内存不够存放所有数据的情况下快Hadoop 10倍。

2.通过建立在Java、Scala、Python、SQL(应对交互式查询)的标准API以方便各行各业使用,同时还含有大量开箱即用的机器学习库。

3.与现有Hadoop v1 (SIMR) 和2.x (YARN) 生态兼容,因此机构可以进行无缝迁移。

4.方便下载和安装。方便的shell(REPL: Read-Eval-Print-Loop)可以对API进行交互式的学习。

5.借助高等级的架构提高生产力,从而可以讲精力放到计算上。

同时,Apache Spark由Scala实现,代码非常简洁。


三、安装Apache Spark

下表列出了一些重要链接和先决条件:

Current Release 1.0.1 @ http://d3kbcqa49mib13.cloudfront.net/spark-1.0.1.tgz
Downloads Page https://spark.apache.org/downloads.html
JDK Version (Required) 1.6 or higher
Scala Version (Required) 2.10 or higher
Python (Optional) [2.6, 3.0)
Simple Build Tool (Required) http://www.scala-sbt.org
Development Version git clone git://github.com/apache/spark.git
Building Instructions https://spark.apache.org/docs/latest/building-with-maven.html
Maven 3.0 or higher

如图6所示,Apache Spark的部署方式包括standalone、Hadoop V1 SIMR、Hadoop 2 YARN/Mesos。Apache Spark需求一定的Java、Scala或Python知识。这里,我们将专注standalone配置下的安装和运行。

1.安装JDK 1.6+、Scala 2.10+、Python [2.6,3] 和sbt

2.下载Apache Spark 1.0.1 Release

3.在指定目录下Untar和Unzip spark-1.0.1.tgz 

akuntamukkala@localhost~/Downloads$ pwd 
/Users/akuntamukkala/Downloads akuntamukkala@localhost~/Downloads$ tar -zxvf spark- 1.0.1.tgz -C /Users/akuntamukkala/spark

4.运行sbt建立Apache Spark

akuntamukkala@localhost~/spark/spark-1.0.1$ pwd /Users/akuntamukkala/spark/spark-1.0.1 akuntamukkala@localhost~/spark/spark-1.0.1$ sbt/sbt assembly

5.发布Scala的Apache Spark standalone REPL

/Users/akuntamukkala/spark/spark-1.0.1/bin/spark-shell

如果是Python

/Users/akuntamukkala/spark/spark-1.0.1/bin/ pyspark

6.查看SparkUI @ http://localhost:4040

四、Apache Spark的工作模式

Spark引擎提供了在集群中所有主机上进行分布式内存数据处理的能力,下图显示了一个典型Spark job的处理流程。

 

下图显示了Apache Spark如何在集群中执行一个作业。

 

Master控制数据如何被分割,利用了数据本地性,并在Slaves上跟踪所有分布式计算。在某个Slave不可用时,其存储的数据会分配给其他可用的Slaves。虽然当下(1.0.1版本)Master还存在单点故障,但后期必然会被修复。


五、弹性分布式数据集(Resilient Distributed Dataset,RDD)

弹性分布式数据集(RDD,从Spark 1.3版本开始已被DataFrame替代)是Apache Spark的核心理念。它是由数据组成的不可变分布式集合,其主要进行两个操作:transformation和action。Transformation是类似在RDD上做 filter()、map()或union() 以生成另一个RDD的操作,而action则是count()、first()、take(n)、collect() 等促发一个计算并返回值到Master或者稳定存储系统的操作。Transformations一般都是lazy的,直到action执行后才会被执行。Spark Master/Driver会保存RDD上的Transformations。这样一来,如果某个RDD丢失(也就是salves宕掉),它可以快速和便捷地转换到集群中存活的主机上。这也就是RDD的弹性所在。

下图展示了Transformation的lazy:


我们可以通过下面示例来理解这个概念:从文本中发现5个最常用的word。下图显示了一个可能的解决方案。

 

在上面命令中,我们对文本进行读取并且建立字符串的RDD。每个条目代表了文本中的1行。

scala> val hamlet = sc.textFile(“/Users/akuntamukkala/temp/gutenburg.txt”)
hamlet: org.apache.spark.rdd.RDD[String] = MappedRDD[1] at textFile at <console>:12

scala> val topWordCount = hamlet.flatMap(str=>str.split(“ “)). filter(!_.isEmpty).map(word=>(word,1)).reduceByKey(_+_).map{case (word, count) => (count, word)}.sortByKey(false)
topWordCount: org.apache.spark.rdd.RDD[(Int, String)] = MapPartitionsRDD[10] at sortByKey at <console>:14

1. 通过上述命令我们可以发现这个操作非常简单——通过简单的Scala API来连接transformations和actions。

2. 可能存在某些words被1个以上空格分隔的情况,导致有些words是空字符串,因此需要使用filter(!_.isEmpty)将它们过滤掉。

3. 每个word都被映射成一个键值对:map(word=>(word,1))。

4. 为了合计所有计数,这里需要调用一个reduce步骤——reduceByKey(_+_)。 _+_ 可以非常便捷地为每个key赋值。

5. 我们得到了words以及各自的counts,下一步需要做的是根据counts排序。在Apache Spark,用户只能根据key排序,而不是值。因此,这里需要使用map{case (word, count) => (count, word)}将(word, count)流转到(count, word)。

6. 需要计算最常用的5个words,因此需要使用sortByKey(false)做一个计数的递减排序。

上述命令包含了一个.take(5) (an action operation, which triggers computation)和在 /Users/akuntamukkala/temp/gutenburg.txt文本中输出10个最常用的words。在Python shell中用户可以实现同样的功能。

RDD lineage可以通过toDebugString(一个值得记住的操作)来跟踪。

scala> topWordCount.take(5).foreach(x=>println(x))
(1044,the)
(730,and)
(679,of)
(648,to)
(511,I)

常用的Transformations:

Transformation & Purpose Example & Result
filter(func) Purpose: new RDD by selecting those data elements on which func returns true scala> val rdd = sc.parallelize(List(“ABC”,”BCD”,”DEF”)) scala> val filtered = rdd.filter(_.contains(“C”)) scala> filtered.collect() Result: 
Array[String] = Array(ABC, BCD)
map(func) Purpose: return new RDD by applying func on each data element scala> val rdd=sc.parallelize(List(1,2,3,4,5)) scala> val times2 = rdd.map(_*2) scala> times2.collect() Result: 
Array[Int] = Array(2, 4, 6, 8, 10)
flatMap(func) Purpose: Similar to map but func returns a Seq instead of a value. For example, mapping a sentence into a Seq of words scala> val rdd=sc.parallelize(List(“Spark is awesome”,”It is fun”)) scala> val fm=rdd.flatMap(str=>str.split(“ “)) scala> fm.collect() Result: 
Array[String] = Array(Spark, is, awesome, It, is, fun)
reduceByKey(func,[numTasks]) Purpose: To aggregate values of a key using a function. “numTasks” is an optional parameter to specify number of reduce tasks scala> val word1=fm.map(word=>(word,1)) scala> val wrdCnt=word1.reduceByKey(_+_) scala> wrdCnt.collect() Result: 
Array[(String, Int)] = Array((is,2), (It,1), (awesome,1), (Spark,1), (fun,1))
groupByKey([numTasks]) Purpose: To convert (K,V) to (K,Iterable<V>) scala> val cntWrd = wrdCnt.map{case (word, count) => (count, word)} scala> cntWrd.groupByKey().collect() Result: 
Array[(Int, Iterable[String])] = Array((1,ArrayBuffer(It, awesome, Spark, fun)), (2,ArrayBuffer(is)))
distinct([numTasks]) Purpose: Eliminate duplicates from RDD scala> fm.distinct().collect() Result: 
Array[String] = Array(is, It, awesome, Spark, fun)

常用的集合操作:

Transformation and Purpose Example and Result
union() 
Purpose: new RDD containing all elements from source RDD and argument.
Scala> val rdd1=sc.parallelize(List(‘A’,’B’)) 
scala> val rdd2=sc.parallelize(List(‘B’,’C’)) 
scala> rdd1.union(rdd2).collect() 
Result: 
Array[Char] = Array(A, B, B, C)
intersection() 
Purpose: new RDD containing only common elements from source RDD and argument.
Scala> rdd1.intersection(rdd2).collect() 
Result: 
Array[Char] = Array(B)
cartesian() 
Purpose: new RDD cross product of all elements from source RDD and argument
Scala> rdd1.cartesian(rdd2).collect() 
Result: 
Array[(Char, Char)] = Array((A,B), (A,C), (B,B), (B,C))
subtract() 
Purpose: new RDD created by removing data elements in source RDD in common with argument
scala> rdd1.subtract(rdd2).collect() Result: 
Array[Char] = Array(A)
join(RDD,[numTasks]) 
Purpose: When invoked on (K,V) and (K,W), this operation creates a new RDD of (K, (V,W))
scala> val personFruit = sc.parallelize(Seq((“Andy”, “Apple”), (“Bob”, “Banana”), (“Charlie”, “Cherry”), (“Andy”,”Apricot”))) 
scala> val personSE = sc.parallelize(Seq((“Andy”, “Google”), (“Bob”, “Bing”), (“Charlie”, “Yahoo”), (“Bob”,”AltaVista”))) 
scala> personFruit.join(personSE).collect() 
Result: 
Array[(String, (String, String))] = Array((Andy,(Apple,Google)), (Andy,(Apricot,Google)), (Charlie,(Cherry,Yahoo)), (Bob,(Banana,Bing)), (Bob,(Banana,AltaVista)))
cogroup(RDD,[numTasks]) 
Purpose: To convert (K,V) to (K,Iterable<V>)
scala> personFruit.cogroup(personSe).collect() 
Result: 
Array[(String, (Iterable[String], Iterable[String]))] = Array((Andy,(ArrayBuffer(Apple, Apricot),ArrayBuffer(google))), (Charlie,(ArrayBuffer(Cherry),ArrayBuffer(Yahoo))), (Bob,(ArrayBuffer(Banana),ArrayBuffer(Bing, AltaVista))))

更多transformations信息,请查看http://spark.apache.org/docs/latest/programming-guide.html#transformations

常用的actions

Action & Purpose Example & Result
count() Purpose: get the number of data elements in the RDD scala> val rdd = sc.parallelize(list(‘A’,’B’,’c’)) scala> rdd.count() Result: 
long = 3
collect() Purpose: get all the data elements in an RDD as an array scala> val rdd = sc.parallelize(list(‘A’,’B’,’c’)) scala> rdd.collect() Result: 
Array[char] = Array(A, B, c)
reduce(func) Purpose: Aggregate the data elements in an RDD using this function which takes two arguments and returns one scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.reduce(_+_) Result: 
Int = 10
take (n) Purpose: : fetch first n data elements in an RDD. computed by driver program. Scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.take(2) Result: 
Array[Int] = Array(1, 2)
foreach(func) Purpose: execute function for each data element in RDD. usually used to update an accumulator(discussed later) or interacting with external systems. Scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.foreach(x=>println(“%s*10=%s”. format(x,x*10))) Result: 
1*10=10 4*10=40 3*10=30 2*10=20
first() Purpose: retrieves the first data element in RDD. Similar to take(1) scala> val rdd = sc.parallelize(list(1,2,3,4)) scala> rdd.first() Result: 
Int = 1
saveAsTextFile(path) Purpose: Writes the content of RDD to a text file or a set of text files to local file system/ HDFS scala> val hamlet = sc.textFile(“/users/akuntamukkala/ temp/gutenburg.txt”) scala> hamlet.filter(_.contains(“Shakespeare”)). saveAsTextFile(“/users/akuntamukkala/temp/ filtered”) Result: 
akuntamukkala@localhost~/temp/filtered$ ls _SUCCESS part-00000 part-00001

更多actions参见http://spark.apache.org/docs/latest/programming-guide.html#actions 

六、RDD持久性

Apache Spark中一个主要的能力就是在集群内存中持久化/缓存RDD。这将显著地提升交互速度。下表显示了Spark中各种选项。

Storage Level Purpose
MEMORY_ONLY (Default level) This option stores RDD in available cluster memory as deserialized Java objects. Some partitions may not be cached if there is not enough cluster memory. Those partitions will be recalculated on the fly as needed.
MEMORY_AND_DISK This option stores RDD as deserialized Java objects. If RDD does not fit in cluster memory, then store those partitions on the disk and read them as needed.
MEMORY_ONLY_SER This options stores RDD as serialized Java objects (One byte array per partition). This is more CPU intensive but saves memory as it is more space efficient. Some partitions may not be cached. Those will be recalculated on the fly as needed.
MEMORY_ONLY_DISK_SER This option is same as above except that disk is used when memory is not sufficient.
DISC_ONLY This option stores the RDD only on the disk
MEMORY_ONLY_2, MEMORY_AND_DISK_2, etc. Same as other levels but partitions are replicated on 2 slave nodes

上面的存储等级可以通过RDD. cache()操作上的 persist()操作访问,可以方便地指定MEMORY_ONLY选项。关于持久化等级的更多信息,可以访问这里http://spark.apache.org/docs/latest/programming-guide.html#rdd-persistence。

Spark使用Least Recently Used (LRU)算法来移除缓存中旧的、不常用的RDD,从而释放出更多可用内存。同样还提供了一个unpersist() 操作来强制移除缓存/持久化的RDD。

七、变量共享

Accumulators。Spark提供了一个非常便捷地途径来避免可变的计数器和计数器同步问题——Accumulators。Accumulators在一个Spark context中通过默认值初始化,这些计数器在Slaves节点上可用,但是Slaves节点不能对其进行读取。它们的作用就是来获取原子更新,并将其转发到Master。Master是唯一可以读取和计算所有更新合集的节点。举个例子:

akuntamukkala@localhost~/temp$ cat output.log
error
warning
info
trace
error
info
info
scala> val nErrors=sc.accumulator(0.0)
scala> val logs = sc.textFile(“/Users/akuntamukkala/temp/output.log”)
scala> logs.filter(_.contains(“error”)).foreach(x=>nErrors+=1)
scala> nErrors.value
Result:Int = 2

Broadcast Variables。实际生产中,通过指定key在RDDs上对数据进行合并的场景非常常见。在这种情况下,很可能会出现给slave nodes发送大体积数据集的情况,让其负责托管需要做join的数据。因此,这里很可能存在巨大的性能瓶颈,因为网络IO比内存访问速度慢100倍。为了解决这个问题,Spark提供了Broadcast Variables,如其名称一样,它会向slave nodes进行广播。因此,节点上的RDD操作可以快速访问Broadcast Variables值。举个例子,期望计算一个文件中所有路线项的运输成本。通过一个look-up table指定每种运输类型的成本,这个look-up table就可以作为Broadcast Variables。

akuntamukkala@localhost~/temp$ cat packagesToShip.txt ground
express
media
priority
priority
ground
express
media
scala> val map = sc.parallelize(Seq((“ground”,1),(“med”,2), (“priority”,5),(“express”,10))).collect().toMap
map: scala.collection.immutable.Map[String,Int] = Map(ground -> 1, media -> 2, priority -> 5, express -> 10)
scala> val bcMailRates = sc.broadcast(map)

上述命令中,我们建立了一个broadcast variable,基于服务类别成本的map。

scala> val pts = sc.textFile(“/Users/akuntamukkala/temp/packagesToShip.txt”)

在上述命令中,我们通过broadcast variable的mailing rates来计算运输成本。

scala> pts.map(shipType=>(shipType,1)).reduceByKey(_+_). map{case (shipType,nPackages)=>(shipType,nPackages*bcMailRates. value(shipType))}.collect()

通过上述命令,我们使用accumulator来累加所有运输的成本。详细信息可通过下面的PDF查看http://ampcamp.berkeley.edu/wp-content/uploads/2012/06/matei-zaharia-amp-camp-2012-advanced-spark.pdf。

八、Spark SQL

通过Spark Engine,Spark SQL提供了一个便捷的途径来进行交互式分析,使用一个被称为SchemaRDD类型的RDD。SchemaRDD可以通过已有RDDs建立,或者其他外部数据格式,比如Parquet files、JSON数据,或者在Hive上运行HQL。SchemaRDD非常类似于RDBMS中的表格。一旦数据被导入SchemaRDD,Spark引擎就可以对它进行批或流处理。Spark SQL提供了两种类型的Contexts——SQLContext和HiveContext,扩展了SparkContext的功能。

SparkContext提供了到简单SQL parser的访问,而HiveContext则提供了到HiveQL parser的访问。HiveContext允许企业利用已有的Hive基础设施。

这里看一个简单的SQLContext示例。

下面文本中的用户数据通过“|”来分割。

John Smith|38|M|201 East Heading Way #2203,Irving, TX,75063 Liana Dole|22|F|1023 West Feeder Rd, Plano,TX,75093 Craig Wolf|34|M|75942 Border Trail,Fort Worth,TX,75108 John Ledger|28|M|203 Galaxy Way,Paris, TX,75461 Joe Graham|40|M|5023 Silicon Rd,London,TX,76854

定义Scala case class来表示每一行:

case class Customer(name:String,age:Int,gender:String,address: String)

下面的代码片段体现了如何使用SparkContext来建立SQLContext,读取输入文件,将每一行都转换成SparkContext中的一条记录,并通过简单的SQL语句来查询30岁以下的男性用户。

val sparkConf = new SparkConf().setAppName(“Customers”)
val sc = new SparkContext(sparkConf)
val sqlContext = new SQLContext(sc)
val r = sc.textFile(“/Users/akuntamukkala/temp/customers.txt”) val records = r.map(_.split(‘|’))
val c = records.map(r=>Customer(r(0),r(1).trim.toInt,r(2),r(3))) c.registerAsTable(“customers”)
sqlContext.sql(“select * from customers where gender=’M’ and age <
            30”).collect().foreach(println) Result:[John Ledger,28,M,203 Galaxy Way,Paris,
            TX,75461]

更多使用SQL和HiveQL的示例请访问下面链接https://spark.apache.org/docs/latest/sql-programming-guide.html、https://databricks-training.s3.amazonaws.com/data-exploration-using-spark-sql.html。

九、Spark Streaming

Spark Streaming提供了一个可扩展、容错、高效的途径来处理流数据,同时还利用了Spark的简易编程模型。从真正意义上讲,Spark Streaming会将流数据转换成micro batches,从而将Spark批处理编程模型应用到流用例中。这种统一的编程模型让Spark可以很好地整合批量处理和交互式流分析。下图显示了Spark Streaming可以从不同数据源中读取数据进行分析。

 

Spark Streaming中的核心抽象是Discretized Stream(DStream)。DStream由一组RDD组成,每个RDD都包含了规定时间(可配置)流入的数据。图12很好地展示了Spark Streaming如何通过将流入数据转换成一系列的RDDs,再转换成DStream。每个RDD都包含两秒(设定的区间长度)的数据。在Spark Streaming中,最小长度可以设置为0.5秒,因此处理延时可以达到1秒以下。

Spark Streaming同样提供了 window operators,它有助于更有效率在一组RDD( a rolling window of time)上进行计算。同时,DStream还提供了一个API,其操作符(transformations和output operators)可以帮助用户直接操作RDD。下面不妨看向包含在Spark Streaming下载中的一个简单示例。示例是在Twitter流中找出趋势hashtags,详见下面代码。

spark-1.0.1/examples/src/main/scala/org/apache/spark/examples/streaming/TwitterPopularTags.scala
val sparkConf = new SparkConf().setAppName(“TwitterPopularTags”)
val ssc = new StreamingContext(sparkConf, Seconds(2))
val stream = TwitterUtils.createStream(ssc, None, filters)

上述代码用于建立Spark Streaming Context。Spark Streaming将在DStream中建立一个RDD,包含了每2秒流入的tweets。

val hashTags = stream.flatMap(status => status.getText.split(“ “).filter(_.startsWith(“#”)))

上述代码片段将Tweet转换成一组words,并过滤出所有以a#开头的。

val topCounts60 = hashTags.map((_, 1)).reduceByKeyAndWindow(_ + _, Seconds(60)).map{case (topic, count) => (count, topic)}. transform(_.sortByKey(false))

上述代码展示了如何整合计算60秒内一个hashtag流入的总次数。

topCounts60.foreachRDD(rdd => {
val topList = rdd.take(10)
println(“\nPopular topics in last 60 seconds (%s
total):”.format(rdd.count())) topList.foreach{case (count, tag) => println(“%s (%s
tweets)”.format(tag, count))} })

上面代码将找出top 10趋势tweets,然后将其打印。

ssc.start()

上述代码让Spark Streaming Context 开始检索tweets。一起聚焦一些常用操作,假设我们正在从一个socket中读入流文本。

al lines = ssc.socketTextStream(“localhost”, 9999, StorageLevel.MEMORY_AND_DISK_SER)

 

更多operators请访问http://spark.apache.org/docs/latest/streaming-programming-guide.html#transformations

Spark Streaming拥有大量强大的output operators,比如上文提到的 foreachRDD(),了解更多可访问   http://spark.apache.org/docs/latest/streaming-programming-guide.html#output-operations

十、附加学习资源

原文链接:Apache Spark:An Engine for Large-Scale Data Processing (责编/仲浩)



作者:zhengyongqianluck 发表于2016/7/30 1:08:45 原文链接
阅读:4 评论:0 查看评论

JSP---JSP中4个容器-pageContext使用

$
0
0

这里重点只讲pageContext容器的用法哦。
因为另外的3个容器(request,session,application)在前面的servlet中已经演示过很多遍了


  容器                 作用域
pageContex         仅仅是当前页面,无法传参
request            当前页面,可以传参
session            同一个JSESSIONID共用一个
application       只要服务器还没重新启动,就一直存在

详细介绍:

pageContext – 它的作用范围仅为当前JSP页面。

request – 对于用户的一次请求有效,请求/响应结束即消失。
更多细节:
一个请求通过Servlet访问资源,在Servlet中将数据封装到request中,这在单位中是通常的做法。必须记住、必须记住、必须记住。
然后将请求转发到JSP页面,在从JSP页面上将封装到request中的信息取出。MVC
注意我上面说的是转发,而不是重定向。
对于用户的一次请求,并且请求完成后,数据将不再使用可使用request进行封装,以节省内存。

session – 对于用户的一次会话有效,通常我们用此域来封装用户登录的信息。也必须记住。

application – 在整个Web项目的生命周期内有效,不建议使用或谨慎使用。实际项目中根本不用。
开发原则 – 能用小的域尽量使用小的域。

setAttribute()和getAttribute:

第一种:

index.jsP;

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
  </head>

  <body>
    <h2>演示一下JSP中的4个容器---重点是pageContext,因为之前3个在servlet中学了</h2>
    <%
        //从小到大的4个容器
        pageContext.setAttribute("name", "page-jack");
        request.setAttribute("name", "request-jack");
        session.setAttribute("name", "session-jack");
        application.setAttribute("name", "application-jack");
    %>

    OKOK-------<br/>

    <%
        //读取4个容器中的name属性值
        out.println(pageContext.getAttribute("name"));
        out.println("<br/>");
        out.println(request.getAttribute("name"));
        out.println("<br/>");
        out.println(session.getAttribute("name"));
        out.println("<br/>");
        out.println(application.getAttribute("name"));
    %>

  </body>
</html>

演示结果:

演示用pageContext设置4个容器的属性:

index.jsp:

<%
        //从小到大的4个容器
        //这一段的功能等价于那4个容器设置属性,这里全部通过pageContext.setAttribute()实现
        pageContext.setAttribute("name", "PAGE-Jack",PageContext.PAGE_SCOPE);
        pageContext.setAttribute("name", "REQUEST-Jack",PageContext.REQUEST_SCOPE);
        pageContext.setAttribute("name", "SESSION-Jack",PageContext.SESSION_SCOPE);
        pageContext.setAttribute("name", "APPLICATION-Jack",PageContext.APPLICATION_SCOPE);
    %>
    <br/>
    <%
    //这一段的功能等价于之前用4个容器分别读取属性值,这里全部通过pageContext.getAttribute()实现
        out.println( pageContext.getAttribute("name", PageContext.PAGE_SCOPE) );
        out.println("<br/>");
        out.println( pageContext.getAttribute("name", PageContext.REQUEST_SCOPE) );
        out.println("<br/>");
        out.println( pageContext.getAttribute("name", PageContext.SESSION_SCOPE) );
        out.println("<br/>");
        out.println( pageContext.getAttribute("name", PageContext.APPLICATION_SCOPE) );
    %>

演示结果:

演示pageContext.findAttribute():

pageContext.findAttribute()方法会依次从pageContext、request、session和application中(页面、请求、会话、app)查找对应的属性,找到一个,后面的就不会再去找了。如果没有就返回null.

    <%
        out.println( pageContext.findAttribute("name") );
    %>

演示结果:

顺便提一下ContentType与pageEncoding的区别:

pageEncoding是jsp文件本身的编码 ,把jsp文件编译成java的时候给编译器用的 。
contentType的charset是指服务器发送给客户端时的内容编码 ,是浏览器解析网页的时候用的
如果两个任意设置了其中一个,另一个即会与此保持一致。但,contentType除可以设置charset外,还可以设置MIME类型,如text/html

作者:qq_26525215 发表于2016/7/30 1:30:03 原文链接
阅读:0 评论:0 查看评论

项目开发经验谈:如何成为出色的开发人员

$
0
0

前言:之所以有此一文,不是空穴来风,也不是故意的哗众取宠,而是最近的一些所见,所感。在本文中总结出来,希望对大家有帮助。

  因为一些工作原因,其他的系列文章没有接着写下去,还望大家见谅。

  不要成为代码的机器

  开发人员的事情就是coding,没日没夜的coding,而且大家都是这样在coding,但是效果和结局就不一样:有人coding了N多年,技术还是原地踏步,编写出来的代码还是bug连连;有人coding就变成了技术骨干,甚至有人成为了CTO, 架构师等。

  为什么?http://cv.qiaobutang.com/post/579c8eef0cf2faf95392e130
http://cv.qiaobutang.com/post/579c8edc0cf264e620bcc233
http://cv.qiaobutang.com/post/579c8ec80cf2faf95392e11d
http://cv.qiaobutang.com/post/579c8ec70cf2faf95392e11c
http://cv.qiaobutang.com/post/579c8eab0cf2faf95392e107
http://cv.qiaobutang.com/post/579c8e850cf2faf95392e0f8
http://cv.qiaobutang.com/post/579c8e800cf264e620bcc21a
http://cv.qiaobutang.com/post/579c8e800cf2faf95392e0f5
http://cv.qiaobutang.com/post/579c8e590cf2faf95392e0e1
http://cv.qiaobutang.com/post/579c8e440cf2faf95392e0d9
http://cv.qiaobutang.com/post/579c8e3a0cf2faf95392e0d5
http://cv.qiaobutang.com/post/579c8e380cf264e620bcc202
http://cv.qiaobutang.com/post/579c8e040cf2faf95392e0b7
http://cv.qiaobutang.com/post/579c8dff0cf2faf95392e0b5
http://cv.qiaobutang.com/post/579c8de90cf2faf95392e0ad
http://cv.qiaobutang.com/post/579c8de80cf264e620bcc1e3
http://cv.qiaobutang.com/post/579c8dc30cf2faf95392e09d
http://cv.qiaobutang.com/post/579c8dbd0cf2faf95392e099
http://cv.qiaobutang.com/post/579c8d970cf2faf95392e088
http://cv.qiaobutang.com/post/579c8d920cf264e620bcc1cb
http://cv.qiaobutang.com/post/579c8d830cf2faf95392e080
http://cv.qiaobutang.com/post/579c8d720cf2faf95392e07a
http://cv.qiaobutang.com/post/579c8d4b0cf264e620bcc1ac
http://cv.qiaobutang.com/post/579c8d430cf2faf95392e06c
http://cv.qiaobutang.com/post/579c8d3a0cf2faf95392e064
http://cv.qiaobutang.com/post/579c8d200cf2faf95392e056
http://cv.qiaobutang.com/post/579c8d020cf2faf95392e04b
http://cv.qiaobutang.com/post/579c8d010cf264e620bcc198
http://cv.qiaobutang.com/post/579c8ceb0cf2faf95392e045
http://cv.qiaobutang.com/post/579c8cc30cf2faf95392e02f
http://cv.qiaobutang.com/post/579c8cc20cf2faf95392e02e
http://cv.qiaobutang.com/post/579c8ca90cf264e620bcc17d
http://cv.qiaobutang.com/post/579c8ca10cf2faf95392e01e
http://cv.qiaobutang.com/post/579c8c810cf2faf95392e00e
http://cv.qiaobutang.com/post/579c8c7f0cf2faf95392e00c
http://cv.qiaobutang.com/post/579c8c590cf2faf95392dfe7
http://cv.qiaobutang.com/post/579c8c520cf264e620bcc15f
http://cv.qiaobutang.com/post/579c8c3f0cf2faf95392dfd8
http://cv.qiaobutang.com/post/579c8c2b0cf2faf95392dfcd
http://cv.qiaobutang.com/post/579c8c050cf2faf95392dfc1
http://cv.qiaobutang.com/post/579c8bfe0cf2faf95392dfbf
http://cv.qiaobutang.com/post/579c8bf50cf264e620bcc140
http://cv.qiaobutang.com/post/579c8bd80cf2faf95392dfad
http://cv.qiaobutang.com/post/579c8bbd0cf2faf95392df99
http://cv.qiaobutang.com/post/579c8bb00cf2faf95392df93
http://cv.qiaobutang.com/post/579c8b9f0cf264e620bcc110
http://cv.qiaobutang.com/post/579c8b7d0cf2faf95392df79
http://cv.qiaobutang.com/post/579c8b7d0cf2faf95392df78
http://cv.qiaobutang.com/post/579c8b5e0cf2faf95392df60
http://cv.qiaobutang.com/post/579c8b3f0cf264e620bcc0ef
http://cv.qiaobutang.com/post/579c8b3c0cf2faf95392df52
http://cv.qiaobutang.com/post/579c8b0d0cf2faf95392df2f
http://cv.qiaobutang.com/post/579c8b070cf2faf95392df2a
http://cv.qiaobutang.com/post/579c8afb0cf2faf95392df23
http://cv.qiaobutang.com/post/579c8af80cf264e620bcc0d0
http://cv.qiaobutang.com/post/579c8ac20cf2faf95392df04
http://cv.qiaobutang.com/post/579c8abc0cf2faf95392defe
http://cv.qiaobutang.com/post/579c8ab10cf2faf95392def9
http://cv.qiaobutang.com/post/579c8ab00cf264e620bcc0af
http://cv.qiaobutang.com/post/579c8a7b0cf2faf95392dee5
http://cv.qiaobutang.com/post/579c8a690cf264e620bcc099
http://cv.qiaobutang.com/post/579c8a5a0cf2faf95392ded4
http://cv.qiaobutang.com/post/579c8a480cf2faf95392dec7
http://cv.qiaobutang.com/post/579c8a3a0cf2faf95392dec2
http://cv.qiaobutang.com/post/579c8a030cf264e620bcc06f
http://cv.qiaobutang.com/post/579c89f90cf2faf95392dea5
http://cv.qiaobutang.com/post/579c89f50cf2faf95392dea1
http://cv.qiaobutang.com/post/579c89f10cf2faf95392de9f
http://cv.qiaobutang.com/post/579c89b90cf2faf95392de8c
http://cv.qiaobutang.com/post/579c89a60cf264e620bcc059
http://cv.qiaobutang.com/post/579c899f0cf2faf95392de83
http://cv.qiaobutang.com/post/579c899d0cf2faf95392de81
http://cv.qiaobutang.com/post/579c897b0cf2faf95392de6a
http://cv.qiaobutang.com/post/579c895a0cf264e620bcc040
http://cv.qiaobutang.com/post/579c89530cf2faf95392de5e
http://cv.qiaobutang.com/post/579c89500cf2faf95392de59
http://cv.qiaobutang.com/post/579c893d0cf2faf95392de4e
http://cv.qiaobutang.com/post/579c89070cf264e620bcc022
http://cv.qiaobutang.com/post/579c88fd0cf2faf95392de2c
http://cv.qiaobutang.com/post/579c88f90cf2faf95392de28
http://cv.qiaobutang.com/post/579c88f90cf2faf95392de27
http://cv.qiaobutang.com/post/579c88be0cf2faf95392de07
http://cv.qiaobutang.com/post/579c88af0cf264e620bcbfff
http://cv.qiaobutang.com/post/579c88a60cf2faf95392ddff
http://cv.qiaobutang.com/post/579c88980cf2faf95392ddf9
http://cv.qiaobutang.com/post/579c887f0cf2faf95392dde8
http://cv.qiaobutang.com/post/579c885c0cf264e620bcbfd7
http://cv.qiaobutang.com/post/579c88560cf2faf95392ddcf
http://cv.qiaobutang.com/post/579c88480cf2faf95392ddc6
http://cv.qiaobutang.com/post/579c88410cf2faf95392ddc4
http://cv.qiaobutang.com/post/579c88120cf264e620bcbfbf
http://cv.qiaobutang.com/post/579c88050cf2faf95392dd9e
http://cv.qiaobutang.com/post/579c88020cf2faf95392dd9c
http://cv.qiaobutang.com/post/579c87f70cf2faf95392dd99
http://cv.qiaobutang.com/post/579c87c80cf264e620bcbfa6
http://cv.qiaobutang.com/post/579c87c10cf2faf95392dd86
http://cv.qiaobutang.com/post/579c87bc0cf2faf95392dd82
http://cv.qiaobutang.com/post/579c87a80cf2faf95392dd72
http://cv.qiaobutang.com/post/579c87760cf264e620bcbf8b
http://cv.qiaobutang.com/post/579c87760cf2faf95392dd5a
http://cv.qiaobutang.com/post/579c875c0cf2faf95392dd4e
http://cv.qiaobutang.com/post/579c87310cf2faf95392dd38
http://cv.qiaobutang.com/post/579c872c0cf264e620bcbf78
http://cv.qiaobutang.com/post/579c87090cf2faf95392dd22
http://cv.qiaobutang.com/post/579c86fa0cf2faf95392dd1b
http://cv.qiaobutang.com/post/579c86e20cf2faf95392dd0f
http://cv.qiaobutang.com/post/579c86dc0cf264e620bcbf55
http://cv.qiaobutang.com/post/579c86c60cf2faf95392dd04
http://cv.qiaobutang.com/post/579c86980cf264e620bcbf35
http://cv.qiaobutang.com/post/579c86930cf2faf95392dce6
http://cv.qiaobutang.com/post/579c86760cf2faf95392dcd0
http://cv.qiaobutang.com/post/579c86530cf264e620bcbf19
http://cv.qiaobutang.com/post/579c86230cf2faf95392dcb3
http://cv.qiaobutang.com/post/579c86080cf264e620bcbefc
http://cv.qiaobutang.com/post/579c85c70cf2faf95392dc9a
http://cv.qiaobutang.com/post/579c85c00cf264e620bcbee1
http://cv.qiaobutang.com/post/579c856d0cf264e620bcbec4
http://cv.qiaobutang.com/post/579c85310cf2faf95392dc73
http://cv.qiaobutang.com/post/579c85270cf264e620bcbead
http://cv.qiaobutang.com/post/579c84df0cf264e620bcbe96
http://cv.qiaobutang.com/post/579c849c0cf264e620bcbe6a
http://cv.qiaobutang.com/post/579c84540cf264e620bcbe4c
http://cv.qiaobutang.com/post/579c84150cf264e620bcbc79
http://cv.qiaobutang.com/post/579c84100cf2faf95392dc0c
http://cv.qiaobutang.com/post/579c83d20cf264e620bcb83e
http://cv.qiaobutang.com/post/579c83950cf264e620bcb819
http://cv.qiaobutang.com/post/579c83560cf264e620bcb803
http://cv.qiaobutang.com/post/579c82e10cf264e620bcb7e4

  首先从一个小的故事说起:一个项目,分配给了项目组的人开发。于是大家就热火朝天的干了起来。当时,就发现了一个现象:任务已分配完成之后,有人就开始coding了,噼里啪啦的开始敲代码,不久之后又开始噼里啪啦的改代码,然后又开始不断的调试,找出bug,然后修改bug。很快,这个迭代的期限就到了,原计划要完成的功能很多没有实现,有的人也确实做完了,问题也一大堆,有人也确实完成了,没有bug,但是在审查他的代码的时候,就是看不懂。

  这里想起了自己刚刚步入IT开发行业时候的情景。总是希望尽快的把事情搞完,因为只要没有做完,就拖了项目的后腿,很可能被leader训导,甚至可能被认为没有能力而被开除。在写代码的过程中,发现一点:尽管写代码的速度是快了,但是在写完了之后,每次编译,都发现错误,编译通过了吧,逻辑又有错误,然后就这样不断的缝缝补补,功能是完成了,但是心里有一种想法:以后千万不要让我来看和来改这段代码。因为代码写的很烂,烂的连自己都不想看,而且很多实现的方式也是很诡异。反正结果是:功能完成了。其实自己心里也很清楚,在写代码的时候,脑子有点糊,而且写着写着就不知道写什么了。

  后来慢慢的发现:如果再这样下去,对自己的发展是没有好处的,而且原本认为很有技术含量的编程活动也变成了一种没有技术含量体力活,有时候甚至不用脑子。

  就如软件开发中的需求一样:变化。

  人也要:改变。

  所以在之后的项目开发过程中,就给自己定了一个目标:写完一个小功能的代码,确保一次就编译通过(当然,在写代码的时候肯定得注意逻辑,但是偏重在使得编译通过),于是,在开发的时候,就开始“用心”了,或者说是更加的细心了。

  一段时间的磨练之后,这个目标达到了,而且还超出了期望的值:写完一个大的功能代码之后,编译也没有错误。

  所以这里悟出了一点:同样是做事情,做的也是同一件事,目标不同,结果就不一样。

  这样之后,写的代码质量确实是提高了,但是另外的问题又出来了:写出来的代码总是在缝缝补补,总是这里差一点,那里差一点。很多地方很欠考虑。

  怎么办?

  发现了怎么一个问题:每次在任务分配了之后,就开始coding。这没有错。大家都这么做的。但是有一点:每次在实现一个功能的时候,总是一边写代码,一边思考,就这样,一步步的把功能实现。其实这就是问题所在。

   就好比下棋,你总是走一步算一步,但是你的对手在走一步的时候,已经想到了后面的3步,4步,甚至更多步怎么走。如果你和这样的对手下棋,输家常常是你。

  写代码也是一样的,不要走一步算一步。在写代码之前,首先从业务上,要把这个功能的流程搞清楚,然后再考虑:如果实现这个流程,要怎么写代码。然后在开始写代码,于是带着这个思想,发现自己写出的代码逻辑错误就少了,起码在总体的流程上是对的,可能在有些地方缺少一些考虑,如验证等。这样bug也少了,开发的速度自然快了。

   说白了,就是在实现一个功能之前,先要设计,或者专业一点,画画流程图,其实流程图也不一定非得用UML画的那么标准,很多时候,就是拿一支笔和一个练习本开始画了,只要自己认识就行了。

  采用这种方式之后,发现不仅自己的设计能力提高了,而且对开发出来的功能也是很有信心的。

  一个功能,在草纸上设计,一个模块,也这样设计,甚至一个小的体统也这样设计,慢慢的,就可以上机coding之前,整个功能就已经在头脑中实现了,剩下的就是转为代码的事情了。

  如何有效的项目评估

  在项目中,总是想控制项目的进度,但是每次在开会的估算一个功能什么时候可以做完的时候,往往听到的却是这样的话“应该可以在一周之类完成”,“估计应该可以吧”,等等,诸如此类的没有把握的话,最后的结果是:什么时间规划都是白搭,延期,再延期。

  其实很大的程度上就反映了设计的问题。

  怎么说?

  其实当我们在估算项目的时候,很多的时候我们没有一个准确的估算,或者说只有一个大概的估算。其实这就是设计做的不够。

  当一个任务分配下来之后,我们确实一直在考虑业务流程和怎么实现,但是终究只是停在“考虑”上,没有进一步的细化,细化的颗粒度不够,没有细化到用到几个类,几个方法,很多的时候只是大概的想想怎么实现。就因为这样,项目的风险很大,甚至不能控制项目,而且也不知道项目是否按照计划在在进行。

  如果设计做的充分的好,最后的结果就是:整个类,流程都基本敲定,只是填充方法的实现而已,这样项目是否按照计划进行就一目了然。

  当然,这里不是闲着没事专门的说教,也不是说些大话,空谈,大谈。

  其实,编程终究是需要动脑的,多多的思考。

作者:yazi282 发表于2016/7/30 23:22:28 原文链接
阅读:43 评论:0 查看评论

关于Cool Compiler

$
0
0

这个Project其实是斯坦福cs143课程的一个assignment,这个作业的工作量确实很大。我花了大概整整二十天才完成了大部分的工作。没看错,是整整二十天,如果你没有一整段的时间来完成这个作业的话,我估计这个时间可能会延长个2~3倍的样子。但是如果光上课不做assignment的话,说实话,课程的效果会大打折扣,所以还是非常推荐大家来做一做这个Project的。好吧,随着我终于完成了这个assignment,非常矫情的说一句,我研一的生涯也随之结束了。

这堂课曾经在coursearea上出现过,但是现在coursearea上的课程现在已经下线了,我估计也不会再上线了,好在斯坦福自己弄了一个mooc网站,这里是compiler课程的链接地址:https://lagunita.stanford.edu/courses/Engineering/Compilers/Fall2014/info

你要注册了才能看到课程的完整信息,包括教学视频,ppt,以及作业。

class

我稍微来说一下我做的这个Project吧。我采用的是Java语言来编写,用Java的话,没有什么特殊的原因,纯粹是因为我想熟悉一下Java,另外Javalinux下有一个强大的IDE,会给代码的编写带来很多的便利。貌似在windows上编写的话,光是环境的配置都够人折腾了,我是个比较懒的人,所以直接在ubuntu下开干了。

虽然说,作业提供了一个虚拟机文件,你可以直接在虚拟机上完成你的工作,不过,我想吐槽的一点是,虚拟机的性能是在是太弱了,里面几乎无法流畅地运行IDE,除非你对C++/Java的语言特性非常熟悉,能够徒手用emacs或者vim这样的编辑器来书写代码的话,我还是推荐你直接在linux真机上书写你的代码。IDE你可以采用IntelliJ或者CLion,当然,现在巨硬貌似又出了黑科技,可以在windows上使用vs远程书写linux端的程序,你也可以试一试,当然,所有的这一切努力,都是为了写起来会舒心一点。对于Java来说,还是IntelliJ大法好!

IntelliJ

第一次和第二次的作业非常简单,第一次是要你写正则表达式,来匹配字符,第二次作业是要你写一些文法,构造出parser,第一次和第二次的代码量都不是很大,但是要求你阅读的东西却非常多,包括各种工具的使用文档,我开始也是一头雾水,但是读了读了之后就好了很多,作业其实不能啦,难的是每次编码前都要读一堆的文档,所以这两次作业,我每次作业花费了大概三天。

从第三次作业开始,代码量陡然增加,第三次要求你进行语义分析,对AST进行遍历来标注各个表达式的类型,虽然题目给了一个框架,但是没有写死,所以这次的作业非常开放,你想怎么来实现你的结构都行,但是自由度的放开,带来了难度的增加,以前从来没有写过的同学在这里可能会感到有一些吃力,我还好,看过了EOPL这本书之后,这次的语义分析难度倒不是很大,所以我大概花了四天,将测试用例逐条逐条都通过了。

在这里,我推荐你的写代码方式是测试驱动,通过作业给出的一个个测试用例来解决你代码中存在的一个又一个坑。这种方法非常高效。

非常有挑战度的是第四次作业,这次是汇编代码的生成,题目给了你一本汇编代码的查询手册,还给了n多的资料,汇编代码,要你一一查看,编译器其实就是这样啦,既然要写编译器,这意味着你必须要对另外一门汇编语言要特别熟悉,这样才能够做好代码翻译的工作。作业要求你将代码翻译成MIPS代码,好吧,对于我这种之前都没有接触过MIPS的人,难度确实是杠杠的。好在之前大学的时候手写过x86的汇编代码,对汇编代码的书写流程还算是比较熟悉啦,但是其实第四次的难度在于最开始,因为你压根就不知道应该如何开始,更加坑爹的是,github上压根找不到靠谱的代码,大部分人压根就没做这个assignment(确实有难度)。

当然,我光看这些东西也看不出个所以然来,所以我也参考了一下别人怎么弄的,一步一步理解,一步一步推敲,这次作业我差不多做了10天左右,你可以看到,这次作业的工作量等于前三次作业的总和,前五天基本天天在抓瞎,后来参考了一下之后对于大概怎样来书写有了一个比较清楚的认识,所以大家也不要害怕,其实还挺简单的,大概就这么几条规则吧,翻译一个函数的时候,首先要约定好堆栈的次序,约定这个寄存器里放什么,那个寄存器里放什么,只有每个函数都遵循这样的规则,你翻译出来的代码才不会出错。调用函数前,记得要将可能被调用的函数改变的寄存器里面的内容保存起来,你可以压栈,也可以保存在别的不用的寄存器里,调用返回之后要记得恢复回来。此外,如果想加快程序的运行速度的话,多用寄存器。否则,你翻译出来的代码会这里不对,那里又不对。

大概就这几条吧,当然翻译完成之后,必须要推荐一个利器,叫做QtSpim,方便汇编代码的调试,记住这个玩意太重要了,你不会使用的话,基本上这次作业你是完不成的,我也是通过对汇编代码的debug才解决了几个重大的bug,才最终完成了这次作业,当然,我并没有进行垃圾收集,所以扣除了5分。

我这里稍微讲解一些如何使用QtSpim吧!毕竟我也是折腾了7天左右还不会用这玩意,我讲了一下这个玩意之后,你的难度就会降低很多了。

怎样在ubuntu上安装QtSpim呢?首先你要安装Qtsdk

sudo apt-get install qt-sdk

然后,你要下载QtSpim提供的安装包,https://sourceforge.net/projects/spimsimulator/files/

下载deb格式的安装包,安装的事情,我就不说了。为了能够调试我们程序生成的代码,我们还需要做一些设置,首先要复制出/usr/class/cs143/lib/trap.handler文件,这个文件是作业提供给我们的,里面有一些basic class函数的定义,在运行我们的代码之前,我们必须首先加载这个文件。

copy file

接下来你要用你的软件加载这个文件。先点击设置:

settings

然后在这个页面加载原来的那个文件。

load file

现在差不多就可以加载你的汇编代码了:

all_done

好了,然后自己去实验吧,最关键的步骤我已经告诉你了,我差不多就是上面卡了几天,搞的我还以为这个玩意没法调试生成的代码,不过总算是完成了。总算是如释重负了。

总体来说,这是非常优秀的一个Project,因为最终的完成品功能其实已经很强大了,这可比一般的玩具语言要强得多。做完之后的收获其实也是仁者见仁智者见智啦,对于我来说,只有第四次assignment才真正有收获,前三个作业的内容其实写解释器的时候已经搞过几遍了,所以没什么吃惊的,而第四次的代码生成是我第一次从汇编的程度来看待我们使用的编程语言,特别是OOP的语言,我差不多彻底理解了C++的虚函数表的概念了,原来这个玩意是这样来实现的,光是如此,我就觉得这么多天的花费值得了。这个Project,打通了编程语言和底层的隔阂,使得我们可以从更加底层的角度来看待这个构建在抽象中的计算机世界。

现在在我看来,一切的语法都没有太大的意义了,其实所有的语言在内存里面差不多都是AST的形式,语法只是表象而已,将AST的节点加上一些所谓的书写方法,加上一些语法糖,可以很轻松地得到一门新的语言,所以,我们看到的都是假象而已。

世界本不是如此,一切都是假象,我们要感谢那些构建抽象的人们。

好了,我研一差不多也该结束了,回首这一年,无怨无悔,因为我做了很多我之前就想做的事情,比如写编译器,解释器,比如说读书,编码,这一年我读完了60多本计算机技术类经典,对于计算机有了一番新的认识,编程的能力比以前提升了不少,不为别的,因为我明白了递归的含义,递归是编程中最强大的利器之一,知道了将大问题拆解,分散成小的问题,通过小的函数组合起来形成强大的功能,感谢SICP以及EOPL以及 The little schemer,让我看到了另外一片天地。

好吧,要参考的同学可以查看这里:https://github.com/lishuhuakai/CS/tree/master/CS143_Compiler/Cool

That‘s All!

作者:lishuhuakai 发表于2016/7/30 23:24:32 原文链接
阅读:40 评论:0 查看评论

Spring探究(1)——功能特性

$
0
0

依赖注入

DamselRescuingKnight只能执行RescueDamselQuest探险任务

public class DamselRescuingKnight implements Knight {
  private RescueDamselQuest quest;

  public DamselRescuingKnight() {
    quest = new RescueDamselQuest(); //DamselRescuingKnight 紧密地与RescueDamselQuest 耦合在一起
  }

  public void embarkOnQuest() throws QuestException {
    quest.embark();
  }
}

为DamselRescuingKnight类编写单元测试出奇的难,必须保证embarkOnQuest()被调用的同时embark()也被调用,没有哪种简明的方式能够实现。
耦合具有两面性:

①紧密耦合代码难测试、难复用、难理解,而且会引发连环bug;
②一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了。

BraveKnight足够灵活,可以接受任何赋予他的探险任务

public class BraveKnight implements Knight {
  private Quest quest;//探险类型是Quest接口,能够响应多种探险实现类

  public BraveKnight(Quest quest) {
    this.quest = quest; //依赖注入方式之一:构造器注入
  }

  public void embarkOnQuest() throws QuestException {
    quest.embark();
  }
}

依赖注入的最大好处:松耦合。若一个对象只通过接口来标明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。
以下为实例核心代码:

-> knights.xml
  <bean id="knight" class="com.springinaction.knights.BraveKnight">
    <constructor-arg ref="quest" />    
  </bean>
  <bean id="quest" class="com.springinaction.knights.SlayDragonQuest" />

    ApplicationContext context = 
        new ClassPathXmlApplicationContext("knights.xml");//加载spring上下文
    Knight(接口) knight = (Knight) context.getBean("knight");
    knight.embarkOnQuest();

应用切面

依赖注入让相互协作的软件组件保持松散耦合,而AOP允许你把遍布应用各处的功能分离出来形成可重用组件。AOP编程是一项分离关注点的技术。诸如日志、事务管理和安全的【系统服务】经常融入到自身的核心业务逻辑组件中,这些系统服务通常被称为横切关注点,因为他们总是跨越系统的多个组件。

示意图如下:
这里写图片描述

AOP使这些服务模块化,并以声明的方式将它们应用到需要影响的组件中。更高的内聚性,更加关注自身业务。总之AOP确保POJO保持简单。

没有使用AOP之前:

public class Minstrel {//诗人类
  public void singBeforeQuest() {//探索前(前置通知)
      ...
  }

  public void singAfterQuest() {//探索后(后置通知)
      ...
  }
}

public class BraveKnight implements Knight {
  private Quest quest;
  private Minstrel minstrel;//诗人

  public BraveKnight(Quest quest,Minstrel minstrel) {
    this.quest = quest;
    this.minstrel = minstrel;
  }

  public void embarkOnQuest() throws QuestException {
    minstrel.singBeforeQuest();
    quest.embark();
    minstrel.singAfterQuest();
  }
}

这样会对BraveKnight类产生侵入。再假如我们有一个不需要吟游诗人的骑士,那还要考虑minstrel为null的情况,为其增加业务逻辑。但使用AOP可以声明吟游诗人必须歌颂骑士的探索事迹,而骑士就不再直接访问吟游诗人的方法。

  <bean id="knight" class="com.springinaction.knights.BraveKnight">
    <constructor-arg ref="quest" />       
  </bean>

  <bean id="quest"
        class="com.springinaction.knights.SlayDragonQuest" />

  <bean id="minstrel" 
     class="com.springinaction.knights.Minstrel" /> <!--声明Minstrel bean-->

  <aop:config>
    <aop:aspect ref="minstrel">

      <aop:pointcut id="embark" 
          expression="execution(* *.embarkOnQuest(..))" />  <!--声明切面,切入点id为embark。expression属性值采用AspectJ的切点表达式语言-->

      <aop:before pointcut-ref="embark"
                  method="singBeforeQuest"/><!--声明前置通知-->

      <aop:after pointcut-ref="embark"
                 method="singAfterQuest"/><!--声明后置通知-->

    </aop:aspect>
  </aop:config>

Bean应用上下文

在spring应用中,对象由spring容器创建、装配和管理。
容器是spring框架的核心。spring自带两类不同类型的容器。Bean工厂(BeanFactory,比较低级,少用)、应用上下文(ApplicationContext)。

三类常见的spring应用上下文:
FileSystemXmlApplicationContext->从文件系统加载应用上下文
ClassPathXmlApplicationContext->从类路径加载应用上下文
XmlWebApplicationContext->读取web应用下的XML并加载上下文

ApplicationContext ctx = new FileSystemXmlApplicationContext("C:/XXX.xml");
ApplicationContext ctx = new ClassPathXmlApplicationContext("XXX.xml");
ApplicationContext ctx = new XmlWebApplicationContext("XXX.xml");

Bean生命周期

这里写图片描述

①spring对bean进行实例化;
②spring将值和引用注入bean对应的属性中;
③若bean实现了BeanNameAware接口,spring将bean的id传入setBeanName接口方法;

④若bean实现了BeanFactoryAware接口,spring将调用setBeanFactory接口方法,将BeanFactory容器(spring容器)的实例传入;
⑤若bean实现了ApplicationContextAware接口,spring将调用setApplicationContext接口方法,将应用上下文(spring容器)的引用传入;

说明
BeanPostProcessor接口包含两个method。
postProcessBeforeInitialization方法:在Spring调用任何bean的初始化钩子(例如InitializingBean.afterPropertiesSet或者init方法)之前被调用;
postProcessAfterInitialization方法:Spring在成功完成嵌入初始化以后调用它。

⑥ 若bean实现了BeanPostProcessor接口,先调用postProcessBeforeInitialization接口方法;
⑦若bean实现了InitializingBean接口,会调用afterPropertiesSet接口方法,若使用了init生命初始化方法也会被调用;
⑧若bean实现了BeanPostProcessor接口,会调用postProcessAfterInitialization接口方法;

⑨准备就绪,可被程序使用,一直滞留在上下文中,直到该上下文被销毁。

Spring模块

这里写图片描述

按六个模块看 Spring的主要模块分别是核心Spring容器,spring的AOP模块,数据访问与集成,web和远程调用,测试。

  • 核心spring容器:容器是spring框架最核心的部分,它负责spring应用中Bean的创建、配置和管理。
  • Spring的AOP模块:在AOP模块中,spring对面向对象切面编程提供了丰富的支持。这个模块是spring应用系统开发切面的基础。
  • 数据访问与集成:使用jdbc编写代码通常会导致大量的样板式代码,例如获得数据库连接、创建语句、处理结果集到最后关闭数据库连接。Spring的jdbc和dao模块封装了这些样板代码,使我们的数据库代码变得简单明了,还可以避免因为释放数据库资源失败而引发的问题。该模块在几种数据库服务的错误信息之上构建了一个语义丰富的异常层,以后我们再也不需要解释那些隐晦专有的sql错误信息了。
  • Web和远程调用:mvc模式已经被普遍的接受为一种构建web应用的方法,它有助于将用户界面逻辑与应用逻辑分离。Spring虽然集成了多种主流的mvc框架,但他的web和远程调用模块自带了一个强大的mvc框架,有助于应用提升web层技术的松散耦合。
  • 测试:鉴于开发者自测的重要性,spring提供了测试模块来测试spring应用。

按七个模块看 组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:

  • 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写 的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

总结
Spring致力于简化企业级java开发,促进代码松散耦合。成功关键在于依赖注入和AOP,这两者是Spring框架最核心的部分。

参考:《spring in action》
http://blog.csdn.net/liu904139492/article/details/44226985
http://blog.knowsky.com/200197.htm

作者:Nanphonfy 发表于2016/7/30 23:32:14 原文链接
阅读:33 评论:0 查看评论

框架 day71 Solr 全文检索服务 入门

$
0
0

 Solr

全文搜索服务

 

  


1     Solr介绍


1.1     什么是solr


Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器Solr可以独立运行在Jetty、Tomcat等这些Servlet容器中。

Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化

使用Solr 进行创建索引和搜索索引的实现方法很简单,如下:

* 创建索引:客户端(可以是浏览器可以是Java程序)用 POST 方法向 Solr 服务器发送一个描述 Field 及其内容的 XML 文档,Solr服务器根据xml文档添加、删除、更新索引 。

* 搜索索引:客户端(可以是浏览器可以是Java程序)用 GET方法向 Solr 服务器发送请求,然后对Solr服务器返回Xml、json等格式的查询结果进行解析,组织页面布局。Solr不提供构建页面UI的功能,但是Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况

 

1.2     Solr和Lucene的区别

Lucene是一个开放源代码的全文检索引擎工具包,它不是一个完整的全文检索应用。Lucene仅提供了完整的查询引擎和索引引擎,目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者以Lucene为基础构建全文检索应用。

 Solr的目标是打造一款企业级的搜索引擎系统,它是基于Lucene一个搜索引擎服务,可以独立运行,通过Solr可以非常快速的构建企业的搜索引擎,通过Solr也可以高效的完成站内搜索功能。



2     Solr安装配置


2.1     下载solr


Solr和lucene的版本是同步更新的,最新的版本是5.2.1

本课程使用的版本:4.10.3

 

下载地址:http://archive.apache.org/dist/lucene/solr/

下载版本:4.10.3

Linux下需要下载lucene-4.10.3.tgzwindows下需要下载lucene-4.10.3.zip

 

下载lucene-4.10.3.zip并解压:

bin:solr的运行脚本

contrib:solr的一些扩展jar包,用于增强solr的功能。

dist:该目录包含build过程中产生的war和jar文件,以及相关的依赖文件。

docs:solr的API文档

example:solr工程的例子目录:

example/solr

         该目录是一个标准的SolrHome,它包含一个默认的SolrCore

example/multicore

         该目录包含了在Solr的multicore中设置的多个Core目录。

example/webapps

    该目录中包括一个solr.war,该war可作为solr的运行实例工程。

licenses:solr相关的一些许可信息

 


 

2.2     运行环境



solr 需要运行在一个Servlet容器中,Solr4.10.3要求jdk使用1.7以上,Solr默认提供Jetty(java写的Servlet容器),本教程使用Tocmat作为Servlet容器,相关环境如下:

 

l  Solr:4.10.3

l  Jdk环境:1.7.0_72(solr4.10 不能使用jdk1.7以下

l  Web服务器(servlet容器):Tomcat 7X

 

2.3     SolrCore配置



2.3.1 SolrHome和SolrCore



SolrHome是Solr运行的主目录,该目录中包括了多个SolrCore目录。SolrCore目录中包含了运行Solr实例所有的配置文件和数据文件,Solr实例就是SolrCore

一个SolrHome可以包括多个SolrCore(Solr实例),每个SolrCore提供单独的搜索和索引服务。



2.3.2 目录结构

SolrHome目录:


SolrCore目录:




2.3.3 创建SolrCore


创建SolrCore先要创建SolrHome。在solr解压包下solr-4.10.3\example\solr文件夹就是一个标准的SolrHome。

 

l  拷贝solr解压包下solr-4.10.3\example\solr文件夹。



l  复制该文件夹到本地的一个目录,把文件名称改为solrhome。

注:改名不是必须的,只是为了便于理解


 

l  打开SolrHome目录


SolrCore创建成功。



2.3.4 配置SolrCore


在conf文件夹下有一个solrconfig.xml。这个文件是来配置SolrCore实例的相关信息。如果使用默认配置可以不用做任何修改。它里面包含了不少标签,但是我们关注的标签为:lib标签、datadir标签、requestHandler标签

 

2.3.4.1  lib 标签

在solrconfig.xml中可以加载一些扩展的jar,solr.install.dir表示solrCore的目录位置,需要如下修改:

 

然后将contribdist两个目录拷贝到E:\12-solr\0505


2.3.4.2  datadir标签

每个SolrCore都有自己的索引文件目录 ,默认在SolrCore目录下的data中。


data数据目录下包括了index索引目录 和tlog日志文件目录。

如果不想使用默认的目录也可以通过solrConfig.xml更改索引目录 ,如下:

 


2.3.4.3  requestHandler标签

requestHandler请求处理器,定义了索引和搜索的访问方式。

通过/update维护索引,可以完成索引的添加、修改、删除操作。


提交xml、json数据完成索引维护,索引维护小节详细介绍。

 

 

通过/select搜索索引。


设置搜索参数完成搜索,搜索参数也可以设置一些默认值,如下:

 

<requestHandlername="/select" class="solr.SearchHandler">

    <!-- 设置默认的参数值,可以在请求地址中修改这些参数-->

    <lst name="defaults">

        <strname="echoParams">explicit</str>

        <intname="rows">10</int><!--显示数量-->

        <str name="wt">json</str><!--显示格式-->

        <strname="df">text</str><!--默认搜索字段-->

    </lst>

</requestHandler>

 

2.4     Solr工程部署


由于在项目中用到的web服务器大多数是用的Tomcat,所以就讲solr和Tomcat的整合。



2.4.1 安装Tomcat

2.4.2 把solr.war部署到Tomcat中

1、 从solr解压包下的solr-4.10.3\example\webapps目录中拷贝solr.war



2、 复制到tomcat安装目录的webapps文件夹下



2.4.3 解压缩solr.war

使用压缩工具解压或者启动tomcat自动解压。解压之后删除solr.war

2.4.4 添加solr服务的扩展依赖包(日志包)

l  把solr解压包下的solr-4.10.3\example\lib\ext目录下的所有jar包拷贝。


l  复制到解压缩后的solr工程的WEB-INF\lib目录


 

2.4.5 添加log4j.properties

1、 把solr解压包下solr-4.10.3\example\resources\log4j.properties文件进行拷贝


2、 在解压缩后的solr工程中的WEB-INF目录中创建classes文件夹


 

3、 复制log4j.properties文件到刚创建的classes目录

 



2.4.6 在solr应用的web.xml文件中,加载SolrHome

修改web.xml使用jndi的方式告诉solr服务器。

Solr/home名称必须是固定的。

 

 

2.4.7 启动Tomcat进行访问

访问http://localhost:8080/solr/

出现以下界面则说明solr安装成功!!!

 

 

2.5     管理界面功能介绍


2.5.1 Dashboard

仪表盘,显示了该Solr实例开始启动运行的时间、版本、系统资源、jvm等信息。

2.5.2 Logging

Solr运行日志信息

2.5.3 Cloud

Cloud即SolrCloud,即Solr云(集群),当使用SolrCloud模式运行时会显示此菜单,该部分功能在第二个项目,即电商项目会讲解。

2.5.4 Core Admin

Solr Core的管理界面。在这里可以添加SolrCore实例。

2.5.5 java properties

Solr在JVM 运行环境中的属性信息,包括类路径、文件编码、jvm内存设置等信息。

2.5.6 Tread Dump

显示Solr Server中当前活跃线程信息,同时也可以跟踪线程运行栈信息。

2.5.7 Core selector(重点)

选择一个SolrCore进行详细操作,如下:


2.5.7.1  Analysis(重点)


通过此界面可以测试索引分析器和搜索分析器的执行情况。

注:solr中,分析器是绑定在域的类型中的

 

2.5.7.2  dataimport

可以定义数据导入处理器,从关系数据库将数据导入到Solr索引库中。

默认没有配置,需要手工配置。

2.5.7.3  Document(重点)

通过/update表示更新索引,solr默认根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新

 

通过此菜单可以创建索引、更新索引、删除索引等操作,界面如下:

 

l  overwrite="true" : solr在做索引的时候,如果文档已经存在,就用xml中的文档进行替换

l  commitWithin="1000" : solr 在做索引的时候,每个1000(1秒)毫秒,做一次文档提交。为了方便测试也可以在Document中立即提交,</doc>后添加“<commit/>”

2.5.7.4  Query(重点)

通过/select执行搜索索引,必须指定“q”查询条件方可搜索。

 

 

2.6     多solrcore配置

配置多SolrCore的好处:

1.      一个solr工程对外通过SorlCore 提供服务,每个SolrCore相当于一个数据库,这个功能就相当于一个mysql可以运行多个数据库。

2.      将索引数据分SolrCore存储,方便对索引数据管理维护。

3.      SolrCloud集群需要使用多core。

 

 

复制原来的core目录为collection2,目录结构如下:


修改collection2下的core.properties,如下:


演示多core的使用,在collection1和collection2中分别创建索引、搜索索引。

 

 

3     solr基本使用


3.1     schema.xml

schema.xml文件在SolrCore的conf目录下,它是Solr数据表配置文件,在此配置文件中定义了域以及域的类型还有其他一些配置,solr中域必须先定义后使用

3.1.1 field

<field name="id" type="string" indexed="true" stored="true" required="true" multiValued="false" />

l  Name:域的名称

l  Type:域的类型

l  Indexed:是否索引

l  Stored:是否存储

l  Required:是否必须

l  multiValued:是否是多值,存储多个值时设置为true,solr允许一个Field存储多个值,比如存储一个用户的好友id(多个),商品的图片(多个,大图和小图)

3.1.2 fieldType(域类型)

    <fieldType name="text_general" class="solr.TextField" positionIncrementGap="100">
      <analyzer type="index">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <!-- in this example, we will only use synonyms at query time
        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
        -->
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
      <analyzer type="query">
        <tokenizer class="solr.StandardTokenizerFactory"/>
        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" />
        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
        <filter class="solr.LowerCaseFilterFactory"/>
      </analyzer>
    </fieldType>


l  name:域类型的名称

l  class:指定域类型的solr类型。

l  analyzer:指定分词器。在FieldType定义的时候最重要的就是定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤。

l  type:index和query。Index 是创建索引,query是查询索引。

l  tokenizer:指定分词器

l  filter:指定过滤器

3.1.3 uniqueKey

<uniqueKey>id</uniqueKey>

相当于主键,每个文档中必须有一个id域。

3.1.4 copyField(复制域)

<copyField source="cat" dest="text" />

可以将多个Field复制到一个Field中,以便进行统一的检索。当创建索引时,solr服务器会自动的将源域的内容复制到目标域中。

l  source:源域

l  dest:目标域,搜索时,指定目标域为默认搜索域,可以提供查询效率。

 

定义目标域:


必须要使用:multiValued="true"

 

3.1.5 dynamicField(动态域)

<dynamicField  name="*_i"  type="int"  indexed="true"  stored="true"/>

l  Name:动态域的名称,是一个表达式,*匹配任意字符,只要域的名称和表达式的规则能够匹配就可以使用。

 

例如:搜索时查询条件【product_i:钻石】就可以匹配这个动态域,可以直接使用,不用单独再定义一个product_i


3.2     配置中文分析器

使用IKAnalyzer中文分析器。

第一步:把IKAnalyzer2012FF_u1.jar添加到solr/WEB-INF/lib目录下。

第二步:复制IKAnalyzer的配置文件和自定义词典和停用词词典到solr的classpath下。

第三步:在schema.xml中添加一个自定义的fieldType,使用中文分析器。

<!-- IKAnalyzer-->

    <fieldType name="text_ik" class="solr.TextField">

      <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>

    </fieldType>

 

第四步:定义field,指定field的type属性为text_ik

<!--IKAnalyzer Field-->

   <field name="title_ik" type="text_ik" indexed="true" stored="true" />

   <field name="content_ik" type="text_ik" indexed="true" stored="false" multiValued="true"/>

 

第五步:重启tomcat

测试:


3.3     配置业务field


3.3.1 需求

要使用solr实现电商网站中商品搜索。

 

电商中商品信息在mysql数据库中存储了,将mysql数据库中数据在solr中创建索引。

 

需要在solr的schema.xml文件定义商品Field。

 

3.3.2 定义步骤

 

先确定定义的商品document的field有哪些?

可以根据mysql数据库中商品表的字段来确定:

 

products商品表:


商品document的field包括:pid、name、catalog、catalog_name、price、description、picture

 

 

先定义Fieldtype:

solr本身提供的fieldtype类型够用了不用定义新的了。

 

 

 

再定义Field:

 

pid:商品id主键

使用solr本身提供的:

<field name="id"type="string" indexed="true" stored="true"required="true" multiValued="false" />

 

name:商品名称

<field name="product_name"type="text_ik" indexed="true" stored="true"/>

 

catalog:商品分类

<field name="product_catalog"type="string" indexed="true" stored="true"/>

 

catalog_name:商品分类名称

<field name="product_catalog_name"type="text_ik" indexed="true" stored="true"/>

 

price:商品价格

<fieldname="product_price" type="float" indexed="true"stored="true"/>

 

description:商品描述

<fieldname="product_description" type="text_ik"indexed="true" stored="false"/>

 

picture:商品图片

<field name="product_picture"type="string" indexed="false" stored="true"/>

 

<!--product-->
<field name="product_name" type="text_ik" indexed="true" stored="true"/>
<field name="product_catalog" type="string" indexed="true" stored="true"/>
<field name="product_catalog_name" type="string" indexed="true" stored="true" />
<field name="product_price"  type="float" indexed="true" stored="true"/>
<field name="product_description" type="text_ik" indexed="true" stored="false" />
<field name="product_picture" type="string" indexed="false" stored="true" />
<field name="product_keywords" type="text_ik" indexed="true" stored="false" multiValued="true"/>



3.4     dataimportHandler插件

3.4.1 第一步:添加jar包

l  Dataimport的jar

从solr-4.10.3\dist目录下拷贝solr-dataimporthandler-4.10.3.jar,复制到以下目录:


修改schema.xml如下:


 

l  数据库驱动包

把mysql数据库驱动包,拷贝到以下目录:


修改schema.xml,如下:


 

 

3.4.2 第二步:配置solrconfig.xml,添加一个requestHandler

 <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler">

    <lst name="defaults">

      <str name="config">data-config.xml</str>

     </lst>

  </requestHandler> 

 

3.4.3 第三步:创建一个data-config.xml


在collection1\conf\目录下创建data-config.xml文件

<?xml version="1.0" encoding="UTF-8" ?>  
<dataConfig>   
<dataSource type="JdbcDataSource"   
		  driver="com.mysql.jdbc.Driver"   
		  url="jdbc:mysql://localhost:3306/solr"   
		  user="root"   
		  password="root"/>   
<document>   
	<entity name="product" query="SELECT pid,name,catalog,catalog_name,price,description,picture FROM products ">
		 <field column="pid" name="id"/> 
		 <field column="name" name="product_name"/> 
<field column="catalog" name="product_catalog"/>
		 <field column="catalog_name" name="product_catalog_name"/> 
		 <field column="price" name="product_price"/> 
		 <field column="description" name="product_description"/> 
		 <field column="picture" name="product_picture"/> 
	</entity>   
</document>   

</dataConfig>


3.4.4 第四步:重启tomcat

3.4.5 第五步:点击“execute”按钮导入数据

 

注意:到入数据前会先清空索引库,然后再导入。导入完成需手动刷新或者选择下面自动刷新


 

4     Solrj的使用



4.1     什么是solrj

solrj是访问Solr服务的java客户端,提供索引和搜索的请求方法,SolrJ通常在嵌入在业务系统中,通过SolrJ的API接口操作Solr服务,如下图:


Solrj和图形界面操作的区别就类似于数据库中你使用jdbc和mysql客户端的区别一样。


4.2     需求

使用solrj调用solr服务实现对索引库的增删改查操作。

 

4.3     环境准备

l  Solr:4.10.3

l  Jdk环境:1.7.0_72(solr4.10 不能使用jdk1.7以下

l  Ide环境:eclipse indigo


4.4     工程搭建


4.4.1 第一步:创建java工程

4.4.2 第二步:添加jar

l  Solrj的包


l  Solr服务的依赖包



4.5     代码实现

4.5.1 添加\修改索引

4.5.1.1  步骤

1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。

2、 创建SolrInputDocument对象,然后通过它来添加域。

3、 通过HttpSolrServer对象将SolrInputDocument添加到索引库。

4、 提交。

4.5.1.2  代码

说明:根据id(唯一约束)域来更新Document的内容,如果根据id值搜索不到id域则会执行添加操作,如果找到则更新

        @Test
	public void addDocument() throws Exception {

		// 1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
		// 参数:solr服务器的访问地址
		HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr/");
		// 2、 创建SolrInputDocument对象,然后通过它来添加域。
		SolrInputDocument document = new SolrInputDocument();
		// 第一个参数:域的名称,域的名称必须是在schema.xml中定义的
		// 第二个参数:域的值
		// 注意:id的域不能少
		document.addField("id", "c0001");
		document.addField("title_ik", "使用solrJ添加的文档");
		document.addField("content_ik", "文档的内容");
		document.addField("product_name", "商品名称");
		// 3、 通过HttpSolrServer对象将SolrInputDocument添加到索引库。
		server.add(document);
		// 4、 提交。
		server.commit();
	}


4.5.1.3  查询测试

 

4.5.2 删除索引

4.5.2.1  根据ID删除

4.5.2.1.1代码

        @Test
	public void deleteDocument() throws Exception {
		// 1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
		// 参数:solr服务器的访问地址
		HttpSolrServer server = new HttpSolrServer(
				"http://localhost:8080/solr/");
		// 根据ID删除
		server.deleteById("c0001");
		// 提交
		server.commit();
	}


4.5.2.1.2查询测试

4.5.2.2  根据条件删除

@Test
	public void deleteDocumentByQuery() throws Exception {
		// 1、 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
		// 参数:solr服务器的访问地址
		HttpSolrServer server = new HttpSolrServer(
				"http://localhost:8080/solr/");
		// 根据ID删除
		server.deleteByQuery("id:c0001");
		// 全部删除
		// server.deleteByQuery("*:*");
		// 提交
		server.commit();
	}


4.5.3 查询索引

4.5.3.1  solr的查询语法

1.      q - 查询关键字,必须的,如果查询所有使用*:*。

请求的q是字符串

 

 

2.      fq- (filter query)过虑查询,作用:在q查询符合结果中同时是fq查询符合的,例如::

请求fq是一个数组(多个值)


过滤查询价格从1到20的记录。

也可以在“q”查询条件中使用product_price:[1TO 20],如下:


也可以使用“*”表示无限,例如:

20以上:product_price:[20TO *]

20以下:product_price:[*TO 20]

 

3.      sort - 排序,格式:sort=<fieldname>+<desc|asc>[,<field name>+<desc|asc>]… 。示例:


按价格降序

4.      start - 分页显示使用,开始记录下标,从0开始

5.      rows - 指定返回结果最多有多少条记录,配合start来实现分页。

实际开发时,知道当前页码和每页显示的个数最后求出开始下标。

 

6.      fl - 指定返回那些字段内容,用逗号或空格分隔多个。


显示商品图片、商品名称、商品价格(不写默认返回全部配置的)

 

7.      df-指定一个搜索Field


也可以在SolrCore目录 中conf/solrconfig.xml文件中指定默认搜索Field,指定后就可以直接在“q”查询条件中输入关键字。

 

 

8.      wt - (writer type)指定输出格式,可以有xml, json, php, phps, 后面 solr 1.3增加的,要用通知我们,因为默认没有打开。

 

9.      hl 是否高亮 ,设置高亮Field,设置格式前缀和后缀。

 

 

4.5.3.2  简单查询

@Test
	public void queryIndex() throws Exception {
		// 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
		// 参数:solr服务器的访问地址
		HttpSolrServer server = new HttpSolrServer(
				"http://localhost:8080/solr/");

		// 创建SolrQuery对象
		SolrQuery query = new SolrQuery();
		// 设置查询条件,名称“q”是固定的且必须 的
		query.set("q", "id:2");

		// 调用server的查询方法,查询索引库
		QueryResponse response = server.query(query);

		// 查询结果
		SolrDocumentList results = response.getResults();

		// 查询结果总数
		long cnt = results.getNumFound();
		System.out.println("查询结果总数:" + cnt);

		for (SolrDocument solrDocument : results) {
			System.out.println(solrDocument.get("id"));
			System.out.println(solrDocument.get("product_name"));
			System.out.println(solrDocument.get("product_price"));
			System.out.println(solrDocument.get("product_catalog_name"));
			System.out.println(solrDocument.get("product_picture"));

		}
	}


4.5.3.3  复杂查询

复杂查询中包括高亮的处理


@Test
	public void queryIndex2() throws Exception {
		// 创建HttpSolrServer对象,通过它和Solr服务器建立连接。
		// 参数:solr服务器的访问地址
		HttpSolrServer server = new HttpSolrServer("http://localhost:8080/solr/");

		// 创建SolrQuery对象
		SolrQuery query = new SolrQuery();

		// 设置查询条件
		query.setQuery("钻石");
		// 设置过滤条件
		query.setFilterQueries("product_catalog_name:幽默杂货");
		// 设置排序
		query.setSort("product_price", ORDER.desc);
		// 设置分页信息
		query.setStart(0);
		query.setRows(10);

		// 设置显得的域的列表
		query.setFields("id", "product_name", "product_price",
				"product_catalog_name", "product_picture");

		// 设置默认搜索域
		query.set("df", "product_name");

		// 设置高亮
		query.setHighlight(true);
		query.addHighlightField("product_name");
		query.setHighlightSimplePre("<em>");
		query.setHighlightSimplePost("</em>");

		// 调用server的查询方法,查询索引库
		QueryResponse response = server.query(query);

		// 查询结果
		SolrDocumentList results = response.getResults();

		// 查询结果总数
		long cnt = results.getNumFound();
		System.out.println("查询结果总数:" + cnt);

		for (SolrDocument solrDocument : results) {
			System.out.println(solrDocument.get("id"));

			String productName = (String) solrDocument.get("product_name");

			//获取高亮列表
			Map<String, Map<String, List<String>>> highlighting = response
					.getHighlighting();
			//获得本文档的高亮信息
			List<String> list = highlighting.get(solrDocument.get("id")).get(
					"product_name");
			//如果有高亮,则把商品名称赋值为有高亮的那个名称
			if (list != null) {
				productName = list.get(0);
			}

			System.out.println(productName);
			System.out.println(solrDocument.get("product_price"));
			System.out.println(solrDocument.get("product_catalog_name"));
			System.out.println(solrDocument.get("product_picture"));

		}
	}



5     案例



5.1     需求

使用Solr实现电商网站中商品信息搜索功能,可以根据关键字、分类、价格搜索商品信息,也可以根据价格进行排序。

界面如下:

 

5.2     分析

开发人员需要的文档:静态页面(根据UI设计由美工给出)、数据库设计、原型设计

5.2.1 UI分析


5.2.2 架构分析


架构分为:

1、 solr服务器

2、 自己开发的应用(重点)

3、 数据库mysql

 

业务流程


自己开发的应用

1、 Controller

和前端页面进行请求和响应的交互

2、 Service

使用solrj来调用solr的服务进行索引和搜索

Service调用dao进行商品数据的维护时,要同步更新索引库

3、 Dao

对商品数据进行维护和查询

5.3     环境准备

l  Solr:4.10.3

l  Jdk环境:1.7.0_72(solr4.10 不能使用jdk1.7以下

l  Ide环境:eclipse indigo

l  Web服务器(servlet容器):Tomcat 7X

5.4     工程搭建

5.4.1 第一步:创建web工程

5.4.2 第二步:添加jar包

l  Solrj的包

l  Solr服务的日志包

l  Spring的包(包含springmvc)

l  Jstl包

l  Commons日志包


5.5     代码实现

5.5.1 Pojo

5.5.1.1  分析

索引查询结果


通过分析得出:

l  需要一个商品的pojo(Product),存放商品信息

l  还需要一个包装pojo(ResultModel),它包括商品列表信息、商品分页信息

5.5.1.2  代码

Product.java

public class Product {

    // 商品编号

    private Stringpid;

    // 商品名称

    private Stringname;

    // 商品分类名称

    private Stringcatalog_name;

    // 价格

    private float price;

    // 商品描述

    private Stringdescription;

    // 图片名称

    private String picture;

}

 

ResultModel.java

public class ResultModel {

    // 商品列表

    private List<Product>productList;

    // 商品总数

    private LongrecordCount;

    // 总页数

    private int pageCount;

    // 当前页

    private int curPage;

 

}

 

5.5.2 Dao

功能:接收service层传递过来的参数,根据参数查询索引库,返回查询结果。

public interface ProductDao {

 

    //查询商品信息,包括分页信息

    public ResultModel queryProduct(SolrQuery query) throws Exception;

}

public class ProductDaoImpl implements ProductDao {

	@Autowired
	private HttpSolrServer server;

	@Override
	public ResultModel queryProduct(SolrQuery query) throws Exception {
		ResultModel result = new ResultModel();
		// 通过server查询索引库
		QueryResponse response = server.query(query);
		// 获得查询结果
		SolrDocumentList documentList = response.getResults();
		// 把查询结果总数设置到ResultModel
		result.setRecordCount(documentList.getNumFound());

		List<Product> productList = new ArrayList<>();
		Product product = null;
		// 高亮信息
		Map<String, Map<String, List<String>>> highlighting = response
				.getHighlighting();
		for (SolrDocument solrDocument : documentList) {
			product = new Product();
			product.setPid((String) solrDocument.get("id"));

			String prodName = (String) solrDocument.get("product_name");

			List<String> list = highlighting.get(solrDocument.get("id")).get(
					"product_name");
			if (list != null)
				prodName = list.get(0);

			product.setName(prodName);
			product.setCatalog_name((String) solrDocument
					.get("product_catalog_name"));
			product.setPrice((float) solrDocument.get("product_price"));
			product.setPicture((String) solrDocument.get("product_picture"));

			productList.add(product);
		}

		// 把商品列表放到ResultMap中
		result.setProductList(productList);
		return result;
	}

}


5.5.3 Service

功能:接收action传递过来的参数,根据参数拼装一个查询条件,调用dao层方法,查询商品列表。接收返回的商品列表和商品的总数量,根据每页显示的商品数量计算总页数。

public interface ProductService {

 

    public ResultModelqueryProduct(String queryString, String cataName,

             String price, String sort, Integer curPage) throws Exception;

}

 


@Service
public class ProductServiceImpl implements ProductService {

	@Autowired
	private ProductDao dao;

	@Override
	public ResultModel queryProduct(String queryString, String cataName,
			String price, String sort, Integer curPage) throws Exception {
		// 封装SolrQuery
		SolrQuery query = new SolrQuery();
		// 设置查询条件
		if (queryString != null && !"".equals(queryString)) {
			query.setQuery(queryString);
		}else{
			query.setQuery("*:*");
		}
		// 设置过滤条件
		if (cataName != null && !"".equals(cataName)) {
			query.addFilterQuery("product_catalog_name:" + cataName);
		}
		if (price != null && !"".equals(price)) {
			String[] strings = price.split("-");
			if (strings.length == 2) {
				query.addFilterQuery("product_price:[" + strings[0] + " TO "
						+ strings[1] + "]");
			}
		}
		// 设置排序(1:降序 0:升序)
		if ("1".equals(sort)) {
			query.setSort("product_price", ORDER.desc);
		} else {
			query.setSort("product_price", ORDER.asc);
		}
		// 设置分页信息
		if (curPage == null)
			curPage = 1;
		query.setStart((curPage - 1) * Constants.rows);
		query.setRows(Constants.rows);

		// 设置默认搜索域
		query.set("df", "product_name");

		// 设置高亮
		query.setHighlight(true);
		query.addHighlightField("product_name");
		query.setHighlightSimplePre("<font style=\"color:red\">");
		query.setHighlightSimplePost("</font>");

		ResultModel resultModel = dao.queryProduct(query);
		resultModel.setCurPage(curPage);
		return resultModel;
	}

}


5.5.4 Controller

5.5.4.1  代码

@Controller
public class ProductController {

	@Autowired
	private ProductService service;

	@RequestMapping("/list")
	public String queryProduct(String queryString, String catalog_name,
			String price, String sort, Integer page, Model model)
			throws Exception {

		ResultModel resultModel = service.queryProduct(queryString, catalog_name,
				price, sort, page);
		model.addAttribute("result", resultModel);
		model.addAttribute("queryString", queryString);
		model.addAttribute("caltalog_name", catalog_name);
		model.addAttribute("price", price);
		model.addAttribute("sort", sort);
		model.addAttribute("page", page);

		return "product_list";
	}
}


5.5.4.2  配置

5.5.4.2.1Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	<display-name>jd0723</display-name>
	<!-- SpringMVC配置 -->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>*.action</url-pattern>
	</servlet-mapping>
	<filter>
		<filter-name>Character Encoding</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>Character Encoding</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
</web-app>


5.5.4.2.2Springmvc.xml

在config包下,创建springmvc.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-3.1.xsd 
		http://www.springframework.org/schema/mvc 
		http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd 
		http://www.springframework.org/schema/context 
		http://www.springframework.org/schema/context/spring-context-3.1.xsd 
		http://www.springframework.org/schema/aop 
		http://www.springframework.org/schema/aop/spring-aop-3.1.xsd 
		http://www.springframework.org/schema/tx 
		http://www.springframework.org/schema/tx/spring-tx-3.1.xsd ">
	<!-- 配置扫描包 -->
	<context:component-scan base-package="cn.itcast" />
	<!-- 配置注解驱动 -->
	<mvc:annotation-driven />
	<!-- jsp视图解析器 -->
	<bean
		class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<!-- 前缀 -->
		<property name="prefix" value="/WEB-INF/jsp/"></property>
		<!-- 后缀 -->
		<property name="suffix" value=".jsp"></property>
	</bean>
	<bean class="org.apache.solr.client.solrj.impl.HttpSolrServer">
		<constructor-arg value="http://localhost:8080/solr/"></constructor-arg>
	</bean>

</beans>


5.5.5 启动Tomcat

访问:http://localhost:8180/jd0723/list.action

 

作者:opopopwqwqwq 发表于2016/7/30 23:34:18 原文链接
阅读:47 评论:0 查看评论

微信公众号开发

$
0
0

首先去微信公众号官网
注册订阅号
微信接入官方文档
这里写图片描述
这里写图片描述
在新浪服务器下新建一个SAE应用
这里写图片描述
配置服务器,并把地址带到公众号的
这里演示的是用新浪SAE云服务器
新浪云

这里写图片描述
在新浪云的代码管理中把链接粘贴到微信的基本配置中的URL输入框
可参考官方token的设置方法
或者用下面的代码

<?php
/**
 * wechat php wsscat test
 */

//define your token
define("TOKEN", "weixin");
$wechatObj = new wechatCallbackapiTest();
if (isset($_GET['echostr'])) {
    $wechatObj -> valid();
} else {
    $wechatObj -> responseMsg();
}

class wechatCallbackapiTest {
    public function valid() {
        $echoStr = $_GET["echostr"];

        //valid signature , option
        if ($this -> checkSignature()) {
            echo $echoStr;
            exit ;
        }
    }

    public function responseMsg() {
        //get post data, May be due to the different environments
        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

        //extract post data
        if (!empty($postStr)) {
            /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
             the best way is to check the validity of xml by yourself */
            libxml_disable_entity_loader(true);
            $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
            $fromUsername = $postObj -> FromUserName;
            $toUsername = $postObj -> ToUserName;
            $keyword = trim($postObj -> Content);
            $time = time();
            $textTpl = "<xml>
                            <ToUserName><![CDATA[%s]]></ToUserName>
                            <FromUserName><![CDATA[%s]]></FromUserName>
                            <CreateTime>%s</CreateTime>
                            <MsgType><![CDATA[%s]]></MsgType>
                            <Content><![CDATA[%s]]></Content>
                            <FuncFlag>0</FuncFlag>
                            </xml>";

            if (!empty($keyword)) {
                $msgType = "text";
                $contentStr = "Welcome to wechat world! I'am wsscat,nice to meet you";
                $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
                echo $resultStr;
            } else {
                echo "Input something...";
            }

        } else {
            echo "";
            exit ;
        }
    }

    private function checkSignature() {
        // you must define TOKEN by yourself
        if (!defined("TOKEN")) {
            throw new Exception('TOKEN is not defined!');
        }

        $signature = $_GET["signature"];
        $timestamp = $_GET["timestamp"];
        $nonce = $_GET["nonce"];

        $token = TOKEN;
        $tmpArr = array($token, $timestamp, $nonce);
        // use SORT_STRING rule
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);

        if ($tmpStr == $signature) {
            return true;
        } else {
            return false;
        }
    }

}
?>

token可以自行更改,现在这里我用的是weixin
- [x] URL:应用所在服务器地址[云应用的域名]
- [x] Token:填weixin即可 [服务器中设置]
- [x] EncodingAESKey:随机生成即可
这里写图片描述

设置成功后微信的服务器配置项会变成如下

这里写图片描述
配置成功后按启动

如果食官方的示例代码,再启动成功后记得再更改服务器上面的这部分代码为下面,让它接受信息后调用responseMsg()函数

//define your token
define("TOKEN", "weixin");
$wechatObj = new wechatCallbackapiTest();
if (isset($_GET['echostr'])) {
    $wechatObj -> valid();
} else {
    $wechatObj -> responseMsg();
}

在微信搜索或者扫描二维码并关注公众号即可看到以下回复
这里写图片描述

来到这一步我们可以接入图灵测试的接口来测试
图灵机器人接口

public function responseMsg() {
        //get post data, May be due to the different environments
        $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

        //extract post data
        if (!empty($postStr)) {
            /* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
             the best way is to check the validity of xml by yourself */
            libxml_disable_entity_loader(true);
            $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
            $fromUsername = $postObj -> FromUserName;
            $toUsername = $postObj -> ToUserName;
            $keyword = trim($postObj -> Content);
            $time = time();
            $textTpl = "<xml>
                            <ToUserName><![CDATA[%s]]></ToUserName>
                            <FromUserName><![CDATA[%s]]></FromUserName>
                            <CreateTime>%s</CreateTime>
                            <MsgType><![CDATA[%s]]></MsgType>
                            <Content><![CDATA[%s]]></Content>
                            <FuncFlag>0</FuncFlag>
                            </xml>";

            if (!empty($keyword)) {
                $apiKey = "输入自己的apiKey";
                $apiURL = "http://www.tuling123.com/openapi/api?key=KEY&info=INFO";
                $reqInfo = $keyword;
                $url = str_replace("INFO", $reqInfo, str_replace("KEY", $apiKey, $apiURL));
                $res = file_get_contents($url);
                $resObj = json_decode($res);
                $contentStr = $resObj->text;
                $msgType = "text";
                //$contentStr = "Welcome to wechat world! I'am wsscat,nice to meet you";
                $resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
                echo $resultStr;
            } else {
                echo "Input something...";
            }

        } else {
            echo "";
            exit ;
        }
    }

微信JS-SDK

微信JS-SDK官方说明文档
jweixin-1.0.0.js
JS-SDK官方DEMO

首先绑定域名
先登录微信公众平台左边设置进入公众号设置功能设置里填写“JS接口安全域名”
这里写图片描述

然后,如果要JSSDK开动,必须满足下面三个条件:

  • [x] 你有一个域名
  • [x] 你有一个微信公众号,服务号或者订阅号
  • [x] 有后台生成签名(php、java、nodejs等)

第一二步我们前面都配置好了,现在我们缺的是第三步
我们下载后台生成签名的代码官网JSSDK后台服务代码DEMO并替换里面的AppIDAppSecret
打开上传的后端代码测试,新浪SAE有可能会报以下的错误,原因是没有权限读写,JSSDK要把access_token和expire_time写到本地被SAE拒绝了
这里写图片描述

这里可以参考这两篇文章来解决
解决新浪SAE无法写入jssdk.php的问题
http://www.henkuai.com/thread-1404-1-1.html

当然也可以用我的方法来解决
在SAE上用Storage的Bucket管理管理,把需要的文件放上去,这里我放了主要产生问题的access_token.phpjsapi_ticket.php
这里写图片描述
然后获取这些文件的路径,并把他替换到jssdk.php对应的地方,如下
这里写图片描述
那么我们就可以打开微信访问我们服务器上的这个测试文件来检测是否可以调用微信的接口了

作者:qq_27080247 发表于2016/7/30 23:35:20 原文链接
阅读:20 评论:0 查看评论

网页布局

$
0
0

一、新式布局案例

1、垂直分割布局

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

2、块状分割布局

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

3、单屏布局

这里写图片描述

这里写图片描述

这里写图片描述

二、网页布局的结构类型
T型布局
这是一个形象的说法,是指页面的定不是“网站标志+广告条”,左面是主菜单,右面是主要内容。这种布局的优点是页面结构清晰、主次分明,是初学者最容易上手的布局方法;缺点是页面呆板,如果不注意细节上的色彩,很容易让人“看之乏味”。

这里写图片描述

“同”字形布局
所谓“同”字形结构,就是整个页面布局类似“同”字,页面顶部是主导航栏,下面左右两侧是二级导航条、登录区、搜索区等,中间是主内容区。

“国”字形布局
它是在“同”字形布局上演化而来的,它在保留“同”字形的同时,在页面的下方增加一横条状的菜单或广告。这种网站的布局是目前最流行的网站布局模式,主要应用于一些大型的门户网站。

这里写图片描述

“匡”字形布局
这种布局结构去掉了“国”字形布局的右边的边框部分,给主内容区释放了更多空间,内容虽看起来比较多,但布局整齐又不过于拥挤,适合一些下载类和贺卡类站点使用。

“三”字形布局
一般应用在简洁明快的艺术性网页布局,这种布局一般采用简单的图片和线条代替拥挤的文字,给浏览者以强烈的视觉冲击。 这种布局多用于国外站点,国内用的不多。特点是在页面上有横向两条色块,将页面分割为三部分,色块中大多放广告条、更新和版权提示。

“川”字形布局
整个页面在垂直方向分为三列,网站的内容按栏目分布在这三列中,最大限度地突出主页的索引功能,一般适用在栏目较多的网站里。

拐角型的网站布局
这种布局与“国”字型网站的布局知识形式上是有区别的,其他的相似,模式为,最上面是标题或者广告的横幅,中间左侧是导航是链接,右侧是网页正文,最下面是一些网站的辅助信息,这种布局的网站多用于网站的产品展示。

标题正文型的网站布局
这种布局的网站页面上面是网页标题,下面是正文。

左右框架型的网站布局
这种布局的网站页面,常分为左右两个部分,右边是导航的链接,右边是正文,有时最上边还会有一个小的标题或者是标致等,这种结构常用于论坛型的网站布局。

上下框架型的网站布局
顾名思义,就是这种布局的网站页面分为上下两个部分。

综合框架型的网站布局
这种布局的网站页面是左右框架型以及上下框架型的结合体,是一种相对较复杂的网站布局结构。

封面型的网站布局
这种类型的基本上是出现在一些网站的首页,大部分是一些精美的平面设计结合一些小的动画再放上几个简单的链接或仅是一个“进入”的链接甚至直接在首页的图片上做链接,而没有任何提示。这种类型的大部分出现在企业的网站和个人主页,如果说处理的好,会给读者带来赏心悦目的感觉。

对比布局
顾名思义,这种布局采取左右或者上下对比的方式:一半深色,一半浅色。一般用于设计型站点。优点是视觉冲击力强;缺点是将两部分有机地结合比较困难。

POP布局
POP引自广告术语,是指页面布局像一张宣传海报,以一张精美图片作为页面的设计中心。这种类型基本上是出现在一些网站的首页,大部分为一些精美的平面设计结合一些小的动画,放上几个简单的链接或者仅是一个“进入”的链接,甚至直接在首页的图片上做链接而没有任何提示。这种布局大部分出现在企业网站和个人首页,如果处理得好的话,会给人带来赏心悦目的感觉。

flash型网站布局
其实这与封面型结构类似,只是这种类型采用了目前非常流行的flash,与封面型想必,页面所表达的信息更丰富了,其视觉效果以及听觉效果如果处理的好,那绝不差于传统的多媒体。

作者:riddle1981 发表于2016/7/30 23:37:05 原文链接
阅读:22 评论:0 查看评论

cocos2dx学习之路----第十一篇(对声音的操作处理)

$
0
0

这一篇就来谈谈关于声音的加载和播放等问题吧。

在cocos里面对声音的加载有两种声音引擎选择供我们使用。一种是比较简单的声音引擎,而另外一种会比较复杂一点。

一般来说,如果我们只是要简单的播放音乐音效,就可以选择用第一种,而如果要对音乐音效的流程进行控制,比如播放完成之后要进行怎样的处理,它将有一个完成音乐或音效播放后的回调方法。

我们先来看下第一种:

这个类命名为SimpleAudioEngine。顾名思义,简单的声音播放引擎。

对于这个类的接口我就不一一列出来了,不过至少需要知道以下的这些接口:

virtual unsigned int playEffect(const char* filePath, bool loop = false,float pitch = 1.0f, float pan = 0.0f, float gain = 1.0f);//播放音效
virtual void pauseEffect(unsigned int soundId);//暂停音效播放
virtual void resumeEffect(unsigned int soundId);//恢复音效播放
virtual void stopEffect(unsigned int soundId);//停止音效播放
virtual void preloadEffect(const char* filePath);//预加载一个压缩的声音文件
virtual void unloadEffect(const char* filePath);//卸载内部加载的音效缓存

这些接口是音效处理的接口。对于音乐文件的处理,这一种中提供了两种音乐类型分别做处理,即背景音乐和音效。一般来说,背景音乐播放的时间会比音效长。而这里的背景音乐与音效的区别就是背景音乐只能同时播放一个,而音效可以多个。并且在win32中,cocos内部目前并没有对背景音乐的预加载进行处理。所以,个人觉得可以把背景音乐也归为音效一类。

在测试中,我们也是如此,对于背景音乐的加载也是通过预加载音效文件。

好,我们来看看具体的操作吧,这次先看看我所做测试的源码:

SimpleAudioEngineTest.h:

#ifndef __SIMPLE_AUDIO_ENGINE_TEST_H__
#define __SIMPLE_AUDIO_ENGINE_TEST_H__

#include"cocos2d.h"
#include"SimpleAudioEngine.h"<span style="white-space:pre">	</span>//包含声音引擎头文件

USING_NS_CC;
using namespace CocosDenshion;<span style="white-space:pre">	</span>//使用该声音引擎的命名空间

class SimpleAudioEngineTest :public Layer{
public:
	SimpleAudioEngineTest();

	static Scene *createScene();
	virtual bool init();
	CREATE_FUNC(SimpleAudioEngineTest);

	virtual void onEnter();<span style="white-space:pre">	</span>//重写onEnter方法
	virtual void onExit();<span style="white-space:pre">	</span>//重写onExit方法

private:
	SimpleAudioEngine *_engine;<span style="white-space:pre">	</span>//声音引擎单例指针
	unsigned int _audioID;<span style="white-space:pre">		</span>//声音文件ID
	bool _loop;<span style="white-space:pre">			</span>//是否循环播放
};

#endif

SimpleAudioEngineTest.cpp:

#include"SimpleAudioEngineTest.h"
#include"SoundPlayerTest.h"<span style="white-space:pre">	</span>//包含第二个测试的头文件,以便跳转至该场景

SimpleAudioEngineTest::SimpleAudioEngineTest(){
	//变量初始化
	_engine = nullptr;
	_audioID = 0;
	_loop = false;

}
Scene *SimpleAudioEngineTest::createScene(){
	auto scene = Scene::create();
	auto layer = SimpleAudioEngineTest::create();
	scene->addChild(layer);
	return scene;
}
bool SimpleAudioEngineTest::init(){
	if (!Layer::init()){
		return false;
	}
	auto visibleSize = Director::getInstance()->getVisibleSize();


	//当前测试标签描述
	auto test_label = Label::createWithSystemFont("SimpleAudioEngine Test", "", 30);
	test_label->setPosition(Vec2(visibleSize.width / 2, visibleSize.height - test_label->getContentSize().height));
	this->addChild(test_label);

	//Play
	auto Play_Item = MenuItemFont::create("Play", [&](Ref *sender){
		_audioID = _engine->playEffect("music/background.mp3", _loop);//条目被点击时,调用播放音效的方法,返回一个文件ID
	});
	Play_Item->setPosition(Vec2(-visibleSize.width / 4, visibleSize.height / 4));

	//Stop
	auto Stop_Item = MenuItemFont::create("Stop", [&](Ref *sender){
		if (_audioID != 0){
			_engine->stopEffect(_audioID);//条目被点击时,调用停止播放音效方法,参数为音乐文件ID
			_audioID = 0;
		}

	});
	Stop_Item->setPosition(Vec2(visibleSize.width / 4, visibleSize.height / 4));

	//Pause
	auto Pause_Item = MenuItemFont::create("Pause", [&](Ref *sender){
		if (_audioID != 0){
			_engine->pauseEffect(_audioID);<span style="font-family: SimSun;">//条目被点击时,调用暂停播放音效方法,参数为音乐文件ID</span>

		}

	});
	Pause_Item->setPosition(Vec2(-visibleSize.width / 4, 0));

	//Resume
	auto Resume_Item = MenuItemFont::create("Resume", [&](Ref *sender){
		if (_audioID != 0){
			_engine->resumeEffect(_audioID);<span style="font-family: SimSun;">//条目被点击时,调用恢复播放音效方法,参数为音乐文件ID</span>

		}

	});
	Resume_Item->setPosition(Vec2(visibleSize.width / 4, 0));

	//第二个测试跳转标签
	auto EnterNextTest = MenuItemLabel::create(Label::createWithSystemFont("Click Here Enter Next Test", "", 35),
		[](Ref *sender){
		Director::getInstance()->replaceScene(SoundPlayerTest::createScene());
	});
	EnterNextTest->setPosition(Vec2(0, -visibleSize.height/4));

	auto menu = Menu::create(Play_Item, Stop_Item, Pause_Item, Resume_Item, EnterNextTest, NULL);
	addChild(menu, 10);


	return true;
}

void SimpleAudioEngineTest::onEnter(){
	Layer::onEnter();

	//声音引擎初始化
	_engine = SimpleAudioEngine::getInstance();

	//预加载声音文件
	_engine->preloadEffect("music/background.mp3");


	CCLOG("OnEnter....");
}
void SimpleAudioEngineTest::onExit(){
	if (_engine){
		_engine->unloadEffect("music/background.mp3");//清除内部声音文件缓存
	}
	Layer::onExit();
}

好,我们再来看看测试二。

测试二将会用到另一种声音引擎,这个类叫AudioEngine。它是在3.x之后开出的另外一个声音播放引擎,功能比第一个声音引擎强大的一点就是可以控制声音的播放进度,并且也实现了在win32平台没有实现的音量的控制。下面我们来看看那需要了解的接口有哪些吧:

static int play2d(const std::string& filePath, bool loop = false, float volume = 1.0f, const AudioProfile *profile = nullptr);//播放音乐文件,返回音乐文件ID
static void setVolume(int audioID, float volume);//设置音量大小
static void pause(int audioID);//通过指定音乐文件ID暂停播放音乐
static void resume(int audioID); //通过指定音乐文件ID恢复播放音乐
static void stop(int audioID); //通过指定音乐文件ID停止播放音乐
static void setFinishCallback(int audioID, const std::function<void(int,const std::string&)>& callback);//音乐文件播放完成后调用指定函数
static void preload(const std::string& filePath);//预加载音乐文件至内部缓存
static void uncache(const std::string& filePath);//通过指定文件清除其在内部中的缓存

以上就是测试二所需要了解的接口。对于声音音量的控制,我们还需要添加一个滑动条的UI来控制。

好,现在先看看我实现的参考代码:

SoundPlayerTest.h:

#ifndef __SOUND_PLAYER_TEST_H__
#define __SOUND_PLAYER_TEST_H__

#include"cocos2d.h"
#include"AudioEngine.h" //声音引擎头文件的引入
#include"ui\CocosGUI.h"<span style="white-space:pre">	</span>//UI控件头文件的引入

USING_NS_CC;
using namespace experimental;//使用声音引擎命名空间
using namespace ui;<span style="white-space:pre">	</span>     //使用UI命名空间

class SoundPlayerTest :public Layer{
public:
	SoundPlayerTest();

	static Scene *createScene();
	virtual bool init();
	CREATE_FUNC(SoundPlayerTest);

	virtual void onEnter();
	virtual void onExit();

private:
	int _audioID;
	bool _loop;
};

#endif

SoundPlayerTest.cpp:

#include"SoundPlayerTest.h"

SoundPlayerTest::SoundPlayerTest(){
	//变量初始化
	_audioID = AudioEngine::INVALID_AUDIO_ID;//这个值为AudioEngine中的一个初始值,为-1
	_loop = false;

}
Scene *SoundPlayerTest::createScene(){
	auto scene = Scene::create();
	auto layer = SoundPlayerTest::create();
	scene->addChild(layer);
	return scene;
}
bool SoundPlayerTest::init(){
	if (!Layer::init()){
		return false;
	}
	auto visibleSize = Director::getInstance()->getVisibleSize();


	//当前测试标签描述
	auto test_label = Label::createWithSystemFont("Sound Player Test", "", 30);
	test_label->setPosition(Vec2(visibleSize.width / 2, visibleSize.height - test_label->getContentSize().height));
	this->addChild(test_label);

	//Play
	auto Play_Item = MenuItemFont::create("Play", [&](Ref *sender){
		if (_audioID == AudioEngine::INVALID_AUDIO_ID){
			_audioID = AudioEngine::play2d("music/background.mp3", _loop);//播放音乐文件
		}
		if (_audioID != AudioEngine::INVALID_AUDIO_ID){
			AudioEngine::setFinishCallback(_audioID, [&](int id, const std::string &filePath){
				_audioID = AudioEngine::INVALID_AUDIO_ID; //播放完成时,再次初始化声音文件ID
			});
		}

	});
	Play_Item->setPosition(Vec2(-visibleSize.width / 4, visibleSize.height / 4));

	//Stop
	auto Stop_Item = MenuItemFont::create("Stop", [&](Ref *sender){
		if (_audioID != AudioEngine::INVALID_AUDIO_ID){
			AudioEngine::stop(_audioID);<span style="white-space:pre">	</span>//停止播放音乐文件
			_audioID = AudioEngine::INVALID_AUDIO_ID;
		}

	});
	Stop_Item->setPosition(Vec2(visibleSize.width / 4, visibleSize.height / 4));

	//Pause
	auto Pause_Item = MenuItemFont::create("Pause", [&](Ref *sender){
		if (_audioID != AudioEngine::INVALID_AUDIO_ID){
			AudioEngine::pause(_audioID);//暂停播放音乐文件
		}

	});
	Pause_Item->setPosition(Vec2(-visibleSize.width / 4, 0));

	//Resume
	auto Resume_Item = MenuItemFont::create("Resume", [&](Ref *sender){
		if (_audioID != AudioEngine::INVALID_AUDIO_ID){
			AudioEngine::resume(_audioID);//恢复播放音乐文件
		}

	});
	Resume_Item->setPosition(Vec2(visibleSize.width / 4, 0));

	auto menu = Menu::create(Play_Item, Stop_Item, Pause_Item, Resume_Item, NULL);
	addChild(menu,10);

	//初始化控制音量的滑动条UI
	Slider *slider = Slider::create();
	slider->loadBarTexture("cocosui/sliderTrack.png");//滑动条背景纹理
	slider->loadSlidBallTextures("cocosui/sliderThumb.png", "cocosui/sliderThumb.png");//滑动按钮纹理,第一个为正常,第二个为被点击时
	slider->loadProgressBarTexture("cocosui/sliderProgress.png");//进度纹理
	slider->setPosition(Vec2(visibleSize.width / 2, 100));
	slider->setScale(1.5f);
	slider->setPercent(100);
<span style="white-space:pre">	</span>
	slider->addEventListener([&](Ref *sender,Slider::EventType type)
	{
		auto s = dynamic_cast<Slider*>(sender);
		auto volum = 1.0f * s->getPercent() / s->getMaxPercent();//计算音量的值,音量的值应为:0~1之间
		if (_audioID != AudioEngine::INVALID_AUDIO_ID){
			AudioEngine::setVolume(_audioID, volum);
		}
	});
	addChild(slider);



	return true;
}

void SoundPlayerTest::onEnter(){
	Layer::onEnter();

	//声音引擎初始化
	AudioEngine::lazyInit();

	//加载声音文件
	AudioEngine::preload("music/background.mp3");

	
	CCLOG("OnEnter....");
}
void SoundPlayerTest::onExit(){
	if (_audioID != AudioEngine::INVALID_AUDIO_ID){
		AudioEngine::uncache("music/background.mp3");//清除音乐文件的缓存
	}
	Layer::onExit();
}

好,以上就是测试的内容。需要注意的是我这里的资源文件,都需要放在Resource文件夹里,因为资源的默认工作目录就在这。我在Resource里创建了music文件夹,里面就是放置所有声音资源文件,而创建一个cocosui文件夹则是放置所有UI相关控件的纹理文件,如下图所示:



这些资源都可以在源代码包中的testcpp找到。

好了,最后我们再来看运行的结果吧~(虽然声音听不到,但是可以感受一下哈~)


好了,如果想了解关于声音的内容,可以去看看官方提供的例子或是它们里面的提供的接口哈~对于声音的内容就谈到这里吧~下一篇就来谈谈关于Node节点中的自我更新接口。在游戏开发中可是占具重要位置的!大笑



作者:qq_30501909 发表于2016/7/30 23:38:08 原文链接
阅读:56 评论:0 查看评论

Spring中基于Java的容器配置(二)

$
0
0

使用@Configuration注解

@Configuration注解是一个类级别的注解,表明该对象是用来指定Bean的定义的。@Configuration注解的类通过@Bean注解的方法来声明Bean。通过调用注解了@Bean方法的返回的Bean可以用来构建Bean之间的相互依赖关系,可以通过前文来了解其基本概念。

注入inter-bean依赖

@Bean方法依赖于其他的Bean的时候,可以通过在另一个方法中调用即可。

@Configuration
public class AppConfig {

    @Bean
    public Foo foo() {
        return new Foo(bar());
    }

    @Bean
    public Bar bar() {
        return new Bar();
    }
}

在上面的例子当中,foo这个bean是通过构造函数来注入另一个名为bar的Bean的。

前面提到过,使用@Component注解的类也是可以使用@Bean注解来声明Bean的,但是通过@Component注解类内的Bean之间是不能够相互作为依赖的。

查找方法注入

前文中描述了一种我们很少使用的基于查找方法的注入。这个方法在单例Bean依赖原型Bean的时候尤为有效。基于Java的配置也支持这种模式。

public abstract class CommandManager {
    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();

        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

通过基于Java的配置,开发者可以创建一个CommandManager的自雷来重写createCommand()方法,这样就会查找一个新的(原型)命令对象。

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with command() overridden
    // to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

基于Java配置的内部工作原理

下面的例子展示了@Bean注解了的方法被调用了两次:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

clientDao()方法被clientService1()调用了一次,也被clientService2()调用了一次。因为这个方法创建了一个新的ClientDaoImpl的实例,开发者通常认为这应该是两个实例(每个服务都有一个实例)。如果那样的话,就产生问题了:在Spring中,实例化的Bean默认情况下是单例的。这就是神奇的地方了,所有配置了@Configuration的类都是通过CGLIB来子类化的。在子类当中,所有的子类方法都会检测容器是否有缓存的对象,然后在调用父类方法,创建一个新的实例。在Spring 3.2 以后的版本之中,开发者不再需要在classpath中增加CGLIB的jar包了,因为CGLIB的类已经被打包到org.springframework.cglib之中,直接包含到了spring-core的jar包之中。

当然,不同作用域的对象在CGLIB中的行为是不同的,前面提到的都是单例的Bean。

当然,使用CGLIB的类也有一些限制和特性的:
* 因为需要子类化,所以配置类不能为final的
* 配置类需要包含一个无参的构造函数

组合基于Java的配置

使用@Import注解

跟XML中通过使用<import/>标签来实现模块化配置类似,基于Java的配置中也包含@Import注解来加载另一个配置类之中的Bean:

@Configuration
public class ConfigA {

     @Bean
    public A a() {
        return new A();
    }

}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }
}

现在,在启动的配置当中不再需要制定ConfigA.class以及ConfigB.class来实例化上下文了,仅仅配置一个ConfigB就足够精确了:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器的实例化,仅仅处理一个类就可以了,而不需要开发者记住大量的配置类的信息。

对导入的Bean进行依赖注入

上面的例子是OK的,但是太过简化了,在大多数场景下,Bean可能依赖另一个配置类中定义的Bean。当使用XML的时候,这完全不是问题,因为并没有编译器参与其中,开发者可以通过简单的声明ref="someBean"就引用了对应的依赖,Spring完全可以正常初始化。当然,当使用@Configuration`注解类的时候,Java编译器会约束配置的模型,对其他Bean的引用是必须符合Java的语法的。

幸运的是,我们解决这个问题的方法很简单,如之前所讨论的,@Bean注解的方法可以有任意数量的参数来描述其依赖,让我们考虑一个包含多个配置类,且其中的Bean跨类相互引用的例子:

@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }

}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

当然,也有另一种方式来达到相同的结果,记得前文提到过,注解为@Configuration的配置类也会被声明为Bean由容器管理的:这就意味着开发者可以通过使用@Autowired@Value来注入。

开发者需要确保需要注入的依赖是简单的那种依赖。@Configuration类在容器的初始化上下文中处理过早或者强迫注入可能产生问题。任何的时候,尽量如上面的例子那样来确保依赖注入正确。
而且,需要特别注意通过@Bean注解定义的BeanPostProcessor以及BeanFactoryPostProcessor。这些方法通常应定义为静态的@Bean方法,而不要触发其他配置类的实例化。否则,@Autowired@Value在配置类中无法生效,因为它过早的实例化了。

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }

}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

在Spring 4.3中,只支持基于构造函数的注入。在4.3的版本中,如果Bean仅有一个构造函数的话,是不需要指定@Autowired注解的。在上面的例子当中,@AutowiredRepositoryConfig的构造函数上是不需要的。

在上面的场景之中,使用@Autowired注解能很好的工作,但是精确决定装载的Bean还是容易引起歧义的。比如,开发者在查看ServiceConfig配置,开发者怎么能够知道@Autowired AccountRepositorybean在哪里声明?在代码中这点并不明显,但是也不要紧。通过一些IDE或者是Spring Tool Suite提供了一些工具让开发者来看到装载的Bean之间的联系图。

如果开发者不希望通过IDE来找到对应的配置类的话,可以通过注入的方式来消除语义的不明确:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }

}

在上面的情况中,就可以清晰的看到AccountRepository这个Bean在哪里定义了。然而,ServiceConfig现在耦合到了RepositoryConfig上了,这也是折中的办法。这种耦合可以通过使用基于接口或者抽象类的的方式在某种程度上将减少。看下面的代码:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();

}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }

}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

现在ServiceConfigDefaultRepositoryConfig松耦合了,而IDE中的工具仍然可用,让开发者可以找到RepositoryConfig的实现。通过这种方式,在类依赖之间导航就普通接口代码没有区别了。

选择性的包括配置类和Bean方法

通常有需求根据系统的状态来选择性的使能或者趋势能一个配置类,或者独立的@Bean方法。一个普遍的例子就是使用@Profile注解来在Spring的Environment中符合条件才激活Bean。

@Profile注解是通过使用一个更加灵活的注解@Confitional来实现的。@Conditional注解表明Bean需要实现org.springframework.context.annotation.Condition接口才能作为Bean注册到容器中。

实现Condition接口仅仅提供一个matches(...)方法返回true或者false。如下为一个实际的Condition接口的实现:

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

更多的信息请参考@Conditional的Javadoc。

同时使用XML以及基于Java的配置

Spring中的@Configuration类的支持并不是为了100%的替代XML的配置的。SpringXML中的诸如命名空间等还是很理想的用来配置容器的方式。在那些使用XML更为方便的情况,开发者可以选择使用XML方式的配置,比如ClassPathXmlApplicationContext,或者是以Java为核心的AnnotationConfigApplicationContext并使用@ImportResource注解来导入必须的XML配置。

以XML为中心,同时使用配置类

比较好的方式是在Spring容器相关的XML中包含@Configuration类的信息。举例来说,在很多机遇Spring XML配置方式中,很容易创建一个@Configuration的类,然后作为Bean包含到XML文件中。下面的代码中将会在以XML为中心的配置下使用配置类。

配置类其实根本上来说只是容器中定义的Bean集合而已。在这个例子中,我们创建了个配置类叫做AppConfig并将其包含在了system-test-config.xml之中。因为存在<context:annotation-config/>标签,容器可以自发的识别@Configuration注解。

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }

}

system-test-config.xml配置如下:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

jdbc.properties:

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

system-test-config.xml之中,AppConfig这个bean并没有声明id元素,当然使用id属性完全可以,但是通常没有必要,因为一般不会引用这个Bean,而且一般可以通过类型来获取,比如其中定义的DataSource,除非是必须精确匹配,才需要一个id属性。

因为@Configuration是通使用@Component的元注解的,所以由@Configuration注解的类是会自动匹配组件扫描的。所以在上面的配置中,我们可以利用这一点来很方便的配置Bean。而且我们也不需要指定<context:annotation-config>标签,因为<context:component-scan>会使能这一功能。

现在的system-test-config.xml如下:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

以配置类为中心,同时导入XML配置

在应用中,如果使用了配置类作为主要的配置容器的机制的话,在某些场景仍然有必要用些XML来辅助配置容器。在这些场景之中,通过使用@ImportResource并且就可以了。如下:

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }

}

properties-config.xml中的配置:


<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=

代码中就可以通过AnnotationConfigApplicationContext来配置容器了:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
作者:EthanWhite 发表于2016/7/30 23:46:45 原文链接
阅读:58 评论:0 查看评论

javaee之标签的运用

$
0
0

在javaee中,需要了解的还有其丰富的核心标签库,包括c标签、struts标签....

现在先来了解标签的底层机制:

package gz.itcast.tags.cases;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

//自定义的标签需要继承SimpleTagSupport接口,并且实现doTag()方法
public class WhenTag extends SimpleTagSupport{
	private boolean test;
	public void setTest(boolean test) {
		this.test = test;
	}


	@Override
	public void doTag() throws JspException, IOException {
		//根据test的值判断是否显示标签内容
		
		if(test){
<span style="white-space:pre">			</span>//通过getJspBody()在自定义标签内写出数据
			this.getJspBody().invoke(null);
		}
		
		//得到父标签对象
		ChooseTag parent = (ChooseTag)this.getParent();
		parent.setFlag(test);
		
	}
}

package gz.itcast.tags.cases;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

//登录UI界面的标签
public class LoginUITag extends SimpleTagSupport{
	private String name;
	private String password;
	
	//用于给属性赋值的
	public void setName(String name) {
		this.name = name;
	}
	public void setPassword(String password) {
		this.password = password;
	}


	@Override
	public void doTag() throws JspException, IOException {
		
		String html = "";
		html += "<table border='1' width='300px'>";
		html += "<tr>";
		html += "<td>用户名</td>";
		html += "<td><input type='text' name='"+name+"'/></td>";
		html += "</tr>";
		html += "<tr>";
		html += "<td>密码</td>";
		html += "<td><input type='password' name='"+password+"'/></td>";
		html += "</tr>";
		html += "<tr>";
		html += "<td colspan='2' align='center'><input type='submit' value='登录'/><input type='reset' value='重置'/></td>";
		html += "</tr>";
		html += "</table>";
		
<span style="white-space:pre">		</span>//通过getJspContext()方法可以得到PageContext对象,并可以得到其他的对象和方法
		this.getJspContext().getOut().write(html);
	}
}

除了定义完doTag方法以后,还需要在web-inf的目录下创建一个.tld文件,该文件是一个自定义标签的配置文件,每一个自定义的标签都需要在该文件进行配置才能使用,如下就是tld文件的具体配置


<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE taglib
        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
        "http://java.sun.com/j2ee/dtd/web-jsptaglibrary_1_2.dtd">
<!-- 标签声明文件 -->
<!-- 标签库 -->
<taglib>

  <!-- 标签库的版本:不改 -->
  <tlib-version>1.0</tlib-version>
  <!-- 运行的jsp版本 :不改-->
  <jsp-version>1.2</jsp-version>
  <!-- 在jsp页面上访问标签库的前缀 建议名称 -->
  <short-name>itcast</short-name>
  <!-- 在jsp页面导入标签库时的唯一的名称(路径) -->
  <uri>http://www.itcast.cn</uri>

  <!-- 声明一个标签 -->
  <tag>
  	<!-- 标签名称 -->
    <name>showip</name>
    <!-- 指定标签的类全名(用于找到标签处理类) -->
    <tag-class>gz.itcast.tags.ShowIPTag</tag-class>
    <!-- 标签内容类型 -->
    <body-content>scriptless</body-content>
  </tag>

	<tag>
		<name>demo1</name>
		<tag-class>gz.itcast.tags.Demo1Tag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	
	<tag>
		<name>demo2</name>
		<tag-class>gz.itcast.tags.Demo2Tag</tag-class>
		<body-content>scriptless</body-content>
	</tag>

	<tag>
		<name>demo3</name>
		<tag-class>gz.itcast.tags.Demo3Tag</tag-class>
		<body-content>scriptless</body-content>
		<!-- 声明一个属性 -->
		<attribute>
			<name>num</name>
			<!-- 是否必须填写 -->
			<required>false</required>
			<!-- 是否支持EL表达式 -->
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
	
	
	<tag>
		<name>demo4</name>
		<tag-class>gz.itcast.tags.Demo4Tag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	
	
	<tag>
		<name>loginUI</name>
		<tag-class>gz.itcast.tags.cases.LoginUITag</tag-class>
		<body-content>scriptless</body-content>
		<attribute>
			<name>name</name>
			<required>true</required>
			<rtexprvalue>false</rtexprvalue>
		</attribute>
		<attribute>
			<name>password</name>
			<required>true</required>
			<rtexprvalue>false</rtexprvalue>
		</attribute>
	</tag>
	
	
	<tag>
		<name>if</name>
		<tag-class>gz.itcast.tags.cases.IfTag</tag-class>
		<body-content>scriptless</body-content>
		<attribute>
			<name>test</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
	
	<tag>
		<name>when</name>
		<tag-class>gz.itcast.tags.cases.WhenTag</tag-class>
		<body-content>scriptless</body-content>
		<attribute>
			<name>test</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
	</tag>
	
	<tag>
		<name>otherwise</name>
		<tag-class>gz.itcast.tags.cases.OtherwiseTag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	
	<tag>
		<name>choose</name>
		<tag-class>gz.itcast.tags.cases.ChooseTag</tag-class>
		<body-content>scriptless</body-content>
	</tag>
	
	<tag>
		<name>forEach</name>
		<tag-class>gz.itcast.tags.cases.ForEachTag</tag-class>
		<body-content>scriptless</body-content>
		<attribute>
			<name>items</name>
			<required>true</required>
			<rtexprvalue>true</rtexprvalue>
		</attribute>
		<attribute>
			<name>var</name>
			<required>true</required>
			<rtexprvalue>false</rtexprvalue>
		</attribute>
	</tag>
</taglib>



javaee自带的标签c标签,使用c标签需要导入该标签的uri

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>    //这个是导入的标签的地址
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>核心标签库</title>
    
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">
	<!--
	<link rel="stylesheet" type="text/css" href="styles.css">
	-->

  </head>
  
  <body>
  <%--数据操作: --%>
  
  <%--c:set :保存数据到域对象中
  	page: 默认  pageContext
  	request: 
  	session
  	application
  --%>
  
  <%--<c:set value="狗娃" var="name" scope="request"></c:set> --%>
  <%--
  	pageContext.setAttribute("name","狗娃");
   --%>
  
  ${name}
  
  <hr/>
  
  <%-- c:out: 从域对象中取出数据
  	default: 默认值,当没有数据时生效
  	escapeXml: 是否需要对其内容进行转义  
   --%>
  <c:out value="${name}" default="<h3>标题</h3>" escapeXml="false"></c:out>
  
  
  <hr/>
  
  <%-- 条件判断 --%>
  
  <%--c:if --%>
  <c:if test="false">
  	是否被显示
  </c:if>
  
  <%--c:choose + c:when + c:othwise --%>
  <c:choose>
  	<c:when test="false">
  		没有登录
  	</c:when>
  	<c:otherwise>
  		登录成功了
  	</c:otherwise>
  </c:choose>
  <hr/>
  <%--循环 --%>
  
  <%--c:forEach:循环集合或者数组 --%>
  <%
  	List<String> list = new ArrayList<String>();
  	list.add("eric");
  	list.add("rose");
  	list.add("lucy");
  	request.setAttribute("list", list);
   %>
   <%--
   itmes: 需要遍历的数据
   var: 每个对象的名称(使用时)
   begin: 从哪个开始
   end: 到哪个结束
   step:每次跳过几个
    --%>
   <c:forEach items="${list}" var="name">
   		姓名:${name}<br/>
   </c:forEach>
   
   <hr/>
   
  <%
  	Map<String,String> map = new HashMap<String,String>();
  	map.put("s1", "狗娃");
  	map.put("s2", "狗蛋");
  	map.put("s3", "狗剩");
  	request.setAttribute("map", map);
   %>
	<c:forEach items="${map }" var="entry">
		key: ${entry.key },value: ${entry.value}<br/>
	</c:forEach>  
  
  
  <hr/>
  
  <%--c:forTokens :循环特殊字符串--%>
  <c:set value="java-net-ios-php" var="course"></c:set>
  
  <c:forTokens items="${course}" delims="-" var="c">
  	课程:${c}<br/>
  </c:forTokens>
  
  
  <%-- c:redirect --%>
  <%
  	//response.sendRedirect(request.getContextPath()+"/target.jsp");
   %>
   <%--
   url: 自动带上项目名称
    --%>
  <%--<c:redirect url="/target.jsp"></c:redirect> --%>
  
  
  <%-- c:url: 简化路径写法 --%>
  <a href="<c:url value="/target.jsp"/>">跳转</a>
 
  
  </body>
</html>

通过javaee的标签,以后能够在页面尽可能的不写入java脚本代码,通过el表达式和标签的使用能够取代之

javaee自带的标签c标签,使用c标签需要导入该标签的uri
作者:css1223mjf 发表于2016/7/30 23:48:33 原文链接
阅读:19 评论:0 查看评论

UVA1584 UVALive3225 Circular Sequence

$
0
0

Regionals 2004 >> Asia - Seoul

问题链接:UVA1584 UVALive3225 Circular Sequence。基础训练级的题,用C语言编写。

这个问题是寻找循环串中的最小者。

不移动字符串是关键,不然就会浪费时间。

程序中,封装了两个功能函数cirstrcmp()和cirstrprintf(),使得主程序的逻辑大为简化。这两个函数是通用性的函数,完全封装,与全局变量没有关系。

AC通过的C语言程序如下:

/* UVA1584 UVALive3225 Circular Sequence */

#include <stdio.h>
#include <string.h>

#define MAXN 100

/* 循环串比较,a[s]和a[t]开始的两个串进行比较,s>t,s=t,s<t返回值分别为负,0,正 */
int cirstrcmp(char a[], int s, int t, int length)
{
    int count, i, j;

    count = length;
    for(i = s, j = t; count-- > 0; i = ++s % length, j = ++t % length) {
        if(a[i] == a[j])
            continue;
        return a[i] - a[j];
    }

    return 0;
}

void cirstrprintf(char a[], int start, int length)
{
    int count=0, i;

    for(i = start; count++ < length; i = (i + 1) % length)
        putchar(a[i]);
    putchar('\n');
}

int main(void)
{
    int t, min, len, i;
    char s[MAXN+1];

    scanf("%d", &t);
    while(t--) {
        scanf("%s", s);

        len = strlen(s);

        min = 0;
        for(i=1; i<len; i++)
            if(cirstrcmp(s, i, min, len) < 0)
                min = i;

        cirstrprintf(s, min, len);
    }

    return 0;
}


作者:tigerisland45 发表于2016/7/30 23:50:21 原文链接
阅读:74 评论:0 查看评论

iOS开发从入门到精通--XIB使用介绍

$
0
0

XIB使用介绍:
首先我们删除一些不需要的东西:
这里写图片描述

然后我们创建一个新的视图控制器
这里写图片描述

红色箭头Also create XIB file要勾选上
这里写图片描述

这个时候,我们可以看到有三个文件创建成功了,其中有一个RootController.xib文件,在这个里面就看到了一个像手机一样的视图,我们可以在右边进行拖拽一些控件在上面

下面启动这个视图代码要在代理AppDelegate.m书写:
要引入#import “RootController.h”

#import "AppDelegate.h"
#import "RootController.h"
@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //创建一个窗口对象
    self.window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];

    //方法一:显式加载xib文件
    //创建根视图控制器对象
    //参数一:创建时加载xib资源文件名,加载xib作为视图控制器视图
    //参数二:是指主文件包,xib所在的位置,mainBundle是主资源文件包。如果传nil,函数会自动到mainBundle中查找

//    RootController * root = [[RootController alloc]initWithNibName:@"RootController" bundle:[NSBundle mainBundle]];

    //RootController * root = [[RootController alloc]initWithNibName:@"RootController" bundle:nil];

    //方法二:隐式加载xib文件
    //如果系统中有xib的名字(RootController.xib)和类名字相同(RootController),
    //init函数会自动去加载RootController.xib文件
    RootController * root  = [[RootController alloc]init];

    self.window.rootViewController=root;

    [self.window makeKeyAndVisible];

    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

- (void)applicationWillTerminate:(UIApplication *)application {
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end
作者:android_it 发表于2016/7/30 23:52:11 原文链接
阅读:46 评论:0 查看评论
Viewing all 35570 articles
Browse latest View live


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