文档库 最新最全的文档下载
当前位置:文档库 › Commons DbUtils 源码阅读

Commons DbUtils 源码阅读

Commons DbUtils 源码阅读
Commons DbUtils 源码阅读

Commons DbUtils 源码阅读一

想起读源码了!

主要是出于这几个方面的考虑:

1)提高自己的编码及代码阅读能力和水平;

2)学习大师们写代码所用到的设计模式以及思路。

选择阅读Dbutils主要有以下缘由:

1)开源中国社区https://www.wendangku.net/doc/259965529.html,/ ,站长红薯大哥数据的提取就是用的这个开源项目,并且省了很多映射,有较大的编码灵活性;

2)项目不大,代码少,我能在短时间内看完。

DbUtils主要是用来简化JDBC数据的各种操作, 项目的使用范围以及如何使用我在这里就不说了,看看该项目所有代码的结构以及各类的说明:

https://www.wendangku.net/doc/259965529.html,mons.dbutils

|__DbUtils :JDBC辅助方法集合,提供了一系列的JDBC API的静态方法供用户使用,比如数据库驱动的加载、Connection、Statement等资源的释放等等,该类线程安全。

|__QueryRunner :简化处理SQL查询以及与ResultSetHandler处理ResultSet

|__ResultSetHandler :它的实现类借助于RowProcesser实现类将java.sql.ResultSet转换成其它对象,以下为它的一些具体实现类

|__https://www.wendangku.net/doc/259965529.html,mons.dbutils.handlers

|__AbstractKeyedHandler

|__KeyedHandler

|__AbstractListHandler

|__ArrayListHandler

|__ColumnListHandler

|__MapListHandler

|__ArrayHandler

|__BeanHandler

|__BeanListHandler

|__MapHandler

|__ScalarHandler

|__QueryLoader :加载指定Properties的SQL映射到内存中并以Map返回

|__ProxyFactory :创建JDBC API接口的一些代理类,比如:java.sql.Connection、

java.sql.Statement等等

java.util

|_Iterator

|_ResultSetIterator

|__RowProcessor :定义了java.sql.ResultSet转变为其它对象的一些方法,在其实现类BasicRowProcessor中有具体的操作

|__BasicRowProcessor

|__BeanProcessor :匹配java.sql.ResultSet的列表到bean的属性,相当于做一些Hibernate XML 文件映射之类的工作!

https://www.wendangku.net/doc/259965529.html,ng.reflect

|_InvocationHandler

|_https://www.wendangku.net/doc/259965529.html,mons.dbutils.wrappers:代理实例的调用处理程序 ,做一些空值数据的处理

|__StringTrimmedResultSet

|__SqlNullCheckedResultSet

代码的结构算是理清楚了,下一步,对这个结构里的每一个内容和结构来进一步的查看,正式要开始罗,

期待!

Commons DbUtils 源码阅读二

DbUtils组件的整个代码结构做了一个简单的疏导,现在来看看一些比较核心的接口:

1)RowProcessor

/**

*RowProcessor的实现类将ResultSet转换为其它的各种对象.

*具体的实现细节可查看:BasicRowProcessor

*/

public interface RowProcessor

{

/**

*通过指定有效的ResultSet对象,创建一个Object[].

*ResultSet在传入这个方法之前,必须指定一个有效的位置,

*这个方法的实现必须不能改变ResultSet的行位置

*@param rs要转换为数组的ResultSet

*@throws SQLException数据访问异常

*@return创建的一个新的数组

*/

public Object[] toArray(ResultSet rs) throws SQLException;

/**

*通过指定有效的ResultSet,创建一个JavaBean实例.

*ResultSet在传入这个方法之前,必须指定一个有效的位置,

*这个方法的实现必须不能改变ResultSet的行位置

*@param创建的bean的类型

*@param rs同上

*@param type创建bean实例的类

*@throws SQLException同上

*@return创建的一个新的实例对象

*/

public T toBean(ResultSet rs, Class type) throws SQLException;

/**

*通过遍历ResultSet,创建一个bean集合,

*在执行这个方法之前,不能调用ResultSet.next()方法。

*@param同上

*@param rs同上

*@param type同上

*@throws SQLException同上

*@return指定类型的bean集合

*/

public List toBeanList(ResultSet rs, Class type) throws SQLException;

/**

*将指定的ResultSet行转换为一个Map。

*必须指定一个有效的位置传入该方法.

*该方法的实现必须保证不改变ResultSet的行位置

*@param rs同上

*@throws SQLException同上

*@return新创建的Map集合

*/

public Map toMap(ResultSet rs) throws SQLException;

}

这个呢,是 DbUtils组件核心接口之一,也就是真正解析java.sql.ResultSet的接口,当然了,真正做事情的是

它的实现类了:BasicRowProcessor,这个类的分析下一节再说,再来看看另一个重要的接口:

2)ResultSetHandler

/**

*该接口实现类将java.sql.ResultSet转换为其它的对象

*/

public interface ResultSetHandler {

/**

*

*将java.sql.Result转换为一个对象

*

*@param rs待转换的java.sql.ResultSet

*

*@return java.sql.ResultSet转换的对象.

*如果ResultSet为空,刚返回一个null

*

*@throws SQLException数据访问异常

*/

public T handle(ResultSet rs) throws SQLException;

看看这两个接口的说明,有点奇怪对不对,为什么这两个类都说将一个java.sql.ResultSet转换为其它对象呢?

实际上呢,如果你提前看过了它们的实现类,那么你可能就不会那么奇怪了,RowProcessor这个接口,顾名思义啊,它是处理java.sql.ResultSet中单个行的数据的,而我们的数据集怎么可能是只有一条呢,所以,要处理所有的ResultSet里面的数据怎么办?OK,对,ResultSetHandler,如果你对ResultSetHandler的实现类目堵过,那么,你就不难发现,RowProcessor是ResultSetHandler的成员变量,也可以理解为ResultSetHandler的实现类是RowProcessor的包装类。

有点明目没?!

下一节,具体来看里面的实现

Commons DbUtils 源码阅读三

前两天着实被javaeye关闭着急了一下,还好,总算开放了!

继续我们的DbUtils组件的阅读研究吧。

RowProcessor的实现子类:BasicRowProcessor

/**

*RowProcessor接口的实现类

*/

public class BasicRowProcessor implements RowProcessor

{

/**

*默认转换器

*如果没有指定,则使用这个进行resultSet的转换

*/

private static final BeanProcessor defaultConvert = new

BeanProcessor();

private static final BasicRowProcessor instance = new

BasicRowProcessor();

/**

*返回BasicRowProcessor的单实例

*过期方法,建议使用构造器创建对象,这个方法将在DbUtils 1.1中去掉

*@return这个类的单实例.

*@deprecated

*/

public static BasicRowProcessor instance()

{

return instance;

}

/**

*使用这个实例来进行bean的转换

*/

private final BeanProcessor convert;

/**

*BasicRowProcessor构造器.

*默认使用BeanProcessor转换

*/

public BasicRowProcessor()

{

this(defaultConvert);

}

/**

*BasicRowProcessor构造器.

*@param convert当需要将表属性名转换为bean属性时要使用的BeanProcessor实例*@since DbUtils 1.1

*/

public BasicRowProcessor(BeanProcessor convert)

{

super();

this.convert = convert;

}

/**

*将ResultSet的一行转换为一个Object[]数组

*这个实现将ResultSet中的数据顺序的存入数组.

*如果列值为NULL,则数组元素设置为null.

*/

public Object[] toArray(ResultSet rs) throws SQLException

{

ResultSetMetaData meta = rs.getMetaData();//获取ResultSet的MetaData 对象

int cols = meta.getColumnCount();//获取列数

Object[] result = new Object[cols];

for (int i = 0; i < cols; i++)

{

result[i] = rs.getObject(i + 1);//获取每列对应的值,并赋给新数组

}

return result;

}

/**

*将ResultSet的指定行转换为一JavaBean.

*这个实现代理需要一个BeanProcessor实例.

*具体的可参见:BeanProcessor的toBean(java.sql.ResultSet,

https://www.wendangku.net/doc/259965529.html,ng.Class)方法

*/

public T toBean(ResultSet rs, Class type) throws SQLException

{

return this.convert.toBean(rs, type);

}

/**

*将ResultSet的指定行转换为一JavaBean集合.

*该方法同样需要一个BeanProcessor实例

*具体的可参见:BeanProcessor的toBeanList(java.sql.ResultSet,

https://www.wendangku.net/doc/259965529.html,ng.Class)方法

*/

public List toBeanList(ResultSet rs, Class type) throws

SQLException {

return this.convert.toBeanList(rs, type);

}

/**

*将指定的ResultSet行转换为一个Map

*这个实现返回一个以大小写无关的列名为键值的Map集合

*例如:调用map.get("COL")或者map.get("col")返回相同的值.

*/

public Map toMap(ResultSet rs) throws SQLException

{

Map result = new CaseInsensitiveHashMap();

//创建一个自定义的大小写无关的Map实例

ResultSetMetaData rsmd = rs.getMetaData();

int cols = rsmd.getColumnCount();

for (int i = 1; i <= cols; i++)

{

result.put(rsmd.getColumnName(i), rs.getObject(i));

//这一用法就是将列名为键值

}

return result;

}

}

BasicRowProcessor这个类实现了RowProcessor接口的ResultSet转换为其它对象的方法,现对这个类的几点具体说明如下:

1) public Object[] toArray(ResultSet rs) throws SQLException

这个方法也实在是没啥好说的,一切尽在掌握,很直白,就是利用ResultSet的MetaData对象来对当前

行进行数据的处理操作,最后存入一数组对象,所以,略过!

2) public T toBean(ResultSet rs, Class type) throws SQLException

{

return this.convert.toBean(rs, type);

}

public List toBeanList(ResultSet rs, Class type) throws SQLException

{

return this.convert.toBeanList(rs, type);

}

这两个方法之所以会要一起列出来,主要是出于以下几个原因的考虑,一点是,它们都需要一个BeanProcessor实例来对ResultSet进行处理;另一点则是,我不想把对这两个的分析放在这里,而是想考虑

放到下一节:BeanProcessor的分析(对BeanProcessor的分析也会碰到一新东西,那就是Java的内省Introspector,挺有意思的一知识点,你,值得期待)。实际上呢,我们通过对BasicRowProcessor的分析,我们也可以知道,它默认是采用这个BeanProcessor来处理ResultSet的。

3) public Map toMap(ResultSet rs) throws SQLException

根据这个方法的注释说明,它根据列名获取值,是大小写无关的,实际上一般的Map不可能做到这一点,因为Java是个区分大小写的语言,所以,它肯定对Map进行了封装。实际上呢,这个类里面有个静态的内部类CaseInsensitiveHashMap,真正的奥秘就在这里!这里我把它移出来了,现对此Map的分析如下:

/**

*为了不区分大小写查找而将所有的key转换为小写字符串的Map.

*这个HashMap的实现不允许键值为null,因为我们要将所有的键值变为小写,

*即调用key.toString().toLowerCase().

*/

private static class CaseInsensitiveHashMap extends HashMap {

/**

*以小写字母为键值的内部Map集合

*所有的查询操作步骤操作经过以下三步:

*1)、将参数Key转换为小写

*2)、获取小写Key获取真实Key

*3)、通过真实的Key值获取到值

*/

private final Map lowerCaseMap = new

HashMap();

/**

*Required for serialization support.

*

*@see java.io.Serializable

*/

private static final long serialVersionUID = -2848100435296897392L;

@Override

public boolean containsKey(Object key)

{

Object realKey = lowerCaseMap.get(key.toString().toLowerCase());

return super.containsKey(realKey);

// Possible optimisation here:

// Since the lowerCaseMap contains a mapping for all the keys,

// we could just do this:

// return lowerCaseMap.containsKey(key.toString().toLowerCase()); }

@Override

public Object get(Object key)

{

Object realKey = lowerCaseMap.get(key.toString().toLowerCase());

//获取真正的Key值,然后回去值

return super.get(realKey);

}

@Override

public Object put(String key, Object value)

{

/*

* In order to keep the map and lowerCaseMap synchronized,

* we have to remove the old mapping before putting the

* new one. Indeed, oldKey and key are not necessaliry equals.

* (That's why we call super.remove(oldKey) and not just

* super.put(key, value))

*/

Object oldKey = lowerCaseMap.put(key.toLowerCase(), key);

//返回之前存在的值,实际上对应的Key

Object oldValue = super.remove(oldKey);

super.put(key, value);

return oldValue;//返回之前的值

}

@Override

public void putAll(Map m)

{

for (Map.Entry entry : m.entrySet())

{

String key = entry.getKey();

Object value = entry.getValue();

this.put(key, value);

}

}

@Override

public Object remove(Object key)

{

Object realKey =

lowerCaseMap.remove(key.toString().toLowerCase());//返回该键对应的值//实际上我们可以理解为拿到真正的Key值,然后删除对应的值

return super.remove(realKey);

}

}

很一目了然了已经,通过成员变量lowerCaseMap来达到大小写通吃的目的,这个lowerCaseMap,Key值为小写化的列名,Value值才对应真实列名(即未做最小化处理的列名)。所以说,每次在以列名获取值时,先对其进行最小化,再通过lowerCaseMap获取对应的真实列名,然后再获取到值,这样就达到了一个不区分大小写的效果。OK,这个Map转换总算整明白了。这样呢,我们对这个BasicRowProcessor类的解读也算是圆满完成了。

Commons DbUtils 源码阅读四

在读BasicRowProcessor 类时,它有两个方法(toBean和toBeanList)都将最终的处理工作交给了BeanProcessor,所以,今天来拜读一下此类, 在读此类的时候,决定换个方式,就一个方法慢慢的展开分析,这样或许也会更有趣味和吸引力吧:

public T toBean(ResultSet rs, Class type) throws SQLException {

PropertyDescriptor[] props = this.propertyDescriptors(type);

// 1)获取指定类的属性描述对象集合

ResultSetMetaData rsmd = rs.getMetaData();

int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

//2)列名转换为bean属性

return this.createBean(rs, type, props, columnToProperty);//3)

}

这个方法呢,是将ResultSet的指定行创建一个指定类型的Bean实例返回:

1) 这个是利用内省机制,获取指定类的属性描述数组,来看看这个propertyDescriptors方法

private PropertyDescriptor[] propertyDescriptors(Class c) throws SQLException

{

// Introspector caches BeanInfo classes for better performance

BeanInfo beanInfo = null;

try

{

beanInfo = Introspector.getBeanInfo(c);

} catch (IntrospectionException e)

{

throw new SQLException("Bean introspection failed: "

+ e.getMessage());

}

return beanInfo.getPropertyDescriptors();

}

通过Introspector的静态方法getBeanInfo,获取指定类型的BeanInfo实例,再调用getPropertyDescriptors()方法返回该类的所有PropertyDescriptor数组,非常的简洁明了,这个就是传

说中的内省了

2) 这个方法的功能呢,是将column数组与类的properties匹配,两个都是数组,然后返回还是个数组,居然还map名,所以,看看里面是如何实现的:

protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,

PropertyDescriptor[] props) throws SQLException

{

int cols = rsmd.getColumnCount();

int columnToProperty[] = new int[cols + 1];

Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

for (int col = 1; col <= cols; col++)

{

String columnName = rsmd.getColumnLabel(col);//获取列名

if (null == columnName || 0 == columnName.length())

{

columnName = rsmd.getColumnName(col);

}

for (int i = 0; i < props.length; i++)

{

if (columnName.equalsIgnoreCase(props[i].getName()))

{//与属性名进行匹配 columnToProperty[col] = i;

break;

}

}

}

// comlumnToProperty 里面返回的是column下标对应的PropertyDescriptor属性的下标值

return columnToProperty;

}

这个方法的所做的工作实际上是这样的,将ResultSet中的列名与Bean的属性进行匹配,声明的数组的长度是以ResultSet中的列数量来确定的,所以下标从1开始,然后与Bean的PropertyDescriptor数组进行属性名匹配,如果有找到,则在对应的列名下标数组里存入bean属性的位置。这样一来,我们就可以清晰的知道,ResultSet列名对应的Bean属性了。

OK,也许你现在看这段话有些稀里糊涂,你只要记住,这个返回的数组是按照ResultSet的列标匹配bean 属性数组对应的位置的。

3)看关键方法

private T createBean(ResultSet rs, Class type,

PropertyDescriptor[] props, int[] columnToProperty)

throws SQLException

{

T bean = this.newInstance(type);//创建该类实例

//循环列名与bean属性匹配情况数组

for (int i = 1; i < columnToProperty.length; i++)

{

if (columnToProperty[i] == PROPERTY_NOT_FOUND)

{//该下标没有匹配的属性,则不做处理

continue;

}

PropertyDescriptor prop = props[columnToProperty[i]];

//获取对应属性的描述对象

Class propType = prop.getPropertyType();//获取类型

Object value = this.processColumn(rs, i, propType);//获取rs对应的值

if (propType != null && value == null && propType.isPrimitive())

{

value = primitiveDefaults.get(propType);//为空,获取默认值

}

this.callSetter(bean, prop, value);//调用属性的setter方法 }

return bean;

}

这个方法呢,首先来生成一个指定类型的实例,然后循环之前处理ResultSet列名与Bean属性名匹配情况的数组,获取Bean属性特定的PropertyDescriptor实例,通过this.processColumn(rs, i, propType)这个方法获取数据库中对应的值,再调用callSetter(...)方法来为新建Bean实例赋值上值,来看看它是怎么实现的: Method setter = prop.getWriteMethod();//通过PropertyDescriptor获取对应属性的Setter方法

然后匹配数据库类型与Bean方法的类型,如果不匹配,则抛出异常,否则通过反射来为Bean对象填入对应的值,来看具体实现:

Class[] params = setter.getParameterTypes();//参数类型列表

......................................................................... ...

// Don't call setter if the value object isn't the right type if (this.isCompatibleType(value, params[0]))

{

setter.invoke(target, new Object[] { value });

} else

{

throw new SQLException(

"Cannot set " + prop.getName() + ": incompatible types.");

}

我们深刻的分析了一下toBean方法,toBeanList方法呢,实际上就是循环ResultSet,获取每一行,然后交由toBean()方法处理,贴出具体的代码:

public List toBeanList(ResultSet rs, Class type) throws SQLException {

List results = new ArrayList();

if (!rs.next())

{

return results;

}

PropertyDescriptor[] props = this.propertyDescriptors(type);

ResultSetMetaData rsmd = rs.getMetaData();

int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

do

{

results.add(this.createBean(rs, type, props, columnToProperty));

} while (rs.next());

return results;

}

这个方法,我没有搞明白为什么不直接循环调用toBean()方法,直接调用toBean()方法不好嘛?!(或许是我资力尚浅,没有明白作者的意图)

这里也贴出个人认为可以完善的这个方法实现吧

//PropertyDescriptor[] props = this.propertyDescriptors(type);

//ResultSetMetaData rsmd = rs.getMetaData();

//int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

do {

// results.add(this.createBean(rs, type, props, columnToProperty));

results.add(this.toBean(rs, type));//修改部分

} while (rs.next());

如果有朋友认为我这样写不对,还请不吝教导。

Commons DbUtils 源码阅读五

关于DbUtils,我们深入剖析了对ResultSet解析处理的两个核心类:BeanProcessor和BasicRowProcessor,可以说,这两个类,是对ResultSet的解析有了一个完整的支持。虽然真正做解析工作的是这两个类,但用户对ResultSet的解析是通过接口ResultSetHandler的这个实现类来封装解决的。但要解析的是,我们也得通过SQL语句获取ResultSet对象呀,所以,看看DbUtils是怎么做的。

一、QueryRunner类,利用可插拨的策略执行SQL查询来处理ResultSets,大致看了一下,该类的重载方法确实有够多。来一类一类的解决:

1)构造器有多个重载方法,有必要说明解析一下,现列出部分代码:

/**

*QueryRunner默认构造器

*/

public QueryRunner()

{

super();

ds = null;

}

/**

*允许Oracle驱动程序的解决方案

*@param pmdKnownBroken如果是Oracle drivers,则不支持

ParameterMetaData.getParameterType(int)这个方法;

*if pmdKnownBroken参数设置为true,则我们不做

*ParameterMetaData.getParameterType(int)方法;

*如果为false,那将会尝试获取,如果不支持有异常抛出,则不再使用

*/

public QueryRunner(boolean pmdKnownBroken)

{

super();

this.pmdKnownBroken = pmdKnownBroken;

ds = null;

}

/**

*QueryRunner构造器,Oracle drivers的解决方案.通过DataSource

*获取数据源连接

*@param ds数据源,用于获取数据连接Connection

*/

public QueryRunner(DataSource ds) {

super();

this.ds = ds;

}

/**

*QueryRunner构造器,Oracle drivers的解决方案.通过DataSource

*获取数据源连接

*@param ds数据源,用于获取数据连接Connection.

*@param pmdKnownBroken如果是Oracle drivers,则不支持

ParameterMetaData.getParameterType(int)这个方法;

*if pmdKnownBroken参数设置为true,则我们不做

*ParameterMetaData.getParameterType(int)方法;

*如果为false,那将会尝试获取,如果不支持有异常抛出,则不再使用

*/

public QueryRunner(DataSource ds, boolean pmdKnownBroken) { super();

this.pmdKnownBroken = pmdKnownBroken;

this.ds = ds;

}

本身这个构造器并没有什么,关键是boolean类型的pmdKnownBroken和DataSource类型的ds。DataSource 呢,很明显,是通过它来获取数据库连接,如何程式获取DataSource对象,这得需要借助于commons里的dbcp和pool这两个组件。具体的如何获取,可以参考DBCP组件的官方示例程序,具体网址如下:

https://www.wendangku.net/doc/259965529.html,/viewvc/commons/proper/dbcp/trunk/doc/BasicDataSourceExample.java?vi ew=markup

这个类中的静态方法setupDataSource就是用来获取数据源的,说个题外话,DBCP和pool这两个组件对于数据源的管理,可谓是鼎鼎大名啊,Spring的数据源管理也是基于该组件,当然了还有另外一个数据源C3P0。关于数据源这一知识点,各位有兴趣的朋友可以参考在下写的“Spring 数据源不同配置 ”,扯远了啊,呵~咱接着说pmdKnownBroken这个变量,虽然现在说起来可能感觉有些抽象。源码的解释是这样的:Oracle的驱动程序不支持ParameterMetaData.getParameterType方法,如果pmdKnownBroken设置为true,则我们甚至不进行尝试处理,而false,我们则会尝试着使用ParameterMetaData.getParameterType 方法,如果有异常抛出,则不再使用。

2)一般来说数据库操作的时候,总是会顺口溜似的:增删改查,所以我们先从QueryRunner类的SQL增加操作说起,看了一下这个类的大概实现,实际上呢,update方法它不仅充当了SQL增加操作,同时也充当了更新和删除的操作,所以,一并了解了吧:

/**

*执行一个没有参数的SQL插入、更新或者删除操作

*Execute an SQL INSERT,UPDATE,or DELETE query without replacement

*parameters.

*

*@param conn数据连接The connection to use to run the query.

*@param sql要执行的SQL语句The SQL to execute.

*@return更新的行数The number of rows updated.

*@throws SQLException数据库访问异常if a database access error occurs

*/

public int update(Connection conn, String sql) throws SQLException

{

return this.update(conn, sql, (Object[]) null);

}

/**

*执行只有一个参数的SQL插入、修改或者删除操作

*@param conn执行查询的数据库连接

*@param sql要执行的SQL语句

*@return更新的行数

*@throws SQLException数据库访问异常

*/

public int update(Connection conn, String sql, Object param) throws SQLException

{

return this.update(conn, sql, new Object[] { param });

}

/**

*执行指定没有参数的插入、修改或者删除的SQL语句。

*数据连接通过DataSource(在构造器中指定)获取。

*此连接必须在自动提交模式,否则会导致更新操作不会保存。

*@param sql要执行的SQL语句

*@throws SQLException数据库访问异常

*@return更新的行数

*/

public int update(String sql) throws SQLException

{

return this.update(sql, (Object[]) null);

}

/**

*执行指定只有一个参数的插入、修改或者删除的SQL语句。

*数据连接通过DataSource(在构造器中指定)获取。

*此连接必须在自动提交模式,否则会导致更新操作不会保存。

*

*@param sql要执行的SQL语句

*@param param参数

*@throws SQLException数据库访问异常

*@return更新的行数

*/

public int update(String sql, Object param) throws SQLException

{

return this.update(sql, new Object[] { param });

}

/**

*执行指定的插入、修改或者删除的SQL语句。

*数据连接通过DataSource(在构造器中指定)获取。

*此连接必须在自动提交模式,否则会导致更新操作不会保存。

*

*@param sql要执行的SQL语句

*@param params初始化PreparedStatement参数

*@throws SQLException数据库访问异常

*@return更新的行数

*/

public int update(String sql, Object... params) throws SQLException

{

Connection conn = this.prepareConnection();

try {

return this.update(conn, sql, params);

} finally {

close(conn);

}

}

真是巨多啊!我在每个方法上,都将源码上面的一些说明解释成了中文,各位有兴趣的可以看看。 挑两个具有代表性的方法来读一下:

2-1)获取数据库连接,这个呢,源码上面的说明也说了,是通过DataSource来获取的,具体看看prepareConnection()这个方法:

protected Connection prepareConnection() throws SQLException

{

if(this.getDataSource() == null) {

throw new SQLException("QueryRunner requires a DataSource to be " + "invoked in this way, or a Connection should be passed in");

}

return this.getDataSource().getConnection();

}

这个方法比较的简单,首先是获取数据源实例,如果数据源为空,则抛出异常:必须要有一个DataSource,然后呢,就会获取一个Connection实例返回。这个DataSource实例呢,是在实例化的时候指定的,当然了,我们也可以子类重写这个prepareConnection方法,来实现一个指定的获取数据库连接的方法。

2-2)

/**

*执行一个SQL插入、更新或者删除操作

*@param conn执行查询的数据库连接

*@param sql要执行的SQL语句

*@return更新的行数

*@throws SQLException数据库访问异常

*/

public int update(Connection conn, String sql, Object... params)

throws SQLException

{

PreparedStatement stmt = null;

int rows = 0;

try {

stmt = this.prepareStatement(conn, sql);//通过Connection和sql获取

PreparedStatement实例

this.fillStatement(stmt, params);

rows = stmt.executeUpdate();

} catch (SQLException e) {

this.rethrow(e, sql, params);

} finally {

close(stmt);

}

return rows;

}

来,一步一步的执行这个核心方法,首先,通过prepareStatement这个方法,传入数据库连接和SQL这两

个参数

得到一个PreparedStatement对象实例;然后通过fillStatement方法填充参数值,看看具体实现:

/**

*通过指定对象填充PreparedStatement的代替参数。

*@param stmt PreparedStatement to fill

*@param params查询替代参数;null也是有效的参数。

*@throws SQLException数据库访问异常

*/

public void fillStatement(PreparedStatement stmt, Object... params)

throws SQLException

{

if (params == null)

{//参数为空,则返回

return;

}

ParameterMetaData pmd = null;

if (!pmdKnownBroken)

{//false,we try it

pmd = stmt.getParameterMetaData();

//获取关于PreparedStatement 对象中参数的类型和属性信息的对象

if (pmd.getParameterCount() < params.length)

{

//如果PreparedStatement需要的参数数量少于指定参数数量,则抛出数量不匹配异常

throw new SQLException("Too many parameters: expected "

+ pmd.getParameterCount() + ", was given " + params.length);

}

}

//循环参数

for (int i = 0; i < params.length; i++)

{

if (params[i] != null)

{//如果指定的参数不为空,则指定参数值

stmt.setObject(i + 1, params[i]);

} else

{

// VARCHAR类型可以与许多的驱动工作,而不管真实的列类型.

// 奇怪的是,NULL和OTHER与Oracle的驱动不能工作.

// VARCHAR works with many drivers regardless

// of the actual column type. Oddly, NULL and

// OTHER don't work with Oracle's drivers.

int sqlType = Types.VARCHAR;

if (!pmdKnownBroken)

{//false

try

{

sqlType = pmd.getParameterType(i + 1);//获取特定的参数类型 } catch (SQLException e)

{

pmdKnownBroken = true;

//如果不支持getParameterType方法,则不再尝试使用

}

}

stmt.setNull(i + 1, sqlType);//为特定类型赋空值

}

}

}

我已经对这个方法做了一些必要的说明,实际上呢,最需要强调的,就是pmdKnownBroken参数以及Oracle 驱动的关系,pmdKnownBroken这个参数呢,我们已经在构造器那一块说过了,它实际上用于区别Oracle

驱动, 说是Oracle驱动不支持getParameterType方法,我是不清楚了,没有使用过,所以没有发言权,但我想这个问题应该会有所解决.另一个批量查询方法batch,主要方法是与update方法类似的,故不再解析。现在呢,主要的方法体功能已经了解完了。

3)接下来呢,理论上应该是SQL的查询方法解析了,但我看了一下query方法,需要说明的,我们都已经在之前的update方法里拜读过了,唯一不同的是就是多了一个ResultSetHandler参数,之前呢,我有说过ResultSetHandler这个接口,它通过调用handler方法处理ResultSet结果集完成指定类型的转换,本身dbUtils组件呢,提供了众多的ResultSetHandler实现类,它们都位于

https://www.wendangku.net/doc/259965529.html,mons.dbutils.handlers的包下,我会在以后的章节中具体解析。

4)具体来说明一下QueryRunner这个类中的fillStatementWithBean这个方法,在整个组件中暂未用到,但,我想在面向对象的Java开发中,通过指定的bean实例,为SQL语句参数指定bean变量值肯定是会广泛应用的,也就是JavaBean与特定数据表的映射了。Hibernate、JPA等框架能够自动完成对象与关系型

数据库的映射,底层的实现也诸如此类吧!

/**

*根据bean的属性值填充PerparedStatement的参数

*@param stmt

*待填充值的PreparedStatement

*@param bean

*JavaBean对象

*@param propertyNames

*有序的属性名称数组(这些名字应该有

*getters/setters方法匹配);这个属性数组顺序与statement的插入参数顺序匹配

*@throws SQLException

*数据访问异常

*/

public void fillStatementWithBean(PreparedStatement stmt, Object bean,

String... propertyNames) throws SQLException

{

PropertyDescriptor[] descriptors;

try

{

descriptors = Introspector.getBeanInfo(bean.getClass())

.getPropertyDescriptors(); //4-1

}

catch(IntrospectionException e)

{

throw new RuntimeException("Couldn't introspect bean " +

bean.getClass().toString(), e);

}

PropertyDescriptor[] sorted = new

PropertyDescriptor[propertyNames.length];//4-2

//参数名与Bean的属性进行比较,确保属性的完整性

//确保为每个属性名找到在bean中对应的PropertyDescriptor实例

for (int i = 0; i < propertyNames.length; i++)

{

String propertyName = propertyNames[i];

if (propertyName == null)

{//属性列表里的属性不能为空

throw new NullPointerException("propertyName can't be null: " + i); }

boolean found = false;

for (int j = 0; j < descriptors.length; j++)

{//4-3

PropertyDescriptor descriptor = descriptors[j];

if (propertyName.equals(descriptor.getName()))

{

sorted[i] = descriptor;//此属性在bean中存在,赋于PropertyDescriptor

实例

found = true;

break;

}

}

if (!found)

{

throw new RuntimeException("Couldn't find bean property: "

如何阅读别人代码

如何阅读别人代码 code reading ++++++++++++ 第一章: 导论 ++++++++++++ 1.要养成一个习惯, 经常花时间阅读别人编写的高品质代码. 2.要有选择地阅读代码, 同时, 还要有自己的目标. 您是想学习新的模式|编码风格|还是满足某些需求的方法. 3.要注意并重视代码中特殊的非功能性需求, 这些需求也许会导致特殊的实现风格. 4.在现有的代码上工作时, 请与作者和维护人员进行必要的协调, 以避免重复劳动或产生厌恶情绪. 5.请将从开放源码软件中得到的益处看作是一项贷款, 尽可能地寻找各种方式来回报开放源码社团. 6.多数情况下, 如果您想要了解"别人会如何完成这个功能呢?", 除了阅读代码以外, 没有更好的方法. 7.在寻找bug时, 请从问题的表现形式到问题的根源来分析代码. 不要沿着不相关的路径(误入歧途). 8.我们要充分利用调试器|编译器给出的警告或输出的符号代码|系统调用跟踪器|数据库结构化查询语言的日志机制|包转储工具和Windows的消息侦查程序, 定出的bug的位置. 9.对于那些大型且组织良好的系统, 您只需要最低限度地了解它的全部功能, 就能够对它做出修改. 10.当向系统中增加新功能时, 首先的任务就是找到实现类似特性的代码, 将它作为待实现功能的模板. 11.从特性的功能描述到代码的实现, 可以按照字符串消息, 或使用关键词来搜索代码. 12.在移植代码或修改接口时, 您可以通过编译器直接定位出问题涉及的范围, 从而减少代码阅读的工作量. 13.进行重构时, 您从一个能够正常工作的系统开始做起, 希望确保结束时系统能

java 基础知识之hadoop源码阅读必备(一)

java 程序员你真的懂java吗? 一起来看下hadoop中的如何去使用java的 大数据是目前IT技术中最火热的话题,也是未来的行业方向,越来越多的人参与到大数据的学习行列中。从最基础的伪分布式环境搭建,再到分布式环境搭建,再进入代码的编写工作。这时候码农和大牛的分界点已经出现了,所谓的码农就是你让我做什么我就做什么,我只负责实现,不管原理,也不想知道原理。大牛就开始不听的问自己why?why?why?于是乎,很自然的去看源码了。然而像hadoop这样的源码N多人参与了修改和完善,看起来非常的吃力。然后不管如何大牛就是大牛,再硬的骨头也要啃。目前做大数据的80%都是从WEB开发转变过来的,什么spring mvc框架、SSH框架非常熟悉,其实不管你做了多少年的WEB开发,你很少接触到hadoop中java代码编写的风格,有些人根本就看不懂什么意思。下面我来介绍下hadoop源码怎么看。 hadoop体现的是分布式框架,因此所有的通信都基于RPC来操作,关于RPC的操作后续再介绍。hadoop源码怎么看系列分多个阶段介绍,下面重点介绍下JA V A基础知识。 一、多线程编程 在hadoop源码中,我们能看到大量的类似这样的代码 return executor.submit(new Callable() { @Override public String call() throws Exception { //方法类 } 下面简单介绍下java的多线程编程 启动一个线程可以使用下列几种方式 1、创建一个Runnable,来调度,返回结果为空。 ExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(new Runnable() { @Override public void run() { System.out.println("runnable1 running."); } }); 这种方式启动一个线程后,在后台运行,不用等到结果,因为也不会返回结果 2、创建一个Callable,来调度,有返回结果 Future future1 = executor.submit(new Callable() { @Override public String call() throws Exception { // TODO Auto-generated method stub //具体执行一些内部操作 return "返回结果了!"; } }); System.out.println("task1: " + future1.get());

Linux 0.1.1文件系统的源码阅读

Linux 0.11文件系统的源码阅读总结 1.minix文件系统 对于linux 0.11内核的文件系统的开发,Linus主要参考了Andrew S.Tanenbaum 所写的《MINIX操作系统设计与实现》,使用的是其中的1.0版本的MINIX文件系统。而高速缓冲区的工作原理参见M.J.Bach的《UNIX操作系统设计》第三章内容。 通过对源代码的分析,我们可以将minix文件系统分为四个部分,如下如1-1。 ●高速缓冲区的管理程序。主要实现了对硬盘等块设备进行数据高速存取的函数。 ●文件系统的底层通用函数。包括文件索引节点的管理、磁盘数据块的分配和释放 以及文件名与i节点的转换算法。 ●有关对文件中的数据进行读写操作的函数。包括字符设备、块设备、管道、常规 文件的读写操作,由read_write.c函数进行总调度。 ●涉及到文件的系统调用接口的实现,这里主要涉及文件的打开、关闭、创建以及 文件目录等系统调用,分布在namei和inode等文件中。 图1-1 文件系统四部分之间关系图

1.1超级块 首先我们了解一下MINIX文件系统的组成,主要包括六部分。对于一个360K软盘,其各部分的分布如下图1-2所示: 图 1-2 建有MINIX文件系统的一个360K软盘中文件系统各部分的布局示意图 注释1:硬盘的一个扇区是512B,而文件系统的数据块正好是两个扇区。 注释2:引导块是计算机自动加电启动时可由ROM BIOS自动读入得执行代码和数据。 注释3:逻辑块一般是数据块的2幂次方倍数。MINIX文件系统的逻辑块和数据块同等大小 对于硬盘块设备,通常会划分几个分区,每个分区所存放的不同的文件系统。硬盘的第一个扇区是主引导扇区,其中存放着硬盘引导程序和分区表信息。分区表中得信息指明了硬盘上每个分区的类型、在硬盘中其实位置参数和结束位置参数以及占用的扇区总数。其结构如下图1-3所示。 图1-3 硬盘设备上的分区和文件系统 对于可以建立不同的多个文件系统的硬盘设备来说,minix文件系统引入超级块进行管理硬盘的文件系统结构信息。其结构如下图1-4所示。其中,s_ninodes表示设备上得i节点总数,s_nzones表示设备上的逻辑块为单位的总逻辑块数。s_imap_blocks 和s_zmap_blocks分别表示i节点位图和逻辑块位图所占用的磁盘块数。 s_firstdatazone表示设备上数据区开始处占用的第一个逻辑块块号。s_log_zone_size 是使用2为底的对数表示的每个逻辑块包含的磁盘块数。对于MINIX1.0文件系统该值为0,因此其逻辑块的大小就等于磁盘块大小。s_magic是文件系统魔幻数,用以指明文件系统的类型。对于MINIX1.0文件系统,它的魔幻数是0x137f。

源代码是什么

源代码是什么 源代码(也称源程序),是指一系列人类可读的计算机语言指令。在现代程序语言中,源代码可以是以书籍或者磁带的形式出现,但最为常用的格式是文本文件,这种典型格式的目的是为了编译出计算机程序。计算机源代码的最终目的是将人类可读的文本翻译成为计算机可以执行的二进制指令,这种过程叫做编译,通过编译器完成。 代码组合 源代码作为软件的特殊部分,可能被包含在一个或多个文件中。一个程序不必用同一种格式的源代码书写。例如,一个程序如果有C语言库的支持,那么就可以用C语言;而另一部分为了达到比较高的运行效率,则可以用汇编语言编写。较为复杂的软件,一般需要数十种甚至上百种的源代码的参与。为了降低种复杂度,必须引入一种可以描述各个源代码之间联系,并且如何正确编译的系统。在这样的背景下,修订控制系统(RCS)诞生了,并成为研发者对代码修订的必备工具之一。还有另外一种组合:源代码的编写和编译分别在不同的平台上实现,专业术语叫做软件移植。 质量 对于计算机而言,并不存在真正意义上的“好”的源代码;然而作为一个人,好的书写习惯将决定源代码的好坏。源代码是否具有可读性,成为好坏的重要标准。软件文档则是表明可读性的关键。 作用 源代码主要功用有如下2种作用: 生成目标代码,即计算机可以识别的代码。 对软件进行说明,即对软件的编写进行说明。为数不少的初学者,甚至少数有经验的程序员都忽视软件说明的编写,因为这部分虽然不会在生成的程序中直接显示,也不参与编译。但是说明对软件的学习、分享、维护和软件复用都有巨大的好处。因此,书写软件说明在业界被认为是能创造优秀程序的良好习惯,一些公司也硬性规定必须书写。

如何读源代码

如何阅读源代码 --转自CSDN_oncoding +++++++++++ 第一章: 导论 ++++++++++++ 1.要养成一个习惯, 经常花时间阅读别人编写的高品质代码. 2.要有选择地阅读代码, 同时, 还要有自己的目标. 您是想学习新的模式|编码风格|还是满足某些需求的方法. 3.要注意并重视代码中特殊的非功能性需求, 这些需求也许会导致特殊的实现风格. 4.在现有的代码上工作时, 请与作者和维护人员进行必要的协调, 以避免重复劳动或产生厌恶情绪. 5.请将从开放源码软件中得到的益处看作是一项贷款, 尽可能地寻找各种方式来回报开放源码社团. 6.多数情况下, 如果您想要了解"别人会如何完成这个功能呢?", 除了阅读代码以外, 没有更好的方法. 7.在寻找bug时, 请从问题的表现形式到问题的根源来分析代码. 不要沿着不相关的路径(误入歧途). 8.我们要充分利用调试器|编译器给出的警告或输出的符号代码|系统调用跟踪器|数据库结构化查询语言的日志机制|包转储工具和Windows的消息侦查程序, 定出的bug的位置. 9.对于那些大型且组织良好的系统, 您只需要最低限度地了解它的全部功能, 就能够对它做出修改. 10.当向系统中增加新功能时, 首先的任务就是找到实现类似特性的代码, 将它作为待实现功能的模板. 11.从特性的功能描述到代码的实现, 可以按照字符串消息, 或使用关键词来搜索代码. 12.在移植代码或修改接口时, 您可以通过编译器直接定位出问题涉及的范围, 从而减少代码阅读的工作量.

13.进行重构时, 您从一个能够正常工作的系统开始做起, 希望确保结束时系统能够正常工作. 一套恰当的测试用例(test case)可以帮助您满足此项约束. 14.阅读代码寻找重构机会时, 先从系统的构架开始, 然后逐步细化, 能够获得最大的效益. 15.代码的可重用性是一个很诱人, 但难以理解与分离, 可以试着寻找粒度更大一些的包, 甚至其他代码. 16.在复查软件系统时, 要注意, 系统是由很多部分组成的, 不仅仅只是执行语句. 还要注意分析以下内容: 文件和目录结构|生成和配置过程|用户界面和系统的文档. 18.可以将软件复查作为一个学习|讲授|援之以手和接受帮助的机会. ++++++++++++++++++++ 第二章: 基本编程元素 ++++++++++++++++++++ 19.第一次分析一个程序时, main是一个好的起始点. 20.层叠if-else if-...-else序列可以看作是由互斥选择项组成的选择结构. 21.有时, 要想了解程序在某一方面的功能, 运行它可能比阅读源代码更为恰当. 22.在分析重要的程序时, 最好首先识别出重要的组成部分. 23.了解局部的命名约定, 利用它们来猜测变量和函数的功能用途. 24.当基于猜测修改代码时, 您应该设计能够验证最初假设的过程. 这个过程可能包括用编译器进行检查|引入断言|或者执行适当的测试用例. 25.理解了代码的某一部分, 可能帮助你理解余下的代码. 26.解决困难的代码要从容易的部分入手. 27.要养成遇到库元素就去阅读相关文档的习惯; 这将会增强您阅读和编写代码的能力. 28.代码阅读有许多可选择的策略: 自底向上和自顶向下的分析|应用试探法和检查注释和外部文档, 应该依据问题的需要尝试所有这些方法. 29.for (i=0; i

如何看懂源代码--(分析源代码方法)

如何看懂源代码--(分析源代码方法) 4 推 荐 由于今日计划着要看Struts 开源框架的源代码 昨天看了一个小时稍微有点头绪,可是这个速度本人表示非常不满意,先去找了下资 料, 觉得不错... 摘自(繁体中文 Traditional Chinese):http://203.208.39.132/translate_c?hl=zh-CN&sl=en&tl=zh-CN&u=http://ww https://www.wendangku.net/doc/259965529.html,/itadm/article.php%3Fc%3D47717&prev=hp&rurl=https://www.wendangku.net/doc/259965529.html,&usg=AL kJrhh4NPO-l6S3OZZlc5hOcEQGQ0nwKA 下文为经过Google翻译过的简体中文版: 我们在写程式时,有不少时间都是在看别人的代码。 例如看小组的代码,看小组整合的守则,若一开始没规划怎么看,就会“噜看噜苦(台语)”不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力。网路上有一篇关于分析看代码的方法,做为程式设计师的您,不妨参考看看,换个角度来分析。也能更有效率的解读你想要的程式码片段。 六个章节: ( 1 )读懂程式码,使心法皆为我所用。( 2 )摸清架构,便可轻松掌握全貌。( 3 )优质工具在手,读懂程式非难事。( 4 )望文生义,进而推敲组件的作用。( 5 )找到程式入口,再由上而下抽丝剥茧。( 6 )阅读的乐趣,透过程式码认识作者。 程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。但是,与其抗拒接收别人的程式码,不如彻底了解相关的语言和惯例,当成是培养自我实力的基石。 对大多数的程式人来说,撰写程式码或许是令人开心的一件事情,但我相信,有更多人视阅读他人所写成的程式码为畏途。许多人宁可自己重新写过一遍程式码,也不愿意接收别人的程式码,进而修正错误,维护它们,甚至加强功能。 这其中的关键究竟在何处呢?若是一语道破,其实也很简单,程式码是别人写的,只有原作者才真的了解程式码的用途及涵义。许多程式人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程式码。这是来自于人类内心深处对于陌生事物的原始恐惧。 读懂别人写的程式码,让你收获满满 不过,基于许多现实的原因,程式人时常受迫要去接收别人的程式码。例如,同事离职了,必须接手他遗留下来的工作,也有可能你是刚进部门的菜鸟,而同事经验值够了,升级了,风水轮流转,一代菜鸟换菜鸟。甚至,你的公司所承接的专案,必须接手或是整合客户前一个厂商所遗留下来的系统,你们手上只有那套系统的原始码(运气好时,还有数量不等的文件)。 诸如此类的故事,其实时常在程式人身边或身上持续上演着。许多程式人都将接手他人的程式码,当做一件悲惨的事情。每个人都不想接手别人所撰写的程式码,因为不想花时间去探索,宁可将生产力花在产生新的程式码,而不是耗费在了解这些程式码上。

教你如何读懂源代码

分析源代码方法 如何看懂源代码--(分析源代码方法> 我们在写程序时,有不少时间都是在看别人的代码。 例如看小组的代码,看小组整合的守则,若一开始没规划怎么看, 就会“噜看噜苦<台语)” 不管是参考也好,从开源抓下来研究也好,为了了解箇中含意,在有限的时间下,不免会对庞大的源代码解读感到压力。 网路上有一篇关于分析看代码的方法,做为程序设计师的您,不妨参考看看, 换个角度来分析。也能更有效率的解读你想要的程序码片段。 六个章节:< 1 )读懂程序码,使心法皆为我所用。< 2 )摸清架构,便可轻松掌握全貌。< 3 )优质工具在手,读懂程序非难事。< 4 )望文生义,进而推敲组件的作用。< 5 )找到程序入口,再由上而下抽丝剥茧。< 6 )阅读的乐趣,透过程

序码认识作者。 阅读他人的程序码< 1 ) ---读懂程序码,使心法皆为我所用 程序码是别人写的,只有原作者才真的了解程序码的用途及涵义。许多程序人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程序码。但是,与其抗拒接收别人的程序码,不如彻底了解相关的语言和惯例,当成是培养自我实力的基石。 对大多数的程序人来说,撰写程序码或许是令人开心的一件事情,但我相信,有更多人视阅读他人所写成的程序码为畏途。许多人宁可自己重新写过一遍程序码,也不愿意接收别人的程序码,进而修正错误,维护它们,甚至加强功能。 这其中的关键究竟在何处呢?若是一语道破,其实也很简单,程序码是别人写的,只有原作者才真的了解程序码的用途及涵义。许多程序人心里都有一种不自觉的恐惧感,深怕被迫去碰触其他人所写的程序码。这是来自于人类内心深处对于陌生事物的原始恐惧。 读懂别人写的程序码,让你收获满满

程序员阅读源代码的5种方法

程序员阅读源代码的5种方法 摘要:不吃猪肉也看过猪爬树,阅读好的源代码,可以大幅度提高程序员小伙伴们的编程水平。因为研究源代码其一可以让你学习代码的架构,其二可以让你明白算法是如何实现的。 关键词:源代码程序员 代码中自有黄金屋,代码中自由颜如玉。 不吃猪肉也看过猪爬树,阅读好的源代码,可以大幅度提高程序员小伙伴们的编程水平。

因为研究源代码其一可以让你学习代码的架构,其二可以让你明白算法是如何实现的。 程序员阅读源代码的5种方法,阅读源代码要带哪些目的? 下面给程序员小伙伴们分享阅读源代码的5种奇技淫巧: 0、读代码时刻思考这两个问题 读代码理应是抱着一定的目的阅读。你应该时刻思考: A、代码要解决的问题是什么? B 、代码是如何实现的? 程序员阅读源代码的5种方法,阅读源代码要带哪些目的? 1、让代码飞起来 我们除了阅读代码,运行代码是必不可少的。 唯有运行代码,你才能清楚它使用的库、它所依赖的开发框架等。

2、对代码做些调查 阅读代码理应具备立体感。也就说,我们需要从整体的角度去审视代码。 所以,我们不妨对代码做些调查。譬如看官网介绍,也可以参考维基百科。 总之一定要了解主要功能,被应用于哪些项目,其实这就是弄清代码的一个背景问题。 程序员阅读源代码的5种方法,阅读源代码要带哪些目的? 3、重视代码 人和人之间是有差距的,这一点必须承认。有人会说,读源代码没有用,前提是人家会造轮子。 当你要说阅读源代码没用时,应当反思:自己能否造轮子? 4、带着目的阅读代码

阅读代码最怕陷进去,源代码从头读到尾,结果看的云里雾里的。 最重要的是带着目的阅读。搞清楚为什么要阅读代码?你要学习架构、学习业务、学习模式、学习编码风格、学习类库还是什么? 设置一些小目标,这可以让你进阶得更快。

Source Insight:Linux源代码阅读的利器

阅读源代码是钻研技术的最佳手段,而Linux提供了一个庞大的源代码库,但是,由于缺乏良好的源代码阅读工具,使得阅读Linux源代码尤其是内核源代码十分困难,在本文中,笔者向大家推荐一个优秀的源代码阅读工具,并介绍了它的使用方法。 作为一个开放源代码的操作系统,Linux附带的源代码库使得广大爱好者有了一个广泛学习、深入钻研的机会,特别是Linux内核的组织极为复杂,同时,又不能像windows平台的程序一样,可以使用集成开发环境通过察看变量和函数,甚至设置断点、单步运行、调试等手段来弄清楚整个程序的组织结构,使得Linux内核源代码的阅读变得尤为困难。 当然Linux下的vim和emacs编辑程序并不是没有提供变量、函数搜索,彩色显示程序语句等功能。它们的功能是非常强大的。比如,vim和emacs就各自内嵌了一个标记程序,分别叫做ctag和etag,通过配置这两个程序,也可以实现功能强大的函数变量搜索功能,但是由于其配置复杂,linux附带的有关资料也不是很详细,而且,即使建立好标记库,要实现代码彩色显示功能,仍然需要进一步的配置(在另一片文章,我将会讲述如何配置这些功能),同时,对于大多数爱好者来说,可能还不能熟练使用vim和emacs那些功能比较强大的命令和快捷键。 为了方便的学习Linux源程序,我们不妨回到我们熟悉的window环境下,也算是“师以长夷以制夷”吧。但是在Window平台上,使用一些常见的集成开发环境,效果也不是很理想,比如难以将所有的文件加进去,查找速度缓慢,对于非Windows平台的函数不能彩色显示。于是笔者通过在互联网上搜索,终于找 到了一个强大的源代码编辑器,它的卓越性能使得学习Linux内核源代码的难度大大降低,这便是Source Insight3.0,它是一个Windows平台下的共享软件,可以从https://www.wendangku.net/doc/259965529.html,/上边下载30天试用版本。也可以在 https://www.wendangku.net/doc/259965529.html,/index.php?option=com_remository&Itemid=67&func=fileinfo &parent=folder&filecatid=3由于Source Insight是一个Windows平台的应用软件,所以首先要通过相应手段把Linux系统上的程序源代码弄到Windows平台下,这一点可以通过在linux平台上将/usr/src目录下的文件拷贝到Windows平台的分区上,或者从网上光盘直接拷贝文件到Windows平台的分区来实现。 下面主要讲解如何使用Source Insight,考虑到阅读源程序的爱好者都有相当的软件使用水平,本文 对于一些琐碎、人所共知的细节略过不提,仅介绍一些主要内容,以便大家能够很快熟练使用本软件,减少摸索的过程。 安装Source Insight并启动程序,可以进入图1界面。在工具条上有几个值得注意的地方,如图所示,图中内凹左边的是工程按钮,用于显示工程窗口的情况;右边的那个按钮按下去将会显示一个窗口,里边提供光标所在的函数体内对其他函数的调用图,通过点击该窗体里那些函数就可以进入该函数所在的地方。

别人的原代码程序员怎样阅读

别人的原代码程序员怎样阅读 源码就是指编写的最原始程序的代码。运行的软件是要经过编写的,程序员编写程序的过程中需要他们的“语言”。音乐家用五线谱,建筑师用图纸,那程序员的工作的语言就是“源码”了。人们平时使用软件时就是程序把“源码”翻译成我们可直观的形式表现出来供我们使用的。任何一个网站页面,换成源码就是一堆按一定格式书写的文字和符号,但我们的浏览器帮我们翻译成眼前的模样了。 计算机里面运行的所有东西都是用程序编出来的(包括操作系统,如Windows,还有Word等,网络游戏也一样),而编写程序要用到计算机语言,用计算机语言直接编出来的程序就叫源码,比如用VisualBasic编写的源码文件一般为.bas文件,而用C++编写的一般为.cpp文件,源代码不能直接在Windows下运行,必须编译后才能运行。源码经过编译处理后就可以直接在操作系统下运行了。很多的站长都喜欢使用建网站的程序源码,因为可以很方便的修改,对于任何一个seo人员来说,都是非常好的一个切入点。从字面意义上来讲,源文件是指一个文件,指源代码的集合.源代码则是一组具有特定意义的可以实现特定功能的字符(程序开发代码),源代码”在大多数时候等于“源文件”。比如在这个网页上右键鼠标,选择查看源文件.出来一个记事本,里面的内容就是此网页的源代码."这句话就体现了他们的关系,此处的源文件是指网页的源文件,而源代码就是源文件的内容,所以又可以称做网页的源代码..,源代码是指原始代码,可以是任何语言代码。汇编码是指源代码编译后的代码,通常为二进制文件,比如DLL、EXE、.NET中间代码、JAVA中间代码等。高级语言通常指C/C++、BASIC、C#、JAVA、PASCAL、易语言等等。汇编语言就是ASM,只有这个,比这个更低级的就是机器语言了。 网站源码作为软件的特殊部分,可能被包含在一个或多个文件中。一个程序不必用同一种格式的源代码书写。例如,一个程序如果有C语言库的支持,那么就可以用C语言;而另一部分为了达到比较高的运行效率,则可以用汇编语言编写。较为复杂的软件,一般需要数十种甚至上百种的源代码的参与。为了降低种复杂度,必须引入一种可以描述各个源代码之间联系,并且如何正确编译的系统。在这样的背景下,修订控制系统(RCS)诞生了,并成为研发者对代码修订的必备工具之一。还有另外一种组合:源代码的编写和编译分别在不同的平台上实现,专业术语叫做软件移植。 阅读别人的代码作为开发人员是一件经常要做的事情。一个是学习新的编程语言的时候通过阅读别人的代码是一个最好的学习方法,另外是积累编程经验。如果你有机会阅读一些操作系统的代码会帮助你理解一些基本的原理。还有就是在你作为一个质量保证人员或一个小领导的时候如果你要做白盒测试的时

OpenvSwitch源码阅读笔记-SDNLAB

Open vSwitch源码阅读笔记 引言 本文主要对OpenvSwitch(基于2.3.90版本)重点模块的源码实现流程做了简要的阅读记录,适合阅读OpenvSwitch源码的初级读者参考使用,任何错误和建议欢迎加作者QQ 号38293996沟通交流。 1. OVS网络架构 Openvswitch是一个虚拟交换机,支持Open Flow协议(也有一些硬件交换机支持Open Flow),他们被远端的controller通过Open Flow协议统一管理着,从而实现对接入的虚拟机(或设备)进行组网和互通,整体组网结构如下图: 2. OVS内部架构

●ovs-vswitchd 主要模块,实现vswitch的守候进程daemon; ●ovsdb-server 轻量级数据库服务器,用于ovs的配置信息; ●ovs-vsctl 通过和ovsdb-server通信,查询和更新vswitch的配置; ●ovs-dpctl 用来配置vswitch内核模块的一个工具; ●ovs-appctl 发送命令消息到ovs进程; ●ovs-ofctl 查询和控制OpenFlow虚拟交换机的流表; ●datapath 内核模块,根据流表匹配结果做相应处理; 3. OVS代码架构

●vswitchd是ovs主要的用户态程序,它从ovsdb-server读取配置并发送到ofproto 层,也从ofproto读取特定的状态和统计信息并发送到数据库; ●ofproto是openflow的接口层,负责和Openflow controller通信并通过 ofproto_class与ofproto provider底层交互; ●ofproto-dpif是ofproto接口类的具体实现; ●netdev是ovs系统的网络设备抽象(比如linux的net_device或交换机的port), netdev_class定义了netdev-provider的具体实现需要的接口,具体的平台实现需要支持这些统一的接口,从而完成netdev设备的创建、销毁、打开、关闭等一系列操作; 3.1 datapath 由于openvswitch用户态代码相对复杂,首先从内核模块入手分析。 datapath为 ovs内核模块,负责执行数据处理,也就是把从接收端口收到的数据 包在流表中进行匹配,并执行匹配到的动作。一个datapath可以对应多个vport,一个vport类似物理交换机的端口概念。一个datapth关联一个flow table,一个flow table 包含多个条目,每个条目包括两个内容:一个match/key和一个action。 3.1.1 数据流向

LINUX源代码阅读报告

进程调度代码分析 ——关于LINUX源代码中进程调度部分的读书报告 在多进程的操作系统中,进程调度是一个全局性、关键性的问题,它对系统的总体设计、系统的的实现、功能设置以及各个方面的性能都有着决定性的影响。根据调度的结果所作的进程切换的速度,也是衡量一个操作系统性能的重要指标。进程调度机制的设计,还对系统的复杂性有着极大的影响,常常会由于实现的复杂程度而在功能与性能方面作出必要的权衡和让步。 一.进程调度的基本原理 一个好的系统的进程调度机制,应当考虑以下几个问题: (1)公平:保证每个进程得到合理的CPU 时间。 (2)高效:使CPU 保持忙碌状态,即总是有进程在CPU 上运行。 (3)响应时间:使交互用户的响应时间尽可能短。 (4)周转时间:使批处理用户等待输出的时间尽可能短。 (5)吞吐量:使单位时间内处理的进程数量尽可能多。 显然,要同时满足这几个方面是不可能的,所以,形成一个操作系统就必须在这几个方面中做出权衡和必要的取舍,从而确定自己的调度算法。例如:UNIX采用动态优先数调度,5.3BSD采用舵机反馈队列调度,windows采用抢先多任务调度。 为了满足以上五个方面,设计一个进程调度机制时要考虑以下问题: (1)调度的时机:在什么情况什么时候进行调度; (2)调度的“政策”(policy):根据什么准则挑选下一个进入运行的进程; (3)调度的方式:是抢占的,还是非抢占的。 对于一个进程,其状态转换关系图具体如下所示:

Linux 的调度程序是一个叫Schedule()的函数,这个函数被调用的频率很高,由它来决定是否要进行进程的切换,如果要切换的话,切换到哪个进程等。即调度时机:Linux 调度时机主要有: (1)进程状态转换的时刻:进程终止、进程睡眠; (2)当前进程的时间片用完时(current->counter=0); (3)设备驱动程序; (4)进程从中断、异常及系统调用返回到用户态时。 时机1,进程要调用sleep()或exit()等函数进行状态转换,这些函数会主动调用调度程序进行进程调度。 时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4 是一样的。 时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查need_resched的值,如果必要,则调用调度程序schedule()主动放弃CPU。 时机4,不管是从中断、异常还是系统调用返回,最终都调ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。考虑到效率,则必须从系统调用返回时要调用调度程序。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。 在操作系统课程当中,我们已经了解到了六种调度算法,下面进行简单的介绍: A.先到先服务(first come ,first served, FCFS) B.最短作业优先调度(shortest-job-first, SJF) (a)抢占的SJF; (b)非抢占的SJF; C.优先权调度(priority-scheduling algorithm) D.轮转法(round-robin, RR) E.多级队列调度(multilevel queue-scheduling algorithm) F.多级反馈队列调度(multilevel feedback queue scheduling algorithm) 在实时系统中,广泛采用抢占调度方式,特别是对于那些要求严格的实时系统。因为这种调度方式既具有较大的灵活性,又能获得很小的调度延迟;但是这种调度方式也比较复杂。二.LINUX进程调度的源代码分析 在LINUX内核中,所有的调度方式基本上都是从UNIX继承下来的以优先级为基础的调度。内核为系统中的每个进程计算出一个反映其运行资格的权值(即优先级),然后挑选其权值最高的进程投入运行。在运行过程中,当前进程的资格随时间而递减,从而在下一次调度的时候原来资格较低的进程可能就更有资格运行了。到所有的权值都为0时,则重新计算所有进程的资格。 为了适应各种不同的应用的需要,内核在此基础上实现了三种不同的调度策略:SCHED_FIFO、SCHED_RR以及SCHED_OTHER。每个进程都有自己适用的调度算法,并且,进程还可以通过系统调用sched_setscheduler()设定自己使用的调度政策。其中SCHED_FIFO适合于时间性要求比较强,但每一次运行所需的时间比较短的进程,实时的应用大都具有这样的特点。SCHED_RR适合每次运行需要时间很长的进程。SCHED_OTHER则是传统的调度算法,比较适合于交互式的分时应用。 下面是对内核代码中的sched.c进行分段的分析:

php学习方法-如何阅读php源代码

php学习方法-如何阅读php源代码 2011-10-28 22:58:47| 分类:PHP | 标签:|举报|字号大中小订阅 php技术的快速进步,最好的途径就是阅读源代码了。自己也阅读了很多开源的程序,感觉方法很重要,好的方法可以达到事半功倍的效果。 一份好的源代码例如dz的论坛,wind论坛,帝国cms,dedecms等,都具有自己的一套设计思路和设计模式,所以在看某个产品之前就要做好心理准备,可以把自己的经验和这些产品做对比,但千万别一直用自己的思维去评判。一句话就是“以学习和批评的辩证思想去看待”。 那具体的我们怎样去阅读一份php的源代码呢? 一。先把源代码安装起来,结合它的文档和手册,熟悉其功能和它的应用方式。 二。浏览源代码的目录结构,了解各个目录的功能。 三。经过以上两步后相信你对这个开源的产品有了一个初步的了解了,那现在就开始分析它的源码吧。这一步我们开始分析源代码框架。例如入口方式是单入口还是多入口,页面之间的调用规则,能根据规则找出某个功能用到的页面。 四。熟悉源代码的代码写作风格,例如缩进方式,排版格式等。 五。熟悉一下源代码用到的数据库和表,可以参考它的技术支持文档。 六。经过以上几步相信大家已经对这份源代码有了更深刻的了解,不过这种了解还只是表面的,下来我们从6个方面具体的去分析它吧: 1.入口构造以及页面调用方式的具体实现,如果阅读时看到工具类和工具函数,尽量去熟悉一下。这一步的分析可以学习到源代码的系统架构方式。 2.分析源代码用到的工具类和工具函数,这样可以学到很多程序编写技巧。可以提升自己编程功力。 3.结合一些安全规则,研究这个源代码是怎样实现安全方面的设计的。这样可以提高自己在安全方面的意识和功力。 4.如果有模板引擎的话,研究一下源代码的模板引擎。大致从实现方式,效率,易用性等几个方面去考虑。 5.研究系统的各个功能模块,这样既能学习编程技巧还能打开自己的编程思路,下次遇到类似的东东就心里有谱了。 6.研究系统所用到设计模式,一样的功能实现,用到的设计模式可能相差很多,对比我们之前所作的东东分析设计模式,是提升我们驾驭代码的不二法门。 7.研究源代码对访问压力,执行效率,系统效率,数据库查询的优化。 方法只是途径和工具,具体实践还需要大家的努力。自己的感想是不要着急,认真分析,把分析心得用到自己的具体项目上。

linux内核源码阅读工具eclipse qemu

linux内核源码阅读工具eclipse + qemu 一linux内核源码阅读工具windows下当然首选source insight,但是linux下就没有source insight这么优秀的工具了,但是也有不少的替代品,但觉绝对部分人会选择 vim+ctags+cscope的组合,还有部分人或选择wine中的source insight或选择navigatror,当然对于代码阅读来说vim+ctags+cscope的组合还是比较好的一个选择方案,但是,当我使用了eclipse之后,个人感觉用eclipse作为linux 环境下源码阅读工具确实比vim+ctags+cscope的组合方便很多。下面是linux环境下eclipse的配置安装方案:eclipse 下载地址: https://www.wendangku.net/doc/259965529.html,/downloads/?osType=linuxeclipse环境配置方案: https://www.wendangku.net/doc/259965529.html,/viewtopic.php?t=183803二eclipse + qemu 进行linux源码的编译和调试最初调试内核采用了qemu + insight 或qemu + ddd的组合,相比来说insgiht的界面更加有好些,但是ubuntu 10.04以上的版本,删除了对Insight的默认支持,只能下载insight的源码编译安装,而且insight更新非常慢。很久以前就看到有人用eclipse + qemu进行linux内核源码的编译和调试,这次终于抽了个时间尝试一下,毕竟eclipse的debugger是非常强

大的。工具组合:Eclipse IDE for C/C++ Linux Developers + qemu-0.12.3 (最好用kvm, 不幸的是机器太旧,不支持硬件虚拟化) 1.首先我们要从https://www.wendangku.net/doc/259965529.html,下载内核源码,在这里我选择的是linux-2.6.32.tar.bz2。我将其下载到我的主目录下,然后在terminal下输入以下命令。$ cd (回到主目录)$ tar xf linux-2.6.32.tar.bz2 (解压源码)$ mkdir linux-2.6.32-obj (创建一个编译内核的目标文件输出目录)$ cd linux-2.6.32 (进入内核源码树根目录)$ make O=~/linux-2.6.28-obj menuconfig (这里我们要配置内核,并在~/linux-2.6.32-obj目录下生成内核配置文 件.config)$ make mrproper 2. 接下来我们打开elicpse,第一次打开时有一个欢迎画面,我们单击右边的workbench 图片关掉欢迎画面。由于eclipse cdt是一个非常强大的 c/c++ ide,它默认会自动的解析工程中源程序并编译工程和产生智能提示信息。但由于我们调试内核过程中暂不会用到这些功能,所以要关闭他们。首先我们到 Window->Preferences->General->Workspace 中将Build Automatically选项去掉。然后到 Window->Preferences->C/C++ -> Indexer中,将默认的Fast c/c++ indexer改为No indexer。然后我们开始创建一个新的工程。从菜单中选择File -> New -> Project… -> C/C++ -> C Project 然后单击Next按钮。

源代码是什么

我们电脑上安装的软件都是目标程序。除了脚本语言的源程序外,其他源程序是不能直接运行的。 提倡软件开源的人士认为应该提供源程序给用户,让用户自己修改,有利于软件行业的发展。反对的人觉得这样不利于保护版权。 你如果不懂编程,源程序可以不管它。不影响正常使用。 源代码作为软件的特殊部分,可能被包含在一个或多个文件中。一个程序不必用同一种格式的源代码书写。例如,一个程序如果有C语言库的支持,那么就可以用C语言;而另一部分为了达到比较高的运行效率,则可以用汇编语言编写。 较为复杂的软件,源程序文件可以达到成千上万个。为了降低复杂度,必须引入一种可以描述各个源代码之间联系,并且如何正确编译的系统。在这样的背景下,修订控制系统(RCS)诞生了,并成为研发者对代码修订的必备工具之一。 还有另外一种组合:源代码的编写和编译分别在不同的平台上实现,专业术语叫做软件移植。关于开放源代码的定义以及解释 作者:王立来源:eNet硅谷动力 【译者的声明】 本文是开放源代码定义、开放源代码定义原理以及OSI Certified标志与纲要的中文译文。本文由王立在1999年8月翻译。本人在翻译时为确保译文与原文在含义上一致性付出了最大努力,但是本人不能对由于译文与原文在含义上的差异而造成的任何误解或对译文的误解所造成的任何直接的、间接的损失承担任何责任。 开放源代码并不仅仅意味着对源代码的访问权。开放源代码软件的发布条款必须满足以下条件: 我们认为本开放源代码定义涵盖了由绝大多数软件团体使用的术语"开放源代码"的最初含义和当前含义。然而,该术语被广泛地应用,并且它的含义变得不精确了。OSI Certified 标志是某个软件发布许可证是否服从开放源代码定义的OST认证方式。一般的术语"开放源代码"并不提供这种担保,但我们仍然鼓励使用"开放源代码"这一术语以表明它符合"开放源代码定义"。关于OSI Certified标志的信息,以及已经通过了OSI Certified、符合"开放源代码定义"的许可证,请参见OSI Certified标志与纲要。 【开放源代码定义之原理】 给出开放源代码的定义的目的是:把我们所确信的、由软件开发团体所公认的"开放源代码"的含义作为一组具体的准则写下来---该准则确保按照开放源代码许可证发布的软件可以得到与其它软件同样认真的评审、使软件可以不断地得到改良和遴选,从而提供非开放软件所难以提供的可靠性与能力。为了使此项工作持续发展,我们必须抵制人们为了短期利益而中止为软件开发做出贡献。这意味着,许可证的条款必须防止人们藏匿(lock up)源代码从而导致只有很少的人才能够阅读和修改它。当软件的开发者按照由OSI认证的许可证发布他们的软件时,他们可以在软件中使用"OSI Certified"标志。这种认证标志告知用户,该软件所采用的许可证符合开放源代码定义。关于我们的认证标志的更多信息及其纲要,请参见OSI Certified标志与纲要。 1.自由地再发布 通过强制要求许可证允许自由地再发布,我们抵制了任何为了获得少量短期销售金额而放弃长期效益的诱惑。如果我们不这样做,就会有很多压力迫使合作者放弃承诺。 2.源代码 由于软件只有通过修改才能够得到改进,因此我们要求获得易于理解的源代码。因为我们的目的是使软件易于改进,我们也就希望软件易于修改。 3.派生作品

如何阅读Linux源码

如何阅读Linux源码 Linux内核的配置系统由三个部分组成,分别是: Makefile:分布在Linux 内核源代码中的Makefile,定义Linux 内核的编译规则;? 配置文件(config.in):给用户提供配置选择的功能;? 配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于Ncurses 图形界面以及基于Xwindows 图形界面的用户配置界面,各自对应于Make config、Make menuconfig 和make xconfig)。? 这些配置工具都是使用脚本语言,如Tcl/TK、Perl 编写的(也包含一些用 C 编写的代码)。本文并不是对配置系统本身进行分析,而是介绍如何使用配置系统。所以,除非是配置系统的维护者,一般的内核开发者无须了解它们的原理,只需要知道如何编写Makefile 和配置文件就可以。所以,在本文中,我们只对Makefile 和配置文件进行讨论。另外,凡是涉及到与具体CPU 体系结构相关的内容,我们都以ARM 为例,这样不仅可以将讨论的问题明确化,而且对内容本身不产生影响。 2.Makefile 2.1 Makefile 概述 Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成Linux 内核二进制文件。 由于Linux 内核源代码是按照树形结构组织的,所以Makefile 也被分布在目录树中。Linux 内核中的Makefile 以及与Makefile 直接相关的文件有: Makefile:顶层Makefile,是整个内核配置、编译的总体控制文件。? .config:内核配置文件,包含由用户选择的配置选项,用来存放内核配置后的结果(如make config)。? arch/*/Makefile:位于各种CPU 体系目录下的Makefile,如arch/arm/Makefile,是针对特定平台的Makefile。? 各个子目录下的Makefile:比如drivers/Makefile,负责所在子目录下源代码的管理。?Rules.make:规则文件,被所有的Makefile 使用。? 用户通过make config 配置后,产生了 .config。顶层Makefile 读入 .config 中的配置选择。顶层Makefile 有两个主要的任务:产生vmlinux 文件和内核模块(module)。为了达到此目的,顶层Makefile 递归的进入到内核的各个子目录中,分别调用位于这些子目录中的Makefile。至于到底进入哪些子目录,取决于内核的配置。在顶层Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定CPU 体系结构下的Makefile,这个Makefile 中包含了平台相关的信息。 位于各个子目录下的Makefile 同样也根据 .config 给出的配置信息,构造出当前配置下需要的源文件列表,并在文件的最后有include $(TOPDIR)/Rules.make。 Rules.make 文件起着非常重要的作用,它定义了所有Makefile 共用的编译规则。比如,如果需要将本目录下所有的 c 程序编译成汇编代码,需要在Makefile 中有以下的编译规则:%.s: %.c $(CC) $(CFLAGS) -S $< -o $@ 有很多子目录下都有同样的要求,就需要在各自的Makefile 中包含此编译规则,这会比较麻

相关文档