浅谈cpp中vector中的迭代器

使用cpp STL都不会不知道迭代器,那迭代器到底是什么呢?

http://www.cplusplus.com 中对迭代器的定义为:An iterator is any object that, pointing to some element in a range of elements (such as an array or a container), has the ability to iterate through the elements of that range using a set of operators (with at least the increment (++<) operators). 即迭代器就是指向一系列元素中的某一个元素,它能通过一系列操作迭代访问其中的元素。另外,需要注意的是cpp中的迭代器虽然绝大多数的本质就是指针,但并不是所有的迭代器都是指针,也有可能是其他形式,并且不保证迭代器具有指针的所有功能,比如input 和 output iterator就是一种单向受限的迭代器,执行--操作没有任何意义。cpp中,根据迭代器实现的不同功能,大体可以把迭代器分为5大类,具体各个类支持什么操作,查看迭代器

现在重点看看cpp中vector中的迭代器,vector的迭代器是一个random access iterator,即随机访问迭代器,具有指针的所有操作。现在看看sgi源码中关于迭代器的实现(stl 3.3)。首先看看iterator的类型定义:

  typedef _Tp value_type;
  typedef value_type* iterator;
  typedef const value_type* const_iterator; 

其中_Tp是范型参数类型,比如声明vector<int>,则_Tp为int,根据类型定义,很显然vector中的iterator 本质就是一个指针,vector<int>::iterator其实就是int *类型。

看vector的方法begin() 和 end()

  iterator begin() { return _M_start; }
  const_iterator begin() const { return _M_start; }
  iterator end() { return _M_finish; }
  const_iterator end() const { return _M_finish; }
  

可以看到,begin和end方法直接返回_M_start 和_M_finish,这两个私有属性就是iterator类型,即指针,前者指示。由于vector实现本质就是一个动态数组,_M_start则指示数组第一个元素,而_M_finish则指示数组最后一个元素的下一个单元,注意是下一个元素,因此_M_finish指向的单元不能访问,只是一个哨兵。vector更新时(比如初始化、push_back,pop_back等)都需要不断的维护这两个指针(实际上是3个,还有个_M_end_of_storage用于指示当前已分配内存的最后一个单元),而vector中的很多方法也是需要利用这两个指针的,比如以下方法:

  size_type size() const
    { return size_type(end() - begin()); }
  size_type capacity() const
    { return size_type(_M_end_of_storage - begin()); }
  bool empty() const
    { return begin() == end(); }

我们也可以发现,既然是迭代器指针,那就存在不安全性,有可能越界访问,更有可能所引用的迭代器是失效的,即在返回迭代器后,又对容器进行了修改,而这些都需要用户自己注意,库并没有对这些进行检查。java在这方面会比较好点。

根据以上模拟一个简单的vector(只有push_back 和 pop_back方法),对vector原理会更清楚些:

template <class T> class vector {
	public:
		typedef T value_type;
		typedef size_t size_type;
		typedef ptrdiff_t difference_type;
		typedef value_type* iterator;
		typedef value_type* pointer;
		typedef value_type& reference;
		iterator begin() {
			return M_start;
		}
		iterator end() {
			return M_end;
		}
		size_type size() {
			return size_type(end() - begin());
		}
		size_type capacity() {
			return size_type(end_of_storage-begin());
		}
		bool empty() {
			return begin() == end();
		}
		reference operator[](size_type n) {
			return *(begin() + n);
		}
		vector() {
			M_start = M_end = end_of_storage = 0;
		}
		vector(size_type n) {
			M_start = new T[n]();
			M_end = M_start;
			end_of_storage = M_start + n;
		}
		~vector() {
			destroy(begin(), end());
			delete[] M_start;
		}
		void push_back(value_type i) {
			if (M_end < end_of_storage) {
				*M_end++ = i;
				return;
			}
			size_type old_capacity = capacity();
			size_type new_capacity = old_capacity == 0 ? 16 : old_capacity << 1;
			iterator new_M_start = new T[new_capacity]();
			iterator new_M_end = new_M_start;
			iterator iter;
			for(iter = begin(); iter != end(); ++iter) {
				*new_M_end++ = *iter;
			}
			delete [] M_start;
			M_start = new_M_start;
			M_end = new_M_end;
			end_of_storage = M_start + new_capacity;
			*M_end++ = i;
		}
		void pop_back() {
			if (begin() == end())
				return;
			--M_end;
			destroy(M_end);
		}
	private:
		iterator M_start;
		iterator M_end;
		iterator end_of_storage;
		void destroy(pointer p) {
			p->~T();
		}
		void destroy(iterator start, iterator end)
		{
			iterator iter;
			for (iter = start; iter != end; iter++)
			{
				iter->~T();
			}
		}
};

分配内存时按照原有内存2倍关系分配,即如果当前分配了8B, 则下次分配16B,这样能够减少频繁内存分配,提高性能。可以不难看出,在访问元素时,无论是用迭代器还是[ ]运算符,都没有进行越界检测。

看看java的迭代器,主要是看看与cpp中vector迭代器的不同。

java支持迭代,必须实现Iterator接口,该接口定义为:

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

该接口只有三个方法,实现该接口必须实现这三个方法。 下面看看ArrayList如何实现该接口,从而了解其原理。 ArrayList 的iterator方法:

public Iterator<E> iterator() {
            return Itr();
        }

看来只好再看看ArrayList的基类 AbstractList,可以发现其实 Itr是AbstractList的私有内部类,其实现为:

private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size();
        }

        public E next() {
		/* 检查列表是否被修改过 */
            checkForComodification();
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
		    /* 调用外部类的remove方法 */
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

我们发现java中的迭代器是通过实现Iterator接口实现的,为了方便访问内部属性,一般声明为一个实现Iterator的内部类,因为内部类能够访问外部类的所有属性和方法。而实现方法也很简单,就是用一个cursor游标标识当前位置。我们也发现,java中迭代器比cpp中迭代器更安全,首先能够保证不会越界访问,另外还会检查是否原来的实例被修改过,即保证迭代器是有效的。

 

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

常量存储问题

学过c语言的都知道,如果两个字符指针char *s1, char *s2(假设已初始化过), 运算符== 是比较两个字符的首地址是否相同(java语言字符串比较则是引用是否相同),而不是比较本身字符串是否相等。我们也知道在采用段式存储管理中,局部自动变量是存储在栈上的,动态分配则在堆上分配,而局部常量往往存储在代码段,java 中jvm为了优化存储,专门有一个string pool,用于存储字符串常量,并且保证相同的字符串只有一个拷贝。而全局已初始化变量或者静态变量往往存储在数据段,未初始则存在在BSS段中。这些都是学过语言的一些基本常识。

那么根据以上知识,看看下面的代码:

#include <stdio.h>
char *s4 = "hello";
char *s5 = "hello";
const int g = 5;
const int h = 5;
const int aa[] = {1, 2, 3};
const int bb[] = {1, 2, 3};
int main(int argc, char **argv)
{
	char *s1 = "hello";
	char *s2 = "hello";
	const char s3[] = "hello";
	int a = 5;
	int b = 5;
	const int c = 5;
	const int d = 5;
	static const int e = 5;
	static const int f = 5;
	printf("%d\n", s1 == s2); /* 1 */
	printf("%d\n", s2 == s3); /* 0 */
	printf("%d\n", s4 == s5); /* 1 */
	printf("%d\n", &a == &b); /* 0 */
	printf("%d\n", &c == &d); /* 0 */
	printf("%d\n", &e == &f); /* 0 */
	printf("%d\n", &g == &h); /* 0 */
	printf("%d\n", aa == bb); /* 0 */
	return 0;
}

首先s1和s2是相同的字符串常量,保存在代码段,并且是只可读的,不允许修改,由程序的输出(编译时用的gcc),可知类似java的string pool,为了优化存储,对于相同的字符串,只有一个副本。s3是一个字符数组,虽然被声明为const只读,但我们还是可以认为s3是一个变量,只是认为加上了const变量,致使s3指向的内存不可修改。注意s1和s2是字面字符串,固化在代码段中,无论如何都不能修改,它不是人为控制的,而是编译器决定的。s4和s5显然虽然本身是全局变量,应该存储在数据段,但指向的内存却是字面字符串常量,因此实际上指向的是和s1,s2一样的字符串,即s1, s2, s4,s5指向同一个字符串,他们的值(即指向的内存地址)是一样的。

讨论过了字符串常量,那其他常量是否也有像字符串一样的特性呢?c 和 d 显然是常量,但类似s3它也可以看成是受限的变量.因此他们的地址是不同的。aa和bb也类似。

现在看一段java代码:

class Main {
	public static void main(String[] args) {
		String s1 = "hello";
		String s2 = "hello";
		System.out.println(s1 == s2); /* true */
		System.out.println(s1 + s2 == "hellohello");/* ? */
		final String s3 = "hello";
		final String s4 = "hello";
		System.out.println(s3 == s4); /* true */
		System.out.println(s3 + s4 == "hellohello"); /* ? */
		System.out.println(s1 == s3); /* true */
		final String s5 = new String("hello");
		final String s6 = new String("hello");
		System.out.println(s5 == s6); /* false */


	}
}

java中 == 运算符是比较引用对象是否相同。为了存储优化,在方法区的常量池中有一块用于存储字符串常量的区,称为String Pool,并且在编译时就已经确定,保存在.class文件中,若有多个相同的变量引用相同的对象,池中只会存在一份拷贝,即在池中创建对象时,先看看池中是否存在值相同的常量,存在直接返回引用,不存在时在真正创建。因此显然s1 和 s2 是相同的引用,返回true。我们也知道,java中String对象是不允许修改的,可以看作是final的,任何对String对象进行操作,均返回一个新的对象,其本身并没有改变。s1 + s2显然需要创建新的String 对象,为了效率,首先会利用s1 new 一个StringBuilder对象,然后执行append操作,最后再调用toString方法返回新的String对象,因此这个新的对象应该是在堆中或栈中,而不在池中,而"hellohello"是在池中的,它们显然不是同一个对象。注意s1和s2引用的对象是常量,不允许修改,但他们本身还是变量,能够引用其他对象。

然而s3和s4被声明为final变量,即s3 和 s4本身也是不可修改的,一旦初始化,不能再引用其他对象。关键在于此时s3 + s4 == "hellohello"返回true还是false呢?java中声明为final的变量会被初始化为编译时常量(compile-time constant expression),当使用这些变量时其实只是内联过去,相当于替换,或者类似c中的宏,因此声明为final的s3和s3, s3 + s4 实际上运行时只能看到"hello" + "hello",依然是字符串常量,注意s1和s2编译时是无法确定它的值的,因此编译完后我们仍然看到的是s1 + s2。因此s1 + s2 == "hellohello"返回false,而s3 + s4返回true。

而s5和s6实际先在池中创建了一个字符串常量(若池中存在,则直接引用),然后再调用String的构造方法又创建了一个新的对象,这个对象是在堆上分配的,因此实际new String("string")创建了两个String对象。当然有一个没有用了,会由垃圾回收清理掉。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

小米2013年招聘笔试算法题--朋友圈

题目描述:假如已知有n个人和m对好友关系(存于数字r)。如果两个人是直接或间接的好友(好友的好友的好友...),则认为他们属于同一个朋友圈,请写程序求出这n个人里一共有多少个朋友圈。

假如:n = 5 , m = 3 , r = {{1 , 2} , {2 , 3} , {4 , 5}},表示有5个人,1和2是好友,2和3是好友,4和5是好友,则1、2、3属于一个朋友圈,4、5属于另一个朋友圈,结果为2个朋友圈。 最后请分析所写代码的时间、空间复杂度。评分会参考代码的正确性和效率。

显然本质就是求无向图的连通分量个数。而要求连通分量数,就是遍历图的过程。遍历完所有节点,需要调用遍历几次就是连通分量个数。比如题目中使用DFS,从节点1出发,可以遍历节点2,3,而要遍历完所有节点还需从节点4出发,再遍历一次,共遍历两次,因此连通分量数为2。实现代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 10000
char map[N][N];
char used[N];
void dfs(int i, int n)
{
	int j;
	used[i] = 1;
	for(j = 1; j <= n; j++) {
		if (map[i][j] && !used[j])
			dfs(j, n);
	}
}
/* 判断是否存在未访问节点
 * 若存在,则返回第一个未访问节点编号
 * 若不存在,则返回-1
 */
int isVisitedAll(int n)
{
	int i;
	for (i = 1; i <= n; i++)
		if (used[i] == 0)
			return i;
	return -1;
}
int main(int argc, char **argv)
{
	int n, m;
	int a, b, i, sum, cur;
	while (scanf("%d%d", &n, &m) != EOF) {
		if (n == 0)
			break;
		memset(map, 0, sizeof(map));
		memset(used, 0, sizeof(used));
		sum = 0;
		for (i = 0; i < m; i++) {
			scanf("%d%d", &a, &b);
			map[a][b] = map[b][a] = 1;
		}
		while((cur = isVisitedAll(n)) != -1) {
			sum++;
			dfs(cur, n);
		}
		printf("%d\n", sum);
	}
	return 0;
}

暂且不说时间复杂度吧,空间复杂度就足够吓人了。首先需要一个表示图的01矩阵,大小为O(n  * n), 还需要记录是否节点是否已经被访问,需要大小为O(n)的空间。

换一种思路,其实根据题目朋友圈,我们就应该想到每一个圈其实就是一个集合,存在关系的,归为一个集合中,最后即需要求有多少个不相交的集合即有多少个圈子。由此不难想出,这其实就是并查集。不了解并查集可以查看维基百科并查集

想到了并查集,不难写出代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 100000
int father[N];
void init(int n)
{
	int i;
	for (i = 1; i <= n; i++)
		father[i] = i;
}
int getFather(int v)
{
	if (father[v] == v)
		return v;
	else {

		father[v] = getFather(father[v]);
		return father[v];
	}
}
void merge(int x, int y)
{
	int fx = getFather(x);
	int fy = getFather(y);
	if (fx < fy)
		father[fx] = fy;
	else
		father[fy] = fx;
}
int same(int x, int y)
{
	return getFather(x) == getFather(y);
}
int main(int argc, char **argv)
{
	int n, m;
	int a, b;
	int i;
	int sum;
	while (scanf("%d%d", &n, &m) != EOF) {

		if (n == 0)
			break;
		init(n);
		sum = 0;
		for (i = 1; i <= m; i++) {
			scanf("%d%d", &a, &b);
			merge(a, b);
		}
		for (i = 1; i <= n; i++) {
			if (getFather(i) == i)
				sum++;
		}
		printf("%d\n", sum);
	}
	return 0;
}

显然空间大大减少了,只需要O(n)的空间。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

switch可接受的变量数据类型

教科书都会说switch只能用于整型变量,于是c语言中很显然int, short, long, enum, char(包括无符号类型和long long)都可以作为switch变量,而float,double,struct,union显然不可以,那指针可不可以呢? 指针本质上就是一个无符号整数,根据定义应该是可以的,但在c语言中是非法的!即int i = 0; int *j = &i; switch(j){}是非法的(至少gcc是非法).

java中同样有switch语法,如果我们照般c语言的,很显然可接受的类型为int, byte, short, enum, char, long。我们觉得理应就是这样的,以至于我们不需要再作任何解释。可是当我们声明了一个long类型的变量,却发现我们怎么也编译不过去!是的,java的switch不支持long类型,这我也不理解这其中的缘由。那对象是否支持呢?也许你会很果断的回答,不支持,因为它不是整型,更确切的说不是int类型!可是事实并非如此,其实也不难理解,由于Integer, Byte等具有自动拆箱功能,因此可以作为switch变量也就不足为奇了。 好了,感觉所有的问题都解决了。但在java7中,switch变量可以传递String对象,这是java7中新增的特性。于是我是否可以作个这样的假定,switch可以传递任何对象,类似System.out.println函数,会自动调用对象的toString()方法呢? 事实上不可以,这样做是合理的。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

求数对之差最大值

给定一个数组a, 长度n,索引从0开始,定义数对之差为a[i] - a[j], 其中0 <= i < j < n,即数组前面的数减去数组后面任意一个元素。现在要求数对之差的最大值。如数组int a[] = {2, 4, 1, 16, 7, 5, 11, 9},数对之差最大值为11, 16 - 5 的结果。

最直接的方法就是暴力枚举法,即求出所有的数对之差,这样的时间复杂度为O(n2)。那有没有更高效的方法呢?

其实这个和求数组最大连续和有点类似,我们知道如果减少不变,被减数越大,则差越大。于是我们利用动态规划的思想,假定减数固定为数组某个元素,设为a[i],那么与a[i]形成的数对之差最大值就是a[i]前面的最大值(这个最大值我们可以保存在一个临时变量中)减去a[i]的值,我们可以依次遍历整个数组,分别求出与这个元素作为减数的最大数对之差,他们的最大值即为所求。这样只需要遍历数组一遍,时间复杂度为O(n)。附上代码:

#include <stdio.h>
#include <assert.h>
#include <limits.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
static int maxDiff(const int *a, const int n)
{
	assert(a != NULL && n > 1);
	int cur_diff, i;
	int max_diff = INT_MIN;
	int max = INT_MIN;
	for (i = 1; i < n; i++) {
		max = MAX(max, a[i - 1]);
		cur_diff = max - a[i];
		max_diff = MAX(max_diff, cur_diff);
	}
	return max_diff;
}
转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

求不相交的两个子数组的最大连续和

求一个数组的两段连续的部分的最大和,例如1,-2,2,3,-4,结果就是6,第一段就是1,第二段就是2,3。而数组-2,3,-1,2,-5,-6,7,-3,4,-9,2,1,第一段是3,-1,2,第二段是7,-3,4,结果就是12。
这个其实就是数组最大连续和的扩展问题,经典DP问题。求最大连续和并不难,我们只需要遍历数组一次,并用一个dp[i]函数记录从数组开头到第i个元素的子数组的最大和,显然if i == 0, dp[i] = a[0], 而if i > 0, 需要考虑dp[i - 1] 的符号, 如果dp[i - 1] <= 0,则dp[i] = a[i],因为前面i-1个元素最大连续和不大于0, 加上当前元素不可能大于当前元素,只会越加越小, 即小于a[i]。 而当dp[i - 1] > 0时, dp[i] = dp[i - 1] + a[i],原因很简单,前面的连续和大于0, 加上当前元素a[i],肯定会大于a[i]. 有了这个思路,算法就很简单了。这里时间复杂度是O(n),空间复杂度也是O(n),但我们发现我们只需要一个最大的和,那些前面算出的和,如果不是最大,根本没有必要保存,我们只需维护一个保存当前最大和的变量就可以了,因此空间复杂度可以降低为O(1)。
要求两段连续的部分和的最大值,可以先把数组分成两个各不相交的子数组,然后分别求子数组的最大连续和,相加最大即为所求。
附上代码:
/* coding=UTF-8 */
#include <stdio.h>
#include <limits.h>
#define MAX_LENGTH 1000
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int errno;
/* 求一维数组最大连续和 */
int max_subarray(int *a, int len)
{
	errno = 0;
	if (NULL == a || len < 1) {
		errno = 1;
		return 1;
	}
	int max = a[0], i, sum = a[0];
	for (i = 1; i < len; i++) {
		if (sum < 0) {
			sum = a[i];
		} else {
			sum += a[i];
		}
		max = MAX(max, sum);
	}
	return max;
}
/* 求一个数组的两段连续的部分的最大和
 * 输入一个数组a,以及长度len
 * 思路:先把a分成前后两部分,分别求两部分的最大连续和
 * 依次遍历求得两个子数组的最大连续和最大即为所求
 */
int op(int *a, int len)
{
	errno = 0;
	/* 输入异常处理 */
	if (NULL == a || len < 2) {
		errno = 1;
		return 0;
	}
	int i, max = INT_MIN;
	for (i = 1; i < len; i++) {
		int t1 = max_subarray(a + i, len - i);
		int t2 = max_subarray(a, i);
		max = MAX(max, t1 + t2);
	}
	return max;
}
int main(int argc, char **argv)
{
	int a[] = {1,-2,2,3,-4};
	int b[] = {-1, -1, -1, -1};
	int c[] = {-1, 1};
	printf("%d\n", op(a, 5));
	printf("%d\n", op(b, 4));
	printf("%d\n", op(c, 2));
	return 0;
}

 

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

java 异常处理

现在大多数高级语言都支持异常处理机制,而C语言是根据返回值(一般是定义为宏的整型数,如EXIT_FAILURE)判断错误类型加上goto语言处理异常,在linux内核中有大量goto语句的出现。而java支持异常处理,异常会改变程序运行顺序,也就说改变程序顺序的有if语句(循环也算是间接的if)、函数调用(或方法调用)和return语句、异常等。首先看一段代码:

OutputStream out = ...;
java.sql.Connection conn = ...;
try {
	Statement stat = conn.createStatement();
	ResultSet rs = stat.executeQuery("select uid, name from user");
	while (rs.next()) {
		out.println(rs.getString("uid") + ": " + rs.getString("name"));
	}
	conn.close();
	out.close();
} catch (Exception e) {
	e.printStackTrace();
}

以上代码是http://www.blogjava.net/freeman1984/archive/2013/07/26/148850.html中关于异常处理习惯的反例,不过这在大多数java程序员中经常出现的问题。

1,一个Exception 试图处理所有的异常,这是相当之不科学的,一个好的习惯是根据不同的异常类型做不同的处理,通过不同的catch块并指定具体的异常处理具体类,作不同的异常,并且catch异常类的顺序是有讲究的,一般先子类,然后再基类,在所有已知具体类都catch后,最后最好在捕获一个Exception类异常,防止可能由于疏忽丢了某些异常。如果需要同时捕获几个异常做统一处理,java7中支持catch (xx1Exception e | xx2Wxception 2 ...)的语法,可以同时对多个类型的异常做统一处理。

2,代码中并没有处理异常,而只是打印一个异常调用栈,并没有具体作处理,一般应该具体输出些日志信息,或者抛出自定义异常,并做一些工作。只要捕获了异常,就要好好处理,否则就不要捕获。

3, 资源的释放一般在finally块中处理,而不应该放在try块中,finally块不管异常是否出现,都会在整个try块退出前执行,一般执行一些清理工作,保证资源正确释放。当然在java7中如果实现了java.lang.AutoCloseable接口,则jvm会自动调用close方法进行资源释放。

4,try块不能太大,有时为了图方便,把一大片代码放入一个try块中,这样不仅出现代码混乱,而且也不利于针对具体异常作处理。

最后修改后的代码:

OutputStream out = ...;
java.sql.Connection conn = ...;
try {
	Statement stat = conn.createStatement();
	ResultSet rs = stat.executeQuery("select uid, name from user");
	while (rs.next()) {
		out.println(rs.getString("uid") + ": " + rs.getString("name"));
	}
} catch (SQLException e) {
	log("...");
	throw new ApplicationException("...");
} catch (IOException e) {
	log("...");
	throw new ApplicationException("...");
} catch (Exception e) {
	//do something else
} finally {
	if (conn != null) {
		try {
			conn.close();
		} catch (SQLException e) {
			// do something else
		}
	}
	if (out != null) {
		try {
			out.close();
		} catch (IOException e) {
			// do something else
		}
	}
}

总之要好好利用java的异常处理机制,不能图方便,只为能正确编译,而把一大片代码放入一个块,然后捕获一个Exception类。

参考:http://www.blogjava.net/freeman1984/archive/2013/07/26/148850.html

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

java 本地方法调用(JNI)

java使用native关键字声明本地方法,和声明通常的方法没有什么区别,只有两点不同:一是必须有native关键字,二是以分号结束,没有{},因为在类中并没有实现,这和声明抽象方法类似。本地方法一般是c/c++实现。下面以一个简单的例子来实现一个类,这个类调用本地方法。

public class JNIDemo {
	public native String input(String prompt);
	public native int sum(int a, int b);
	public native int sum(int[] a);
	static {
		System.load("/home/fgp/program/java/jni/libutil.so");

		/* 以下方法,需要设置lib库的路径 */
		//System.loadLibrary("util");

	}
	public static void main(String[] args) {
		JNIDemo jni = new JNIDemo();
		String name = jni.input("Type your name:");
		System.out.println("Your name is " + name);
		System.out.println(jni.sum(1, 2));
		System.out.println(jni.sum(new int[]{1, 2, 3, 4, 5}));
	}
}

我们声明了三个方法,同时使用了方法重载。这个三个方法均在main方法中被调用。

使用javac命令编译:

javac JNIDemo.java

此时如果运行java JNIDemo,则会出现异常,因为我们还没有实现本地方法。

在实现本地方法之前,最好先自动生成c头文件,使用javah:

javah -jni JNIDemo

此时会自动产生c头文件,

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNIDemo */

#ifndef _Included_JNIDemo
#define _Included_JNIDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JNIDemo
 * Method:    input
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JNIDemo_input
  (JNIEnv *, jobject, jstring);

/*
 * Class:     JNIDemo
 * Method:    sum
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_JNIDemo_sum__II
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     JNIDemo
 * Method:    sum
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_JNIDemo_sum___3I
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

初次看起来与我们平常的头文件有点不同,这就是java与本地方法通信的协议,或者说规范。比如函数名必须是Java_ClassName_MethodName__Sigature(JNIEnv *, jobject, arg...)。Sigature主要解决c没有实现重载的问题。函数形参JNIEnv* 和jobject 是必须的,分别表示JNI接口指针和对象本身(类似c++ 的this指针)。

我们不难看出,java的原始数据类型与c类型一一对应,本地方法可以直接访问,java中int对应jint,float对应jfloat等。而非原始类型,则直接对应jobject,比如java中的Object对应jobject,当然为了减少程序出错,引入了jobject的子类,比如jstring,jarray,jintArray等等。

这样以上的头文件也就不难理解了。

下面实现本地方法,注意本地方法实现时需要和头文件一致。

先从input方法说起。

#include <stdio.h>
#include "JNIDemo.h"
JNIEXPORT jstring JNICALL
Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt)
{
    
}

首先jstring并不是c中常规字符串(char *),而是自己重新封装了下,

因此直接调用printf("%s", prompt)是错误的。必须调用JNI的函数GetStringUTFChars把它转化成c字符串。同样的c字符串(char *)可以调用newStringUTF函数转化成jstring。以下就简单多了。实现如下:

JNIEXPORT jstring JNICALL
Java_Prompt_input(JNIEnv *env, jobject obj, jstring prompt)
{
	char buf[128];
	const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
	printf("%s\n", str);
	(*env)->ReleaseStringUTFChars(env, prompt, str);
	scanf("%s", buf);
	return (*env)->NewStringUTF(env, buf);
}

注意不再使用的jstring,需要手动调用ReleaseXX释放内存。

以下两个方法实现也不难理解,最后的完整版本为:

#include <stdio.h>
#include "JNIDemo.h"
JNIEXPORT jstring JNICALL
Java_JNIDemo_input(JNIEnv *env, jobject obj, jstring prompt)
{
	char buf[128];
	const char *str = (*env)->GetStringUTFChars(env, prompt, 0);
	printf("%s\n", str);
	/* 必须调用释放函数,否则内存泄漏 */
	(*env)->ReleaseStringUTFChars(env, prompt, str);
	scanf("%s", buf);
	return (*env)->NewStringUTF(env, buf);
}
JNIEXPORT jint JNICALL
Java_JNIDemo_sum__II(JNIEnv *env, jobject obj, jint a, jint b)
 {
	  return a + b;
 }
JNIEXPORT jint JNICALL
Java_JNIDemo_sum___3I(JNIEnv *env, jobject obj, jintArray array)
 {
	  int i;
	  int sum = 0;
	  jsize len = (*env)->GetArrayLength(env, array);
	  jint *a = (*env)->GetIntArrayElements(env, array, 0);
	  for (i = 0; i < len; i++)
		  sum += a[i];
	  (*env)->ReleaseIntArrayElements(env, array, a, 0);
	  return sum;
 }

最后编译c文件为动态链接库(windows为dll文件,unix为libXX.so文件),

#!/bin/bash
gcc  -I/usr/lib/jvm/java-7-openjdk-i386/include/ -I/usr/lib/jvm/java-7-openjdk-i386/include/linux/ JNIDemo.c -shared -o libutil.so

然后运行java JNIDemo,输出结果为:Type your name:

Mary
Your name is Mary
3
15

另外注意System.load()必须使用绝对路径,否则会出错,如果你的动态链接库已在环境变量中可以直接调用System.loadLibrary("util");

如果出现java.lang.UnsatisfiedLinkError,则可能路径不对,jvm没有找到对应的动态链接库。

参考:http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/index.html

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

C代码求迄今已知最大的素数

Fabrice Bellard仅仅只用447B代码, 计算出已知最大的素数, 大约有1700万位。

int m=1711276033,N=1,t[1<<25]={2},a,*p,i,e=39717691,s,c,U=1;g(d,h){for(i=s;i<1<<
24;i*=2)d=d*1LL*d%m;for(p=t;p<t+N;p+=s)for(i=s,c=1;i;i--)a=p[s]*(h?c:1LL)%m,p[s]
=(m*1U+*p-a)*(h?1LL:c)%m,*p=(a*1U+*p)%m,p++,c=c*1LL*d%m;}main(){while(e/=2){N*=2
;U=U*1LL*(m+1)/2%m;for(s=N;s/=2;)g(40,0);for(p=t;p<t+N;p++)*p=*p*1LL**p%m*U%m;
for(s=1;s<N;s*=2)g(983983719,1);for(a=0,p=t;p<t+N;)a+=*p<<(e&1),*p++=a%10,a/=10;
}while(!*--p);for(t[0]--;p>=t;)putchar(48+*p--);}

编译器要求支持64B的long long 类型,Linux 下gcc编译成功,2.3Ghz 双核CPU大约需要2分钟的运行时间。

转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!

利用curl发送post数据进行网络认证

可以把脚本放到自启动脚本目录下, 这样不用每次进行网页认证. 另外对于没有图形界面的纯字符终端也非常有用. 还有就是当chrome打开时, 如果没有认证, 则会把所有已保存的页面重定向到登录页面, 造成非常麻烦!

#!/bin/bash
declare -f showHelp
showHelp()
{
cat <<EOF
usage: $0 [-u username] [-p password] [-i ip] [-h]
EOF
exit 1
}

# 解析参数
while getopts ":u:p:i:h" arg
do
	case $arg in
		u)
			user=$OPTARG
			;;
		p)
			password=$OPTARG
			;;
		h)
			showHelp
			;;
		i)
			ip=$OPTARG
			;;
		:|?|*)
			showHelp
			;;
	esac
done

# 如果没有获取参数,则交互式输入
if [ -z "$user" ]
then
	read -p"username:" user
fi

if [ -z "$password" ]
then
	stty -echo
	read -p"password:" password
	stty echo
	echo
fi

# 检查参数是否成功输入
[ -z "$user" ] && showHelp
[ -z "$password" ] && showHelp

# 发送post数据, 注意这里没有保存cookies
ip=${ip:-"10.3.8.211"} # 这里设置认证ip的缺省值
curl -A "Mozilla/4.0(compatible;MSIE 6.0;Windows NT 5.0)" -d "username=$user&upass=$password&save_me=1&R1=0" "$ip"  -o output.html 2>/dev/null

# 本来想用nc的,结果发现即使不登录也会返回0 
# FIXME: 下面这段检测网络是否正确连接的代码,不健壮,不保证输出是正确结果, 运行后请手动ping
if ping -c 1 -w 2 baidu.com &>/dev/null
then
	echo "The internet is Ok!"
else
	echo "Fail to login!"
fi
转载请注明:http://krystism.is-programmer.com/若有错误,请多多指正,谢谢!