黑马程序员—Java提高2(泛型,内省)
来源:互联网 发布:php面试自我介绍 编辑:程序博客网 时间:2024/05/21 07:55
泛型
泛型是用于解决安全问题的,是一个安全机制。JDK1.5以后出现的新特性
JDK1.5的集合类希望在定义集合时,明确表明你要向集合中装入那种类型的数据,无法加入指定类型以外的数据。
泛型是提供给javac编译器使用的可以限定集合中的输入类型说明的集合时,会去掉“类型”信息,使程序运行效率不受影响,对参数化的泛型类型,getClass()方法的返回值和原始类型完全一样。
由于编译生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用add方法即可。
使用泛型的好处
1、使用泛型集合,可将一个集合中的元素限定为一个特定类型,集合中只能存储同一个类型的对象;这样就将运行时期出现的问题ClassCastException转移到了编译时期,方便与程序员解决问题,让运行时期问题减少,提高安全性。
2、当从集合中获取一个对象时,编译器也可知道这个对象的类型,不需要对对象进行强制转化,避免了强制转换的麻烦,这样更方便。
泛型格式:通过<>来定义要操作的引用数据类型
如:TreeSet<String> -----> 来定义要存入集合中的元素指定为String类型
泛型定义中的术语
如:ArrayList<E>类和ArrayList<Integer>
1、ArrayList<E>整个称为泛型类型
2、ArrayList<E>中的E称为类型变量或类型参数
3、整个ArrayList<Integer>称为参数化类型
4、ArrayList<Integer>中的Integer称为类型参数的实例或实际类型参数
5、ArrayList<Integer>中的<>称为typeof
6、ArrayList称为原始类型
在使用java提供的对象时,何时写泛型?
通常在集合框架中很常见,只要见到<>就要定义泛型,其实<>就是用来接收类型的,当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。
关于参数化类型的几点说明
1、参数化类型与原始类型的兼容性
第一、参数化类型可引用一个原始类型的对象,编译只是报警告,能不能通过编译,是编译器说了算。
如:Collection<String> coll = new Date();
第二、原始类型可引用一个参数化类型的对象,编译报告警告
如:Collection coll = new Vector<String>();
原来的方法接受一个集合参数,新类型也要能传进去。
2、参数的类型不考虑类型参数的继承关系:
Vector<String> v = new Vector<Objec>();//错误的
不写Object没错,写了就是明知故犯
Vector<Objec> v = new Vector<String>();//错误的
3、在创建数组实例时,数组的元素不能使用参数化的类型
如:Vector<Integer> v[] = newVector<Integer>[10];//错误的
<span style="font-family:Arial;">import java.lang.reflect.Constructor;import java.util.*;public class Generic {public static void main(String[] args) throws Exception {ArrayList<String> al = new ArrayList<String>();al.add("25");al.add("b");System.out.println(al.get(1));ArrayList<Integer> at = new ArrayList<Integer>();at.add(23);at.add(3);System.out.println(at.get(1));//编译器生成的字节码会去掉泛型的类型信息System.out.println((al.getClass() == at.getClass()) + "-->" + at.getClass().getName());//at.add("ab")-->报错,存储的应为Integer类型 //反射方式,由于编译器生成的字节码会去掉泛型的类型信息,//所以用反射可跳过编译器,存入任何类型at.getClass().getMethod("add",Object.class).invoke(at,"abcd");at.getClass().getMethod("add",Object.class).invoke(at,5);System.out.println("反射方式:" + at.get(3));System.out.println("反射方式:" + at.get(4));//反射方式获得new String(new StringBuffer("abc"));Constructor<String> cons = String.class.getConstructor(StringBuffer.class);String st = cons.newInstance(new StringBuffer("abc"));System.out.println(st);</span>
泛型中的通配符
当传入的类型不确定时,可以使用通配符
1、使用?通配符可引用其他各种类型化的类型,通配符的变量主要用作引用,也可调用与参数化无关的方法,但不能调用与参数化有关的方法。
2、可对通配符变量赋任意值:
如:Collection<?> coll coll = newHashSet<Date>();
泛型的限定:对于一个范围内的一类事物,可以通过泛型限定的方式定义,有两种方式:1、? extends E:可接收E类型或E类型的子类型;称之为上限。
如:Vector<? extends Number> x = newvector<Integer>();
2、? super E:可接收E类型或E类型的父类型;称之为下限。
如:Vector<? super Integer>x = newvector<Number>();
/* 泛型的限定: */ import java.util.*; class GenerticXian2 { public static void main(String[] args) { TreeSet<Student> s = new TreeSet<Student>(new Comp()); s.add(new Student("stu0")); s.add(new Student("stu3")); s.add(new Student("stu1")); print(s); System.out.println("Hello World!"); TreeSet<Worker> w = new TreeSet<Worker>(new Comp()); w.add(new Worker("Worker0")); w.add(new Worker("Worker3")); w.add(new Worker("Worker1")); print(w); } public static void print(TreeSet<? extends Person> ts) { Iterator<? extends Person> it = ts.iterator(); while (it.hasNext()){ Person p = it.next(); System.out.println(p.getName()); } } } class Person implements Comparable<Person> { private String name; Person(String name) { this.name = name; } public String getName() { return name; } public int compareTo(Person p){ return this.getName().compareTo(p.getName()); } } class Comp implements Comparator<Person> { public int compare(Person p1,Person p2){ return p1.getName().compareTo(p2.getName()); } } class Student extends Person { Student(String name){ super(name); } } class Worker extends Person { Worker(String name){ super(name); } }
1、何时定义泛型方法?
为了让不同方法可以操作不同的类型,而且类型不确定,那么就可以定义泛型方法
2、特殊之处:静态方法不可以访问类上定义的泛型,如果静态方法操作的引用数据类型不确定,可以将泛型定义在方法上。
泛型方法的特点:
1、位置:用于放置泛型的类型参数的<>应出现在方法的其他所有修饰符之后和在方法的返回类型之前,也就是紧邻返回值之前,按照惯例,类型参数通常用单个大写字母表示。
2、只有引用类型才能作为泛型方法的实际参数
3、除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符。
4、普通方法、构造函数和静态方法中都可以使用泛型。
5、可以用类型变量表示异常,称之为参数化的异常,可用于方法的throws列表中,但是不能用于catch子句中。
6、在泛型中可同时有多个类型参数,在定义它们的<>中用逗号分开。
T和?有什么区别呢?
1、T限定了类型,传入什么类型即为什么类型,可以定义变量,接收赋值的内容。
2、?为通配符,也可以接收任意类型但是不可以定义变量。
若类实例对象中多出要使用到同一泛型参数,即这些地方引用类型要保持同一个实际类型时,这时候就要采用泛型类型的方式进行定义,也就是类级别的泛型。
当类中要操作的引用数据类型不确定时,在早期定义Object来完成扩展,而现在定义泛型。
泛型类定义的泛型,在整个类中都有效,如果被方法调用,那么泛型类的对象要明确需要操作的具体类型后,所有要操作的类就已经固定了。
类级别的泛型是根据引用该类名时指定的类型信息来参数化类型变量的。
总结
对泛型的定义:
第一、定义泛型:当又不确定的类型需要传入到集合中,需要定义泛型
第二、定义泛型类:如果类型确定后,所操作的方法都是属于此类型,则定义泛型类
第三、定义泛型方法:如果定义的方法确定了,里面所操作的类型不确定,则定义泛型方法
<span style="font-family:Arial;">//测试 class GenerticTest { public static void main(String[] args) { //创建泛型类对象 GenClass<Worker> g = new GenClass<Worker> (); g.setTT(new Worker()); Worker w = g.getTT(); g.showC(w); System.out.println("----------------------"); //泛型方法测试 GenMethod<String> g1 = new GenMethod<String>(); GenMethod.showS("SSS"); g1.show("sesf"); g1.print("heheh"); g1.printY(new Integer(5)); System.out.println("------------------------"); //泛型接口测试 GenInter g2 = new GenInter(); g2.show("haha"); System.out.println("Hello World!"); GenImpl<Integer> g3 = new GenImpl<Integer>(); g3.show(new Integer(95)); } } //泛型类 class GenClass<TT> { //定义私有属性 private TT t; //定义公共设置方法,设置属性 public void setTT(TT t) { this.t = t; } //定义公共访问方法,访问属性 public TT getTT() { return t; } //定义方法 public void showC(TT t) { System.out.println("GenClass show:" + t); } } //创建Worker类,作为类型传入泛型类中 class Worker {} //泛型方法 class GenMethod<T> { //静态的泛型方法 public static <S> void showS(S s) { System.out.println("static show:" + s); } //非静态泛型方法 public void show(T t) { System.out.println("未指定T show:" + t); } public void print(T t) { System.out.println("指定T print:" + t); } //指定接受其他类型的泛型方法 public <Y> void printY(Y y) { System.out.println("和类指定的不同,为Y print:" + y); } } //泛型接口 interface Inter<T> { void show(T t); } //一般类实现泛型接口 class GenInter implements Inter<String> { public void show(String s) { System.out.println("接口 show:" + s); } } //泛型类实现泛型接口 class GenImpl<T> implements Inter<T> { public void show(T t) { System.out.println("类接收类型不确定的实现接口 show:" + t); } </span>
类型推断
编译器判断泛型方法的实际参数的过程,称之为类型推断。类型推断是相对于直觉推断的,其实现方法是一种非常复杂的过程
类型推断的具体规则:根据调用泛型方法时,实际传递的参数类型或返回值的类型来推断。
1、当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时,该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时,传递的参数类型或返回值来决定泛型参数的类型,如:
swap(newString[3],1,2) static <E> void swap(E[] a, inti, int j);
2、当某个类型变量在某个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时,这多处的实际应用类型都对应同一种类型来表示,这很容易凭感觉推断出来:
add(3,5) static<T> T add(T a,T b);
3、若对应了不同类型,且没有使用返回值,这是取多个参数中的最大交集类型,如下面的对应类型Number,编译没问题,但是运行会出错:
fill(new Integer[3],3.5f) static<T> void fill(T[] a,T v);
4、若对应了不同类型,且使用了返回值,这时候优先考虑返回值类型,如下面语句实际对应的类型就是Integer了,编译将报错,将变量x类型改为float,对此eclipse报错提示,接着再将变量x类型改为Number,则就没了错误:
int x = add(3,3.5f) static<T> T add(T a,T b);
5、参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没问题,而第二种情况则会根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(newInteger[5],new String[5]); static<T> T copy(T[] a,T[] b);
<span style="font-family:Arial;">package cn.itcast.text2;import java.lang.reflect.*;import java.sql.Date;import java.util.*;import cn.itcast.text1.ReflectPoint;public class GenerticTest {public static void main(String[] args) throws Exception {// TODO Auto-generated method stubObject obj = "abc";String str = autoContor(obj);GenerticDao<ReflectPoint> gd = new GenerticDao<ReflectPoint>();gd.add(new ReflectPoint(3,5));//通过获得方法本身的方法Method applyMethod = GenerticTest.class.getMethod("applyVector", Vector.class);//通过方法的获取泛型参数的方法得到原始参数类型的集合Type[] types = applyMethod.getGenericParameterTypes();//将参数类型转换为参数化类型ParameterizedType pType = (ParameterizedType)types[0];//得到原始类型System.out.println(pType.getRawType());//得到实际参数类型System.out.println(pType.getActualTypeArguments()[0]);}</span>
IntroSpector:即内省,是对内部进行检查,了解更多的底层细节。
内省的作用:主要针对JavaBean进行操作。
JavaBean(存在于java.bean包中)
JavaBean是一种特殊的Java类,主要用于传递数据信息,这种Java类中的方法主要用于访问私有的字段,且方法都符合某种特殊的命名规则。它是一种特殊的Java类,其中的方法名称等,都符合特殊的规则。只要一个类中含有get和set打头的方法,就可以将其当做JavaBean使用。
javabean中字段和属性:
字段就是我们定义的一些成员变量,如private String name;等
而属性是具有某些功能,Bean属性,是含有get或set方法的那些属性的字段,即这个变量的get属性,set属性等。
作用:如果要在两个模板之间传递多个信息,可将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO),这些信息在类中用私有字段来储存,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。
命名方式:JavaBean的属性是根据其中的setter和getter方法来确定的,而不是依据其中的变量,如方法名为setId,则中文意思是设置Id,getId也是如此;去掉前缀,剩余部分就是属性名称,如果剩余部分的第二个字母小写,则把剩余部分改为小写。如:getAge/setAge-->age;gettime-->time;setTime-->time;getCPU-->CPU。
总之:一个类被当做JavaBean使用时,JavaBaan的属性是根据方法名推断出来的,它根本看不到Java类内部的成员变量。
JavaBean的好处:
一个符合JavaBean特点的类当做普通类一样可以使用,但是把它当做JavaBean类用肯定有好处的:
1)在JavaEE开发中,经常要使用JavaBean。很多环境就要求按JavaBean的方式进行操作,别人都这么用,那么就必须要求这么做。
2)JDK中提供了对JavaBean进行操作的API,这套API称为内省,若要自己通过getX的方式来访问私有x,可用内省这套API,操作JavaBean要比使用普通的方式更方便。
package cn.itcast.text1;import java.beans.BeanInfo;import java.beans.IntrospectionException;import java.beans.Introspector;import java.beans.PropertyDescriptor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class IntroSpectorTest {/** * @param args *//* * public static void main(String[] args) throws Exception {// TODO Auto-generated method stubReflectPoint pt1 = new ReflectPoint(3,5);String propertyName = "x";//"x"-->"X"-->"getX"-->MethodGetX-->//内省的方式://属性描述符:PropertyDescriptor//get属性信息PropertyDescriptor pd =new PropertyDescriptor(propertyName,pt1.getClass());Method methodGetX = pd.getReadMethod();Object retVal = methodGetX.invoke(pt1);System.out.println(retVal);//set属性信息Object value = 7;PropertyDescriptor pd2 =new PropertyDescriptor(propertyName,pt1.getClass());Method methodSetX = pd2.getWriteMethod();methodSetX.invoke(pt1,value);System.out.println(pt1.getX()); } *///上面的get或set代码分别通过选中要重构的代码,通过右击选重构获得get和set方法:public static void main(String[] args) throws Exception {// TODO Auto-generated method stubReflectPoint pt1 = new ReflectPoint(3,5);String propertyName = "x";//一般方式:"x"-->"X"-->"getX"-->MethodGetX-->//内省方式://通过get和set方法获取属性值Object retVal = getProperty(pt1, propertyName);System.out.println(retVal);Object value = 7;setProperty(pt1, propertyName, value);System.out.println(pt1.getX());}//设置属性值的方法 //此处的类型为Object,通用,下同private static void setProperty(Object rf, String propertyName,Object value) throws IntrospectionException,IllegalAccessException, InvocationTargetException {//创建属性描述符对象,将属性名称和加载文件等信息写入其中PropertyDescriptor pd =new PropertyDescriptor(propertyName,rf.getClass());//通过反射的方法类Method,获取属性所对应的set方法Method methodSetX = pd.getWriteMethod();methodSetX.invoke(rf, value);}//获取属性值的方法private static Object getProperty(Object rf, String propertyName)throws IntrospectionException, IllegalAccessException,InvocationTargetException {//创建属性描述符对象,获取属性所对应的名称和加载文件等信息PropertyDescriptor pd =new PropertyDescriptor(propertyName,rf.getClass());//通过反射的方法类Method,获取属性所对应的get方法Method methodGetX = pd.getReadMethod();Object retVal = methodGetX.invoke(rf);return retVal;}}
对JavaBean的复杂内省操作
1、在IntroSpector类中有getBeanInfo(Class cls)的方法。
2、获取Class对象的Bean信息,返回的是BeanInfo类型。
3、BeanInfo类中有getPropertyDescriptors()的方法,可获取所有的BeanInfo的属性信息,返回一个PropertyDescriptor[]。
4、在通过遍历的形式,找出与自己想要的那个属性信息。
如:改写get方法:
…BeanInfo beanInfo = Introspector.getBeanInfo(pt1.getClass());PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();Object value = null;for(PropertyDescriptor pd : pds){if(pd.getName().equals(propertyName)){Method methodGetX = pd.getReadMethod();value = methodGetX.invoke(pt1);break;}}…
BeanUtils工具包
1、BeanUtils等工具包都是由阿帕奇提供的,为了便于开发。BeanUtils可以将8种基本数据类型进行自动的转换,因此对于非基本数据类型,就需要注册转换器Converter,这就需要ConverUtils包,
2、好处:
1)提供的set或get方法中,传入的是字符串,返回的还是字符串,因为在浏览器中,用户输入到文本框的都是以字符串的形式发送至服务器上的,所以操作的都是字符串。也就是说这个工具包的内部有自动将整数转换为字符串的操作。
2)支持属性的级联操作,即支持属性链。如可以设置:人的脑袋上的眼镜的眼珠的颜色。这种级联属性的属性连如果自己用反射,那就很困难了,通过这个工具包就可以轻松调用。
3、可以和Map集合进行相互转换:可将属性信息通过键值对的形式作为Map集合存储(通过staticjava.util.Map describe(java.lang.Object bean)的方法),也可以将Map集合转换为JavaBean中的属性信息(通过static voidpopulate(java.lang.Object bean, java.util.Map properties)的方法)。
4、示例:
1)设置和获取属性值:
import java.lang.reflect.InvocationTargetException;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Map;import java.util.TreeMap;import org.apache.commons.beanutils.BeanUtils;import org.apache.commons.beanutils.ConversionException;import org.apache.commons.beanutils.ConvertUtils;import org.apache.commons.beanutils.Converter;import org.apache.commons.beanutils.PropertyUtils;import org.apache.commons.beanutils.locale.converters.DateLocaleConverter;import org.junit.Test;public class BeanUtilDemo {/** * BeanUtils使用 */@Test public void test1() throws Exception{//创建对象,设置属性值Person p = new Person();BeanUtils.setProperty(p, "name", "zzz");String name = BeanUtils.getProperty(p, "name");System.out.println(name);}@Test public void test2() throws Exception{//创建对象,传入属性值Person p = new Person();String name = "wangwu";String age = "23";String hight = "173.5";//设置属性值BeanUtils.setProperty(p, "name", name);BeanUtils.setProperty(p, "age", age);BeanUtils.setProperty(p, "hight", hight);//获取属性值System.out.println(BeanUtils.getProperty(p, "name"));System.out.println(BeanUtils.getProperty(p, "age"));System.out.println(BeanUtils.getProperty(p, "hight"));}2)未注册的属性值的获取和设置
//获取未注册的属性,即非八种基本数据类型的引用类型//private Date birthday@Test public void test3() throws Exception{Person p = new Person();String name = "wangwu";String age = "23";String hight = "173.5";String birthday = "1990-09-09";ConvertUtils.register(new Converter() {//注册器Converter接口中方法的重写@Overridepublic Object convert(Class type, Object value) {if(value == null)return null;if(!(value instanceof String))throw new ConversionException("只支持String类型的转换");String str = (String) value;if(value.equals(""))return null;SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");try{return sdf.parse(str);}catch(ParseException e){throw new RuntimeException(e);//异常链不能掉,这里必须写上e}}},Date.class);//测试BeanUtils.setProperty(p, "name", name);BeanUtils.setProperty(p, "age", age);BeanUtils.setProperty(p, "hight", hight);BeanUtils.setProperty(p, "birthday", birthday);System.out.println(BeanUtils.getProperty(p, "name"));System.out.println(BeanUtils.getProperty(p, "age"));System.out.println(BeanUtils.getProperty(p, "hight"));System.out.println(BeanUtils.getProperty(p, "birthday"));}//使用已经写好的注册器DateLocaleConverter@Test public void test4() throws Exception{Person p = new Person();String name = "wangwu";String age = "23";String hight = "173.5";String birthday = "1990-09-09";//将日期注册到BeanUtils上ConvertUtils.register(new DateLocaleConverter(), Date.class);//提供的注册器不健壮,因为传入空字符串,就会报错//所以,当没有提供注册器或需要加强注册器的时候,可以自己写//测试BeanUtils.setProperty(p, "name", name);BeanUtils.setProperty(p, "age", age);BeanUtils.setProperty(p, "hight", hight);BeanUtils.setProperty(p, "birthday", birthday);System.out.println(BeanUtils.getProperty(p, "name"));System.out.println(BeanUtils.getProperty(p, "age"));System.out.println(BeanUtils.getProperty(p, "hight"));System.out.println(BeanUtils.getProperty(p, "birthday"));Date date = p.getBirthday();System.out.println(date.toLocaleString());}
3)Map集合在BeanUtils中的应用:
//Map集合在BeanUtils中的应用@Testpublic void test5() throws Exception {/* * JDK 7.0新特性: * Map map = {"name" : "zs", "age" : 22, "hight" : 176.5}; *///将数据存入集合Map map = new TreeMap();map.put("name", "zhangsan");map.put("age", "20");map.put("hight", "172.5");map.put("birthday", "1999-10-02");//注册器ConvertUtils.register(new DateLocaleConverter(), Date.class);//获取属性Person p = new Person();BeanUtils.populate(p, map);System.out.println(BeanUtils.getProperty(p, "name"));System.out.println(BeanUtils.getProperty(p, "age"));System.out.println(BeanUtils.getProperty(p, "hight"));System.out.println(BeanUtils.getProperty(p, "birthday"));}//属性链@Testpublic void test6() throws Exception {Person p = new Person();BeanUtils.setProperty(p, "birthday.time", "111212");System.out.println(BeanUtils.getProperty(p, "birthday.time"));}
补充
1)BeanUtils是以字符串的形式进行操作的
2)PropertyUtils是以传入值本身的类型进行操作的。
//PropertyUtils可直接解析为指定类型,而BeanUtils只能指定字符串的类型@Testpublic void test7() throws Exception {Person p = new Person();System.out.println("-----BeanUtiles-------");BeanUtils.setProperty(p, "age", "22");//字符串形式System.out.println(BeanUtils.getProperty(p, "age"));System.out.println(BeanUtils.getProperty(p, "age").getClass().getName());System.out.println("-----PropertyUtiles-------");PropertyUtils.setProperty(p, "age", 22);//Integer形式System.out.println(PropertyUtils.getProperty(p, "age"));System.out.println(PropertyUtils.getProperty(p, "age").getClass().getName());}
详细请查看: http://edu.csdn.net
- 黑马程序员—Java提高2(泛型,内省)
- 黑马程序员—JAVA内省
- 《黑马程序员》java 内省
- 黑马程序员——JAVA(反射,内省)
- Java高新技术-内省----黑马程序员
- 黑马程序员——内省(IntroSpector)
- 黑马程序员_java内省、泛型
- 黑马程序员——内省
- 黑马程序员—Introspector内省
- 黑马程序员--高新技术(内省)
- 【黑马程序员】张孝祥Java高新技术_内省、注解、泛型
- 黑马程序员——Java内省和注释
- 黑马程序员——java加强之反射、内省
- 黑马程序员——【Java】【高新技术】内省 & JavaBean
- 黑马程序员--Java面向对象——JavaBean内省
- 黑马程序员---------Java面向对象——JavaBean内省
- 黑马程序员——java加强之内省
- 黑马程序员—java高新技术_枚举&反射&内省
- linux路由内核实现分析(三)---路由查找过程
- 路庆晖:有效提升电商用户访问体验_亿邦电商两会
- mostFrequentSubArray
- Seven habits of effective text editing using Vim
- 程序崩溃时打印调用栈
- 黑马程序员—Java提高2(泛型,内省)
- 10个你必须掌握的超酷VI命令技巧
- Cauchy-Schwarz不等式的证明
- 初识block
- 实现几个字符串常用函数
- MySQL学习足迹记录07--数据过滤--用正则表达式进行检索
- 图像几何变换之图像位置变换之图像旋转
- linux路由内核实现分析(四)---路由缓存机制(1)
- linux路由内核实现分析(四)---路由缓存机制(2)