-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.json
More file actions
1 lines (1 loc) · 45.2 KB
/
Copy pathsearch.json
File metadata and controls
1 lines (1 loc) · 45.2 KB
1
[{"title":"详解单例模式","url":"/2019/05/13/详解单例模式/","content":"### 1.饿汉式:静态常量,线程安全\n> 这种方法非常的简单,因为单例的实例被声明成static和final变量了,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。\n\n```\n Singleton {\n\tprivate final static Singleton instance = new Singleton();\n\tprivate Singleton(){}\n\tpublic static Singleton1 getInstance(){\n\t\treturn instance;\n\t}\n```\n> 缺点:不是一种懒加载,单例会在加载类后一开始就被初始化,即使客户端没有调用getInstance()方法。饿汉式的创建方式在一些场景中将无法使用:比如Singleton实例的创建是依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那么单例写法就无法使用了。\n### 2.懒汉式:线程不安全\n\n```\npublic class Singleton {\n\tprivate static Singleton instance = new Singleton();\n\n\tprivate Singleton() {\n\t}\n\n\tpublic static Singleton getInstance() {\n\t\tif (null == instance) {\n\t\t\tinstance = new Singleton();\n\t\t}\n\t\treturn instance;\n\t}\n}\n```\n> 这里使用了懒加载模式,但是却存在致命的问题。当多个线程并行调用getInstance()的时候,就会创建多个实例,即在多线程下不能正常工作。\n### 3.懒汉式:线程安全\n\n```\npublic class Singleton {\n\tprivate static Singleton instance = new Singleton();\n\n\tprivate Singleton() {\n\t}\n\n\tpublic static synchronized Singleton getInstance() {\n\t\tif (null == instance) {\n\t\t\tinstance = new Singleton();\n\t\t}\n\t\treturn instance;\n\t}\n}\n```\n> 虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用getInstance()方法,但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。\n### 4.懒汉式:静态内部类\n\n```\npublic class Singleton {\n\tprivate static class SingletonHandler {\n\t\tprivate static final Singleton INSTANCE = new Singleton();\n\t}\n\n\tprivate Singleton() {\n\t}\n\n\tpublic static Singleton getInstance() {\n\t\treturn SingletonHandler.INSTANCE;\n\t}\n}\n```\n> 这种写法仍然使用JVM本身机制保证了线程安全问题;由于SingletonHandler是私有的,除了getInstacne()之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖JDK版本。\n### 5.双重检验锁\n> 双重检验模式,是一种使用同步块加锁的方法。又称其为双重检查锁,因为会有两次检查instance == null,一次是在同步块外,一次是在同步快内。为什么在同步块内还要检验一次,因为可能会有多个线程一起进入同步块外的if,如果在同步块内不进行二次检验的话就会生成多个实例了。\n\n```\npublic class Singleton {\n\tprivate static Singleton instance;\n\n\tprivate Singleton() {\n\t}\n\n\tpublic static Singleton getSingleton6() {\n\t\tif (instance == null) {\n\t\t\tsynchronized (Singleton.class) {\n\t\t\t\tif (instance == null) {\n\t\t\t\t\tinstance = new Singleton();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn instance;\n\t}\n}\n```\n> 其中,instance = new Singleton()并非是原子操作,事实上在JVM中这句话做了三件事:\n> 1. 给instance分配内存\n> 2. 调用Singleton6的构造函数来初始化成员变量\n> 3. 将instance对象指向分配的空间(执行完这一步instance就为null)\n\n> 但是在JVM的即时编译器中存在指令重排序的优化,也就是说上面的第二步和第三步是不能保证顺序的,最终执行的顺序可能是1-2-3或者是1-3-2。如果是后者,则在3执行完毕,2执行之前,被线程2抢占了,这时instance已经是非null了(但却没有初始化),所以线程2会直接返回instance,然后使用,然后会报错。\n\n**使用volatile关键字禁止指令重排序优化**\n\n```\npublic class Singleton {\n\tprivate volatile static Singleton instance;\n\n\tprivate Singleton() {\n\t}\n\n\tpublic static Singleton getSingleton6() {\n\t\tif (instance == null) {\n\t\t\tsynchronized (Singleton.class) {\n\t\t\t\tif (instance == null) {\n\t\t\t\t\tinstance = new Singleton();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn instance;\n\t}\n}\n```\n> 使用volatile的主要原因是其另一个特性:禁止指令重排序优化。也就是说,在volatile变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。比如上面的例子,取操作必须在执行完1-2-3之后或者1-3-2之后,不存在执行到1-3然后取到值的情况。从[先行发生原则]的角度理解的话,就是对于一个volatile变量的写操作都先行发生于后面对这个变量的读操作。\n\n**注意:在Java5以前的版本使用了volatile的双检锁还是有问题的。其原因是Java5以前的JMM(Java内存模型)是存在缺陷的,即时将变量声明成volatile也不能避免重排序。**\n### 6.使用枚举\n\n```\npublic class Singleton {\n\tSingleton() {\n\t\tSystem.out.println(\"创建单例\");\n\t}\n\n\tpublic static void main(String[] args) {\n\t\tfor (int i = 0; i < 200; i++) {\n\t\t\tnew Thread() {\n\t\t\t\tpublic void run() {\n\t\t\t\t\tSingletonEnum.INSTANCE.getInstance();\n\t\t\t\t}\n\t\t\t}.start();\n\t\t}\n\t}\n}\n\nenum SingletonEnum {\n\tINSTANCE;\n\tprivate Singleton instance;\n\n\tSingletonEnum() {\n\t\tinstance = new Singleton();\n\t}\n\n\tSingleton getInstance() {\n\t\treturn instance;\n\t}\n}\n```\n**测试输出结果,可以看出,一共创建了两百个线程,但是只输出了一次结果语句,说明只创建了一个实例。如果需要创建多个实例,只需要在枚举类中新增一个实例(INSTANCE2)。**\n\n\n\n\n#### 参考:\n\n- [设计模式——单例模式的几种实现方式](https://blog.csdn.net/bingogirl/article/details/52388291)\n- [漫画:什么是单例模式?(整合版)](https://blog.csdn.net/bjweimengshu/article/details/78716839/)\n\n\n","tags":["单例模式 设计模式 面试"]},{"title":"Spring整合Junit4进行单元测试","url":"/2018/09/14/Spring整合Junit4进行单元测试/","content":"# Spring整合Junit4进行单元测试\n## 1.导入相关依赖\n使用spring的测试框架需要加入以下依赖包:\n1. JUnit 4 \n2. Spring-Test 包\n3. Spring 相关其他依赖包(不再赘述了,就是context等包)\n\nMaven依赖\n\n```\n<dependency>\n <groupId>junit</groupId>\n <artifactId>junit</artifactId>\n <version>4.9</version>\n <scope>test</scope>\n </dependency> \n <dependency>\n <groupId>org.springframework</groupId>\n <artifactId>spring-test</artifactId>\n <version> 3.2.4.RELEASE </version>\n <scope>provided</scope>\n </dependency>\n```\n## 2.创建测试类\n \n```\npackage Solin.Test;\n \n import org.junit.runner.RunWith;\n import org.springframework.test.context.ContextConfiguration;\n import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;\n \n @RunWith(SpringJUnit4ClassRunner.class) //使用junit4进行测试\n @ContextConfiguration(locations={\"classpath:applicationContext.xml\"}) //加载配置文件 \n @ContextConfiguration(locations={\"classpath:applicationContext.xml\"}) //加载配置文件 \n//如果加入以下代码,所有继承该类的测试类都会遵循该配置,也可以不加,在测试类的方法上///控制事务\n//这个非常关键,如果不加入这个注解配置,事务控制就会完全失效! \n//@Transactional \n//这里的事务关联到配置文件中的事务控制器(transactionManager = \"transactionManager\"),同时//指定自动回滚(defaultRollback = true)。这样做操作的数据才不会污染数据库! \n//@TransactionConfiguration(transactionManager = \"transactionManager\", defaultRollback = true) \n//\n public class BaseJunit4Test{\n \t\n }\n```\n解释下用到的注解:\n**@RunWith**:用于指定junit运行环境,是junit提供给其他框架测试环境接口扩展,为了便于使用spring的依赖注入,spring提供了org.springframework.test.context.junit4.SpringJUnit4ClassRunner作为Junit测试环境\n**@ContextConfiguration**({\"classpath:applicationContext.xml\",\"classpath:spring/buyer/applicationContext-service.xml\"}) \n导入配置文件,这里我的applicationContext配置文件是根据模块来分类的。如果有多个模块就引入多个“applicationContext-service.xml”文件。如果所有的都是写在“applicationContext.xml”中则这样导入: @ContextConfiguration(locations = \"classpath:applicationContext.xml\")\n\n@TransactionConfiguration(transactionManager = \"transactionManager\", defaultRollback = true)这里的事务关联到配置文件中的事务控制器(transactionManager = \"transactionManager\"),同时指定自动回滚(defaultRollback = true)。这样做操作的数据才不会污染数据库! \n@Transactional:这个非常关键,如果不加入这个注解配置,事务控制就会完全失效! \n\nAbstractTransactionalDataSourceSpringContextTests要想构建这一系列的无污染纯绿色事务测试框架就必须找到这个基类!(即所有事务均不生效)\n\n3.测试类\n\n```\npackage Solin.Test;\n \n import java.util.List;\n \n import org.junit.Test;\n import org.springframework.beans.factory.annotation.Autowired;\n import org.springframework.test.annotation.Rollback;\n import org.springframework.transaction.annotation.Transactional;\n \n import Solin.Entity.ImageInfo;\n import Solin.Service.ImageInfoService;\n \n public class ImageInfoTest extends BaseJunit4Test{\n \t@Autowired //自动注入\n \tprivate ImageInfoService imageInfoService;\n \t\n \t@Test\n \t@Transactional //标明此方法需使用事务 \n @Rollback(false) //标明使用完此方法后事务不回滚,true时为回滚 \n \tpublic void test(){\n \t\tSystem.out.println(\"测试Spring整合Junit4进行单元测试\");\n \t\t\n \t\tImageInfo imageInfo = new ImageInfo();\n \t\timageInfo.setParentID(999);\n \t\timageInfo.setImgAddr(\"地球\");\n \t\timageInfoService.saveImageInfo(imageInfo);\n \t\t\n \t\tList<ImageInfo> list = imageInfoService.getImageInfoList(95);\n \t\tfor(ImageInfo img : list){\n \t\t\tSystem.out.println(\"parentID:\"+img.getParentID()+\"------imgAddr:\"+img.getImgAddr());\n \t\t}\n \t}\n }\n```\n","tags":["Spring整合Junit4进行单元测试"]},{"title":"Java面试基础","url":"/2018/09/13/Java面试基础/","content":"# Java面试题—Java基础\n 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语法,集合的语法,io 的语法,虚拟机方面的语法。\n## 1、说说&和&&的区别。\n &和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。\n\n &&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现NullPointerException如果将&&改为&,则会抛出NullPointerException异常。If(x=33 & ++y>0) y会增长,If(x=33 && ++y>0)不会增长\n\n &还可以用作位运算符,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。 \n\n 备注:这道题先说两者的共同点,再说出&&和&的特殊之处,并列举一些经典的例子来表明自己理解透彻深入、实际经验丰富。 \n\n## 2、\"==\"和equals方法究竟有什么区别?\n (单独把一个东西说清楚,然后再说清楚另一个,这样,它们的区别自然就出来了,混在一起说,则很难说清楚)\n\n \\==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用\\==操作符。\n\n 如果一个变量指向的数据是对象类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用\\==操作符进行比较。\n\n equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。例如,对于下面的代码:\n\n```\nString a=new String(\"foo\");\nString b=new String(\"foo\");\n```\n\n 两条new语句创建了两个对象,然后用a,b这两个变量分别指向了其中一个对象,这是两个不同的对象,它们的首地址是不同的,即a和b中存储的数值是不相同的,所以,表达式a\\==b将返回false,而这两个对象中的内容是相同的,所以,表达式a.equals(b)将返回true。\n\n 在实际开发中,我们经常要比较传递进行来的字符串内容是否等,例如,String input = …;input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的,随便从网上找几个项目实战的教学视频看看,里面就有大量这样的错误。**记住,字符串的比较基本上都是使用equals方法。**\n\n 如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:\n\n```\nboolean equals(Object o){\nreturn this==o;\n}\n```\n\n 这说明,如果一个类没有自己定义equals方法,它默认的equals方法(从Object 类继承的)就是使用\\==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用\\==会得到同样的结果,如果比较的是两个独立的对象则总返回false。**如果你编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的。**\n\n## 3.Integer与int的区别\n int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。\n\n 另外,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。\n## 4.Math.round(11.5)等於多少? Math.round(-11.5)等於多少?\n Math类中提供了三个与取整有关的方法:ceil、floor、round,这些方法的作用与它们的英文名称的含义相对应,例如,ceil的英文意义是天花板,该方法就表示向上取整,Math.ceil(11.3)的结果为12,Math.ceil(-11.3)的结果是-11;floor的英文意义是地板,该方法就表示向下取整,Math. floor (11.6)的结果为11,Math. floor(-11.6)的结果是-12;**最难掌握的是round方法,它表示“四舍五入”,算法为Math.floor(x+0.5),即将原来的数字加上0.5后再向下取整,所以,Math.round(11.5)的结果为12,Math.round(-11.5)的结果为-11。**\n\n## 5.下面的代码有什么不妥之处?\n\n```\n1. if(username.equals(“wpc”)){}\n\n 2. int x = 1;\n\n return x==1?true:false;\n```\n回答问题一:如果username初值为null在编译的时候会有警告,运行的时候抛出异常;如果username写在后面即使初值为null也不会抛异常。如下:\n\n```\npublic static void method1() {\n\t\tString username = null;\n \n\t\t// 第一个if如果username在前面,若为null的话会报空指针异常\n//\t\tif (username.equals(\"wpc\")) {// 如果username在编译时发现真的是null就会给出警告提示信息\n//\t\t\tSystem.out.println(\"wpc\");\n//\t\t}\n \n\t\t// 如果放后面即使没有赋初值,运行时也不会报空指针异常\n\t\tif (\"wpc\".equals(username)) {\n\t\t\tSystem.out.println(\"wpc\");\n\t\t}\n\t}\n```\n\n回答问题二:如果要返回boolean, 那么可以直接return x\\==1, (x==1这个表达式本来就返回boolean)\n\n## 6.请说出作用域public,private,protected,以及不写时的区别\n这四个作用域的可见范围如下表所示。\n\n说明:如果在修饰的元素上面没有写任何访问修饰符,则表示friendly。\n\n作用域 | 当前类 | 同一package | 子孙类 |其他package\n-----|-----|-----|-----|-----\npublic | √ | √ | √ | √ \nprotected | √ | √ | √ | × \nfriendly | √ | √ | × | × \nprivate | √ | × | × | × \n\n## 7.抽象类中可以包含静态方法,接口中不能包含静态方法\n 抽象类总可以有抽象方法和非抽象方法,如果抽象类中定义了静态非抽象方法,可以通过类名.静态非抽象方法名调用这个方法。但是在抽象类中不能有静态的抽象方法,抽象方法没有方法体,没有被分配内存,所以如果抽象方法为静态的,没法调用。接口中同理不能有静态抽象方法。\n\n 但是抽象类中可以包含静态方法,接口中只能包含public abstract方法。\n\n延伸:**static变量**在项目启动时就分配内存,直到程序结束释放内存**。static方法**只有在调用的时候才开始分配内存,因为static方法肯定在类里面,并没有实例化类,所以只有在调用static方法的时候才开始分配内存。\n \n## 8.匿名内部类必须实现一个接口或继承其他类\n\n匿名内部类也就是没有名字的内部类\n\n正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写\n\n但使用匿名内部类还有个前提条件:**必须继承一个父类或实现一个接口**\nDemo1:不是用匿名类来实现抽象方法\n\n```\nabstract class Person {\n public abstract void eat();\n}\n \nclass Child extends Person {\n public void eat() {\n System.out.println(\"eat something\");\n }\n}\n \npublic class Demo {\n public static void main(String[] args) {\n Person p = new Child();\n p.eat();\n }\n}\n```\n可以看到,我们用Child继承了Person类,然后实现了Child的一个实例,将其向上转型为Person类的引用\n\n但是,如果此处的Child类只使用一次,那么将其编写为独立的一个类岂不是很麻烦?\n\n这个时候就引入了匿名内部类\n\nDemo2:匿名内部类的基本实现\n\n```\nabstract class Person {\n public abstract void eat();\n}\n \npublic class Demo {\n public static void main(String[] args) {\n Person p = new Person() {\n @Override //此处方法重载 说明是实现父类 即内部类可以继承其他类 而且是必须\n public void eat() {\n System.out.println(\"eat something\");\n }\n };\n p.eat();\n }\n}\n```\n可以看到,我们直接将抽象类Person中的方法在大括号中实现了\n\n这样便可以省略一个类的书写\n\n并且,匿名内部类还能用于接口上\n \n```\ninterface Person {\n public void eat();\n }\n \n public class Demo {\n public static void main(String[] args) {\n Person p = new Person() {\n @Override //此处方法重写 说明是实现父类 即内部类可以实现其他类 而且是必须\n public void eat() {\n System.out.println(\"eat something\");\n }\n };\n p.eat();\n }\n }\n```\n由上面的例子可以看出,只要一个类是抽象的或是一个接口,那么其子类中的方法都可以使用匿名内部类来实现\n\n**最常用的情况就是在多线程的实现上,因为要实现多线程必须继承Thread类或是实现Runnable接口**\n\n## 9.String str = new String(\"xyz\") 会创建几个对象?\n\n```\npublic class StringTest {\n public static void main(String[] args){\n //在常量池中创建对象\"Hello\",s1指向这个对象\n String s1=\"Hello\";\n String s2=\"Hello\";\n //若常量池中存在\"Hello\"对象,则不会再在常量池中创建该对象;\n //若常量池中不存在\"Hello\"对象,则会先在常量池中创建\"Hello\"对象\n //接着执行new String(\"Hello\");又会在内存中创建一个对象\n String s3=new String(\"Hello\");\n \n System.out.println(\"s1和s2 引用地址是否相同:\"+(s1 == s2));\n System.out.println(\"s1和s2 值是否相同:\"+s1.equals(s2));\n \n System.out.println(\"s1和s3 引用地址是否相同:\"+(s1 == s3));\n System.out.println(\"s1和s3 值是否相同:\"+s1.equals(s3));\n }\n }\n```\n\n 输出结果:\n \n s1和s2 引用地址是否相同:true\n s1和s2 值是否相同:true\n s1和s3 引用地址是否相同:false\n s1和s3 值是否相同:true\n\n分析:\"==\"是判断两个对象的值(s1、s2、s3的值即为所指向的对象在内存中的地址),equals()方法则是判断引用对象所指向的对象的值,即本例中常量池中和内存中\"Hello\"对象的值。\n\n## 10.\n\n\n","tags":["java 面试题 基础"]},{"title":"数据库索引","url":"/2018/09/12/数据库索引/","content":"\n# 数据库索引到底是什么,是怎样工作的?\n我们通过一个简单的例子来开始教程,解释为什么我们需要数据库索引。假设我们有一个数据库表 Employee, 这个表有三个字段(列)分别是 Employee_Name、Employee_Age 和Employee_Address。假设表Employee 有上千行数据。\n\n现在假设我们要从这个表中查找出所有名字是‘Jesus’的雇员信息。我们决定使用下面的查询语句:\n```\nSELECT * FROM Employee \nWHERE Employee_Name = 'Jesus'\n```\n## 如果表中没有所以会发生什么?\n一旦我们运行这个查询,在查找名字为Jesus的雇员的过程中,究竟会发生什么?数据库不得不Employee表中的每一行并确定雇员的名字(Employee_Name)是否为 ‘Jesus’。由于我们想要得到每一个名字为Jesus的雇员信息,在查询到第一个符合条件的行后,不能停止查询,因为可能还有其他符合条件的行。所以,必须一行一行的查找直到最后一行-这就意味数据库不得不检查上千行数据才能找到所以名字为Jesus的雇员。这就是所谓的全表扫描。\n\n## 数据库索引是怎样提升性能的?\n你可能会想为如此简单的事情做全表扫描效率欠佳-数据库是不是应该更聪明一点呢?这就像用人眼从头到尾浏览整张表-很慢也不优雅(原文:not at all sleek,不知如何翻译才好)。但是,你可以能根据文章标题已经猜到,这就是索引派上用场的时候。使用索引的全部意义就是通过缩小一张表中需要查询的记录/行的数目来加快搜索的速度。\n\n## 什么是索引?\n一个索引是存储的表中一个特定列的值数据结构(最常见的是B-Tree)。索引是在表的列上创建。所以,要记住的关键点是索引包含一个表中列的值,并且这些值存储在一个数据结构中。请记住记住这一点:索引是一种数据结构 。\n\n## 什么样的数据结构可以作为索引?\nB-Tree 是最常用的用于索引的数据结构。因为它们是时间复杂度低, 查找、删除、插入操作都可以可以在对数时间内完成。另外一个重要原因存储在B-Tree中的数据是有序的。数据库管理系统(RDBMS)通常决定索引应该用哪些数据结构。但是,在某些情况下,你在创建索引时可以指定索引要使用的数据结构。\n\n## 哈希表索引是怎么工作的?\n哈希表是另外一种你可能看到用作索引的数据结构-这些索引通常被称为哈希索引。使用哈希索引的原因是,在寻找值时哈希表效率极高。所以,如果使用哈希索引,对于比较字符串是否相等的查询能够极快的检索出的值。例如之前我们讨论过的这个查询(SELECT * FROM Employee WHERE Employee_Name = ‘Jesus’) 就可以受益于创建在Employee_Name 列上的哈希索引。哈系索引的工作方式是将列的值作为索引的键值(key),和键值相对应实际的值(value)是指向该表中相应行的指针。因为哈希表基本上可以看作是关联数组,一个典型的数据项就像“Jesus => 0x28939″,而0x28939是对内存中表中包含Jesus这一行的引用。在哈系索引的中查询一个像“Jesus”这样的值,并得到对应行的在内存中的引用,明显要比扫描全表获得值为“Jesus”的行的方式快很多。\n\n## 哈希索引的缺点\n哈希表是无顺的数据结构,对于很多类型的查询语句哈希索引都无能为力。举例来说,假如你想要找出所有小于40岁的员工。你怎么使用使用哈希索引进行查询?这不可行,因为哈希表只适合查询键值对-也就是说查询相等的查询(例:like “WHERE name = ‘Jesus’)。哈希表的键值映射也暗示其键的存储是无序的。这就是为什么哈希索引通常不是数据库索引的默认数据结构-因为在作为索引的数据结构时,其不像B-Tree那么灵活\n\n## 还有什么其他类型的索引?\n使用R-Tree作为数据结构的索引通常用来为空间问题提供帮助。例如,一个查询要求“查询出所有距离我两公里之内的星巴克”,如果数据库表使用R- Tree索引,这类查询的效率将会提高。 \n另一种索引是位图索引(bitmap index), 这类索引适合放在包含布尔值(true 和 false)的列上,但是这些值(表示true或false的值)的许多实例-基本上都是选择性(selectivity)低的列。\n\n## 索引是怎么提升性能的?\n因为索引基本上是用来存储列值的数据结构,这使查找这些列值更加快速。如果索引使用最常用的数据结构-B-Tree-那么其中的数据是有序的。有序的列值可以极大的提升性能。下面解释原因。\n\n假设我们在 Employee_Name这一列上创建一个B-Tree索引。这意味着当我们用之前的SQL查找姓名是‘Jesus’的雇员时,不需要再扫描全表。而是用索引查找去查找名字为‘Jesus’的雇员,因为索引已经按照按字母顺序排序。索引已经排序意味着查询一个名字会快很多,因为名字少字母为‘J’的员工都是排列在一起的。另外重要的一点是,索引同时存储了表中相应行的指针以获取其他列的数据。\n\n## 数据库索引里究竟存的是什么?\n你现在已经知道数据库索引是创建在表的某列上的,并且存储了这一列的所有值。但是,需要理解的重点是数据库索引并不存储这个表中其他列(字段)的值。举例来说,如果我们在Employee_Name列创建索引,那么列Employee_Age和Employee_Address上的值并不会存储在这个索引当中。如果我们确实把其他所有字段也存储在个这个索引中,那就成了拷贝一整张表做为索引-这样会占用太大的空间而且会十分低效。\n\n## 索引存储了指向表中某一行的指针\n如果我们在索引里找到某一条记录作为索引的列的值,如何才能找到这一条记录的其它值呢?这是很简单 - 数据库索引同时存储了指向表中的相应行的指针。指针是指一块内存区域, 该内存区域记录的是对硬盘上记录的相应行的数据的引用。因此,索引中除了存储列的值,还存储着一个指向在行数据的索引。也就是说,索引中的Employee_Name这列的某个值(或者节点)可以描述为 (“Jesus”, 0x82829), 0x82829 就是包含 “Jesus”那行数据在硬盘上的地址。如果没有这个引用,你就只能访问到一个单独的值(“Jesus”),而这样没有意义,因为你不能获取这一行记录的employee的其他值-例如地址(address)和年龄(age)。\n\n## 数据库怎么知道什么时候使用索引?\n当这个SQL (SELECT * FROM Employee WHERE Employee_Name = ‘Jesus’ )运行时,数据库会检查在查询的列上是否有索引。假设Employee_Name列上确实创建了索引,数据库会接着检查使用这个索引做查询是否合理 - 因为有些场景下,使用索引比起全表扫描会更加低效。如果想要了解更多这些场景,请阅读这篇文章:[Selectivity in SQL](https://www.programmerinterview.com/index.php/database-sql/selectivity-in-sql-databases/)\n\n## 你能强制数据库使用索引吗?\n通常来说, 你不会告诉数据库什么时候使用索引 - 数据库自己决定。然而,值得注意的是在大多数数据库中(像Oracle 和 MYSQL), 你实际上可以制订你想要使用的索引。\n\n## 如何在使用SQL创建索引:\n之前的例子中,在Employee_Name列上创建索引的SQL如下:\n\n```\nCREATE INDEX name_index\nON Employee (Employee_Name)\n```\n## 如何创建联合索引\n\n```\nCREATE INDEX name_index\nON Employee (Employee_Name, Employee_Age)\n```\n## 把数据库索引类比成什么比较好呢?\n一个非常好的类比是把数据库索引看作是书的索引。如果你有一本关于狗的书,你想要找关于‘黄金猎犬’的那部分。当你可以通过在书背的索引找到哪几页有关于‘黄金猎犬’信息的时候,你为什么要翻完正本书 - 这相当于数据库中的全表扫描。同样的,就像一本书的索引包含页码一样,数据库的索引包含了指针,指向你在SQL中想要查询的值所在的行。\n\n## 使用数据库索引会有什么代价?\n那么,使用数据库索引有什么缺点呢?其一,索引会占用空间 - 你的表越大,索引占用的空间越大。其二,性能损失(主要值更新操作),当你在表中添加、删除或者更新行数据的时候, 在索引中也会有相同的操作。记住:建立在某列(或多列)索引需要保存该列最新的数据。\n\n**基本原则是只如果表中某列在查询过程中使用的非常频繁,那就在该列上创建索引。**\n原文链接:[http://www.programmerinterview.com/index.php/database-sql/what-is-an-index/](http://www.programmerinterview.com/index.php/database-sql/what-is-an-index/)\n","tags":["数据库 索引"]},{"title":"代理模式","url":"/2018/08/22/代理模式/","content":"\n# 1.什么是代理模式?\n\n代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.\n\n这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法\n\n举个例子来说明代理的作用:假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子\n\n# 2.静态代理\n\n**静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.**\n\n下面举个案例来解释: 模拟保存动作,定义一个保存动作的接口:IUserDao.java,然后目标对象实现这个接口的方法UserDao.java,此时如果使用静态代理方式,就需要在代理对象(UserDaoProxy.java)中也实现IUserDao接口.调用的时候通过调用代理对象的方法来调用目标对象. 需要注意的是,代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来调用目标对象的方法\n\n**代码示例:**\n\n 接口:IUserDao.java\n\n```java\n/**\n * 接口\n */\npublic interface IUserDao {\n\n void save();\n}\n```\n\n对象:UserDao.java\n\n```java\n/**\n * 代理对象,静态代理\n */\npublic class UserDaoProxy implements IUserDao{\n //接收保存目标对象\n private IUserDao target;\n public UserDaoProxy(IUserDao target){\n this.target=target;\n }\n\n public void save() {\n System.out.println(\"开始事务...\");\n target.save();//执行目标对象的方法\n System.out.println(\"提交事务...\");\n }\n}\n```\n\n测试类:App.java\n\n```java\n/**\n * 测试类\n */\npublic class App {\n public static void main(String[] args) {\n //目标对象\n UserDao target = new UserDao();\n\n //代理对象,把目标对象传给代理对象,建立代理关系\n UserDaoProxy proxy = new UserDaoProxy(target);\n\n proxy.save();//执行的是代理的方法\n }\n}\n```\n\n**静态代理总结:**\n\n1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.\n\n2.缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.\n\n**如何解决静态代理中的缺点呢?答案是可以使用动态代理方式**\n\n# 3.动态代理\n\n**动态代理有以下特点:** \n\n1. 代理对象,不需要实现接口 \n2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型) \n3. 动态代理也叫做:JDK代理,接口代理","tags":["设计模式"]},{"title":"赠与今年的大学毕业生--胡适","url":"/2018/08/18/赠与今年的大学毕业生/","content":"# 赠与今年的大学毕业生\n\n 这一两个星期里,各地的大学都有毕业的班次,都有很多的毕业生离开学校去开始他们的成人事业。学生的生活是一种享有特殊优待的生活,不妨幼稚一点,不妨吵吵闹闹,社会都能纵容他们,不肯严格的要他们负行为的责任。现在他们要撑起自己的肩膀来挑他们自己的担子了。在这个国难最紧急的年头,他们的担子真不轻! 我们祝他们的成功,同时也不忍不依据我们自己的经验,赠与他们几句送行的赠言——虽未必是救命毫毛,也许作个防身的锦囊罢! \n\n 你们毕业之后,可走的路不出这几条:绝少数的人还可以在国内或国外的研究院继续作学术研究;少数的人可以寻着相当的职业;此外还有做官,办党,革命三条路;此外就是在家享福或者失业闲居了。第一条继续求学之路,我们可以不讨论。走其余几条路的人,都不能没有堕落的危险。堕落的方式很多,总括起来,约有这两大类:\n\n 第一是容易抛弃学生时代的求知识的欲望。你们到了实际社会里,往往所用非所学,往往所学全无用处,往往可以完全用不着学问,而一样可以胡乱混饭吃,混官做。在这种环境里,即使向来抱有求知识学问的决心的人,也不免心灰意懒,把求知的欲望渐渐冷淡下去。况且学问是要有相当的设备的;书籍,试验室,师友的切磋指导,闲暇的工夫,都不是一个平常要糊口养家的人所能容易办到的。没有做学问的环境,又谁能怪我们抛弃学问呢?\n\n 第二是容易抛弃学生时代的理想的人生的追求。少年人初次与冷酷的社会接触,容易感觉理想与事实相去太远,容易发生悲观和失望。多年怀抱的人生理想,改造的热诚,奋斗的勇气,到此时候,好像全不是那么一回事。渺小的个人在那强烈的社会炉火里,往往经不起长时期的烤炼就镕化了,一点高尚的理想不久就幻灭了。抱着改造社会的梦想而来,往往是弃甲曳兵而走,或者做了恶势力的俘虏。你在那俘虏牢狱里,回想那少年气壮时代的种种理想主义,好像都成了自误误人的迷梦!从此以后,你就甘心放弃理想人生的追求,甘心做现成社会的顺民了。\n\n 要防御这两方面的堕落,一面要保持我们求知识的欲望,一面要保持我们对于理想人生的追求。有什么好法子呢?依我个人的观察和经验,有三种防身的药方是值得一试的。\n\n 第一个方子只有一句话:“总得时时寻一两个值得研究的问题!”问题是知识学问的老祖宗;古今来一切知识的产生与积聚,都是因为要解答问题,——要解答实用上的困难或理论上的疑难。所谓“为知识而求知识”,其实也只是一种好奇心追求某种问题的解答,不过因为那种问题的性质不必是直接应用的,人们就觉得这是“无所为”的求知识了。我们出学校之后,离开了做学问的环境,如果没有一个两个值得解答的疑难问题在脑子里盘旋,就很难继续保持追求学问的热心。可是,如果你有了一个真有趣的问题天天逗你去想他,天天引诱你去解决他,天天对你挑衅笑你无可奈他,——这时候,你就会同恋爱一个女子发了疯一样,坐也坐不下,睡也睡不安,没工夫也得偷出工夫去陪她,没钱也得撙衣节食去巴结她。没有书,你自会变卖家私去买书;没有仪器,你自会典押衣服去置办仪器;没有师友,你自会不远千里去寻师访友。你只要能时时有疑难问题来逼你用脑子,你自然会保持发展你对学问的兴趣,即使在最贫乏的智识环境中,你也会慢慢的聚起一个小图书馆来,或者设置起一所小试验室来。所以我说:第一要寻问题,脑子里没有问题之日,就是你的智识生活寿终正寝之时!古人说,“待文王而兴者,凡民也。若夫豪杰之士,虽无文王犹兴。”试想葛理略(Galieo)和牛敦(Newton)有多少藏书?有多少仪器?他们不过是有问题而已。有了问题而后,他们自会造出仪器来解答他们的问题。没有问题的人们,关在图书馆里也不会用书,锁在试验室里也不会有什么发现。\n\n 第二个方子也只有一句话:“总得多发展一点非职业的兴趣。”离开学校之后,大家总得寻个吃饭的职业。可是你寻得的职业未必就是你所学的,或者未必是你所心喜的,或者是你所学而实在和你的性情不想近的。在这种状况之下,工作就往往成了苦工,就不感觉兴趣了。为糊口而作那种非“性之所近而力之所能勉”的工作,就很难保持求知的兴趣和生活的理想主义。最好的救济方法只有多多发展职业以外的正当兴趣与活动。一个人应该有他的职业,又应该有他的非职业的顽艺儿,可以叫做业余活动。凡一个人用他的闲暇来做的事业,都是他的业余活动。往往他的业余活动比他的职业还更重要,因为一个人的前程往往会靠他怎样用他的闲暇时间。他用他的闲暇来打马将,他就成个赌徒;你用你的闲暇来做社会服务,你也许成个社会改革者;或者你用你的闲暇去研究历史,你也许成个史学家。你的闲暇往往定你的终身。英国十九世纪的两个哲人,弥儿(J.S.Mill)终身做东印度公司的秘书,然而他的业余工作使他在哲学上,经济学上,政治思想史上都占一个很高的位置;斯宾塞(Spencer)是一个测量工程师,然而他的业余工作使他成为前世纪晚期世界思想界的一个重镇。古来成大学问的人,几乎没有一个不是善用他的闲暇时间的。特别在这个组织不健全的中国社会,职业不容易适合我们性情,我们要想生活不苦痛或不堕落,只有多方发展业余的兴趣,使我们的精神有所寄托,使我们的剩余精力有所施展。有了这种心爱的顽艺儿,你就做六个钟头的抹桌子工夫也不会感觉烦闷了,因为你知道,抹了六点钟的桌子之后,你可以回家去做你的化学研究,或画完你的大幅山水,或写你的小说戏曲,或继续你的历史考据,或做你的社会改革事业。你有了这种称心如意的活动,生活就不枯寂了,精神也就不会烦闷了。\n\n 第三个方子也只有一句话:“你总得有一点信心。”我们生当这个不幸的时代,眼中所见,耳中所闻,无非是叫我们悲观失望的。特别是在这个年头毕业的你们,眼见自己的国家民族沉沦到这步田地,眼看世界只是强权的世界,望极天边好像看不见一线的光明,——在这个年头不发狂自杀,已算是万幸了,怎么还能够希望保持一点内心的镇定和理想的信心呢?我要对你们说:这时候正是我们培养我们的信心的时候!只要我们有信心,我们还有救。古人说:“信心(Faith)可以移山。”又说:“只要工夫深,生铁磨成绣花针。”你不信吗?当拿破仑的军队征服普鲁士占据柏林的时候,有一位穷教授叫做菲希特(Fichte)的,天天在讲堂上劝他的国人要有信心,要信仰他们的民族是有世界的特殊使命的,是必定要复兴的。菲希特死的时候(1814),谁也不能预料德意志统一帝国何时可以实现。然而不满五十年,新的统一的德意志帝国居然实现了。\n\n 一个国家的强弱盛衰,都不是偶然的,都不能逃出因果的铁律的。我们今日所受的苦痛和耻辱,都只是过去种种恶因种下的恶果。我们要收将来的善果,必须努力种现在的新因。一粒一粒的种,必有满仓满屋的收,这是我们今日应该有的信心。\n\n 我们要深信:今日的失败,都由于过去的不努力。\n\n 我们要深信:今日的努力,必定有将来的大收成\n\n 佛典里有一句话:“福不唐捐。”唐捐就是白白的丢了,我们也应该说:“功不唐捐!”没有一点努力是会白白的丢了的。在我们看不见想不到的时候,在我们看不见想不到的方向,你瞧!你下的种子早已生根发叶开花结果了!\n\n 你不信吗?法国被普鲁士打败之后,割了两省地,赔了五十万万佛郎的赔款。这时候有一位刻苦的科学家巴斯德(Pasteur)终日埋头在他的试验室里做他的化学试验和微菌学研究。他是一个最爱国的人,然而他深信只有科学可以救国。他用一生的精力证明了三个科学问题:(1)每一种发酵作用都是由于一种微菌的发展;(2)每一种传染病都是由于一种微菌在生物体中的发展;(3)传染病的微菌,在特殊的培养之下,可以减轻毒力,使它从病菌变成防病的药苗。——这三个问题,在表面上似乎都和救国大事业没有多大的关系。然而从第一个问题的证明,巴斯德定出做醋酿酒的新法,使全国的酒醋业每年减除极大的损失。从第二个问题的证明,巴斯德教全国的蚕丝业怎样选种防病,教全国的畜牧农家怎样防止牛羊瘟疫,又教全世界的医学界怎样注重消毒以减除外科手术的死亡率。从第三个问题的证明,巴斯德发明了牲畜的脾热瘟的疗治药苗,每年替法国农家灭除了二千万佛郎的大损失;又发明了疯狗咬毒的治疗法,救济了无数的生命。所以英国的科学家赫胥黎(Huxley)在皇家学会里称颂巴斯德的功绩道:“法国给了德国五十万万佛郎的赔款,巴斯德先生一个人研究科学的成绩足够还清这一笔赔款了。”\n\n 巴斯德对于科学有绝大的信心,所以他在国家蒙奇辱大难的时候,终不肯抛弃他的显微镜与试验室。他绝不想他的显微镜底下能偿还五十万万佛郎的赔款,然而在他看不见想不到的时候,他已收获了科学救国的奇迹了。\n\n 朋友们,在你最悲观最失望的时候,那正是你必须鼓起坚强的信心的时候。你要深信:天下没有白费的努力。成功不必在我,而功力必不唐捐。\n\n**<center>胡适 1932年6月27日作</center>**","tags":["杂文"]}]