static关键字代表一个成员变量或者是成员方法能够在没有所属的类的实例变量的状况下被访问。
Java中static方法不能被覆盖,由于方法覆盖(override)是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,因此概念上不适用。
不能够在static 环境中访问非static 变量,static变量在Java中是属于类的,它在全部的实例中的值是同样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化(与类同生死)。非static变量,只有类被建立时,才会分配空间(若是是方法内的局部变量,那么在方法被调用的时候才建立)。javascript
- 接口比抽象类更加抽象,由于抽象类中能够定义构造器,能够有抽象方法和具体方法,而接口中不能定义构造器并且其中的方法所有都是抽象方法。
- 类能够实现不少个接口,可是只能继承一个抽象类。一个类若是继承了某个抽象类或者实现了某个接口都须要对其中的抽象方法所有进行实现, 不然该类仍然须要被声明为抽象类。
- 抽象类能够在不提供接口方法实现的状况下实现接口。
- Java 接口中声明的变量默认都是final的。抽象类能够包含非final的变量。
- Java 接口中的成员函数默认是 public 的。抽象类的成员函数能够是 private,protected 或者是 public。
- 接口是绝对抽象的,不能够被实例化。抽象类也不能够被实例化,可是,若是它包含 main方法的话是能够被调用的。(抽象类和接口都不可以实例化,但能够定义抽象类和接口类型的引用)
- 接口能够继承接口。抽象类能够实现(implements)接口,抽象类可继承具体类,但前提是具体类必须有明确的构造函数。
- 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。
接口能够继承接口,并且支持多重继承。抽象类能够实现(implements)接口,抽象类可继承具体类也能够继承抽象类。
- 进程是程序的一次动态执行过程,每一个进程都有本身独立的内存空间。一个应用程序能够同时启动多个进程(好比浏览器能够开多个窗口,每一个窗口就是一个进程),进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程能够有多个线程。线程又叫作轻量级进程。
- 多进程操做系统可以运行多个进程,每一个进程都可以循环利用所须要的CPU时间片,使的全部进程看上去像在同时运行同样。
- 线程是进程的一个执行流程,是CPU调度和分 派的基本单位。一个进程能够由多个线程组成,也就是一个进程能够同时运行多个不一样的线程,每一个线程完成不一样的任务。
- 线程的并发运行:就是一个进程内若干个线程同时运行。(好比:word的拼写检查功能和首字母自动大写功能是word进程中的线程)
- 线程和进程的关系是一个局部和总体的关系,每一个进程都由操做系统分配独立的内存地址空间,而同一进程的全部线程都在同一地址空间工做。(进程在执行时一般拥有独立的内存单元,而线程之间能够实现内存共享)
- 虽然使用多线程的编程一般可以带来更好的性能和用户体验,可是多线程占用更多的CPU资源,对于其余的进程并不友好(可能占用了更多的CPU 资源),也不是线程越多, 程序的性能就越好,由于线程之间的调度和切换也会浪费CPU 时间。
一个线程的完整生命周期要经历5中状态:新建、就绪、运行、阻塞、死亡。
java
- 新建状态:使用new和某种线程的构造方法来建立线程对象,该线程就会进入新建状态,系统为该线程对象分配内存空间。处于新建状态的线程能够经过调用start()方法进入就绪状态。
- 就绪状态(Runnable):此时线程已经具有了运行的条件,进入了线程队列,等待系统分配CPU资源,一旦得到CPU资源,该线程就会进入运行状态。(线程准备运行,不必定立马就能开始执行)
- 运行状态(Running):进入运行在状态,线程会执行本身的run()方法中的代码。就是进程正在执行线程的代码。
- 阻塞状态(Blocked):一个正在执行的线程,若是执行了suspend、join或sleep方法,或等待io设备的使用权,那么该线程将会让出本身的CUP控制权并暂时停止本身的执行,进入阻塞状态。阻塞的线程,不可以进入就绪队列,只有当阻塞缘由被消除的时候,线程才能进入就绪状态,从新进入线程队列中排队等待CPU资源,而后继续执行。
- 死亡状态(Dead):一个线程完成了所有工做或者被提早强制性的停止,该线程就处于死亡状态。
- 睡眠状态(Sleeping):线程经过
Thread.sleep(线程睡眠)、Object.wait(线程等待)、LockSupport.park(线程暂停)
三种方式 使线程进入休眠状态。
同步阻塞(Blocked on Synchronization):sleep( ) 使线程在必定的时间内进入阻塞状态(不会释放锁资源)、wait( ) 使线程进入阻塞状态,(释放本身占有的锁资源,搭配notify( )使用)、suspend( ) 使线程进入阻塞状态(必须其对应的resume( )被调用,才能使线程从新进入可执行状态)
wait():使一个线程处于等待(阻塞)状态,而且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
notify():唤醒一个处于等待状态的线程,固然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM 肯定唤醒哪一个线程,并且与优先级无关;
notityAll():唤醒全部处于等待状态的线程,该方法并非将对象的锁给全部线程,而是让它们竞争,只有得到锁的线程才能进入就绪状态;mysql
Thread类的sleep()方法和对象的wait()方法c++
sleep()*方法(休眠)是*线程类( Thread)*的*静态方法,调用此方法让当前线程暂停执行指定的时间, 将执行机会( CPU)让给其余线程, 可是
对象的锁依然保持
,所以休眠时间结束后会自动恢复(线程回到就绪状态)。
*wait()* 方法是Object 类的方法,调用对象的wait()方法致使当前线程放弃对象的锁
(线程暂停执行), 进入对象的等待池(wait pool),只有调用对象的notify()
方法(或notifyAll()
方法)时才能唤醒等待池中的线程进入等锁池(lock pool), 若是线程从新得到对象的锁就能够进入就绪状态。程序员线程的sleep()方法和yield()方法web
1)sleep()方法给其余线程运行机会时不考虑线程的优先级,所以会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
2)线程执行sleep()方法后转入阻塞( blocked)状态, 而执行yield()方法后转入就绪( ready)状态;
3)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
4)sleep()方法比yield()方法(跟操做系统CPU调度相关)具备更好的可移植性。面试
在面向对象编程中,建立和销毁对象是很费时间的,由于建立一个对象要获取内存资源或者其它更多资源。在Java 中,虚拟机将试图跟踪每个对象,以便可以在对象销毁后进行垃圾回收。因此提升服务程序效率的一个手段就是尽量减小建立和销毁对象的次数,特别是一些很耗资源的对象建立和销毁,这就是**
池化资源技术
**产生的缘由。
线程池顾名思义就是事先建立若干个可执行的线程放入一个池(容器)中,须要的时候从池中获取线程不用自行建立, 使用完毕不须要销毁线程而是放回池中,从而减小建立和销毁线程对象的开销。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。工具类Executors
面提供了一些静态工厂方法,生成一些经常使用的线程池。
工具类Executors
生成线程池的经常使用方法:正则表达式1)newSingleThreadExecutor:建立一个单线程的线程池。这个线程池只有一个线程在工做,也就是至关于单线程串行执行全部任务。若是这个惟一的线程由于异常结束,那么会有一个新的线程来替代它。此线程池保证全部任务的执行顺序按照任务的提交顺序执行。
2) newFixedThreadPool:建立固定大小的线程池。每次提交一个任务就建立一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,若是某个线程由于执行异常而结束,那么线程池会补充一个新线程(返回到线程池中)。【推荐使用】
3)newCachedThreadPool:建立一个可缓存的线程池。若是线程池的大小超过了处理任务所须要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增长时,此线程池又能够智能的添加新线程来处理任务。此线程池不会对线程池大小作限制,线程池大小彻底依赖于操做系统(或者说JVM)可以建立的最大线程大小。
4)newScheduledThreadPool:建立一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
5)newSingleThreadExecutor:建立一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。算法若是但愿在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来建立线程池,这样能得到更好的性能。sql
java 中的线程分为两种:守护线程( Daemon)和用户线程( User)。
j任何线程均可以设置为守护线程和用户线程,经过方法Thread.setDaemon(boolon);
true则把该线程设置为守护线程,false则设置为用户线程。Thread.setDaemon()
必须在Thread.start()以前调用,不然运行时会抛出异常。守护线程和本地线程二者的区别
惟一的区别是判断虚拟机(JVM)什么时候离开,守护线程Daemon是为其余线程提供服务,若是所有的用户现场Thread 已经撤离, Daemon 没有可服务的线程,JVM 撤离。也可
以理解为守护线程是JVM 自动建立的线程( 但不必定),用户线程是程序建立的线程;好比JVM 的垃圾回收线程是一个守护线程,当全部线程已经撤离,再也不产生垃圾,守护线程天然就没事可干了,当垃圾回收线程是Java 虚拟机上仅剩的线
程时,Java 虚拟机会自动离开。
1)synchronized 关键字能够将对象或者方法标记为同步,以实现对对象和方法的互斥访问,能够用
synchronized(对象) { }
定义同步代码块,或者在声明方法时将synchronized做为方法的修饰符
。
2)不能进入。
其它线程只能访问该对象的非同步方法,同步方法则不能进入。由于非静态方法上的synchronized修饰符要求执行方法时要得到对象的锁,若是已经进入A方法说明对象锁已经被取走,那么试图进入B 方法的线程就只能在等锁池( 注意不是等待池)中等待对象的锁。
Lock是JDK1.5 之后引入的新的API,和关键字synchronized 的异同:
相同点:Lock 能完成synchronized 所实现的全部功能;
主要不一样点:Lock有比synchronized 更精确的线程语义和更好的性能,并且不强制性的要求必定要得到锁。synchronized会自动释放锁,而Lock必定要求程序员手工释放,而且最好在finally 块中释放(这是释放外部资源的最好的地方)。
- 死锁、活锁、饥饿 的概念,之间的区别
1)死锁:两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去
2)活锁:任务或者执行者没有被阻塞,因为某些条件没有知足,致使一直重复尝试,失败,尝试, 失败。
3)饥饿:一个或者多个线程由于种种缘由没法得到所须要的资源,致使一直没法执行的状态。活锁和死锁的区别:
处于活锁的实体是在不断的改变状态,所谓的“ 活”, 而
处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。Java 中致使饥饿的缘由:
1)高优先级线程吞噬全部的低优先级线程的CPU 时间。
2)线程被永久堵塞在一个等待进入同步块的状态,由于其余线程老是能在它以前
持续地对该同步块进行访问。
3)线程在等待一个自己也处于永久等待完成的对象(好比调用这个对象的wait 方
法),由于其余线程老是被持续地得到唤醒。
- 产生死锁的必要条件:
1)互斥条件:所谓互斥就是进程在某一时间内独占资源。
2)请求与保持条件:一个进程因请求资源而阻塞时,对已得到的资源保持不放。
3)不剥夺条件:进程已得到资源,在末使用完以前,不能强行剥夺。
4)循环等待条件:若干进程之间造成一种头尾相接的循环等待资源关系。
- 如何确保 N 个线程能够访问N 个资源同时又不致使死锁
使用多线程的时候,一种很是简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。所以,若是全部的线程都是以一样的顺序加锁和释放锁,就不会出现死锁了。
多线程的上下文
多线程会共同使用一组计算机上的CPU,而线程数大于给程序分配的CPU 数量时,为了让各个线程都有执行的机会,就须要轮转使用CPU。不一样的线程切换使用CPU发生的切换数据等就是上下文切换。
- 堆:存放方法对象(经过new 关键字和构造器建立的对象)
- 栈:存放方法以及方法中的局部变量
- 方法区:(也叫共享区)存放代码片断、静态属性、常量池(好比常量池中存放字符串的值)
一般咱们定义一个基本数据类型的变量,一个对象的引用,函数调用的现场保存都使用JVM 中的栈空间;而经过new 关键字和构造器建立的对象则放在堆空间,堆是垃圾收集器管理的主要区域,因为如今的垃圾收集器都采用分代收集算法,因此堆空间还能够细分为新生代和老生代,再具体一点能够分为Eden、Survivor(又可分为From Survivor 和To Survivor)、Tenured;方法区和堆都
是各个线程共享的内存区域,用于存储已经被JVM 加载的类信息、常量、静态变
量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”
hello” 和常量都是放在常量池中, 常量池是方法区的一部分。栈空间操做起来
最快可是栈很小,一般大量的对象都是放在堆空间,栈和堆的大小均可以经过JVM
的启动参数来进行调整,栈空间用光了会引起StackOverflowError,而堆和常量
池空间不足则会引起OutOfMemoryError。
String str = new String("HelloWorld");
,这个代码中,变量str 存放在栈上,用new 建立出来的字符串对象放在堆上,”HelloWorld” 这个字面量(String)是放在方法区中。
JDK1.5 之前,switch()的参数中,只能是
byte、short、char、int
。
从JDK1.5 开始, Java 中引入了枚举类型与byte short char int的包装类。
从JDK 1.7 开始,参数还能够是字符串( String)类型,可是长整型( long)在目前全部的版本中都是不能够的。
也就是如今switch支持byte、short、char、int、String、枚举
JDK1.5,对四个包装类的支持是由于java编译器在底层手动进行拆箱,而对枚举类的支持是由于枚举类有一个ordinal方法,该方法其实是一个int类型的数值。
jdk1.7开始支持String类型,但实际上String类型有一个hashCode算法,结果也是int类型.而byte short char类型能够在不损失精度的状况下向上转型成int类型,因此总的来讲,能够认为switch中只支持int.
不对,若是两个对象x 和y 知足x.equals(y) == true,它们的哈希码(hash code)
应当相同。
Java 对于eqauls 方法和hashCode 方法是这样规定的:1)若是两个对象相同(equals 方法返回true),那么它们的hashCode 值必定要相同;
2)若是两个对象的hashCode 相同,它们并不必定相同。固然,你未必要按照要求去作,可是(重写hashCode)若是你违背了上述原则就会发如今使用容器时,相同的对象能够出如今Set 集合中,同时增长新元素的效率会大大降低(对于使用哈希存储的系统,若是哈希码频繁的冲突将会形成存取性能急剧降低)。关于equals方法,有如下内容必须知足
首先equals 方法必须知足
1)自反性( x.equals(x)必须返回true)
2)对称性( x.equals(y)返回true 时, y.equals(x)也必须返回true)
3)传递性(x.equals(y)和y.equals(z)都返回true 时, x.equals(z)也必须返回true)
4)一致性(当x和y引用的对象信息没有被修改时,屡次调用x.equals(y)应该获得一样的返回值) ,并且对于任何非null值的引用x,x.equals(null)必须返回false。实现高质量的equals 方法的诀窍包括
1)使用==操做符检查”参数是否为这个对象的引用”;
2)使用instanceof 操做符检查”参数是否为正确的类型”;
3) 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;
4)重写equals方法后,问本身它是否知足自反性、对称性、传递性、一致性;
5) 重写equals 时老是要重写hashCode;
6) 不要将equals 方法参数中的Object 对象替换为其余的类型,在重写时不要忘掉@Override 注解。
Java平台提供了两种类型的字符串: String 和StringBuffer/StringBuilder,它们能够储存和操做字符串。
其中String 是只读字符串,也就意味着String 引用的字符串内容是不能被改变的。
而StringBuffer/StringBuilder类表示的字符串对象能够直接进行修改。StringBuilder 是JDK 1.5 中引入的,它和StringBuffer 的方法彻底相同, 区别在于StringBuffer由于被synchronized 修饰,是线程安全的而StringBuilder是线程不安全的(全部方面都没有被synchronized 修饰)
由于StringBuilder没有被synchronized同步修饰,因此StringBuilder【面试题】什么状况下用+运算符进行字符串链接比调用StringBuffer/StringBuilder 对象的append 方法链接字符串性能更好?
首先要清楚如下两点:
1)String 对象的intern方法会获得字符串对象在常量池中对应的版本的引用(若是常量池中有一个字符串与String 对象的equals结果是true),若是常量池中没有对应的字符串,则该字符串将被添加到常量池中, 而后返回常量池中字符串的引用;
2)字符串的+操做其本质是建立了StringBuilder对象进行append操做,而后将拼接后的StringBuilder 对象用toString方法处理成String 对象。
方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。
所谓的重载是编译时多态性,就是根据实际的参数列表,在编译的时候就可以肯定执行重载方法中的哪个了。
所谓的重写是运行时多态性,就好比父类对象引用子类实例时,调用方法只有在运行时才知道到底执行了哪一个方法。
重载发生在一个类中,同名的方法若是有不一样的参数列表( 参数类型不一样、参数个数不一样或者两者都不一样)则视为重载;(重载对返回类型没有特殊的要求,只考虑参数列表)
重写发生在子类与父类之间(须要继承),重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常( 里氏代换原则)。
Java 虚拟机是一个能够执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。
Java 被设计成容许应用程序能够运行在任意的平台,而不须要程序员为每个平台单独重写或者是从新编译。Java 虚拟机让这个变为可能,由于它知道底层硬件平台的指令长度和其余特性。
Java代码在JVM中的执行流程
JVM的类加载原理图
JVM 中类的装载是由类加载器(ClassLoader
)和它的子类来实现的, Java 中的类加载器是一个重要的Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。
因为Java 的跨平台性,通过编译的Java 源程序并非一个可执行程序,而是一个或多个类class文件。当Java 程序须要使用某个类时,JVM 会确保这个类已经被加载、链接( 验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,一般是建立一个字节数组读入.class 文件,而后产生与所加载类对应的Class 对象。加载完成后,Class 对象还不完整,因此此时的类还不可用。当类被加载后就进入链接阶段,这一阶段包括验证、准备( 为静态变量分配内存并设置默认的初始值)和解析( 将符号引用替换为直接引用)三个步骤。最后JVM 对类进行初始化,包括:
1)若是类存在直接的父类而且这个类尚未被初始化,那么就先初始化父类;
2)若是类中存在初始化语句, 就依次执行这些初始化语句。类的加载是由类加载器完成的
类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器( System)和用户自定义类加载器(java.lang.ClassLoader 的子类) 。
从Java 2( JDK 1.2)开始, 类加载过程采起了父亲委托机制(PDM)。PDM 更好的保证了Java 平台的安全性,在该机制中, JVM 自带的Bootstrap 是根加载器, 其余的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向Java 程序提供对Bootstrap 的引用。
Bootstrap
:通常用本地代码实现,负责加载JVM 基础核心类库(rt.jar
);Extension
:从java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;System
:又叫应用类加载器,其父类是Extension。它是应用最普遍的类加载器。它从环境变量classpath 或者系统属性java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。
理论上,Java由于有垃圾回收机制( GC)不会存在内存泄露问题( 这也是Java 被普遍使用于服务器端编程的一个重要缘由);
然而在实际开发中,可能会存在无用但可达的对象
,这些对象不能被GC 回收,所以也会致使内存泄露的发生。
例如:Hibernate 的Session( 一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,若是不及时关闭(close)或清空( flush)一级缓存就可能致使内存泄露。
//实现了一个栈(先进后出(FILO))结构代码 import java.util.Arrays; import java.util.EmptyStackException; public class MyStack<T> { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } } 123456789101112131415161718192021222324252627282930
上述代码是实现了一个栈的先进后出(FILO)结构,可是其中的pop 方法却存在
内存泄露的问题
当咱们用pop 方法弹出栈中的对象时,该对象不会被看成垃圾回收,即便使用栈的程序再也不引用这些对象, 由于栈内部维护着对这些对象的过时引用(obsolete reference)。
在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无心识的对象保持。若是一个对象引用被无心识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其余对象,即便这样的对象只有少数几个,也可能会致使不少的对象被排除在垃圾回收以外,从而对性能形成重大影响,极端状况下会引起Disk Paging( 物理内存与硬盘的虚拟内存交换数据),甚至形成OutOfMemoryError。
答案是都不能。
1)抽象方法须要子类重写,而静态的方法是没法被重写的,所以两者是矛盾的。
2)本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。
3)synchronized 和方法的实现细节有关,抽象方法不涉及实现细节, 所以也是相互矛盾的。
静态变量是被static修饰符修饰的变量,也称为类变量, 它属于类(与类同生死),不属于类的任何一个对象,一个类无论建立多少个对象, 静态变量在内存中有且仅有一个拷贝;静态变量能够实现让多个对象共享内存。
实例变量必须依存于某一实例,须要先建立对象而后经过对象才能访问到它。
(在Java 开发中, 上下文类和工具类中一般会有大量的静态成员。)
对象是引用数据类型,对象的赋值仅仅是吧对象的引用赋值给另外一个对象,他们的堆空间是相同的。
克隆就是须要有一个彻底相同的对象,并且二者之间彻底互不影响,克隆对象和原对象是两个彻底对立的对象。(java.lang.Object类的clone()方法,对象克隆就是对象的复制,即完整的复制出一个对象。)1)实现
Cloneable
接口并重写Object 类中的clone()方法;(浅克隆对象的克隆默认是浅度克隆,也就是包含在对象内部的对象是不会被克隆的),【经过clone()实现深度克隆:让克隆对象所包含的类也实现clone功能(实现cloneable接口,重载clone()方法)】
2)实现Serializable
接口,经过对象的序列化和反序列化实现克隆,能够实现真正的深度克隆基于序列化和反序列化(
Serializable
)实现的克隆不只仅是深度克隆, 更重要的是经过泛型限定,能够检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object 类的clone 方法克隆对象。让问题在编译的时候暴露出来老是好过把问题留到运行时。
Serializable序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。能够对流化后的对象进行读写操做,也可将流化后的对象传输于网络之间。
序列化是为了解决对象流读写操做时可能引起的问题( 若是不进行序列化可能会存在数据乱序的问题) 。
要实现序列化,须要让一个类实现Serializable
接口,该接口是一个标识性接口,标注该类对象是可被序列化的,而后使用一个输出流来构造一个对象输出流并经过writeObject(Object)方法就能够将实现对象写出(即保存其状态);
若是须要反序列化则能够用一个输入流创建对象输入流,而后经过readObject 方法从流中读取对象。序列化除了可以实现对象的持久化以外,还可以用于对象的深度克隆(上面所说的)。
java中由JVM的垃圾回收管理器(GC)来负责回收不须要再使用的堆内存和栈内存。GC 功能能够自动监测对象是否超过做用域从而达到自动回收内存的目的。
JVM的垃圾回收采用动态存储管理技术,它可以本身检测内存的使用状况,自动地释放再也不被程序引用的对象,按照特定的垃圾收集算法来实现内存资源自动回收功能。
JVM垃圾回收管理器处理释放没用的对象外,还能够清除内存记录的碎片(由于建立对象和垃圾收集齐全不断释放对其对象所占用的空间,所以内存会出现碎片)。碎片整理将所占用的堆内存移动到堆的一端,JVM将整理出的内存分配给新的对象。
JVM垃圾回收器有一个潜在的缺点 : 增长了系统的开销,影响了程序的性能。由于JVM必须追踪运行程序中有用的对象,到最终释放没用的对象,这个过程须要花费处理器的时间。
一个对象若是再也不被任何栈内存所引用,则该对象成为垃圾对象。垃圾收集器对垃圾对象的收集时间是不肯定的,也能够直接使用System.gc()
方法Runtime.getRuntime().gc()
回收垃圾对象,可是这种强制执行垃圾回收程序对系统性能会产生负面影响。(JVM 能够屏蔽掉显示的垃圾回收调用,就是显示调用方法,JVM的GC会自动进行管理)虽然你能够调用System.gc() 或者Runtime.gc(),可是没有办法保证GC的执行。垃圾回收能够有效的防止内存泄露, 有效的使用能够使用的内存。垃圾回收器一般是做为一个单独的低优先级的线程运行,不可预知的状况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回
收器对某个对象或全部对象进行垃圾回收。在Java 诞生初期,垃圾回收是Java
最大的亮点之一,由于服务器端的编程须要有效的防止内存泄露问题,然而时过
境迁,现在Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户一般
以为iOS 的系统比Android 系统有更好的用户体验,其中一个深层次的缘由就在
于Android 系统中垃圾回收的不可预知性。补充:垃圾回收机制有不少种,包括:分代复制垃圾回收、标记垃圾回收、增量
垃圾回收等方式。标准的Java 进程既有栈又有堆。栈保存了原始型局部变量,堆
保存了要建立的对象。Java 平台对堆内存回收和再利用的基本算法被称为标记和
清除,可是Java 对其进行了改进,采用“ 分代式垃圾收集”。这种方法会跟Java
对象的生命周期将堆内存划分为不一样的区域, 在垃圾收集过程当中,可能会将对象
移动到不一样区域:
1)将字符串转换为基本数据类型
调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)
便可返回相应基本类型;
2)将基本数据类型转换为字符串
一种方法是将基本数据类型与空字符串""
经过+
链接便可得到其所对应的字符串;
另外一种方法是调用String 类中的valueOf()方法返回相应字符串。String.valueOf()
Java和JavaScript介绍
JavaScript 与Java 是两个公司开发的不一样的两个产品。Java 是原Sun Microsystems 公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript 是Netscape 公司的产品,为了扩展Netscape浏览器的功能而开发的一种能够嵌入Web 页面中运行的基于对象和事件驱动的解释性语言。
JavaScript 的前身是LiveScript;而Java 的前身是Oak 语言。Java和JavaScript的区别
1)Java是面向对象的而JavaScript是基于对象的。
Java 是一种真正的面向对象的语言,即便是开发简单的程序,必须设计对象;JavaScript 是种脚本语言,它能够用来制做与网络无关的,与用户交互做用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,于是它自己提供了很是丰富的内部对象供设计人员使用。
2)Java是编译运行,而JavaScript是解释执行的。
Java 的源代码在执行以前,必须通过编译。JavaScript 是一种解释性编程语言,其源代码不需通过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提高JavaScript 的运行效率
3)Java变量必须先声明后使用,JavaScript是弱类型语言,直接使用。
Java采用强类型变量检查,即全部变量在编译以前必须做声明;JavaScript 中变量是弱类型的,甚至在使用变量前能够不做声明,JavaScript 的解释器在运行时检查推断其数据类型。
4)Java是静态语言,JavaScript是动态语言。
try{}里有一个return语句返回,那么紧跟在这个try后的finally{}里的代码也是会被执行的,是在方法返回调用前(return前执行)
若是存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕以后再向调用者返回其值(执行return)。
在finally中改变返回值的作法是很差的,若是在finally 中修改了返回值,就会返回修改后的值。在finally 中返回或者修改返回值会对程序形成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也能够经过提高编译器的语法检查级别来产生警告或错误(idea、eclipse也能本身设置)
异常表示程序运行过程当中可能出现的非正常状态。
运行时异常:表示虚拟机的一般操做中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题一般就不会发生。(在运行是抛出)
受检查异常跟程序运行的上下文环境有关,即便程序设计无误, 仍然可能因使用的问题而引起。Java 编译器要求方法必须声明抛出可能发生的受检查异常,可是并不要求必须声明抛出未被捕获的运行时异常。
常见的运行时异常:ArithmeticException(算术异常)
ClassCastException (类转换异常)
IllegalArgumentException (非法参数异常)
IndexOutOfBoundsException (下标越界异常)
NullPointerException (空指针异常)
SecurityException (安全异常)
final关键字
一个基本数据类型声明为final,就只能进行一次赋值(初始化),编译器会自动检查代码,若是须要修改final的初始化,就会编译报错。final声明的引用数据类型,当前引用是不能改变的,可是能够改变引用指向的内存空间的值。final通常和static搭配使用做为常量。
final关键字的三种用法:
1)修饰类:表示该类不能被继承;
2)修饰方法:表示方法不能被重写;
3)修饰变量: 表示变量只能一次赋值之后值不能被修改(常量)(final修饰成员变量必须在声明时初始化或者再构造器中初始化,不然报编译错误,并且final修饰的变量一般都是常量,常量名所有大写)
若是一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,所以它和abstract 是反义词。将变量声明为final,能够保证它们在使用中不被改变,被声明为final 的变量必须在声明时给定初值,而在之后的引用中只能读取不可修改。被声明为final 的方法也一样只能使用,不能在子类中被重写。final优点:
1)final关键字能够提升性能,JVM和Java应用都会缓存final变量。
2)final变量能够在安全的多线程环境下进行资源共享,而不须要额外的同步开销。finally
一般放在try…catch…的后面构造老是执行代码块,这就意味着程序不管正常执行仍是发生异常,这里的代码只要JVM 不关闭都能执行,能够将释放外部资源的代码写在finally 块中(关闭数据库链接、释放资源)。
finalize
Object 类中定义的方法,Java 中容许使用finalize()方法在垃圾收集器将对象从内存中清除出去以前作必要的清理工做。这个方法是由垃圾收集器在销毁对象时调用的,经过重写finalize()方法能够整理系统资源或者执行其余清理工做。
List以特定索引来存取元素,能够有重复元素。
Set 不能存放重复元素(用对象的equals()*方法来区分元素是否重复)。
**Map**保存*键值对( key-value)映射,映射关系能够是一对一或多对一。(Map不支持一对多,可是能够用Map<Integer, List>
这种格式来达到一对多的系,多对多相似)Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实如今插入或删除元素时会按照元素或元素的键( key)构成排序树从而达到排序和去重的效果。
ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,它们都容许直接按序号索引元素,可是插入元素要涉及数组元素移动等内存操做,因此索引查询数据快而插入数据慢,
Vector 中的方法因为添加了synchronized 修饰,所以Vector 是线程安全的容器,但性能上较ArrayList 差,所以已是Java 中的遗留容器。
LinkedList 使用双向链表
实现存储( 将内存中零散的内存单元经过附加的引用关联起来,造成一个能够按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比, 内存的利用率更高) ,按序号索引数据须要进行前向或后向遍历,可是插入数据时只须要记录本项的先后项便可,因此插入速度较快。
Vector 属于遗留容器(Java 早期的版本中提供的容器, 除此以外,Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),已经不推荐使用, 可是因为ArrayList和LinkedListed都是非线程安全的
, 若是遇到多个线程操做同一个容器的场景,则能够经过工具Collections 中的synchronizedList 方法将其转换成线程安全的容器后再使用( 这是对装潢模式的应用, 将已有对象传入另外一个类的构造器中建立新的对象来加强实现)。
Collection是一个接口, 它是Set、List 等容器的父接口。
Collections是个一个工具类,提供了一系列的静态方法来辅助容器操做,这些方法包括对容器的搜索、排序、线程安全化等等。Collections工具类的sort方法有两种重载的形式,第一种要求传入
的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较, 可是要求传入第二个参数, 参数是Comparator接口的子类型(须要重写compare 方法实现元素的比较),至关于一个临时定义的排序规则,其实就是经过接口注入比较元素大小的算法, 也是对回调模式的应用( Java 中对函数式编程的支持)。TreeSet要求存放的对象所属的类必须实现
Comparable
接口,该接口提供了比较元素的compareTo()
方法,当插入元素时会回调该方法比较元素的大小。
TreeMap要求存放的键值对映射的键必须实现Comparable 接口从而根据键对元素进行排序。
1)XML文档定义分为DTD和Schema两种形式,两者都是对XML语法的约束。
2)DTD和Schema两种形式的本质区别在于Schema自己也是一个XML文件,能够被XML 解析器解析,并且能够为XML 承载的数据定义类型,约束能力较之DTD更强大。
3)对XML的解析主要有DOM(文档对象模型,Document Object Model)、SAX( Simple API forXML)和StAX(JDK1.6 中引入的新的解析XML的方式,Streaming API for XML)。其中DOM处理大型文件时其性能降低的很是厉害,这个问题是由DOM 树结构占用的内存较多形成的,并且DOM 解析方式必须在解析文件以前把整个文档装入内存,适合对XML 的随机访问( 典型的用空间换取时间的策略);
SAX是事件驱动型的XML解析方式,它顺序读取XML 文件,不须要一次所有装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户经过事件回调代码来处理XML文件,适合对XML 的顺序访问;顾名思义,
StAX 把重点放在流上,实际上StAX与其余解析方式的本质区别就在于应用程序可以把XML做为一个事件流来处理。SAX 也是这样作的,但不一样之处在于StAX 容许应用程序代码把这些事件逐个拉出来,而不用提供在解析器方便时从解析器中接收事件的处理程序。
XML的主要做用有两个方面:数据交换和信息配置。
在作数据交换时,XML将数据用标签组装成起来, 而后压缩打包加密后经过网络传送给接收者,接收解密与解压缩后再从XML文件中还原相关信息进行处理,XML曾经是异构系统间交换数据的事实标准,但此项功能几乎已经被JSON( JavaScript Object Notation)取而代之。固然,目前不少软件仍然使用XML 来存储配置信息,咱们在不少项目中
一般也会将做为配置信息的硬代码写在XML 文件中,Java 的不少框架也是这么作的, 并且这些框架都选择了dom4j 做为处理XML 的工具,(由于Sun 公司的官方API 实在不怎么好用。)
//1. 加载JDBC驱动程序(加载MySQL驱动类) Class.forName("com.mysql.jdbc.Driver"); //2. 提供JDBC链接的URL来建立链接 //databaseName数据库名称,useUnicode=true:表示使用Unicode字符集,characterEncoding=UF-8字符编码方式utf-8, udrtnsmr和password是mysql链接用户名和密码 Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/databaseName?useUnicode=true&characterEncoding=UF-8;",username,password); // 也能够将mysql驱动写到DriverManager.getConnection()中 //3. PreparedStatement预编译sql String sql = "select * from dept where id = ? and name = ?"; PreparedStatement ps = con.prepareStatement(sql); ps.setInt(1, 10); ps.setInt(2, "研究员"); //4. 执行SQL语句 ResultSet rs = ps.executeQuery(); //5. 处理结果 while(rs.next()) { System.out.println(rs.getInt("id")); System.out.println(rs.getInt("name")); } //6. 在finally里面进行释放资源(关闭外部资源的顺序应该和打开的顺序相反) if(rs !=null){ rs.close; } if(ps != null){ ps.close(); } if(con != null) { con.close(); } 1234567891011121314151617181920212223242526272829
PreparedStatement性能更好
1)PreparedStatement接口表明预编译的语句,它主要的优点在于能够减小SQL 的编译错误并增长SQL的安全性(减小SQL 注射攻击的可能性)
2) PreparedStatement 中的SQL 语句是能够带参数的,避免了用字符串链接拼接SQL 语句的麻烦和不安全;
3)当批量处理SQL 或频繁执行相同的查询时,PreparedStatement 有明显的性能上的优点,因为数据库能够将编译优化后的SQL 语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。
Blob
是指二进制大对象(Binary Large Object)
Clob
是指大字符对象(Character Large Objec)
Blob是为存储大的二进制数据而设计的,而Clob 是为存储大的文本数据而设计的。JDBC 的PreparedStatement和
ResultSet都提供了相应的方法来支持Blob和Clob操做(流的操做)。
因为建立链接和释放链接都有很大的开销(尤为是数据库服务器不在本地时,每次创建链接都须要进行TCP的三次握手,释放链接须要进行四次挥手,形成很大的开销),为了提高系统访问数据库的性能,能够事先建立若干链接置于链接池中,须要时直接从链接池获取, 使用结束时归还链接池而没必要关闭链接,从而避免频繁建立和释放链接所形成的开销,这是典型的用空间换时间的策略(浪费了空间存储链接,但节省了建立和释放链接的时间)。
池化技术在Java 开发中是很常见的,在使用线程时建立线程池的道理与此相同。基于Java 的开源数据库链接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid
等。
其实大型网站性能优化的一个关键就是使用缓存,,而缓
存和链接池很是相似, 也是使用空间换时间的策略。能够将热点数据置于缓存中,当用户查询这些数据时能够直接从缓存中获得, 避免频繁的访问数据库形成大量的开销(如今主要用Redis
实现缓存)
DAO( Data Access Object)是一个为数据库或其余持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各类数据访问操做。在实际的开发中,应该将全部对数据源的访问操做进行抽象化后封装在一个公共API 中。
用程序设计语言来讲, 就是创建一个接口,接口中定义了此应用程序中将会用到的全部事务方法。在这个应用程序中,当须要和数据源进行交互的时候则使用这个接口,而且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO 模式实际上包含了两个模式,一是Data Accessor(数据访问器),二是Data Object(数据对象),前者要解决如何访问数据的问题,然后者要解决的是如何用对象封装数据。
1)原子性(Atomicity):事务是一个不可分割的工做单位,事务中的操做要么都发生,要么都不发生
2)一致性(Consistency):事务在完成后数据的完整性必须保持一致
3)隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其余用户的事务所干扰,多个并发事务之间的数据要相互隔离
4)持久性(Durability):一个事务一旦被提交,它对数据库中数据的改变应该是永久性的,即便数据库发生故障也不该该对其有任何影响若是整个事务执行过程当中,有任何一个地方出现异常/错误,那么都会进行事务回滚,回滚以后数据的状态将和事务执行以前彻底一致。
数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别, 数据库就会经过分析SQL语句而后为事务访问的资源加上合适的锁。
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义有五个表示隔离级别的常量(用于解决并发问题)通常状况下使用中间两种就行。
TransactionDefinition 接口事务隔离级别 | 描述 |
---|---|
READ_UNCOMMITTED | 该隔离级别表示一个事务能够读取另外一个事务修改但尚未提交的数据。该级别不能防止脏读,不可重复读和幻读,所以不多使用该隔离级别。 |
READ_COMMITTED | 系统默认值,该隔离级别表示一个事务只能读取另外一个事务已经提交的数据。能够防止脏读,是大多数状况下的推荐值。 |
REPEATABLE_READ | 该隔离级别表示一个事务在整个过程当中能够屡次重复执行某个查询,而且每次返回的记录都相同。该级别能够防止脏读和不可重复读。 |
SERIALIZABLE | 全部的事务依次逐个执行,事务之间就彻底不可能产生干扰。该级别能够防止脏读、不可重复读以及幻读。可是这将严重影响程序的性能。一般状况下也不会用到该级别。 |
隔离级别(√:容许出现 ×:不容许出现) | 脏读 | 不可重复读 | 幻读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ_UNCOMMITTED | √ | √ | √ | × | √ |
READ_COMMITTED | × | √ | √ | × | √ |
REPEATABLE_READ | × | × | √ | × | × |
SERIALIZABLE | × | × | × | × | × |
事务隔离级别和数据访问的并发性是对立的,事务隔离级别越高并发性就越差。因此要根据具体的应用来肯定合适的事务隔离级别,这个地方没有万能的原则。
首先要知道,只有存在并发数据访问时才须要事务(提交/回滚就结束事务),当多个事务访问同一数据时,可能会存在5类并发问题,包括3 类数据读取问题( 脏读、不可重复读和幻
读和2类数据更新问题(第1 类丢失更新和第2 类丢失更新)
3类数据读取问题1)脏读(Dirty Read): 一个事务读到了另外一个事务的尚未提交数据. (好比A事务读取B事务还没有提交的数据并在此基础上操做,而B事务执行回滚,那么A 读取到的数据就是脏数据。)(很严重的行为,必须处理,否则可能有很大的影响,好比转帐事务)
2)不可重复读(Unrepeatable Read): 一个事务中屡次读到的数据不一致,一个事务读到了另外一个事务修改后的数据。(不可重复读,保证数据修改后,不会出现两次同样的数据)
3) 幻读(虚读Phantom Read):一个事务读到了另外一个事务insert提交的数据。(好比事务A 从新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B 提交的行)(不可能出如今MySQL中,只会出如今Oracle中)两类丢失更新问题
1)第一类丢失更新:
事务A撤销时, 把已经提交的事务B的更新数据覆盖了(好比 取款事务A开启事务查询余额1000元,转帐事务B开启事务转帐100给A,A取出100,提交事务以后,再查询余额仍是1000元)
2)事务A覆盖事务 已经提交的数据,形成事务B 所作的操做丢失(好比:取款事务A和转帐B前后开启事务,前后查询余额都是1000元,取款事务A取出100,余额变成900,提交事务,可是此时转帐事务B存入100,将余额修改成1100元,提交事务,而后再查询账户余额就是1100,取款事务A的操做丢失)
JDBC如何进行事务处理:
Connection
提供了事务处理的方法,经过调用setAutoCommit(false)
能够设置手动提交事务。当事务完成后用commit()
显式提交事务;若是在事务处理过程当中发生异常则经过rollback()
进行事务回滚。除此以外, 从JDBC 3.0 中还引入了Savepoint( 保存点)的概念,容许经过代码设置保存点并让事务回滚到指定的保存点。
在编写处理字符串的程序时,常常会有查找符合某些复杂规则的字符串的须要。正则表达式就是用于描述这些规则的工具。换句话说, 正则表达式就是记录文本规则的代码。正则表达式就是在进行字符串匹配和处理的时候最为强大的工具,绝大多数语言都提供了对正则表达式的支。
Java中的String类提供了支持正则表达式操做的方法,包括:matches()、replaceAll()、replaceFirst()、split()
此外,Java 中能够用Pattern类表示正则表达式对象, 它提供了丰富的API 进行各类正则表达式操做。
获取class对象的三种方法:
1)每一个类经过class属性获取。【
类名.class
】
2) 每一个对象经过getClass()方法获取。【对象.getClass()
】
3)经过Class.forName(“类的完整名”)获取,须要捕获异常。【Class.forName("类的完整名")
】第一种方式,
类名.class
不会将类加载到内存,第三种Class.forName()
会将类加载到内存,对象.getClass()(建立对象了)会加载到内存中
经过反射建立对象的两种方法:
1)经过类对象(class)调用1newInstance()1方法,例如建立String对象:
String.class.newInstance()
2)经过类对象(class)的getConstructor()getDeclaredConstructor()
方法得到构造器(Constructor)对象并调用其newInstance()方法建立对象,例如:
·String.class.getConstructor(String.class).newInstance(“Hello”);·
类型 | 设计模式 |
---|---|
建立型 | 工厂方法模式(FactoryMethod)、抽象工厂模式(AbstractFactory)、建造者模式(Builder)、原型模式(Prototype)、单例模式(Singleton) |
结构型 | 适配器模式(Adapter)、桥接模式(Bridge)、组合模式(Composite)、装饰器模式(Decorator)、门面模式(Facade)、享元模式(Flyweight)、代理模式(Proxy) |
行为型 | 解释器模式(Interpreter)、模板方法模式(TemplateMethod)、责任链模式(ChainofResponsibility)、命令模式(Command)、迭代器模式(Iterator)、调解者模式(Mediator)、备忘录模式(Memento)、观察者模式(Observer)、状态模式(State)、策略模式(Strategy)、访问者模式(Visitor) |
单例模式:指一个类只有一个实例,且该类能自行建立这个实例的一种模式。(懒汉式、饿汉式写法)
原型模式:用一个已经建立的实例做为原型,经过复制该原型对象来建立一个和原型相同或类似的新对象。
策略模式:定义了一系列算法,并将每一个算法封装起来,使它们能够相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它经过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不一样的对象对这些算法进行管理。
工厂模式:定义一个建立产品对象的工厂接口,将产品对象的实际建立工做推迟到具体子工厂类当中。工厂类能够根据条件生成不一样的子类实例,这些子类有一个公共的抽象父类而且实现了相同的方法,可是这些方法针对不一样的数据进行了不一样的操做(多态方法)。当获得子类的实例后,开发人员能够调用基类中的方法而没必要考虑到底返回的是哪个子类的实例。
代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。实际开发中,按照使用目的的不一样,代理能够分为:远程代理、虚拟代理、保护代理、Cache 代理、防火墙代理、同步化代理、智能引用代理。
适配器模式:把一个类的接口变换成客户端所期待的另外一种接口,从而使本来因接口不匹配而没法在一块儿使用的类可以一块儿工做。
模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式实现,而后声明一些抽象方法来迫使子类实现剩余的逻辑。不一样的子类能够以不一样的方式实现这些抽象方法(多态实现),从而实现不一样的业务逻辑。
状态模式:对有状态的对象,把复杂的“判断逻辑”提取到不一样的状态对象中,容许状态对象在其内部状态发生改变时改变其行为。
装饰者模式:指在不改变现有对象结构的状况下,动态地给该对象增长一些职责(即增长其额外功能)的模式,它属于对象结构型模式。
了解其余的能够从这里面看:23种设计模式详解
原则 | 描述 |
---|---|
开闭原则(OCP) | 软件实体应当对扩展开放,对修改关闭 |
里氏替换原则(LSP) | 阐述了有关继承的一些原则(何时使用继承),里氏替换原是继承复用的基础,反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。能够这么理解:子类能够扩展父类的功能,但不能改变父类原有的功能,也就是子类继承父类时,除添加新的方法完成新增功能外,尽可能不要重写父类的方法。 |
依赖倒置原则(DIP) | 核心思想是:要面向接口编程,不要面向实现编程。(抽象层相对稳定,所以以抽象为基础搭建起来的架构要比以细节(实现类)为基础搭建起来的架构要稳定得多) |
单一职责原则(SRP) | 提出对象不该该承担太多职责。单一职责一样也适用于方法。一个方法应该尽量作好一件事情。若是一个方法处理的事情太多,其颗粒度会变得很粗,不利于重用。 |
接口隔离原则(ISP) | 要求程序员尽可能将臃肿庞大的接口拆分红更小的和更具体的接口,让接口中只包含客户感兴趣的方法。接口要小而专,毫不能大而全。 |
迪米特法则(LoD) | 又叫做最少知识原则(LKP),一个对象应当对其余对象有尽量少的了解。若是两个软件实体无须直接通讯,那么就不该当发生直接的相互调用,能够经过第三方转发该调用。其目的是下降类之间的耦合度,提升模块的相对独立性。 |
合成复用原则(CRP) | 叫组合/聚合复用原则(CARP)要求在软件复用时,要尽可能先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。 |
概述:
开闭原则是总纲,它告诉咱们要对扩展开放,对修改关闭;
里氏替换原则告诉咱们不要破坏继承体系;
依赖倒置原则告诉咱们要面向接口编程;
单一职责原则告诉咱们实现类要职责单一;
接口隔离原则告诉咱们在设计接口的时候要精简单一;
迪米特法则告诉咱们要下降耦合度;
合成复用原则告诉咱们要优先使用组合或者聚合关系复用,少用继承关系复用。
UML是统一建模语言( Unified Modeling Language)的缩写,它发表于1997年, 综合了当时已经存在的面向对象的建模语言、方法和过程, 是一个支持模型化和软件系统开发的图形化语言,为软件开发的全部阶段提供模型化和可视化支持。使用UML 能够帮助沟通与交流,辅助应用设计和文档的生成,还可以阐释系统的结构和行为。
经常使用UML图
用例图(use case diagram)、类图(class diagram)、时序图(sequencediagram)、协做图(collaboration diagram)、状态图(statechart diagram)、活动图(activity diagram)、构件图(component diagram)、部署图(deploymentdiagram)
用例图:用来捕获需求,描述系统的功能,经过该图能够迅速的了解系统的功能模块及其关系
类图:描述类以及类与类之间的关系,经过该图能够快速了解系统
时序图:描述执行特定任务时对象之间的交互关系以及执行顺序, 经过该图能够了解对象能接收的消息也就是说对象可以向外界提供的服务。
JDK1.8中,HashMap采用
位桶+链表+红黑树
实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减小了查找时间。
HashCode是jdk根据对象的地址或字符串或者数字利用hash算法计算出的int类型的数值。
HashMap实现原理:首先有一个每一个元素都是链表的数组,当添加一个元素(key-value)时,就首先计算元素key的hash值,以此肯定插入数组中的位置,可是可能存在同一hash值的元素已经被放在数组同一位置了,这时就添加到同一hash值的元素的后面,他们在数组的同一位置,可是造成了链表,同一各链表上的Hash值是相同的,因此说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提升了查找的效率。
在jdk8中,HashMap处理“碰撞”增长了红黑树这种数据结构,当碰撞结点较少时,采用链表存储,当较大时(>8个),采用红黑树(特色是查询时间是O(logn))存储(有一个阀值控制,大于阀值(8个),将链表存储转换成红黑树存储)红黑树是一种自平衡二叉查找树
红黑树是每一个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。
对于任何有效的红黑树都有如下要求:
1)节点是红色或黑色。
2)根节点是黑色。
3)每一个叶节点是黑色的。
4)每一个红色节点的两个子节点都是黑色。(从每一个叶子到根的全部路径上不能有两个连续的红色节点)
5从任一节点到其每一个叶子的全部路径都包含相同数目的黑色节点。
若是系统中存在临界资源(资源数量少于竞争资源的线程数量的资源), 例如正在写的数据之后可能被另外一个线程读到,或者正在读的数据可能已经被另外一个线程写过了,那么这些数据就必须进行同步存取(数据库操做中的排他锁就是最好的例子)。
当应用程序在对象上调用了一个须要花费很长时间来执行的方法,而且不但愿让程序等待方法的返回时, 就应该使用异步编程,在不少状况下采用异步途径每每更有效率。
事实上,所谓的同步就是指阻塞式操做, 而异步就是非阻塞式操做。(同步必须等待返回结果才能继续执行,异步就是浏览器发送请求,无论服务器是否返回结果,均可以继续执行)
Web容器加载Servlet并将其实例化后,Servlet生命周期开始,容器运行其init()方法进行Servlet的初始化;请求到达时调用Servlet的service方法,service方法会调用与请求对应的doGet或doPost等方法;当服务器关闭会项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的destroy方法。
1)get请求用来从服务器上得到资源,而post是用来向服务器提交数据
2)get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,而且二者使用“?”链接,而各个变量之间使用“&”链接;post是将表单中的数据放在HTML头部(header),传递到action所指向URL
3)get传输的数据要受到URL长度限制(1024字节);而post能够传输大量的数据,上传文件只能使用post方式
4)使用get时参数会显示在地址栏上,若是这些数据不是敏感数据,那么能够使用get;对于敏感数据仍是应用使用post
1)Web客户向Servlet容器发出Http请求;
2)Servlet容器解析Web客户的Http请求;
3)Servlet容器建立一个HttpRequest对象,在这个对象中封装Http请求信息;
4)Servlet容器建立一个HttpResponse对象;
5)Servlet容器调用HttpServlet的service方法,这个方法中会根据request的Method来判断具体是执行doGet仍是doPost,把HttpRequest和HttpResponse对象做为service方法的参数传给HttpServlet对象;
6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;
7)HttpServlet调用HttpResponse的有关方法,生成响应数据;
8)Servlet容器把HttpServlet的响应结果传给Web客户
Callable 接口相似于Runnable,可是Runnable 不会返回结果,而且没法抛出返回结果的异常,而Callable 功能更强大一些,被线程执行后,能够返回值,这个返回值能够被Future 拿到,也就是说,Future 能够拿到异步执行任务的返回值。能够认为是带有回调的Runnable。
Future 接口表示异步任务,是尚未完成的任务给出的将来结果。因此说Callable用于产生结果,Future 用于获取结果。
Math.round(11.5)的返回值是12, Math.round(-11.5)的返回值是-11。四舍五
入的原理是在参数上加0.5 而后进行下取整。
short s1 = 1; s1 = s1 + 1;
有错吗? short s1 = 1; s1 += 1;
有错吗?1)
short s1 = 1; s1 = s1 + 1;
编译错误。因为1 是int 类型,所以s1+1运算结果也是int型, 须要强制转换类型才能赋值给short 型。
2)short s1 = 1; s1 += 1;
编译正确,由于s1+= 1;至关于s1 = (short)(s1 + 1);其中有隐含的强制类型转换(自增加自动转换,除非越界)
<<
和>>
的概念)
2 << 3
(左移3 位至关于乘以2 的3 次方,右移3 位至关于除以2 的3 次方。关于运算符
<<
和>>
x >> n
就是 先将x转成二进制,不读后面的n位
x << n
就是 先将x转成二进制,往二进制数据后面加n个0案例:
int x = 16;
x >> 1
输出x=8(先将x转成二进制 10000, 不读最后一位0, 输出 1000, 转为10进制即为8)
x << 1
输出x=32(先将x转成二进制 10000,,往最后再读取一位0( 或根据是否已经有移位), 输出 100000, 转为10进制即为32)
数组没有length()方法,只有有length 的属性。String 有length()方法。可是在JavaScript中 ,得到字符串的长度是经过length 属性获得的(很是容易和Java搞混)
是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例做为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性能够在被调
用过程当中被改变,但对对象引用的改变是不会影响到调用者的。C++和C#中能够
经过传引用或传输出参数来改变传入的参数的值。在C#中能够编写以下所示的代
码, 可是在Java 中却作不到。
String str = new String(“xyz”);
建立了几个字符串对象?两个对象,一个是静态区(方法区常量池)的”xyz“字符串,一个是用new 建立在堆上的对象str。
Java中主要是字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。
在java.io 包中还有许多其余的流,主要是为了提升性能和使用方便
关于Java 的I/O 须要注意的有两点:1)两种对称性( 输入和输出的对称性,字节和字符的对称性)
2)两种设计模式(适配器模式和装潢模式)。
public static void fileCopy(String source, String target) throws IOException { try (InputStream in = new FileInputStream(source)) { try (OutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[4096]; int bytesToRead; while((bytesToRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } } } } 123456789101112
NIO方式
public static void fileCopyNIO(String source, String target) throws IOException { try (FileInputStream in = new FileInputStream(source)) { try (FileOutputStream out = new FileOutputStream(target)) { FileChannel inChannel = in.getChannel(); FileChannel outChannel = out.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(4096); while(inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); } } } } 12345678910111213141516
//统计这个字符串在这个文件中出现的次数 public static int countWordInFile(String filename, String word) { int counter = 0; //统计数 try (FileReader fr = new FileReader(filename)) { try (BufferedReader br = new BufferedReader(fr)) { String line = null; while ((line = br.readLine()) != null) { int index = -1; while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) { counter++; line = line.substring(index + word.length()); } } } } catch (Exception ex) { ex.printStackTrace(); } return counter; } 12345678910111213141516171819
public static void main(String[] args) { File f = new File("/Users/Downloads"); for(File temp : f.listFiles()) { if(temp.isFile()) { System.out.println(temp.getName()); } } } 12345678
2)对于当前文件夹下的文件夹继续展开显示全部文件
private static void queryDirectory(File f, int level) { if(f.isDirectory()) { for(File temp : f.listFiles()) { queryDirectory(temp, level + 1); //递归 } }else { for(int i = 0; i < level - 1; i++) { System.out.print("\t"); //\t四个空格 } System.out.println(f.getName()); //输出文件名 } } public static void main(String[] args) { queryDirectory(new File("/Users/Downloads"),0); } 12345678910111213141516
JDK1.7以后能够使用NIO.2的API实现
public static void main(String[] args) throws IOException { Path initPath = Paths.get("/Users/Downloads"); Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributesattrs) throws IOException { System.out.println(file.getFileName().toString()); return FileVisitResult.CONTINUE; } }); } 12345678910
import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; //socket,TCP编程,服务端程序 public class Server { public static void serverInfo(){ ServerSocket server = null; Socket client = null; PrintStream out = null; try { //在服务器8000端口等待客户链接 server = new ServerSocket(8000); System.out.println("服务器正在等待客户端的链接......"); //程序阻塞,等待客户端的链接 client = server.accept(); System.out.println("链接客户端成功!!"); //实例化打印流对象,用于向客户端发送输出信息 out = new PrintStream(client.getOutputStream()); System.out.println("请输入您要向客户端发送的信息:"); Scanner scan = new Scanner(System.in); //获取输入流 //准备向客户端发送的信息 String info = "服务端向客户端发送信息:" + scan.nextLine(); //输出信息 out.println(info); scan.close(); //关闭输入流 out.close(); //关闭输出打印流 client.close(); //关闭客户端 server.close(); //关闭服务器端的练级 } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { serverInfo();//开启服务端 } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546
客户端:
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; //socket编程,客户端,要与服务端的端口号一致 public class Client { public static void clientInfo(){ //声明socket对象 Socket client = null; try { //实例化socket对象,指定链接的主机名称和端口号 client = new Socket("localhost",8000); System.out.println("客户端链接成功"); //声明缓存字符流,用来接收信息 BufferedReader buf = new BufferedReader(new InputStreamReader(client.getInputStream())); //读取信息 String info = buf.readLine(); //输出读取的信息 System.out.println("客户端收到服务器("+ client.getInetAddress() +")端发来的信息:【" + info + "】"); client.close(); buf.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { clientInfo(); } } 123456789101112131415161718192021222324252627282930313233343536
运行:
一、 开启服务端Server
Server:服务器正在等待客户端的链接…
2.、开启客户端Client
Server:(输出内容)
服务器正在等待客户端的链接…
链接客户端成功!!
请输入您要向客户端发送的信息:
Client:(输出内容)
客户端链接成功三、Server输入内容HelloWorld
客户端Client接收到信息:
客户端收到服务器(localhost/127.0.0.1)端发来的信息:【服务端向客户端发送信息:Hello World!!!】
1) 将构造器私有,不容许外界经过构造器
建立对象;
2) 经过公开的静态方法向外界返回类的惟一实例
饿汉式单例模式:
//饿汉式 不延迟实例化的作法,直接静态实例化建立 保证线程安全 public class HungarySingleton { private static HungarySingleton instance=new HungarySingleton(); private HungarySingleton(){ } public static HungarySingleton getInstance(){ return instance;//返回惟一实例 } } 123456789
懒汉式单例模式:
public class LazySingleton { private static LazySingleton instance = null; private Singleton() {} public static synchronized LazySingleton getInstance(){ if (instance == null) instance = new LazySingleton (); return instance; } } 12345678
volatile关键字,DCL机制实现懒汉式,提升性能
//懒汉式 DCL机制 //T1先从主存拷贝数据到本身的线程内存,进行处理数据,处理完数据更新到主存 //T2从主存拷贝数据到本身的线程内存 public class LazySingleton { private static volatile LazySingleton instance = null; private LazySingleton(){ } //unlock happen-before lock 语义 //T1,T2 ---可见性 T1 happen-before T2 /** * A->B,B->C===A->C * T2调用getInstance()和调用getName() * T1调用getInstance() * a++ * T1 ->T2 * 构造器内存操做->T1 ->T2 */ public static LazySingleton getInstance(){ //T2 if(instance==null){ //T1 同步化 全部为null的只能进入一个,等待执行 synchronized(LazySingleton.class){ if(instance==null){ instance=new LazySingleton(); //JSR-133 //1.开辟空间 //2.初始化对象 //3.把地址给isntance变量---CPU } } } return instance; } } 1234567891011121314151617181920212223242526272829303132333435
能够作强制转换,可是Java中int 是32位(4byte)的,而byte 是8位(1byte)的,因此,若是强制转化是,int 类型的高24 位将会被丢弃,byte 类型的范围是从-128 到128。
java.lang.Cloneable 是一个标示性接口,不包含任何方法, clone 方法在object 类中定义。而且clone() 方法是一个本地方法,这意味着它是由c 或c++ 或其余本地语言实现。
不是线程安全的操做。它涉及到多个指令,如读取变量值,增长/减小,而后存储回内存,这个过程可能会出现多个线程交差。
a = a + b
与a += b
的区别
+=
隐式的将加操做的结果类型强制转换为持有结果的类型(好比a += b
会将运算结果a+b
转换为须要a对应的类型)。若是两这个整型相加,如byte、short 或者int,首先会将它们提高到int 类型,而后在执行加法操做,最后再转换为接收的数据的类型。
而a = a + b
不会进行类型转换,若是越界就抛出异常
// byte取值 -128到128,超过就越界要转换类型 byte a = 120; byte b = 120; a = a + b; //编译报错:cannot convert from int to byte,由于不会将结果(a+b)自动强制类型转换 a += b; //编译正确,b = -16 //隐式的将加操做的结果类型强制转换为持有结果的类型(a+b的时候隐式转换为int相加 //结果强制转换为接收结果a的类型byte,a+b=240(int类型)转换为a的类型byte=-16) 1234567
Integer包装类会占用更多的内存。Integer 是一个对象,须要存储对象的元数据。可是int 是一个原始类型的数据,因此占用的空间更少
若是a和b都是对象,则a==b 是比较两个对象的引用,只有当a和b指向的是堆中的同一个对象才会返回true,a.equals(b) 是进行逻辑比较,因此一般须要重写该方法来提供逻辑一致性的比较。例如,String 类重写equals() 方法, 因此能够用于两个不一样对象,可是包含的字母相同的比较
hashCode()方法是相应对象整型的hash值。它经常使用于基于hash 的集合类,如Hashtable、HashMap、LinkedHashMap 等等。它与equals() 方法关系特别紧密。根据Java 规范,两个使用equal() 方法来判断相等的对象,必须具备相同的hash code。
poll()方法和remove()方法的区别
poll() 和remove() 都是从队列中取出一个元素,可是poll() 在获取元素失败的时候会返回空,可是remove() 失败的时候会抛出异常。
LinkedHashMap 和PriorityQueue 的区别
PriorityQueue保证最高或者最低优先级的的元素老是在队列头部,可是LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个PriorityQueue时,没有任何顺序保证,可是LinkedHashMap 课保证遍历顺序是元素插入的顺序。
ArrayList 与LinkedList 的主要区别
ArrrayList 底层的数据结构是数组,支持随机访问,而
LinkedList的底层数据结构是链表(双向链表),不支持随机访问(可是优化了更新操做)。使用下标访问一个元素,ArrayList 的时间复杂度是O(1),而LinkedList 是O(n)。Hashtable 与HashMap 有什么不一样
1)Hashtable 是JDK 1 遗留下来的类,而HashMap 是后来增长的。
2)Hashtable 是同步的,比较慢,但HashMap 没有同步策略,因此会更快。
3)Hashtable不容许有个空的key,可是HashMap容许出现一个null key。Java中的HashSet,内部是如何工做
HashSet 的内部采用HashMap 来实现。因为Map 须要key 和value,因此全部key 的都有一个默认value。相似于HashMap, HashSet 不容许重复的key,只容许有一个null key,意思就是HashSet 中只容许存储一个null 对象。
ArrayList 和HashMap 的默认大小
ArrayList 的默认大小是10 个元素, HashMap的默认大小是16 个元素(必须是2的幂次)
两个相同的对象的hash code必定相同,两个对象有相同的hash Code,但两个对象不必定相同。
两个不相等的对象可能会有相同的hashcode 值, 这就是为何在hashmap 中会有冲突。相等hashcode 值的规定只是说若是两个对象相等, 必须有相同的hashcode 值, 可是没有关于不相等对象的任何规定
Java中的TreeMap是使用红黑树实现的。
本文做者:strive_day 本文连接:https://blog.csdn.net/qq_40542534/article/details/109241330
更多信息请关注公众号:「软件老王」,关注不迷路,软件老王和他的IT朋友们,分享一些他们的技术看法和生活故事。