我记得有人告诉我。“面试一下spring源代码。看ioc、aop源代码"那为什么要看这些开源框架的源代码呢,事实上非常多人都是"应急式"的去读。就像读一篇文章一下,用最快的速度把文章从头到尾读一遍,那结果就是当你读完它,你也不清楚它讲了一个什么故事,想表达什么。

一个优秀的架构的源代码我觉得就好像一本名著一样。你的“文学”水平越高。你就越能读出作者设计的精妙之处。

一篇源代码在你不同水平的时候,能读出不同的东西。因此,我觉得优秀的框架的源代码是经久不衰的,重复读多少次都不嫌多,直到你能设计出预期并驾齐驱甚至超越它的优美的架构。

读源代码起初是一件非常痛苦的事儿。想赶紧把它像流水账一样的读完;慢慢实力增强后,会感觉到读源代码可以不费力气的读通。再假以时日。就能看出这些精妙的设计模式的组合。我有一个朋友。典型的源代码痴狂症,他跟我说他第一次看见spring的源代码,感觉特别兴奋,读了一宿没睡觉.......好吧,我还有非常长的路须要走~

话说多了。我们赶紧入正题:

JFinal的框架我24号的一篇博文写到过。它优秀的地方在精简代码上,那么有两处源代码是我认为是值得我们要好好解析一下,一处是初始化载入—servlet跳转。还有一处是DB+ActiveRecord的映射

那么DB映射相对照较简单,我们这次就先来看看。

首先我们看看代码。还是之前我写过的 dog与cat的故事。

来自FinalConfig.java

        // 採用DB+ActiveRecord模式
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);
me.add(arp);
// 进行DB映射
arp.addMapping("animal", AnimalModel.class);

这三行代码就是载入DB映射的关键,那么我们复习一下,JFinal的DB映射无需配置文件。无需与DB相应的POJO,仅仅须要写一个类。继承Model<M extends Model>就可以。

第一步:为ActiveRecordPlugin的 private IDataSourceProvider dataSourceProvider 赋值。

那么我们先来看看ActiveRecordPlugin的构造器。

来自ActiveRecordPlugin.java

        public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {
this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);
}

这里重要的是dataSourceProvider。IDataSourceProvider是一个接口,它的执行时类型是

来自C3p0Plugin.java

public class C3p0Plugin implements IPlugin, IDataSourceProvider{...}

那么。能够看到

来自ActiveRecordPlugin.java

this(DbKit.MAIN_CONFIG_NAME, dataSourceProvider);

这段代码又继续读取还有一个重载的构造器,然后调用了

来自ActiveRecordPlugin.java

       public ActiveRecordPlugin(String configName, IDataSourceProvider dataSourceProvider, int transactionLevel) {
if (StrKit.isBlank(configName))
throw new IllegalArgumentException("configName can not be blank");
if (dataSourceProvider == null)
throw new IllegalArgumentException("dataSourceProvider can not be null");
this.configName = configName.trim();
this.dataSourceProvider = dataSourceProvider;
this.setTransactionLevel(transactionLevel);
}

最重要的就是这行代码:        this.dataSourceProvider = dataSourceProvider;

这时。ActiveRecordPlugin的static变量的dataSourceProvider就已经被赋为C3p0Plugin的实例了。

第二步:定义映射用POJO

来自AnimalModel.java

public class AnimalModel extends Model<AnimalModel> {...}

这里Model的源代码我们一会再看。如今不着急。

然后进行映射

来自FinalConfig.java

        // 进行DB映射
arp.addMapping("animal", AnimalModel.class);

这里我们又回到了ActiveRecordPlugin类里。它实际上有两个addMapping方法。仅仅是參数不同。

来自ActiveRecordPlugin.java

        public ActiveRecordPlugin addMapping(String tableName, String primaryKey, Class<?

extends Model<?>> modelClass) {
tableList.add(new Table(tableName, primaryKey, modelClass));
return this;
} public ActiveRecordPlugin addMapping(String tableName, Class<? extends Model<? >> modelClass) {
tableList.add(new Table(tableName, modelClass));
return this;
}

我们看到,第一个方法多了一个參数 String primaryKey。我的代码里用的是第二个方法。这两个方法实际上都调用了tableList.add(Table tbl)方法。我们看看tableList是什么

来自ActiveRecordPlugin.java

private List<Table> tableList = new ArrayList<Table>();

它是ActiveRecordPlugin的一个成员变量,而且是private的,那我们能够猜到,tableList保存了全部的映射关系。(ActiveRecordPlugin真是强大,后面会越来越强大~)。

第三步:创建映射关系

来自ActiveRecordPlugin.java

new Table(tableName, primaryKey, modelClass)
new Table(tableName, modelClass)

我们进去看看

来自Table.java

public Table(String name, Class<? extends Model<?

>> modelClass) {
if (StrKit.isBlank(name))
throw new IllegalArgumentException("Table name can not be blank.");
if (modelClass == null)
throw new IllegalArgumentException("Model class can not be null."); this.name = name.trim();
this.modelClass = modelClass;
} public Table(String name, String primaryKey, Class<? extends Model<?>> modelClass) {
if (StrKit.isBlank(name))
throw new IllegalArgumentException("Table name can not be blank.");
if (StrKit.isBlank(primaryKey))
throw new IllegalArgumentException("Primary key can not be blank.");
if (modelClass == null)
throw new IllegalArgumentException("Model class can not be null."); this.name = name.trim();
setPrimaryKey(primaryKey.trim()); // this.primaryKey = primaryKey.trim();
this.modelClass = modelClass;
}

这两个方法都是为Table里的成员变量赋值,第二个方法,也就是带primaryKey參数的那个多出一行。我们看看这一行干了什么

来自Table.java

setPrimaryKey(primaryKey.trim());	// this.primaryKey = primaryKey.trim();
        void setPrimaryKey(String primaryKey) {
String[] keyArr = primaryKey.split(",");
if (keyArr.length > 1) {
if (StrKit.isBlank(keyArr[0]) || StrKit.isBlank(keyArr[1]))
throw new IllegalArgumentException("The composite primary key can not be blank.");
this.primaryKey = keyArr[0].trim();
this.secondaryKey = keyArr[1].trim();
}
else {
this.primaryKey = primaryKey;
}
}

这种作用就是为Table下的primaryKey 和 secondaryKey赋值。

第四步:载入ActiveRecordPlugin

那么代码好像跟到这里就完事了。怎么回事?是不是跟丢了?

别忘了,ActiveRecordPlugin是在FinalConfig里的configPlugin方法载入的。那么又有谁来载入FinalConfig呢?

PS:(FinalConfig是我自定义的类)

public class FinalConfig extends JFinalConfig 

这儿涉及到初始化的载入了,我简单的讲一下。

整个JFinal的入口是web.xml的一段配置:

来自web.xml

<web-app>
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>com.demo.config.FinalConfig</param-value>
</init-param>
</filter>

接着我们看到了关键的累 JFinalFilter。还是点进去看看。

public final class JFinalFilter implements Filter

这个类实现了Filter接口,那就得实现方法init(),doFilter(),destroy()方法。

我们去看init()方法:

来自JFinalFilter.java

         public void init(FilterConfig filterConfig) throws ServletException {
createJFinalConfig(filterConfig.getInitParameter("configClass")); if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)
throw new RuntimeException("JFinal init error!"); handler = jfinal.getHandler();
constants = Config.getConstants();
encoding = constants.getEncoding();
jfinalConfig.afterJFinalStart(); String contextPath = filterConfig.getServletContext().getContextPath();
contextPathLength = (contextPath == null || "/".equals(contextPath) ? 0 : contextPath.length());
}

绕过其它的载入,直接看这行

来自JFinalFilter.java

if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)

我们看看jfinal的类型是    private static final JFinal jfinal = JFinal.me();

那么我们去JFinal类里看看它的init方法。

来自JFinal.java

        boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
this.servletContext = servletContext;
this.contextPath = servletContext.getContextPath(); initPathUtil(); Config.configJFinal(jfinalConfig); // start plugin and init logger factory in this method
constants = Config.getConstants(); initActionMapping();
initHandler();
initRender();
initOreillyCos();
initI18n();
initTokenManager(); return true;
}

看这行,以下这行主要是通过Config来载入暴露给程序猿的核心文件,JFinalConfig的子类FinalConfig。

来自JFinal.java

Config.configJFinal(jfinalConfig);	// start plugin and init logger factory in this method

再点进去

来自com.jfinal.core.Config.java

        /*
* Config order: constant, route, plugin, interceptor, handler
*/
static void configJFinal(JFinalConfig jfinalConfig) {
jfinalConfig.configConstant(constants); initLoggerFactory();
jfinalConfig.configRoute(routes);
jfinalConfig.configPlugin(plugins); startPlugins(); // very important!!!
jfinalConfig.configInterceptor(interceptors);
jfinalConfig.configHandler(handlers);
}

这段代码实际上有个地方特别坑!就是

来自com.jfinal.core.Config.java

		jfinalConfig.configPlugin(plugins);					startPlugins();	// very important!!!

这行代码一共做了两件事,第一件事是jfinalConfig.configPlugin(plugins);来载入插件。还记得我们之前写的FinalConfig里的configPlugin(Plugins me) 方法吗?

来自FinalConfig.java

    /**
* Config plugin
* 配置插件
* JFinal有自己独创的 DB + ActiveRecord模式
* 此处须要导入ActiveRecord插件
*/
@Override
public void configPlugin(Plugins me) {
// 读取db配置文件
loadPropertyFile("db.properties");
// 採用c3p0数据源
C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcUrl"),getProperty("user"), getProperty("password"));
me.add(c3p0Plugin);
// 採用DB+ActiveRecord模式
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);
me.add(arp);
// 进行DB映射
arp.addMapping("animal", AnimalModel.class);
}

它实际上就是通过me.add来载入插件。通过Config的    private static final Plugins plugins = new Plugins(); 来装载。

第二件事就是 发现没有,后面的startPlugins()不是凝视!是一个方法。这块实在太坑了,恰巧。这就是我们要找到的地方。

这种方法的代码有点长,但由于非常重要,我不得不都贴出来。

来自ActiveRecordPlugin.java

private static void startPlugins() {
List<IPlugin> pluginList = plugins.getPluginList();
if (pluginList != null) {
for (IPlugin plugin : pluginList) {
try {
// process ActiveRecordPlugin devMode
if (plugin instanceof com.jfinal.plugin.activerecord.ActiveRecordPlugin) {
com.jfinal.plugin.activerecord.ActiveRecordPlugin arp = (com.jfinal.plugin.activerecord.ActiveRecordPlugin)plugin;
if (arp.getDevMode() == null)
arp.setDevMode(constants.getDevMode());
} boolean success = plugin.start();
if (!success) {
String message = "Plugin start error: " + plugin.getClass().getName();
log.error(message);
throw new RuntimeException(message);
}
}
catch (Exception e) {
String message = "Plugin start error: " + plugin.getClass().getName() + ". \n" + e.getMessage();
log.error(message, e);
throw new RuntimeException(message, e);
}
}
}
}

上面这种方法一共同拥有两个地方要注意一下。

来自ActiveRecordPlugin.java

			for (IPlugin plugin : pluginList) {

上面这行是循环全部的插件,而且启动插件的start()方法。

那么。我们中有一个插件记不记得是ActiveRecordPlugin的实例?那么

来自ActiveRecordPlugin.java

boolean success = plugin.start();

这行代码就会运行ActiveRecordPlugin下的start()代码。最终绕回来了!!

红军二万五千里长征。为了证明这个调用,我写了多少字....

那么我们看ActiveRecordPlugin下的start()方法吧。实际上这个start()方法是由于实现了IPlugin接口里的start()方法。

来自ActiveRecordPlugin.java

    public boolean start() {
if (isStarted)
return true; if (dataSourceProvider != null)
dataSource = dataSourceProvider.getDataSource();
if (dataSource == null)
throw new RuntimeException("ActiveRecord start error: ActiveRecordPlugin need DataSource or DataSourceProvider"); if (config == null)
config = new Config(configName, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache);
DbKit.addConfig(config); boolean succeed = TableBuilder.build(tableList, config);
if (succeed) {
Db.init();
isStarted = true;
}
return succeed;
}

我们直接看与DB映射有关的代码。首先是取得dataSource,dataSourceProvider这个忘了没。忘了就翻到最前面,第一步讲的。

来自ActiveRecordPlugin.java

            config = new Config(configName, dataSource, dialect, showSql, devMode, transactionLevel, containerFactory, cache);

这行代码中的dataSource 在插件里配置的C3P0数据源。

这里的Config与前面载入FinalConfig的可不是一个啊,千万别看错了,这个是DB的 com.jfinal.plugin.activerecord.Config。

第五步:TableBuilder

来自ActiveRecordPlugin.java

		boolean succeed = TableBuilder.build(tableList, config);

来自TableBuilder.java

static boolean build(List<Table> tableList, Config config) {
Table temp = null;
Connection conn = null;
try {
conn = config.dataSource.getConnection();
TableMapping tableMapping = TableMapping.me();
for (Table table : tableList) {
temp = table;
doBuild(table, conn, config);
tableMapping.putTable(table);
DbKit.addModelToConfigMapping(table.getModelClass(), config);
}
return true;
} catch (Exception e) {
if (temp != null)
System.err.println("Can not create Table object, maybe the table " + temp.getName() + " is not exists.");
throw new ActiveRecordException(e);
}
finally {
config.close(conn);
}
}

这里循环全部的tableList,对每一个Table对象进行建表。那么我们先看看Table是用什么来存储数据库映射关系的,相信大家都能猜到是Map了。

来自Table.java

public class Table {

	private String name;
private String primaryKey;
private String secondaryKey = null;
private Map<String, Class<?>> columnTypeMap; // config.containerFactory.getAttrsMap(); private Class<? extends Model<? >> modelClass;

columnTypeMap是keyword段。暂且记下来。

以下我们还是回到TableBuilder里的doBuild(table, conn, config);方法。

这个才是DB映射的关键。我事实上直接讲这一个类就能够的......这种方法代码实在太多了,我贴部分代码做解说吧。

那么第六步:doBuild具体解释。

这块有点类,我直接在代码里写凝视吧:

来自TableBuilder.java

	@SuppressWarnings("unchecked")
private static void doBuild(Table table, Connection conn, Config config) throws SQLException { // 初始化 Table 里的columnTypeMap字段。
table.setColumnTypeMap(config.containerFactory.getAttrsMap());
// 取得主键,假设取不到的话,默认设置"id"。
// 记不记得最開始的两个同名不同參的方法 addMapping(...),在这才体现出兴许处理的不同。
if (table.getPrimaryKey() == null)
table.setPrimaryKey(config.dialect.getDefaultPrimaryKey());
// 此处假设没有设置方言,则默认 Dialect dialect = new MysqlDialect(); Mysql的方言。
// sql为"select * from `" + tableName + "` where 1 = 2";
String sql = config.dialect.forTableBuilderDoBuild(table.getName());
Statement stm = conn.createStatement();
ResultSet rs = stm.executeQuery(sql);
//取得个字段的信息
ResultSetMetaData rsmd = rs.getMetaData();
// 匹配映射
for (int i=1; i<=rsmd.getColumnCount(); i++) {
String colName = rsmd.getColumnName(i);
String colClassName = rsmd.getColumnClassName(i);
if ("java.lang.String".equals(colClassName)) {
// varchar, char, enum, set, text, tinytext, mediumtext, longtext
table.setColumnType(colName, String.class);
}
else if ("java.lang.Integer".equals(colClassName)) {
// int, integer, tinyint, smallint, mediumint
table.setColumnType(colName, Integer.class);
}
else if ("java.lang.Long".equals(colClassName)) {
// bigint
table.setColumnType(colName, Long.class);
}
// else if ("java.util.Date".equals(colClassName)) { // java.util.Data can not be returned
// java.sql.Date, java.sql.Time, java.sql.Timestamp all extends java.util.Data so getDate can return the three types data
// result.addInfo(colName, java.util.Date.class);
// }
else if ("java.sql.Date".equals(colClassName)) {
// date, year
table.setColumnType(colName, java.sql.Date.class);
}
else if ("java.lang.Double".equals(colClassName)) {
// real, double
table.setColumnType(colName, Double.class);
}
else if ("java.lang.Float".equals(colClassName)) {
// float
table.setColumnType(colName, Float.class);
}
else if ("java.lang.Boolean".equals(colClassName)) {
// bit
table.setColumnType(colName, Boolean.class);
}
else if ("java.sql.Time".equals(colClassName)) {
// time
table.setColumnType(colName, java.sql.Time.class);
}
else if ("java.sql.Timestamp".equals(colClassName)) {
// timestamp, datetime
table.setColumnType(colName, java.sql.Timestamp.class);
}
else if ("java.math.BigDecimal".equals(colClassName)) {
// decimal, numeric
table.setColumnType(colName, java.math.BigDecimal.class);
}
else if ("[B".equals(colClassName)) {
// binary, varbinary, tinyblob, blob, mediumblob, longblob
// qjd project: print_info.content varbinary(61800);
table.setColumnType(colName, byte[].class);
}
else {
int type = rsmd.getColumnType(i);
if (type == Types.BLOB) {
table.setColumnType(colName, byte[].class);
}
else if (type == Types.CLOB || type == Types.NCLOB) {
table.setColumnType(colName, String.class);
}
else {
table.setColumnType(colName, String.class);
}
// core.TypeConverter
// throw new RuntimeException("You've got new type to mapping. Please add code in " + TableBuilder.class.getName() + ". The ColumnClassName can't be mapped: " + colClassName);
}
} rs.close();
stm.close();
}

这里巧妙的运用了 where 1=2的无检索条件结果。通过ResultSetMetaData rsmd = rs.getMetaData(); 导出了DB模型,这招确实美丽。之前我还冥思苦相,他是怎么做的呢,看着此处源代码。茅塞顿开。

接着,把编辑好的Table实例。放到TableMapping的成员变量 Model<?>>, Table> modelToTableMap 里去,TableMapping是单例的。

来自TableMapping.java

private final Map<Class<? extends Model<?

>>, Table> modelToTableMap = new HashMap<Class<?

extends Model<?>>, Table>();
public void putTable(Table table) {
modelToTableMap.put(table.getModelClass(), table);
}

这样。全部的映射关系就都存在TableMapping的modelToTableMap

来自TableBuilder.java

tableMapping.putTable(table);

。再将modelToConfig都放入DbKit.modelToConfig里。

来自TableBuilder.java

DbKit.addModelToConfigMapping(table.getModelClass(), config);

第七步,使用

Model里的save方法举例:

来自Model.java

	/**
* Save model.
*/
public boolean save() {
Config config = getConfig();
Table table = getTable(); StringBuilder sql = new StringBuilder();
List<Object> paras = new ArrayList<Object>();
config.dialect.forModelSave(table, attrs, sql, paras);
// if (paras.size() == 0) return false; // The sql "insert into tableName() values()" works fine, so delete this line // --------
Connection conn = null;
PreparedStatement pst = null;
int result = 0;
try {
conn = config.getConnection();
if (config.dialect.isOracle())
pst = conn.prepareStatement(sql.toString(), new String[]{table.getPrimaryKey()});
else
pst = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS); config.dialect.fillStatement(pst, paras);
result = pst.executeUpdate();
getGeneratedKey(pst, table);
getModifyFlag().clear();
return result >= 1;
} catch (Exception e) {
throw new ActiveRecordException(e);
} finally {
config.close(pst, conn);
}
}
	Config config = getConfig();

上面这行就是调用DbKit的方法,取得DB配置。

来自Model.java

	public static Config getConfig(Class<?

extends Model> modelClass) {
return modelToConfig.get(modelClass);
}

以下这段代码是去单例的TableMapping里取得表的详细信息。

来自Model.java

	Table table = getTable();
	private Table getTable() {
return TableMapping.me().getTable(getClass());
}

以上。就是DB+ActiveRecord的核心调用流程,下次我会带来初始化流程,只是这是个大活的,预计到单独的章节来写。

版权声明:本文博主原创文章,博客,未经同意不得转载。

JFinal 的源代码超具体的分析DB+ActiveRecord的更多相关文章

  1. JFinal 源码分析 [DB+ActiveRecord]

    我记得以前有人跟我说,“面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇文章一下, ...

  2. JFinal极速开发框架使用笔记(三) 分析Model和ActiveRecord

    JFinal框架的一些新发现的用法: 在JFinal框架中,实体类并不需要设置属性,更不需要配置getset方法就可以很方便的操作数据库,如果需要设置或者获取属性,可以直接使用一下方式: User u ...

  3. 1、android源代码下载及目录分析,和eclipser的跟踪

    1.在eclipse中跟踪源代码:假如对mainactivity.java里面的activity按Ctrl+鼠标左键(前提已经导入android源代码:方法1:在项目点击右键,然后找到properti ...

  4. sqlt 之 分析 DB upgrade 导致SQL 性能下降 的方法 xplore

    https://blog.csdn.net/lukeUnique/article/details/79331779 https://mauro-pagano.com/2014/10/27/when-t ...

  5. linux源代码阅读笔记 find_entry分析

    78 static struct buffer_head * find_entry(struct m_inode * dir, 79 const char * name, int namelen, s ...

  6. Android开源框架Volley(Google IO 2013)源代码及内部实现分析

    1.Volley概述 在项目开发过 程中,博主曾写过大量的访问网络重复代码,特别是ListView adapter很难避免getView()方法不被重复调用,如果ImageView不利用缓存机制,那么 ...

  7. JFinal 学习笔记之Handler包分析

    HandlerFactory.java HandlerFactory是不可实例化的,因为 它的构造 函数 特意定位 私有 的:它有一个 静态的方法叫做 getHandler,它有两个参数 ,一个是Ha ...

  8. Php中文件下载功能实现超详细流程分析

    浏览器发送一个请求,请求访问服务器中的某个网页(如:down.php),该网页的代码如下   客户端从服务端下载文件的流程分析: 浏览器发送一个请求,请求访问服务器中的某个网页(如:down.php) ...

  9. Caffe源代码中Solver文件分析

    Caffe源代码(caffe version commit: 09868ac , date: 2015.08.15)中有一些重要的头文件,这里介绍下include/caffe/solver.hpp文件 ...

随机推荐

  1. sys_refcursor的使用方法实例

    --创建过程,參数为sys_refcursor,为out型 create or replace procedure aabbsys_refcursor(o out sys_refcursor) is ...

  2. Windown安装Mysql安装图解

    一.MYSQL的安装 1.打开下载的mysql安装文件mysql-5.0.27-win32.zip,双击解压缩,运行“setup.exe”. 2.选择安装类型,有“Typical(默认)”.“Comp ...

  3. Jersey框架三:Jersey对HTTPS的支持

    Jersey系列文章: Jersey框架一:Jersey RESTful WebService框架简介 Jersey框架二:Jersey对JSON的支持 Jersey框架三:Jersey对HTTPS的 ...

  4. 所有javax包

    所有jar包 > javax javax 下载 javax jar 包 本站下载镜像: javax-jmi-model.jar.zip javax-jmi-reflect.jar.zip jav ...

  5. ZooKeeper场景实践:(2)集中式配置管理

    1. 基本介绍 在分布式的环境中,可能会有多个对等的程序读取相同的配置文件,程序能够部署在多台机器上,假设配置採用文件的话,则须要为部署该程序的机器也部署一个配置文件,一旦要改动配置的时候就会很麻烦, ...

  6. Linking Containers Together

    Linking Containers Together In the Using Docker section we touched on connecting to a service runnin ...

  7. Unity3D中的Update, FixedUpdate, LateUpdate的区别

    MonoBehaviour.Update 更新 当MonoBehaviour启用时,其Update在每一帧被调用. MonoBehaviour.FixedUpdate 固定更新 当MonoBehavi ...

  8. android4.0 USB Camera示例(五个辅助)jpg压缩

    前的最后一个 我们说,一个直接yuv变成jpg该功能 但是转换不成功 主要功能是yuv420转jpg的 根据研究发现 yuv420的序列是这种 YYYY YYYY UVUV 而yuv422的隔行扫描的 ...

  9. Node.js v0.10.31API手冊-控制台

    Node.js v0.10.31API手冊-文件夹 控制台 Object 用于向 stdout 和 stderr 打印字符.类似于大部分 Web 浏览器提供的 console 对象函数,在这里则是输出 ...

  10. 从零开始学Xamarin.Forms(五) 技巧

    原文:从零开始学Xamarin.Forms(五) 技巧 由于HTML5规范于2014年10月终于定稿,公司.net开发人员较少,国内外已有了较为成熟的UI框架.手机软件硬件的快速发展等等原因,所以我就 ...