`
pangyi
  • 浏览: 32801 次
  • 性别: Icon_minigender_1
  • 来自: 古城西安
社区版块
存档分类
最新评论

JNI支持多线程吗?

阅读更多
最近采用JNI来实现访问PI和eDNA的组件。

PI和EDNA都是实时数据库,提供C++的API,遂采用JNI来调用这些函数。开发中发现,通过JNI封装的API,无法并发访问实时数据库,必须在api上加上同步。这样导致访问性能很低。

像Oracle等数据库的JDBC驱动,在Oracle服务端是不是也是采用JNI来实现的?

查了大量的资料,有用的实在寥寥无几。

以下对PI的API封装类源码:
import java.util.Date;

import org.xvolks.jnative.JNative;
import org.xvolks.jnative.Type;
import org.xvolks.jnative.exceptions.NativeException;
import org.xvolks.jnative.pointers.Pointer;
import org.xvolks.jnative.pointers.memory.HeapMemoryBlock;

import com.hrnt.rdbc.exception.RealDBException;
import com.hrnt.util.DateUtil;

/**
 * 实现PI数据库的部分API
 * 
 * @author bruce
 * 
 */
public class PIAPI {

	/**
	 * 
	 */
	private PIAPI() {

	}

	private static PIAPI _instance = new PIAPI();

	public static PIAPI getInstance() {
		return _instance;
	}

	public static final int ARCCOUNT = 149000;
	public static final int ARCTOTAL = 0;// 累计值
	public static final int ARCMINNUM = 1;// 最小值
	public static final int ARCMAXNUM = 2;// 最大值
	public static final int ARCCSTDEV = 3;//
	public static final int ARCRANGE = 4;// 随机范围
	public static final int ARCAVERAGE = 5;// 时间加权平均值
	public static final int ARCMEAN = 6;// 算术平均值

	// 获取瞬时值的mode
	public static final int ARCVALUEBEFORE = 1;
	public static final int ARCVALUEAFTER = 2;
	public static final int ARCVALUEINTERP = 3;
	public static final int ARCVALUECODE = 4;

	// 操作状态
	public static final long STAT_SUC = 0;// 成功
	public static final long STAT_120 = -120;
	public static final long STAT_1 = -1;
	public static final long STAT_105 = -105;
	public static final long STAT_106 = -106;

	// 获得digStart的错误标示
	public static final int DIG_START_ERR = -10;
	// 数据百分比
	public static final float PACGOOD = 0.85f;

	// 系统错误状态最小值
	public static final int PI_SYSERR_MINNUM = -314;
	public static final int PI_SYSERR_MAXNUM = -1;

	/**
	 * 设置服务器地址
	 * 
	 * @author bruce
	 * @param 服务器地址或者名称
	 * @return 返回操作状态,0为成功
	 */
	public synchronized native int piut_setservernode(String servername);

	/**
	 * 关闭连接
	 * 
	 * @author bruce
	 * @return 是否关闭成功,0为成功
	 */
	public synchronized native int piut_disconnect();

	/**
	 * 登录PI数据库
	 * 
	 * @author bruce
	 * @param 用户
	 * @param 密码
	 * @param 登录模式
	 *            1为只读,2为可读可写
	 * @return 登录是否成功,0为登录成功
	 */
	public synchronized native int piut_login(String username, String passwd,
			int[] valid);

	/**
	 * 获得标签的pointId
	 * 
	 * @author bruce
	 * @param 标签名称
	 * @param 标签Id(输入输出参数)
	 * @return 是否成功,0为成功状态
	 */
	public synchronized native int pipt_findpoint(String tagname, int[] tagpoint);

	/**
	 * @author brucepang
	 * @param sum
	 *            参数对象
	 * @return 0 成功,非0失败
	 */
	public synchronized native int piar_summary(PIAR_Summary sum);

	/**
	 * 获得标签的类型
	 * 
	 * @author bruce
	 * @param 测点在实时数据库的Id
	 * @param 测点类型(返回R是模拟量
	 *            I是开关量 D是数字状态 )
	 * @return 如果空
	 * @throws RealDBException
	 */
	public String pipt_pointtype(int pt) throws RealDBException {
		JNative j = null;
		try {
			j = new JNative("piapi32.dll", "pipt_pointtype");
			j.setRetVal(Type.INT);
			j.setParameter(0, pt);
			Pointer p = new Pointer(new HeapMemoryBlock(1024));
			j.setParameter(1, p);
			j.invoke();
			int isSuc = j.getRetValAsInt();
			if (isSuc == 0) {
				return p.getAsString();
			} else if (isSuc == -1) {
				throw new RealDBException("Point does not exist");
			} else {
				throw new RealDBException("System error:" + isSuc);
			}
		} catch (NativeException e) {
			throw new RealDBException(e);
		} catch (IllegalAccessException e) {
			throw new RealDBException(e);
		}

	}

	/**
	 * 获得某个标签的实时数据
	 * 
	 * @author bruce
	 * @param 标签Id
	 * @param 值数
	 * @param 状态
	 * @param 时间
	 * @return 0为成功,-1是标签不存在,>0是系统错误
	 */
	public synchronized native int pisn_getsnapshot(PISN_Getsnapshot snap);

	/**
	 * 获得某个标签的在一段时间的历史数据
	 * 
	 * @author bruce
	 * @param 标签Id
	 * @param 要获得的数量
	 * @param 时间段
	 *            在传入时times[0]为开始时间,times[count-1]是结束时间。在返回时是返回的rvals[]值对应的时间数组
	 * @param 值数组
	 * @param 状态数组
	 * @param 0代表向前搜索,非0代表向后搜索
	 * @return 0为成功,>0是系统错误,-1是错误的pt,-101是开始时间小于结束时间,-103是该测点在这段时间没有数据,-105是时间格式错误
	 *         -121是错误的count参数,-996是消息负载超出PINET协议,-998是内存地址出错
	 */
	public synchronized native int piar_compvalues(PIAR_Compvalues v);

	/**
	 * 根据digcode来获取digstate
	 * 
	 * @author bruce
	 * @param digcode
	 *            系统状态标示
	 * @param digstate
	 *            系统状态字符串(输入输出参数)
	 * @param len
	 *            返回的digstate字符串长度
	 * @return 0为成功,>0是系统错误,-11是digcode out of range
	 * @throws RealDBException
	 */
	// public synchronized native int pipt_digstate(PIPT_Digstate state);
	public String pipt_digstate(int digCode) throws RealDBException {
		JNative j = null;
		try {
			j = new JNative("piapi32.dll", "pipt_digstate");
			j.setRetVal(Type.INT);
			j.setParameter(0, digCode);
			Pointer p = new Pointer(new HeapMemoryBlock(1024));
			j.setParameter(1, p);
			j.setParameter(2, 12);
			j.invoke();
			int isSuc = j.getRetValAsInt();
			if (isSuc == 0) {
				return p.getAsString();
			} else if (isSuc == -11) {
				throw new RealDBException("Digital state code out of range");
			} else {
				throw new RealDBException("System error:" + isSuc);
			}
		} catch (NativeException e) {
			throw new RealDBException(e);
		} catch (IllegalAccessException e) {
			throw new RealDBException(e);
		}
	}

	/**
	 * 获取测点的瞬时值
	 * 
	 * @author bruce
	 * @param 标签
	 * @param 时间戳(输入时是要获得数据的时间)(输出时是数据库中该数据的实际时间)
	 * @param
	 *            获取数据的模式(1是数据时间在要求时间的之前)(2是数据时间在要求时间之后)(3是精确时间如果没有则内插值)(4是综合1、2、3的结果)
	 * @param 值
	 * @param 状态
	 * @return 0是成功,>0是系统错误,-1是错误的pt,-101是非在线时间,-103是没有数据,-105是错误的时间格式
	 */
	public synchronized native int piar_value(PIAR_Value pv);

	/**
	 * 将PI时间转化为常规时间数组 timearray[0] month (1-12) timearray[1] day (1-31)
	 * timearray[2] year (four digit) timearray[3] hour (0-23) timearray[4] min
	 * (0-59) timearray[5] sec (0-59)
	 */
	public synchronized native void pitm_secint(Pitmsecint tm);

	/**
	 * 将时间数组转化为PI识别的时间格式 timearray [0] month (1-12) timearray [1] day (1-31)
	 * timearray [2] year (four digit) timearray [3] hour (0-23) timearray [4]
	 * minute (0-59) timearray [5] second (0-59)
	 * 
	 * @return 返回經轉化的時間數值 t
	 */
	public synchronized native void pitm_intsec(PITM_Intsec pic);

	/**
	 * 获取系统错误的状态码信息
	 * 
	 * @throws RealDBException
	 */
	public String piut_strerror(int stat) throws RealDBException {
		JNative j = null;
		try {
			j = new JNative("piapi32.dll", "piut_strerror");
			j.setRetVal(Type.INT);
			j.setParameter(0, stat);
			Pointer p = new Pointer(new HeapMemoryBlock(1024));
			j.setParameter(1, p);
			Pointer p2 = new Pointer(new HeapMemoryBlock(1024));
			p2.setIntAt(0, 100);
			j.setParameter(2, p2);
			Pointer p3 = new Pointer(new HeapMemoryBlock(1024));
			j.setParameter(3, p3);
			j.invoke();
			int isSuc = j.getRetValAsInt();
			if (isSuc == 0) {
				return p.getAsString();
			} else if (isSuc == 100) {
				throw new RealDBException(
						"No more messages for this errornumber.(PI_NOMOREVALUES)");
			} else if (isSuc == -411) {
				throw new RealDBException("String truncated");
			} else if (isSuc == -993) {
				throw new RealDBException(
						"Length specified for buffer is too small");
			} else if (isSuc == -10007) {
				throw new RealDBException("Null pointer passed for arguments");
			} else {
				throw new RealDBException("System error:" + isSuc);
			}
		} catch (NativeException e) {
			throw new RealDBException(e);
		} catch (IllegalAccessException e) {
			throw new RealDBException(e);
		}
	}

	/**
	 * 将普通时间转化为PI的时间 将这里改造成不需要PI来解析的方式
	 */
	public int getPITimestamp(Date date) {
		long time = (date.getTime() + 8 * 3600 * 1000) / 1000;
		return (int) time;
	}

	/**
	 * 计算开关量状态的起点
	 * 
	 * @author bruce
	 * @param pt
	 *            测点Id
	 * @param digcode
	 *            状态
	 * @param 该开关量的digcode个数
	 * @return 0代表成功,-1代表pt不存在,
	 */
	public synchronized native int pipt_digpointers(PIPT_Digpointers dg);

	/**
	 * 得到时间函数
	 * 
	 * @param reltime
	 * @return
	 */
	public Date getTimedate(int time) {
		long t = time;
		t = t * 1000 - (8 * 3600 * 1000);

		return new Date(t);
	}

	static {
		System.loadLibrary("pijni");
	}

	/**
	 * 写入实时值或历史值
	 * 
	 * @author brucepang
	 * @param pt
	 *            测点Id
	 * @param rval
	 *            数值
	 * @param istat
	 *            状态
	 * @param timedate
	 *            PI的时间量
	 * @return 成功返回0,失败则返回非0
	 */
	public synchronized native int pisn_putsnapshot(PISN_Putsnapshot para);

	/**
	 * 组合写入数据。该函数是组合函数。使用请注意参数说明 可以写入开关量、整型量、实型量以及字符串等格式的数据。
	 * 
	 * @author brucepang
	 * @param ptnum
	 *            测点Id
	 * @param drval
	 *            浮点型数值,如果要写入该数组,bval必须为NULL
	 * @param ival
	 *            整型数值,如果要写入该数组,drval和bval必须为NULL
	 * @param bval
	 *            字节数组,如果为NON-NULL,则写入PI数据库
	 * @param bsize
	 *            字节数组长度。可以不设,PI会调用C的strlen来判断每个字符串的长度
	 * @param istat
	 *            开关量的状态值
	 * @param flags
	 *            数据质量标识数组
	 * @param timestamp
	 *            时间
	 */
	public synchronized native int pisn_putsnapshotx(int ptnum, int year,
			int month, int day, int hour, int minute, int second, byte[] str);

	public int pisn_putsnapshotx2(PISN_Putsnapshotx para)
			throws RealDBException {
		JNative j = null;
		try {
			j = new JNative("piapi32.dll", "pisn_putsnapshotx");
			j.setRetVal(Type.INT);
			j.setParameter(0, para.getPtnum());

			Pointer p = new Pointer(new HeapMemoryBlock(1024));
			p.setFloatAt(0, para.getDrval() == null ? 0 : para.getDrval()[0]);
			j.setParameter(1, p);

			Pointer p2 = new Pointer(new HeapMemoryBlock(1024));
			p.setIntAt(0, para.getIval() == null ? 0 : para.getIval()[0]);
			j.setParameter(2, p2);

			Pointer p3 = new Pointer(new HeapMemoryBlock(1024));
			p.setByteAt(0, para.getBval() == null ? null : para.getBval()[0]);
			j.setParameter(3, p3);

			Pointer p4 = new Pointer(new HeapMemoryBlock(1024));
			p
					.setShortAt(0, para.getBsize() == null ? null : para
							.getBsize()[0]);
			j.setParameter(4, p4);

			Pointer p5 = new Pointer(new HeapMemoryBlock(1024));
			p.setIntAt(0, para.getIstat() == null ? 0 : para.getIstat()[0]);
			j.setParameter(5, p5);

			Pointer p6 = new Pointer(new HeapMemoryBlock(1024));
			p2.setIntAt(0, para.getFlags() == null ? null : para.getFlags()[0]);
			j.setParameter(6, p6);

			Pointer p7 = new Pointer(new HeapMemoryBlock(1024));
			p2.setIntAt(0, para.getTimestamp() == null ? null : para
					.getTimestamp()[0]);
			j.setParameter(7, p7);

			j.invoke();
			int ret = j.getRetValAsInt();
			System.out.println("返回状态:" + ret);
			return ret;
		} catch (NativeException e) {
			throw new RealDBException(e);
		} catch (IllegalAccessException e) {
			throw new RealDBException(e);
		}
	}

	/**
	 * 批量写入实时值或历史值
	 * 
	 * @author brucepang
	 * @param pt
	 *            测点数组
	 * @param rval
	 *            数值数组
	 * @param istat
	 *            状态数组
	 * @param timedate
	 *            PI的时间量数组
	 * @param error
	 *            错误码状态集
	 * @param count
	 *            写入的测点数据个数
	 * @return 0代表成功,非0失败
	 */
	public synchronized native int pisn_putsnapshots(int[] pt, float[] rval,
			int[] istat, int[] timedate, int[] error, int count);

	public static void main(String args[]) {
		PIAPI api = new PIAPI();
		Date date = new Date();
		int time = api.getPITimestamp(date);
		long time2 = (date.getTime() + 8 * 3600 * 1000) / 1000;
		System.out.println("time:" + time + " time2:" + time2);
		int isSuc = api.piut_setservernode("172.16.109.248");
		System.out.println("isSuc:" + isSuc);
		int[] vald = { 2 };
		isSuc = api.piut_login("piadmin", "", vald);
		System.out.println("isSuc:" + isSuc);
		isSuc = api.pipt_findpoint("CDM158", vald);
		System.out.println("isSuc:" + isSuc + " pt:" + vald[0]);
		PIAR_Summary sum = new PIAR_Summary();
		sum.setCode(PIAPI.ARCMAXNUM);
		sum.setEndTime(api.getPITimestamp(DateUtil
				.parseDate("2008-10-15 10:00:00")));
		sum.setStartTime(api.getPITimestamp(DateUtil
				.parseDate("2008-10-15 00:00:00")));
		sum.setPt(vald[0]);
		isSuc = api.piar_summary(sum);
		System.out.println("isSuc:" + isSuc + " value:" + sum.getRval()
				+ " pct:" + sum.getPctgood());
		String t;
		try {
			t = api.pipt_pointtype(vald[0]);
			System.out.println("isSuc:" + isSuc + " type:" + t);
		} catch (RealDBException e1) {
			e1.printStackTrace();
		}

		PISN_Getsnapshot shot = new PISN_Getsnapshot();
		shot.setPt(vald[0]);
		isSuc = api.pisn_getsnapshot(shot);
		System.out.println("isSuc:"
				+ isSuc
				+ " value:"
				+ shot.getRval()
				+ " time:"
				+ DateUtil.format(api.getTimedate(shot.getTimedate()),
						DateUtil.DTRANS_EXTEND_FORMAT) + " istat:"
				+ shot.getIstat());
		PIAR_Compvalues c = new PIAR_Compvalues();
		c.setCount(PIAPI.ARCCOUNT);
		c.setPt(vald[0]);
		int[] times = new int[c.getCount()];
		times[0] = api
				.getPITimestamp(DateUtil.parseDate("2008-10-15 00:00:00"));
		times[times.length - 1] = api.getPITimestamp(DateUtil
				.parseDate("2008-10-15 10:00:00"));
		c.setTimes(times);
		float[] rvals = new float[c.getCount()];
		int[] istats = new int[c.getCount()];
		isSuc = api.piar_compvalues(c);
		System.out.println("count:" + c.getCount() + " isSuc:" + isSuc);

		String state;
		try {
			state = api.pipt_digstate(248);
			System.out.println("isSuc:" + isSuc + " desc:" + state);
		} catch (RealDBException e) {
			e.printStackTrace();
		}

		PIAR_Value pv = new PIAR_Value();
		pv.setMode(3);
		pv.setPt(vald[0]);
		pv.setTimedate(api.getPITimestamp(DateUtil
				.parseDate("2008-10-15 13:00:00")));
		isSuc = api.piar_value(pv);
		System.out.println("isSuc:" + isSuc + " value:" + pv.getRval()
				+ " istat:" + pv.getIstat());
		PIPT_Digpointers dg = new PIPT_Digpointers();
		dg.setPt(vald[0]);
		isSuc = api.pipt_digpointers(dg);
		System.out.println("isSuc:" + isSuc + " code:" + dg.getDigcode()
				+ " number:" + dg.getDignumb());
		try {
			String err = api.piut_strerror(5);
			System.out.println("err:" + err);
		} catch (RealDBException e) {
			e.printStackTrace();
		}

	}



有些方法不知道在如何实现,遂采用JNative代理库实现的。

与大伙一起探讨探讨。
分享到:
评论
3 楼 pangyi 2009-04-29  
tinywind 写道
jni方法就和一般的java方法一样,直接在头上加synchoronized性能当然快不了。jni本身提供了monitor的操作方法,可以作更精细的同步控制。http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/functions.html#wp23124


这些资料,咱也看过。
但具体操作起来,一头雾水啊。

可否给出代码例示。
2 楼 tinywind 2009-04-29  
jni方法就和一般的java方法一样,直接在头上加synchoronized性能当然快不了。jni本身提供了monitor的操作方法,可以作更精细的同步控制。http://java.sun.com/j2se/1.5.0/docs/guide/jni/spec/functions.html#wp23124
1 楼 pangyi 2009-04-28  
实时数据库数据量很大,采用JNI访问,有时会造成JVM崩溃。

是不是Java在异构系统的集成上,是不是很不理想啊?

相关推荐

    ICTCLAS50_Windows_32_C

     ICTCLAS 2011 c/c++/c#版、JNI版均支持多线程调用。 3、支持UTF-8  以往版本需要进行编码转换,统一转换成GB2312之后才能做进一步处理。系统当前版本支持GB2312、GBK、GB18030、UTF-8、BIG5。以上编码无需做任何...

    JCTP for Linux 64位 6.3.0_20140514

    CTP是上期技术推出的综合交易平台,见百科,官方API为C++语言接口。 本软件为Java版CTP接口,支持平台: ...多线程支持 同步API支持,见样例TraderInfo. CTP 接口开发文档见: http://pan.baidu.com/s/1gdmm5KR

    JCTP for Windows 32位 6.3.0_20140514

    CTP是上期技术推出的综合交易平台,见百科,官方API为C++语言接口。 本软件为Java版CTP接口,支持平台: ...多线程支持 同步API支持,见样例TraderInfo. CTP 接口开发文档见: http://pan.baidu.com/s/1gdmm5KR

    《Android C++高级编程——使用NDK》_源码.zip

    主要内容:● 使用JNI将原生代码连接到Java中● 使用SWIG自动生成JNI代码● 使用POSIX线程实现多线程应用● 使用POSIX套接字实现网络应用● 使用logging、GDB和Eclipse调试器进行调试● 使用Valgrind分析内存● 使用...

    Cocos2d-x 3.0多线程异步加载资源实例

    Cocos2d-x从2.x版本到上周刚刚才发布的Cocos2d-x 3.0 Final版,其引擎驱动核心依旧是一个单线程的“死循环”,一旦某一帧遇到了“大活儿”...虽然引擎在某些方面提供了一些支持,但有些时候还是自己祭出Worker线程这个

    JCTP for Windows 64位 6.3.6

    CTP是上期技术推出的综合交易平台,见百科,官方API为C++语言接口。 本软件为Java版CTP接口,支持平台: Windows 64bit, CTP V6.3.6 技术特点: 纯JNI实现,效率最高 ...多线程支持 同步API支持,见样例TraderInfo.

    jctp-6.3.19-all.zip

    本软件为Java版CTP接口,支持平台: Windows 32/64bit Linux 64bit 技术特点: 纯JNI实现,效率最高 多平台支持:Windows/Linux 支持CTP所有的函数和请求相应报文 多线程支持 同步API支持,见样例TraderInfo.

    JCTP 6.3.5/6/8 版本

    CTP是上期技术推出的综合交易平台,见百科,官方API为C++语言接口。 本软件为Java版CTP接口,支持平台: Windows 32/64bit Linux 64bit 技术特点: ...多线程支持 同步API支持,见样例TraderInfo.

    JCTP 6.3.11

    本软件为Java版CTP接口,支持平台: Windows 32/64bit Linux 64bit 技术特点: 纯JNI实现,效率最高 多平台支持:Windows/Linux 支持CTP所有的函数和请求相应报文 多线程支持 同步API支持,见样例TraderInfo.

    JCTP 6.3.5/6.3.6

    CTP是上期技术推出的综合交易平台,见百科,官方API为C++语言接口。 本软件为Java版CTP接口,支持平台: Windows 32/64bit Linux 64bit 技术特点: ...多线程支持 同步API支持,见样例TraderInfo.

    JFEMAS 飞马API Java接口

    FEMAS是中金所推出的股指期货和国债期货的交易平台,见百科,官方API为C++语言接口。 本软件为Java版FEMAS接口,支持平台: windows/Linux 支持版本: 1.01, 1.21, 1.23,...多线程支持 同步API支持,见样例TraderInfo.

    《Java编程技巧典型案例解析》随书光盘

    实例13 基于Java语言的多线程同步机制 实例14 Java程序中的多线程实现 实例15 利用Java的多线程技术实现并行多任务的管理 实例16 在Win32系统中引导Java程序 实例17 利用Java实现一个非线性规划问题 实例...

    JFEMAS 中金飞马 Java API Linux/64bit V1.21

    FEMAS是中金所推出的股指期货和国债期货的交易平台,见百科,官方API为C++语言接口。 本软件为Java版FEMAS接口,支持平台: Windows 32bit, FEMAS V1.21 ...多线程支持 同步API支持,见样例TraderInfo.

    JFEMAS 中金飞马 Java API Windows/32bit V1.21

    FEMAS是中金所推出的股指期货和国债期货的交易平台,见百科,官方API为C++语言接口。 本软件为Java版FEMAS接口,支持平台: Windows 32bit, FEMAS V1.21 ...多线程支持 同步API支持,见样例TraderInfo.

    JCTP for Windows 64位 6.3.0

    CTP是上期技术推出的综合交易平台,见百科,官方API为C++语言接口。 本软件为Java版CTP接口,支持平台: ...多线程支持 同步API支持,见样例TraderInfo. CTP 接口开发文档见: http://pan.baidu.com/s/1gdmm5KR

    DBTSearch软件开发包

    DBTSearch功能简介: 数据库管理系统存储的一般都... * DBTSearch实现的是多线程搜索服务; * DBTSearch每秒可索引3000条记录(主要瓶颈为数据库记录读取效率);搜索速度在毫秒级别。 * DBTSearch支持增量更新。

    Java优化编程(第2版)

    第12章 java多线程技术与应用性能优化 12.1 java多线程技术 12.1.1 进程与线程 12.1.2 线程的生命周期 12.2 并行任务与性能 12.2.1 并行任务与多线程 12.2.2 并行任务与死锁 12.3 线程池技术与应用性能优化 12.3.1 ...

    Netty应用说明笔记

    2、直接使用NIO需要需要额外的技能,例如Java多线程,网络编程; 3、要编写一个可靠的、易维护的、高性能的NIO服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务...

    Android C++高级编程——使用NDK完整版

    Android拥有广大的用户群体,市场前景也很好,所以学习Android的人很多。但是因为Android很容易上手,如果只是单纯的学一些简单的东西很明显没有竞争力。所以必须学一点深层次的东西来提升自己的核心竞争力。第1章 ...

    Thinking in Java 中文第四版+习题答案

    1.9 多线程 1.10 永久性 1.11 Java和因特网 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失 1.12.2 阶段0:拟出一个计划 1.12.3 阶段...

Global site tag (gtag.js) - Google Analytics