Java学习笔记
来源:互联网 发布:云计算基础架构服务 编辑:程序博客网 时间:2024/05/14 04:02
- Java基础知识总结
- 基本类型
- 数组
- 再谈Java的面向对象
- 对象与类
- 类之间的关系
- 对象与对象变量
- 静态域和静态方法
- 静态域
- 静态方法
- Factory方法
- 方法参数
- 对象构造
- 默认域初始化
- 初始化块
- 继承
- 接口与内部类
- 对象克隆
- 接口与回调
- 内部类
- 代理
- 对象与类
- 泛型
- 为什么要使用泛型
- 如何定义泛型
- 异常与调试
- 记录日志
- 使用断言
- Java并发编程
- 线程和进程
- 线程和进程的区别
- 为何要使用线程
- 创建线程
- 中断线程
- 线程状态
- 线程的优先级和守护线程
- 线程的优先级
- 守护线程
- 同步
- 锁对象
- 线程和进程
- Java反射
- Class对象
- 获取Class对象
- 通过class对象构造目标类型的对象
- 通过反射获得类中的函数
- 通过反射获得类中的属性
- 获得父类接口注解信息
- Java反射与代理
- Annotation
- 基本语法
- 定义注解
- 标准注解和元注解
- 编写注解处理器
- 例1 利用注解处理器读取PasswordUtils类并使用反射机制查找UseCase标记为注解处理器提供一组id值并列出PasswordUtils中找到的用例和缺失的用例
- 例2利用注解生成数据库创建语句
- 对Java回调机制的理解
Java基础知识总结
基本类型
Java是一种强类型语言,需要为每一个变量声明一种类型。Java一共有8种基本类型(primitive type).这八种基本类型分别为:4种整形(byte, short, int, long)、2种浮点类型(float, double)、1种用于表示Unicode编码的字符单元的字符类型char和一种表示真值的boolean类型.
数组
Java中的数组是用来存储一组相同类型的数据结构。
使用如下结构去生命一个数组:
int[] a; 或 int[] a = new int[100];
前者只是声明了整形数组变量,而后者则声明了一个长度为100的整形数组。
为数组初始化,可以使用如下形式:
int[] a = {1, 2, 3, 4, 5};
或
int[] a = new int[]{1, 2, 3, 4, 5};
甚至可以初始化一个匿名数组:
new int[]{1, 2, 3, 4, 5};
java SE5.0后使用了for each的循环语句:
for(variable : collection){statement;}
再谈Java的面向对象
对象与类
类之间的关系
在类之间,最常见的关系有:
- 依赖(“uses-a”),一个类的方法操纵另一个类的对象,就可以说一个类依赖于另一个类,应该尽可能地将相互依赖的类减至最少,也就是说减少两个类之间的耦合,当两个类不相互依赖时,一个类的改变不会使另一个类产生任何bug
- 聚合(“has-a”):一个类的对象包含另一个类的对象,就说这两个类有聚合关系。
- 继承(“is-a”):是一种用来表示特殊与一般关系的。
对象与对象变量
要想使用对象,就必须先构造对象,并制定其初始状态。然后对对象施加方法。
Java中使用构造器来构造对象的新实例。构造器的名字与类名相同。
以Java中的Date类为例:
通过
new Date()
构造一个新的对象,或者将新构造出的对象存放在一个变量中:
Date birthday = new Date();
birthday就是一个对象变量,它可以引用Date类型的对象。
** 一个对象变量并没有实际包含一个对象,而是仅仅引用一个对象。在Java中,任何对象变量的值都是对存储在另外一处的一个对象的引用。
静态域和静态方法
静态域
如果将域定义为static,那么每个类中只有一个这样的域,而每个对象对于所有的实例域却都有自己的一份拷贝。
class Employee{ private int id; private static int nextId = 1;}
入上面的例子,每一个Employee都有自己的id域,但这个类的所有实例都讲共享一个nextId域。
也就是说,如果有1000个Employee对象,则有1000个实例域id,但是只有一个静态域nextId。即使没有一个Employee对象,静态域nextId也存在。它属于类,而不属于任何独立的对象。
静态方法
静态方法时不能向对象实施操作的方法。例如Math类的pow()方法就是一个静态方法。
Math.pow(x,a)
就是计算
因此也可以把静态方法理解为没有this参数的方法。
因为静态方法不能操作对象,所以不能在静态方法中访问实例域,但是静态方法可以访问自身类中的静态域。
总结何时使用静态方法:
- 当一个方法不需要访问对象状态,其所需参数都是通过显式参数提供的
- 当一个方法只需要访问类的静态域
main方法也是一个静态方法,当程序启动时,还没有任何一个对象。静态的main方法将执行并创建程序所需的对象。
Factory方法
看下面一个例子
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();NumberFormat percentFormatter = NumberFormat.getPercentInstance();double x = 0.1;System.out.println(currencyFormatter.format(x)); // print $0.10System.out.println(percentFormatter.format(x)); // print 10%
为什么不使用构造器来完成这些操作呢?
- 无法命名构造器。构造器的名字必须与类名相同。但是这里希望得到的货币实例和百分比实例采用不同的名字
- 当使用构造器时,无法改变所构造的对象类型。
方法参数
在程序设计语言中,关于参数传递给方法,主要有两种方式:
- 值调用(call by value)表示方法接收的是调用者提供的值
- 引用调用(call by reference)表示方法接收的是调用者提供的变量位置
Java使用的是值调用,也就是说方法得到的是所有参数值的一个拷贝。
方法参数共有两种类型:
- 基本数据类型(数字,布尔值)
double percent = 0;harry.raiseSalary(percent);
调用raiseSalary(percent)方法时,执行完成后,percent的值还是原值。而传递给raiseSalary(percent)的只是percent的一个拷贝。
- 对象引用
public void tripleSalary(Employee x){ x.raiseSalary(200);}harry = new Employee(...);tirpleSalary(harry);
这里传递给方法的参数是对象的引用,参数x和harry都引用了同一个对象,因此方法结束后,harry继续引用薪金增加200%的雇员对象。
对象构造
默认域初始化
如果在构造器中没有显式的给域赋予初值,它就会被自动的赋予默认值,数值为0,布尔值为false,对象为null。通常情况下,并不推荐这么做。
初始化块
在对象构造过程中,有两种初始化数据域的方法
- 在构造器中设置值
- 在声明中赋值
实际上,Java还有第三种机制,成为初始化块。在一个类的声明中,可以包含多个代码块。制药构造类的对象,这些块就会被执行。
class Employee{ private int id; private static int nextId; public Employee(){} { id = nextId; nextId++ }}
在这段代码中,无论使用哪个构造器构造对象,id域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分。
继承
接口与内部类
接口:主要用来描述类具有什么功能,而并不给每个功能的具体实现。一个类可以实现一个或多个接口。
对象克隆(深拷贝):指创建一个新对象,且新对象的状态与原始对象的状态相同。当对克隆的新对象进行修改时,不会影响原始对象的状态
内部类:定义在一个类的内部,其中的方法可以访问包括它外部类的域,内部类技术主要用于设计具有相互协作关系的类集合。特别是在编写处理GUI事件的代码时,使用内部类可以让代码看起来更加简练专业。
对象克隆
接口与回调
内部类
内部类定义在另一个类的内部
- 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
- 内部类可以对同一个包中的其他类隐藏起来
- 当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
代理
泛型
为什么要使用泛型?
《Thinking In Java》中提到,有时候,我们希望自己编写的代码可以适用于某种不具体的类型,而不是局限于某一类或某一接口。
这时候,泛型为我们提供很大的帮助,在面向对象编程中,多态也是一种泛化机制,比如说定义方法参数时使用基类或接口,这样继承该基类或实现该接口的子类都可以作为该方法的参数,使方法更加通用。
泛型是Java SE5之后有的概念。
如何定义泛型
泛型类:
public class Demo<T>{}
泛型接口
public interface Demo<T>{}
泛型方法
public <T> void method(T t){}
异常与调试
记录日志
JDK1.4中提供了日志API。Logger.global是JDK中的默认日志记录器
Logger.global.info("file->open menu item selected");
通常有以下7个日志记录器级别:
- SERVER
- WARNING
- INFO
- CONFIG
- FINE
- FINER
- FINEST
使用断言
JDK1.4中,java引入了一个新关键字assert。
java语言中,给出了三种处理系统错误的机制:
- 抛出异常
- 日志
- 使用断言
Java并发编程
线程和进程
线程和进程的区别
线程是指程序在执行过程中,能够执行程序代码的一个执行单元。在Java中,线程有四种状态:运行、就绪、挂起和结束。
进程是指一段正在执行的程序。而线程有时也被称为轻量级的进程,线程是程序执行的最小单元。一个进程可以有多个线程,哥哥线程之间共享程序的内存空间(代码段、数据段和堆空间)及一些进程级的资源,但是各个线程都拥有自己的栈空间。
为何要使用线程
在操作系统级别上来看,主要有以下几个方面:
- 使用多线程可以减少程序的响应时间,如果某个操作过于耗时,或者陷入长时间的等待,此时程序将不会响应鼠标和键盘灯的操作,使用多线程可以把这个耗时的线程分配到一个单独的线程去执行,从而使程序具备了更好的交互性。
- 与进程相比,线程创建和切换开销更小,同时多线程在数据共享方面效率非常高;
- 多CPU或者多核计算机本身就具备执行多线程的能力,如果使用单个线程,将无法重复利用计算机资源,造成资源的浪费,在多CPU计算机使用多线程能提高CPU的利用率;
- 使用多线程能简化程序的结构,是程序便于理解和维护。
创建线程
多线程的实现一般有以下三种方法,其中前两种较为常见:
- 集成Thread类,重写run()方法
Thread本质也是实现了Runnable接口的一个实例。需要注意的是调用start()方法后,并不是立即执行多线程代码,而是使该线程变为可运行态,什么时候运行多线程代码由操作系统决定。 - 实现Runnable接口,并实现该接口的run()方法
实现Callable接口,重写call()方法
Callable接口实际是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能。主要表现为一下3点:- Callable可以在任务接受后提供一个返回值,Runnable无法提供这个功能
- Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常
- 运行Callable可以拿到一个Future对象,该对象表示异步计算的结果,它提供了检查计算是否完成的方法。由于现场属于异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下就可以使用Future来监视目标线程调用call()方法的情况。但是调用FUture的get()方法以获取结果时,当前线程就会阻塞,知道call()方法的返回结果。
中断线程
当线程的run()方法执行方法体内最后一条语句后,并经由执行return语句返回时,或者出现在方法中没有捕获的异常时,线程将终止。
interrupt方法可以用来请求终止线程,当一个线程调用interrupt方法时,线程的中断状态将被置位。每个线程都具有boolean的标志来表示线程是否被中断。
可以通过调用Thread.currentThread().isInterrupted()。
线程状态
- 新建状态
- 就绪状态: 该状态的线程位于可运行线程池中,等待获取CPU的使用权
- 运行状态
- 阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂停运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞状态又分三种情况:
等待阻塞:运行的线程执行wait()方法,JVM会把线程放入等待池中。
同步阻塞:运行的线程在获取对象的同步锁时,弱同步锁被别的线程占用,则JVM会把该线程放入锁池中。
其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时,join()等待线程终止或超时、或I/O处理完毕时,线程重新回到就绪状态。 - 死亡状态
线程的优先级和守护线程
线程的优先级
在java中,每一个线程都有一个优先级。
守护线程
调用setDaemon(true)将线程转换为守护线程。守护线程的唯一用途就是为其他线程提供服务。当只剩下守护线程时,虚拟机就退出了。
另外JVM垃圾回收、内存管理等线程都是守护线程。
同步
在多线程应用中,两个或者两个以上的线程需要共享对同一个数据的存取。如果两个线程存取相同的对象,并且每一个线程调用了修改对象的方法,这种情况通常成为竞争条件。
锁对象
Java反射
Class对象
任何一个java文件都会被编译成.class文件。class文件承载着这个类型的父类,接口,构造函数,方法,属性等原始信息。class文件在运行时,会被类加载器classloader加载到内存中,与此同时,JVM也会产生一个Class对象。该对象用于保存对应的类信息。
获取Class对象
获取Class对象有三种方法:
如果已知了对象的类名,可以直接通过类的class获得对应的class对象
Class<?> myObject = MyObject.class;
如果已经有了对象的实例,通过实例的getClass()方法,可以获得对应的class对象
Student student = new Student("zhangsan"); Class<?> stuClass = student.getClass();
通过类的完整包名,获得Class对象
Class<?> myObject = Class.forName("com.demo.Demo");
通过class对象构造目标类型的对象
获取构造函数接口
// 获取一个公有的构造函数,参数为可变参数,如果构造函数有参数,那么需要将参数的类型传递给 getConstructor 方法public Constructor<T> getConstructor (Class...<?> parameterTypes)// 获取目标类所有的公有构造函数public Constructor[]<?> getConstructors ()
try { // 获取 Class 对象 Class<?> clz = Class.forName("org.java.advance.reflect.Student"); // 通过 Class 对象获取 Constructor,Student 的构造函数有一个字符串参数 // 因此这里需要传递参数的类型 ( Student 类见后面的代码 ) Constructor<?> constructor = clz.getConstructor(String.class); // 通过 Constructor 来创建 Student 对象 Object obj = constructor.newInstance("mr.simple"); System.out.println(" obj : " + obj.toString());} catch (Exception e) { e.printStackTrace();}
通过反射获得类中的函数
接口说明
// 获取 Class 对象中指定函数名和参数的函数,参数一为函数名,参数 2 为参数类型列表public Method getDeclaredMethod (String name, Class...<?> parameterTypes)// 获取该 Class 对象中的所有函数( 不包含从父类继承的函数 )public Method[] getDeclaredMethods ()// 获取指定的 Class 对象中的**公有**函数,参数一为函数名,参数 2 为参数类型列表public Method getMethod (String name, Class...<?> parameterTypes)// 获取该 Class 对象中的所有**公有**函数 ( 包含从父类和接口类集成下来的函数 )public Method[] getMethods ()
通过反射获得类中的属性
接口说明:
// 获取 Class 对象中指定属性名的属性,参数一为属性名public Method getDeclaredField (String name)// 获取该 Class 对象中的所有属性( 不包含从父类继承的属性 )public Method[] getDeclaredFields ()// 获取指定的 Class 对象中的**公有**属性,参数一为属性名public Method getField (String name)// 获取该 Class 对象中的所有**公有**属性 ( 包含从父类和接口类集成下来的公有属性 )public Method[] getFields ()
获得父类、接口、注解信息
通过getSuperClass()获得父类信息,通过getInterfaces()获得接口信息。
获得注解信息
// 获取指定类型的注解public <A extends Annotation> A getAnnotation(Class<A> annotationClass) ;// 获取 Class 对象中的所有注解public Annotation[] getAnnotations() ;
参考资料: http://a.codekk.com/blogs/detail/5596953ed6459ae7934997c5
Java反射与代理
参考内容:http://www.cnblogs.com/lianghaoc/p/5699537.html
代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
- 静态代理:
- 动态代理:在程序运行时,由java反射机制动态生成字节码
在动态代理中,最核心的是一个类Proxy和一个接口InvocationHandler。
Annotation
- 本部分内容参考自:《Thinking in Java》
基本语法
注解(也被称为元数据),为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据。
注解是众多引入到Java SE5中的重要的语言变化之一。它们可以提供用来完整的描述程序所需的信息,而这些信息是无法用Java来表达的。因此,
注解使得我们能够以一种可以被编译器测试和验证的格式存储程序的额外信息(Thinking In Java的英文版是这样描述的:Thus, annotations allow you to store extra information about your program in a format that is tested and verified by the compiler)。
通过使用注解,可以将这些元数据保存在Java源代码中,并利用annotation API为自己的注解构造处理工具。
Java在java.lang包中内置了三种基本的注解:
- @Override
- @Deprecated
- @SuppressWarning
定义注解
注解的定义看起来很像接口的定义,而且注解也将会被编译成class文件。
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Test {}
除了@符号外,Test的定义就像是一个空的接口。
在定义注解时,需要一些元注解(meta-annotation),如@Target和@Retention。
- @Target用来定义你的注解将应用于什么地方(例如是一个方法或是一个域);
- @Rectetion用来定义该注解在哪一个级别可用,在源代码中(SOURCE)、类文件中(CLASS)、或者运行时(RUNTIME)。
标准注解和元注解
Java目前内置了三种标准注解和四中元注解
编写注解处理器
注解元素可用的类型如下:
所有基本类型(int, float, boolean等), String, Class, enum, Annotation, 以及以上类型的数组。
如果使用其它类型,编译器就会报错。
例1: 利用注解处理器读取PasswordUtils类,并使用反射机制查找@UseCase标记。为注解处理器提供一组id值,并列出PasswordUtils中找到的用例和缺失的用例。
该例用到了三个资源文件:PasswordUtils.java, UseCase.java, UseCaseTracker.java.
标签@UseCase是由UseCase.java定义的。其中包含int元素id和一个String元素description。
/** * UseCase.java * @author lovekun * */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UseCase { public int id(); public String description() default "no description";}
/** * PasswordUtil.java * @author lovekun * */public class PasswordUtil { /** * 校验密码格式 * @param password * @return */ @UseCase(id=1, description="validatePassword") public boolean validatePassword(String password) { return password.matches("\\w*\\d\\w*"); } /** * 倒序加密密码 * @param password * @return */ @UseCase(id=2) public String encryptPassword(String password) { return new StringBuilder(password).reverse().toString(); } /** * 判断密码是否已存在 * @param prePassword * @param newPassword * @return */ @UseCase(id=3, description="checkForNewPassword") public boolean checkForNewPassword( List<String> prePassword, String newPassword){ return !prePassword.contains(newPassword); }}
/** * UseCaseTracker.java * @author lenovo * */public class UseCaseTracker { /** * 验证是否缺失用例 * @param usecases * @param cl */ public static void trackUsecase( List<Integer> usecases, Class<?> cl){ for (Method m : cl.getDeclaredMethods()) { UseCase uc = m.getAnnotation(UseCase.class); if (uc != null) { System.out.println(uc.id() + "AND" + uc.description()); usecases.remove(new Integer(uc.id())); } } for (int i : usecases) { System.out.println("miss usecase:" + i); } } public static void main(String[] args) { List<Integer> usecases = new ArrayList<Integer>(); Collections.addAll(usecases, 1, 2, 3, 4); trackUsecase(usecases, PasswordUtil.class); }}
例2:利用注解生成数据库创建语句
/** * DBTable.java * @author lenovo * */@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface DBTable { public String name() default "";}
/** * SQLString.java * @author lenovo * */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLString { public int value() default 0; public String name() default ""; public Constraints constraints() default @Constraints;}
/** * SQLInteger.java * @author lenovo * */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface SQLInteger { String name() default ""; public Constraints constraints() default @Constraints;}
/** * Constraints.java * @author lenovo * */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Constraints { public boolean primaryKey() default false; public boolean allowNull() default true; public boolean unique() default false;}
/** * Member.java * @author lenovo * */@DBTable(name="member")public class Member { @SQLString(30) String firstname; @SQLString(50) String lastname; @SQLInteger Integer age; @SQLString(value=30, constraints=@Constraints(primaryKey=true)) String handle; static int memberCount; public String getFirstname() { return firstname; } public String getLastname() { return lastname; } public Integer getAge() { return age; } public String getHandle() { return handle; } @Override public String toString() { return "Member [handle=" + handle + "]"; }}
/** * TableCreator.java * @author lenovo * */public class TableCreator { public static void main(String[] args){ try { Class<?> cl = Class.forName("com.annotation.demo.Member"); DBTable dbtable = cl.getAnnotation(DBTable.class); if (dbtable == null) { System.out.println("no DBTable annotation in class"); return ; } String tablename = dbtable.name(); List<String> columnDefs = new ArrayList<String>(); for (Field field : cl.getDeclaredFields()) { String columnName = ""; Annotation[] anns = field.getDeclaredAnnotations(); if (anns.length < 1) { continue; } if (anns[0] instanceof SQLInteger) { SQLInteger sInt = (SQLInteger)anns[0]; if (sInt.name().length() < 1) { columnName = field.getName().toUpperCase(); }else { columnName = sInt.name(); } columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints())); } if (anns[0] instanceof SQLString) { SQLString sString = (SQLString) anns[0]; if (sString.name().length() < 1) { columnName = field.getName().toUpperCase(); }else { columnName = sString.name(); } columnDefs.add(columnName+" VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints())); } } StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tablename + "("); for (String columnDef : columnDefs) { createCommand.append("\n " + columnDef + ","); } String tableCreate = createCommand.substring(0, createCommand.length() - 1) + ");"; System.out.println(tableCreate); } catch (ClassNotFoundException e) { System.err.println("class not found"); } } public static String getConstraints(Constraints con) { String constraints = ""; if (!con.allowNull()) { constraints += " NOT NULL"; } if (con.primaryKey()) { constraints += " PRIMARY KEY"; } if (con.unique()) { constraints += " UNIQUE"; } return constraints; }}
最终输出结果为:
CREATE TABLE member( FIRSTNAME VARCHAR(30), LASTNAME VARCHAR(50), AGE INT, HANDLE VARCHAR(30) PRIMARY KEY);
对Java回调机制的理解
一个例子让你彻底理解java回调机制
深入浅出java回调机制
举个栗子:
1. 老师给学生布置作业;
2. 学生完成作业,同时老师继续他的工作;
3. 学生完成作业后通知老师;
这是一个典型的异步+回调的栗子。
老师要实现一个接口,告诉学生完后作业后该如何联系他。
public interface MyCallback{ public void callme();}
public class Teacher implements MyCallback{ private Student stu; public Teacher(Student stu) { this.stu = stu; } @Override public void callme(){ System.out.println("OK"); } public void goonwork(){ System.out.println("go on work"); } public void teach(){ new Thread(new Runnable(){ @Override public void run(){ stu.afterdo(Teacher.this); } }).start(); goonwork(); }}
public class Student{ public void dohomework(){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } public void afterdo(MyCallback myCallback){ dohomework(); myCallback.callme(); }}
测试类:
public class Main { public static void main(String[] args){ Student stu = new Student(); Teacher tea = new Teacher(stu); tea.teach(); }}
- Java学习笔记--CSS笔记
- Java学习笔记001
- Java 学习笔记
- java 学习笔记
- Java学习笔记
- java 学习笔记
- java学习笔记
- java学习笔记
- java学习笔记-1
- java虚拟机学习笔记
- java虚拟机学习笔记
- java虚拟机学习笔记
- Java学习笔记1
- Java学习笔记2
- java学习笔记(1)
- java学习笔记#2
- java学习笔记1
- java 学习笔记
- 浅析DDD(领域驱动设计)
- JavaWeb
- Going Home poj 2195 最小费用最大流
- LeetCode题解-15-3Sum
- 数的读法(蓝桥杯)
- Java学习笔记
- php与apache的关系
- 二叉树 前序、中序、后序、层次遍历及非递归实现 查找、统计个数、比较、求深度的递归实现
- 水池数目
- LeetCode题解-16-3Sum Closest
- 数据类型转化Bug
- Hive 窗口函数
- 3.15学习内容
- linux命令大全