最简单的方法是读取RANDOM环境变量,比如生成100以内的随机数,可以这样运行:
echo $((RANDOM % 100))
第二种是调用awk中的rand方法,
awk 'BEGIN{print int(rand() * 100)}'
第三种方法是使用shuf命令:
seq 1 99 | shuf -n 1
其他方法类似读取/dev/random 或者 /dev/urandom 则比较复杂。
另外也可以通过调用perl、python、 php等产生随机数,并且都支持单行解释。
使用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中迭代器更安全,首先能够保证不会越界访问,另外还会检查是否原来的实例被修改过,即保证迭代器是有效的。
学过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对象。当然有一个没有用了,会由垃圾回收清理掉。
题目描述:假如已知有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)的空间。
教科书都会说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()方法呢? 事实上不可以,这样做是合理的。
给定一个数组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; }
/* 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; }
现在大多数高级语言都支持异常处理机制,而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
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