目录css
https://www.cnblogs.com/ilren/p/9451616.htmlhtml
数据类型 | 字节数 | 二进制位数 | 范围 | 规律 |
---|---|---|---|---|
byte | 1 | 8 | -128~127 | -27~27-1 |
short | 2 | 16 | -32768~32767 | -215~215-1 |
int | 4 | 32 | -2147483648~2147483647 | -231~231-1 |
long | 8 | 64 | -9223372036854775808 ~ 9223372036854775807 | -263~263-1 |
float | 4 | 32 | 1.4E-45~3.4028235E38 | |
double | 8 | 64 | 4.9E-324~1.7976931348623157E308 | |
char | 2 | 16 | 0~65535 | 0~216-1 |
boolean | 1 | 8 | true或false | true或false |
Java虚拟机主要分为五大模块:类装载器子系统、运行时数据区、执行引擎、本地方法接口和垃圾收集模块。java
1.加载:经过类的全路径将这个类从外部加载到jvm中,同时在方法区生成该类的描述信息并在内存生成该类的Claas类型。做为方法区这个类的数据访问入口。变为字节流。node
2.验证:字节码格式验证,如对jvm是否有害,释放符合当前虚拟机的要求 ,是否符合规范,有无语法错误。mysql
3.准备:为类的静态变量分配内存并根据这个静态变量所属的数据类型进行初始化。web
4.解析:将符号引用替换成直接引用面试
5.初始化:当初始化一个类的时候,若是发现其父类尚未进行过初始化、则须要先出发其父类的初始化。 初始化就是把变量赋为默认值,把控件设为默认状态,把没准备的准备好。正则表达式
是一块较小内存空间
可看作当前线程执行的字节码的行号指示器,多线程执行时线程轮流切换时恢复到正确执行位置
线程私有
线程执行Java方法,记录虚拟机字节码指令地址,执行Native方法,计数器为空
惟一一个在java虚拟机规范中没有规定任何OutOfMemoryError区域算法
线程私有,生命周期与线程相同
虚拟机栈描述Java方法执行的内存模型,每一个方法执行都会建立一个栈帧,方法的调用和执行完成与该方法栈帧的入栈和出栈对应
栈帧是方法运行时的基础数据结构,用于存储局部变量表、操做数栈、动态连接、方法出口等信息
此区域包含两种异常:若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError;若是虚拟机能够动态扩展,若是扩展时没法申请到足够的内存,就会抛出OutOfMemoryErrorspring
与虚拟机栈所发挥做用类似,区别在于一个为虚拟机执行Java方法服务,一个为虚拟机使用到的Native方法服务
虚拟机规范中未规定具体实现,虚拟机能够自由实现(最流行的Sun HotSpot虚拟机将本地方法栈和虚拟机栈合二为一)
与虚拟机栈同样,抛出StackOverflowError和OutOfMemoryError异常
Java虚拟机管理的内存中最大一块
全部线程共享
虚拟机启动时建立。惟一目的就是存放对象的实例,几乎全部的对象实例都在这里分配内存
Java堆时垃圾收集器的主要区域
java堆能够处于物理不连续的内存空间中,逻辑上连续便可。实现时,既能够固定大小,也能够时可扩展的。不过当前主流虚拟机都是可扩展的(-Xmx -Xms)
若是在堆中没有完成实例分配,而且堆没法再扩展,将会抛出OutOfMemoryError
全部线程共享
用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
Java虚拟机规范将其描述为堆的一个逻辑部分,可是它又一个别名叫作Non-Heap
习惯HotSpot开发部署人员更愿意把方法区称为永久代(Permanent Generation),本质上并不等价,只是HotSpot团队选择把GC分代收集扩展至方法区,以使垃圾收集像管理堆同样管理这部份内存,省去编写方法区内存管理工做,其余虚拟机(J9)不存在永久代概念,实现方法区不受虚拟机规范约束
使用永久代由于经过-XX:MaxPermSize(jdk8已经取消)限制的上限,更容易内存溢出。极少数方法(如String.intern())会因这个致使在不一样虚拟机下有不一样表现。JDK1.7的HotSpot中,已经把本来放在永久代的字符串常量池移出
此区域内存回收主要针对常量池的回收和对类型的卸载。可是回收成绩老是不好,可是确实又是有必要的,不然会有内存泄漏隐患
方法区没法知足内存分布需求时,抛出OutOfMemoryError
方法区的一部分
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池,用于存放编译期生成的各类字面量和符号引用,这部份内容在类加载后进入方法区的运行时常量池中存放
Java虚拟机对于Class文件每一部分的格式都有严格规定,可是运行时常量池没有特殊要求。不过通常来讲,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存在其中
运行时常量池时方法区的一部分,受方法区内存的限制,没法申请到内存是抛出OutOfMemoryError
不属于虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,可是这部份内存也被频繁的使用,也可能致使OutOfMemoryError异常
JDK1.4中加入了NIO,经过Native函数分配堆外内存,而后经过一个存储在Java堆中的DirectbyteBuffer对象做为对这块内存的引用。
这块内存收到本机总内存的限制,设置虚拟机内存时,直接内存也不能够忽略
https://www.cnblogs.com/blogtech/p/10073560.html
https://blog.csdn.net/viola8104/article/details/95201321
原有的 IO 是面向流的、阻塞的,NIO 则是面向块的、非阻塞的。
java1.4之前的io模型,一链接对一个线程。
原始的IO是面向流的,不存在缓存的概念。Java IO面向流意味着每次从流中读一个或多个字节,直至读取全部字节,它们没有被缓存在任何地方。此外,它不能先后移动流中的数据。若是须要先后移动从流中读取的数据,须要先将它缓存到一个缓冲区
Java IO的各类流是阻塞的,这意味着当一个线程调用read或 write方法时,该线程被阻塞,直到有一些数据被读取,或数据彻底写入,该线程在此期间不能再干任何事情了。
NIO是面向缓冲区的。数据读取到一个它稍后处理的缓冲区,须要时可在缓冲区中先后移动,这就增长了处理过程当中的灵活性。
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,可是它仅能获得目前可用的数据,若是目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,因此直至数据变的能够读取以前,该线程能够继续作其余的事情。 非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不须要等待它彻底写入,这个线程同时能够去作别的事情。
通俗理解:NIO是能够作到用一个线程来处理多个操做的。假设有10000个请求过来,根据实际状况,能够分配50或者100个线程来处理。不像以前的阻塞IO那样,非得分配10000个。
在标准IO API中,你能够操做字节流和字符流,但在新IO中,你能够操做通道和缓冲,数据老是从通道被读取到缓冲中或者从缓冲写入到通道中。
NIO核心API Channel, Buffer, Selector
NIO的通道相似于流,但有些区别以下:
\1. 通道能够同时进行读写,而流只能读或者只能写
\2. 通道能够实现异步读写数据
\3. 通道能够从缓冲读数据,也能够写数据到缓冲:
缓冲区本质上是一个能够写入数据的内存块,而后能够再次读取,该对象提供了一组方法,能够更轻松地使用内存块,使用缓冲区读取和写入数据一般遵循如下四个步骤:
\1. 写数据到缓冲区;
\2. 调用buffer.flip()方法;
\3. 从缓冲区中读取数据;
\4. 调用buffer.clear()或buffer.compat()方法;
当向buffer写入数据时,buffer会记录下写了多少数据,一旦要读取数据,须要经过flip()方法将Buffer从写模式切换到读模式,在读模式下能够读取以前写入到buffer的全部数据,一旦读完了全部的数据,就须要清空缓冲区,让它能够再次被写入。
Buffer在与Channel交互时,须要一些标志:
buffer的大小/容量 - Capacity
做为一个内存块,Buffer有一个固定的大小值,用参数capacity表示。
当前读/写的位置 - Position
当写数据到缓冲时,position表示当前待写入的位置,position最大可为capacity – 1;当从缓冲读取数据时,position表示从当前位置读取。
信息末尾的位置 - limit
在写模式下,缓冲区的limit表示你最多能往Buffer里写多少数据; 写模式下,limit等于Buffer的capacity,意味着你还能从缓冲区获取多少数据。
一个组件,能够检测多个NIO channel,看看读或者写事件是否就绪。
多个Channel以事件的方式能够注册到同一个Selector,从而达到用一个线程处理多个请求成为可能。
所谓单例,就是整个程序有且仅有一个实例。该类负责建立本身的对象,同时确保只有一个对象被建立。在Java,通常经常使用在工具类的实现或建立对象须要消耗资源。
特色
public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
饿汉模式
线程安全,比较经常使用,但容易产生垃圾,由于一开始就初始化
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
双重锁模式
线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程状况下能保持高性能。
public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
双重检查模式,进行了两次的判断,第一次是为了不不要的实例,第二次是为了进行同步,避免多线程问题。因为singleton=new Singleton()
对象的建立在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile
修饰signleton
实例变量有效,解决该问题。
静态内部类单例模式
public class Singleton { private Singleton(){ } public static Singleton getInstance(){ return Inner.instance; } private static class Inner { private static final Singleton instance = new Singleton(); } }
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程能够得到对象的初始化锁,其余线程没法进行初始化,保证对象的惟一性。目前此方式是全部单例模式中最推荐的模式,但具体仍是根据项目选择。
java提供的动态执行机制,能够动态的加载类,动态建立对象,动态执行方法。
step1:javac(java编译器)将java文件编译生成*.class文件
step2:jvm在运行过程当中,根据class.forName("")中的来找到硬盘中的***.class文件(这个过程叫作类加载——classLoad)。加载并放到方法区中,这就是
为什么咱们在建立类的时候,类名要和文件名相同,否则jvm没法找到*.class文件。
step3:jvm根据***.class文件建立一个以cls命名的Class对象。这个对象能够通向方法区,咱们能够操做cls来得到类的全部信息。从而动态调用方法,建立对象,访问属性(甚至经过setAccessible来打开属性访问权限)。
java.lang.reflect可以获取java中class文件的字节码,而后将文件中的方法,变量和构造方法映射出来。
1信道
2信号量
3消息队列
4共享内存
5套接字
根本区别:进程是操做系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:每一个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程能够看作轻量级的进程,同一类线程共享代码和数据空间,每一个线程都有本身独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操做系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(经过CPU调度,在每一个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每一个进程分配不一样的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:没有线程的进程能够看作是单线程的,若是一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,因此线程也被称为轻权进程或者轻量级进程。
数组不是面向对象的,存在明显的缺陷,集合弥补了数组的缺点,比数组更灵活更实用,并且不一样的集合框架类可适用不一样场合。以下:
1:数组能存放基本数据类型和对象,而集合类存放的都是对象的引用,而非对象自己!
2:数组容易固定没法动态改变,集合类容量动态改变。
3:数组没法判断其中实际存有多少元素,length只告诉了数组的容量,而集合的size()能够确切知道元素的个数
4:集合有多种实现方式和不一样适用场合,不像数组仅采用顺序表方式
5:集合以类的形式存在,具备封装、继承、多态等类的特性,经过简单的方法和属性便可实现各类复杂操做,大大提升了软件的开发效率
https://www.cnblogs.com/chenglc/p/8073049.html
Set:接口 ---实现类: HashSet、LinkedHashSet
Set的子接口SortedSet接口---实现类:TreeSet
List:接口---实现类: LinkedList,Vector,ArrayList
有序列表,容许存放重复的元素;
实现类:
ArrayList:数组实现,查询快,增删慢,轻量级;(线程不安全)
LinkedList:双向链表实现,增删快,查询慢 (线程不安全)
Vector:数组实现,重量级 (线程安全、使用少)
ArrayList
底层是Object数组,因此ArrayList具备数组的查询速度快的优势以及增删速度慢的缺点。而在LinkedList的底层是一种双向循环链表。在此链表上每个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,造成一个循环。双向循环链表的查询效率低可是增删效率高。ArrayList和LinkedList在用法上没有区别,可是在功能上仍是有区别的。
LinkedList
LinkedList是采用双向循环链表实现的。利用LinkedList实现栈(stack)、队列(queue)、双向队列(double-ended queue )。它具备方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。常常用在增删操做较多而查询操做不多的状况下:
队列和堆栈
队列:先进先出的数据结构。
栈:后进先出的数据结构。
注意:使用栈的时候必定不能提供方法让不是最后一个元素的元素得到出栈的机会。
Vector
(与ArrayList类似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)结论:在考虑并发的状况下用Vector(保证线程的安全)。在不考虑并发的状况下用ArrayList(不能保证线程的安全)。
ArrayList | Vector | |
---|---|---|
初始化策略 | 用懒加载策略,第一次add时才初始化内部数组,默认初始化大小为10。 | 产生对象时就初始化内部数组,默认大小为10。 |
扩容策略 | 扩容为原数组的1.5倍 | 扩容为原数组的2倍 |
线程安全问题 | 采用异步处理线程不安全,性能较高 | 采用synchronized修饰增删查改方法,线程安全,性能较低(锁粒度太粗,将当前集合对象锁住,读读互斥) |
其余 | JDK内置的Stack是Vector的子类 | |
使用场景 | 在大部分场合都是适合使用的,常适用于频繁查找、在集合末端插入与删除元素 | 不建议使用 |
LinkedList采用异步处理,线程不安全,频繁在任意位置的插入与删除考虑使用,LinkedList是Queue接口的经常使用子类。
扩展Collection接口
无序集合,不容许存放重复的元素;容许使用null元素
对 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的实现
Set—》hashSet linkedHashSet
SortedSet —》 TreeSet
HashSet 的后台有一个HashMap;初始化后台容量;只不过生成一个HashSet的话,系统只提供key的访问; 若是有两个Key重复,那么会覆盖以前的;
实现类 :
HashSet:equals返回true,hashCode返回相同的整数;哈希表;存储的数据是无序的。
LinkedHashSet:此实现与 HashSet 的不一样以外在于,后者维护着一个运行于全部条目的双重连接列表。存储的数据是有序的。
TreeSet:有序的。
HashSet类直接实现了Set接口, 其底层实际上是包装了一个HashMap去实现的。HashSet采用HashCode算法来存取集合中的元素,所以具备比较好的读取和查找性能。
HashSet经常使用方法:
public boolean contains(Object o) :若是set包含指定元素,返回true
public Iterator iterator()返回set中元素的迭代器
public Object[] toArray() :返回包含set中全部元素的数组public Object[] toArray(Object[] a) :返回包含set中全部元素的数组,返回数组的运行时类型是指定数组的运行时类型
public boolean add(Object o) :若是set中不存在指定元素,则向set加入
public boolean remove(Object o) :若是set中存在指定元素,则从set中删除
public boolean removeAll(Collection c) :若是set包含指定集合,则从set中删除指定集合的全部元素
public boolean containsAll(Collection c) :若是set包含指定集合的全部元素,返回true。若是指定集合也是一个set,只有是当前set的子集时,方法返回true
实现Set接口的HashSet,依靠HashMap来实现的。
咱们应该为要存放到散列表的各个对象定义hashCode()和equals()。
前面说过,Set集合是不容许重复元素的,不然将会引起各类奇怪的问题。那么HashSet如何判断元素重复呢?
HashSet须要同时经过equals和HashCode来判断两个元素是否相等,具体规则是,若是两个元素经过equals为true,而且两个元素的hashCode相等,则这两个元素相等(即重复)。
因此若是要重写保存在HashSet中的对象的equals方法,也要重写hashCode方法,重写先后hashCode返回的结果相等(即保证保存在同一个位置)。全部参与计算 hashCode() 返回值的关键属性,都应该用于做为 equals() 比较的标准。
试想若是重写了equals方法但不重写hashCode方法,即相同equals结果的两个对象将会被HashSet看成两个元素保存起来,这与咱们设计HashSet的初衷不符(元素不重复)。另外若是两个元素哈市Code相等但equals结果不为true,HashSet会将这两个元素保存在同一个位置,并将超过一个的元素以链表方式保存,这将影响HashSet的效率。若是重写了equals方法但没有重写hashCode方法,则HashSet可能没法正常工做。
如何达到不能存在重复元素的目的?
“键”就是咱们要存入的对象,“值”则是一个常量。这样能够确保,咱们所须要的存储的信息
之是“键”。而“键”在Map中是不能重复的,这就保证了咱们存入Set中的全部的元素都不重复。
HashSet如何过滤重复元素
调用元素HashCode得到哈希码--》判断哈希码是否相等,不相等则录入
---》相等则判断equals()后是否相等,不相等在进行 hashcode录入,相等不录入
LinkedHashSet的特征
LinkedHashSet是HashSet的一个子类,LinkedHashSet也根据HashCode的值来决定元素的存储位置,但同时它还用一个链表来维护元素的插入顺序,插入的时候即要计算hashCode又要维护链表,而遍历的时候只须要按链表来访问元素。查看LinkedHashSet的源码发现它是样的,
TreeSet实现了SortedSet接口,顾名思义这是一种排序的Set集合,查看jdk源码发现底层是用TreeMap实现的,本质上是一个红黑树原理。 正由于它是排序了的,因此相对HashSet来讲,TreeSet提供了一些额外的按排序位置访问元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
TreeSet的排序分两种类型,一种是天然排序,另外一种是定制排序。
TreeSet 会调用compareTo方法比较元素大小,而后按升序排序。因此天然排序中的元素对象,都必须实现了Comparable接口,不然会跑出异常。对于TreeSet判断元素是否重复的标准,也是调用元素从Comparable接口继承而来额compareTo方法,若是返回0则是重复元素(两个元素I相等)。Java的常见类都已经实现了Comparable接口,下面举例说明没有实现Comparable存入TreeSet时引起异常的状况。
还有个重要问题是,由于TreeSet会调用元素的compareTo方法,这就要求全部元素的类型都相同,不然也会发生异常。也就是说,TreeSet只容许存入同一类的元素。例以下面这个例子就会抛出类型转换异常
TreeSet还有一种排序就是定制排序,定制排序时候,须要关联一个 Comparator对象,由Comparator提供排序逻辑。
TreeSet是依靠TreeMap来实现的。
TreeSet是一个有序集合,TreeSet中元素将按照升序排列,缺省是按照天然顺序进行排列,意味着TreeSet中元素要实现Comparable接口
咱们能够在构造TreeSet对象时,传递实现了Comparator接口的比较器对象。
Comparable和Comparator
Comparable 接口以提供天然排序顺序。
对于那些没有天然顺序的类、或者当您想要一个不一样于天然顺序的顺序时,您能够实现
Comparator 接口来定义您本身的排序函数。能够将Comparator传递给Collections.sort或Arrays.sort。
Comparator接口
当一个类并未实现Comparable,或者不喜欢缺省的Comaparable行为。能够实现Comparator接口
直接实现Comparator的compare接口完成自定义比较类。
几种Set的比较:
HashSet外部无序地遍历成员。
成员可为任意Object子类的对象,但若是覆盖了equals方法,同
时注意修改hashCode方法。
TreeSet外部有序地遍历成员;
附加实现了SortedSet, 支持子集等要求顺序的操做
成员要求实现Comparable接口,或者使用Comparator构造
TreeSet。成员通常为同一类型。
LinkedHashSet外部按成员的插入顺序遍历成员
成员与HashSet成员相似
HashSet是基于Hash算法实现的,其性能一般都优于TreeSet。咱们一般都应该使用HashSet,在咱们须要排序的功能时,咱们才使用TreeSet。
HashSet的元素存放顺序和咱们添加进去时候的顺序没有任何关系,而LinkedHashSet 则保持元素的添加顺序。TreeSet则是对咱们的Set中的元素进行排序存放。
通常来讲,当您要从集合中以有序的方式抽取元素时,TreeSet 实现就会有用处。为了能顺利进行,添加到 TreeSet 的元素必须是可排序的。 而您一样须要对添加到TreeSet中的类对象实现 Comparable 接口的支持。通常说来,先把元素添加到 HashSet,再把集合转换为 TreeSet 来进行有序遍历会更快。
集合框架的第二类接口树。
它提供了一组键值的映射。其中存储的每一个对象都有一个相应的关键字(key),关键字决定了对象在Map中的存储位置。
关键字应该是惟一的,每一个key 只能映射一个value。
实现类:
HashMap、TreeMap、LinkedHashMap、Hashtable等
HashMap:键值对,key不能重复,可是value能够重复;key的实现就是HashSet;value对应着放;容许null的键或值;
Hashtable:线程安全的,不容许null的键或值;
Properties::key和value都是String类型,用来读配置文件;
TreeMap:对key排好序的Map; key 就是TreeSet, value对应每一个key; key要实现Comparable接口或TreeMap有本身的构造器;
LinkedHashMap: 此实现与 HashMap 的不一样之处在于,后者维护着一个运行于全部条目的双重连接列表。存储的数
据是有序的。
HashMap:
Map 主要用于存储键(key)值(value)对,根据键获得值,所以键不容许重复,但容许值重复。
HashMap 是一个最经常使用的Map,它根据键的HashCode 值存储数据,根据键能够直接获取它的值,具备很快的访问速度。
HashMap最多只容许一条记录的键为Null;容许多条记录的值为 Null;
HashMap不支持线程的同步,即任一时刻能够有多个线程同时写HashMap;可能会致使数据的不一致。若是须要同步,能够用 Collections的synchronizedMap方法使HashMap具备同步的能力。
HashMap实现原理---散列
Hash哈希算法的意义在于提供了一种快速存取数据的方法,它用一种算法创建键值与真实值之间的对应关系。散列表又称为哈希表。散列表算法的基本思想是:以结点的关键字为自变量,经过必定的函数关系(散列函数)计算出对应的函数值,以这个值做为该结点存储在散列表中地址。
当散列表中的元素存放太满,就必须进行再散列,将产生一个新的散列表,全部元素存放到新的散列表中,原先的散列表将被删除。在Java语言中,经过负载因子(load factor)来决定什么时候对散列表进行再散列。例如:若是负载因子0.75,当散列表中已经有75%位置已经放满,那么将进行再散列。
负载因子越高(越接近1.0),内存的使用效率越高,元素的寻找时间越长。负载因子越低(越接近0.0),元素的寻找时间越短,内存浪费越多。
什么时候需重写equals?
当一个类有本身特有的“逻辑相等”概念(不一样于对象身份的概念);
Object类仅仅提供了一个对引用的比较,若是两个引用不是同一个那就返回false,这是没法知足大多数对象比较的须要的,因此要覆盖;
使用==操做符检查实参是否为指向对象的引用”
使用instanceof操做符检查实参是否为正确的类型
把实参转换到正确的类型;
对于该类中每个“关键”域,检查实参中的域与当前对象中对应的域值是否匹 配。对于既不是float也不是double类型的基本类型的域,能够使用==操做符 进行比较;对于对象引用类型的域,能够递归地调用所引用的对象的equals方法,对于float和double类型的域,先转换成int或long类型的值,而后使用==操做符比较;
当你编写完成了equals方法以后,应该问本身三个问题:它是不是对称的、传 递的、一致的? 若是答案是否认的,那么请找到 这些特性未能知足的缘由,再修改equals方法的代码
equals()和hashCode()同时覆写
尤为强调当一个对象被看成键值(或索引)来使用的时候要重写这两个方法;
覆写equals后,两个不一样实例可能在逻辑上相等,可是根据Object.hashCode方法却产生不一样的散列码,违反“相等的对象必须具备相等的散列码”。
致使,当你用其中的一个做为键保存到hashMap、hasoTable或hashSet中,再以“相等的”找另 一个做为键值去查找他们的时候,则根本找不到
不一样类型的hashCode取值
若是该域是布尔型的,计算(f?0:1)
若是是char,short,byte或int,计算(int)f
若是是long类型,计算(int)(f^(f>>>32))
若是是float类型,计算Float.floatToIntBits(f)
若是是double类型,计算Dobule.doubleToLongBits(f)
若是该域是一个对象引用,递归调用hashCode
若是该域是一个数组,则把每一个元素当作单独的域来处理,对每一个重要的元素计算一个散列码,
Map集合比较:
HashMap的存入顺序和输出顺序无关。
LinkedHashMap 则保留了键值对的存入顺序。
TreeMap则是对Map中的元素进行排序。
由于HashMap和LinkedHashMap 存储数据的速度比直接使用TreeMap 要快,存取效率要高。
当完成了全部的元素的存放后,咱们再对整个的Map中的元素进行排序。这样能够提升整个程序的运行的效率,缩短执行时间。
注意:TreeMap中是根据键(Key)进行排序的。而若是咱们要使用TreeMap来进行正常的排序的话,Key 中存放的对象必须实现Comparable 接口。
Map经常使用方法:
Object put(Object key,Object value):用来存放一个键-值对Map中
Object remove(Object key):根据key(键),移除键-值对,并将值返回
void putAll(Map mapping) :将另一个Map中的元素存入当前的Map中
void clear() :清空当前Map中的元素
Object get(Object key) :根据key(键)取得对应的值
boolean containsKey(Object key) :判断Map中是否存在某键(key)
boolean containsValue(Object value):判断Map中是否存在某值(value)
public Set keySet() :返回全部的键(key),并使用Set容器存放
public Collection values() :返回全部的值(Value),并使用Collection存放
public Set entrySet() :返回一个实现 Map.Entry 接口的元素 Set
集合遍历
一、加强for循环 for(Obj o:c){syso(o)}
二、使用iterator , Iterator it=c.iterator;
while(it.hasNext()){Object o = it.next()}
三、普通循环:for(Iterator it=c.iterator();it.hasNext();){it.next() }
1.ArrayList: 元素单个,效率高,多用于查询
2.Vector: 元素单个,线程安全,多用于查询
3.LinkedList:元素单个,多用于插入和删除
4.HashMap: 元素成对,元素可为空
5.HashTable: 元素成对,线程安全,元素不可为空
HashMap | Hashtable | |
---|---|---|
同步性 | 线程不安全 | 线程安全 |
扩容 | old*2(初始16) | old*2 +1(初始11) |
值 | 可为空 | 不可为空 |
HashMap和Hashtable的区别:
HashMap和Hashtable都是java的集合类,均可以用来存放java对象,这是他们的相同点
如下是他们的区别:
1.历史缘由:
Hashtable是基于陈旧的Dictionary类的,HashMap是java 1.2引进的Map接口的一个现实。
2.同步性:
Hashtable是同步的,这个类中的一些方法保证了Hashtable中的对象是线程安全的,而HashMap则是异步的,所以HashMap中的对象并非线程安全的,由于同步的要求会影响执行的效率,因此若是你不须要线程安全的结合那么使用HashMap是一个很好的选择,这样能够避免因为同步带来的没必要要的性能开销,从而提升效率,咱们通常所编写的程序都是异步的,但若是是服务器端的代码除外。
3.值:
HashMap可让你将空值做为一个表的条目的key或value
Hashtable是不能放入空值(null)的
ArrayList和Vector的区别:
ArrayList与Vector都是java的集合类,都是用来存放java对象,这是他们的相同点,
区别:
1.同步性:
Vector是同步的,这个类的一些方法保证了Vector中的对象的线程安全的,而ArrayList则是异步的,所以ArrayList中的对象并不 是线程安全的,由于同步要求会影响执行的效率,因此你不须要线程安全的集合那么使用ArrayList是一个很好的选择,这样能够避免因为同步带来的没必要 要的性能开销。
2.数据增加:
从内部实现的机制来说,ArrayList和Vector都是使用数组(Array)来控制集合中的对象,当你向两种类型中增长元素的时候,若是元素的数目超过了内部数组目前的长度他们都须要扩展内部数组的长度,Vector缺省状况下自动增加原来一倍的数组长度,ArrayList是原来的50%,因此最后你得到的这个集合所占的空间老是比你实际须要的要大,因此若是你要在集合中保存大量的数据,那么使用Vector有一些优点,由于你能够经过设置集合的初始大小来避免没必要要的资源开销。
总结:
1)若是要求线程安全,使用Vector,Hashtable
2)若是不要求线程安全,使用ArrayList,LinkedList,HashMap
3)若是要求键值对,则使用HashMap,Hashtable
4)若是数据量很大,又要求线程安全考虑Vector
arraylist和linkedlist联系与区别
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList以为优于LinkedList,由于LinkedList要移动指针。
3.对于新增和删除操做add和remove,LinedList比较占优点,由于ArrayList要移动数据。 这一点要看实际状况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但如果批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 由于ArrayList每插入一条数据,要移动插入点及以后的全部数据。
HashMap与TreeMap联系与区别一、 HashMap经过hashcode对其内容进行快速查找,而TreeMap中全部的元素都保持着某种固定的顺序,若是你须要获得一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
二、在Map 中插入、删除和定位元素,HashMap是最好的选择。但若是您要按天然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明肯定义了hashCode()和 equals()的实现。
两个map中的元素同样,但顺序不同,致使hashCode()不同。
一样作测试:
在HashMap中,一样的值的map,顺序不一样,equals时,false;
而在treeMap中,一样的值的map,顺序不一样,equals时,true,说明,treeMap在equals()时是整理了顺序了的。
栈 | |
---|---|
Stack() | 定义一个空栈 |
empty() | 栈空返回真,不然返回假 |
peek() | 获取栈顶值,不出栈 |
pop() | 栈顶值出栈 |
push() | 入栈 |
search(Object o) | 回对象O在栈中的位置(以1开始) |
队列 | |
add() | 入队(若失败则抛出IllegalStateException异常) |
offer() | 将指定元素插入队列,成功返回true,不然返回false |
element() | 获取队头的值,但不出队(若队列为空则抛出异常NoSuchElementException) |
peek() | 获取队头的值,但不出队(若队列为空则返回null) |
poll() | 获取并移除队头(若队列空则返回null) |
remove() | 获取并移除队头(若队列空则抛出NoSuchElementException异常) |
前序遍历:
1.访问根节点
2.前序遍历左子树
3.前序遍历右子树
中序遍历:
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
后序遍历:
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点s
1.new
2.反射
3.clone
4.反序列化
1.public
2.default
3.protect
4.private
//Date类型转为String Date date = new Date(); //设置字符串格式 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); String dateString = simpleDateFormat.format(date); //String类型转为Date String dateString = "2018-10-25"; //注意String的格式要与simpleDateFormat设置的格式相同! SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = simpleDateFormat.parse(dateString);
查找二进制中1的个数。
public class Solution { public int NumberOf1(int n) { int t=0; char[]ch=Integer.toBinaryString(n).toCharArray(); for(int i=0;i<ch.length;i++){ if(ch[i]=='1'){ t++; } } return t; } }
import java.util.Scanner; public class number { public static void main(String[] args) { Scanner sc = new Scanner(System.in); System.out.println("请输入一个二进制数: "); String temp = sc.nextLine(); int two = Integer.parseInt(temp, 2); // 2进制 int eight = Integer.parseInt(temp, 8); // 8进制 System.out.println("二进制转为十进制: " + two); System.out.println("八进制转为十进制: " + eight); } } 结果: 请输入一个二进制数: 1001 二进制转为十进制: 9 八进制转为十进制: 513
// String 转int String str = “123”; int a = Integer.parseInt(str); // int 转 String int b = 1; String str = String.valueOf(b);
System.out.println(Integer.toBinaryString(9^3));//输出二进制时9与3不一样的位数 结果: 1010
1,打开windows--->Preferences---->java---->Editor---->content assist; 2.在右边的框中 Auto-Activation框中有一个Auto activation triggers for Java:选项,在默认的状况下框中只有一个点,在框中"."的后面输入"abcdefghijklmnopqrstuvwxyz ",而后点击apply而后点击OK; 3.这样输入每个字母都有语法提示
输出:7 -> 0 -> 8
缘由:342 + 465 = 807
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */ class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode head = new ListNode(0); ListNode result = head; int temp = 0; while (l1 != null || l2 != null) { int val1 = l1 == null ? 0 : l1.val; int val2 = l2 == null ? 0 : l2.val; int val = val1 + val2 + temp; temp = val / 10; val = val % 10; result.next = new ListNode(val); result = result.next; if (l1 != null) l1 = l1.next; if (l2 != null) l2 = l2.next; } if (temp > 0) { result.next = new ListNode(temp); } return head.next; } }
class Solution { public int lengthOfLongestSubstring(String s) { if (s.isEmpty()){ return 0; }else{ int max=1; int sum=1; String s1=""+s.charAt(0); if(s.length()==1){ return 1; } for (int i = 1; i < s.length(); i++) { if(s1.indexOf(""+s.charAt(i))!=-1){//判断是否有重复的 int n1=s1.length()-s1.indexOf(""+s.charAt(i))-1; sum=n1+1; s1=s1.substring(s1.indexOf(""+s.charAt(i))+1,s1.length())+s.charAt(i); }else{ sum=sum+1; s1=s1+s.charAt(i); } if(sum>max){ max=sum; } } return max; } } }
'.' 匹配任意单个字符 '*' 匹配零个或多个前面的那一个元素 class Solution { public boolean isMatch(String s, String p) { if(s==null&&p==null){ return true; } if(s==null&&p!=null||p==null&&s!=null){ return false; } if (s.equals(p)) { return true; } return s.matches(p);//秀死我了 } }
public class Solution { public String replaceSpace(StringBuffer str) { String s=new String(str); s=s.replaceAll(" ","%20"); return s; } }
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode FindKthToTail(ListNode list,int k) { if (list == null) return list; ListNode node = list; int count = 0; while (node != null) { count++; node = node.next; } if (count < k) return null; ListNode p = list; for (int i = 0; i < count - k; i++) { p = p.next; } return p; } }
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode ReverseList(ListNode head) { if(head==null) return null; ListNode newHead = null; ListNode pNode = head; ListNode pPrev = null; while(pNode!=null){ ListNode pNext = pNode.next; if(pNext==null) newHead = pNode; pNode.next = pPrev; pPrev = pNode; pNode = pNext; } return newHead; } }
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode Merge(ListNode list1,ListNode list2) { ListNode newList =new ListNode(0); if(list1==null&&list2==null) return null; else if(list1==null) return list2; else if(list2==null) return list1; else { if(list1.val<list2.val){ newList=list1; list1=list1.next; }else{ newList=list2; list2=list2.next; } newList.next=Merge(list1,list2); return newList; } } }
Iterator | ListIterator |
---|---|
hasNext | add(E e) |
next() | hasNext() |
remove() | hasPrevious() |
next() | |
nextIndex() | |
previous() | |
previousIndex() | |
remove() | |
set(E e) |
Iterator迭代器包含的方法:
hasNext():若是迭代器指向位置后面还有元素,则返回 true,不然返回false
next():返回集合中Iterator指向位置后面的元素
remove():删除集合中Iterator指向位置后面的元素
ListIterator迭代器包含的方法:
add(E e):将指定的元素插入列表,插入位置为迭代器当前位置以前
hasNext():以正向遍历列表时,若是列表迭代器后面还有元素,则返回 true,不然返回false
hasPrevious():若是以逆向遍历列表,列表迭代器前面还有元素,则返回 true,不然返回false
next():返回列表中ListIterator指向位置后面的元素
nextIndex():返回列表中ListIterator所需位置后面元素的索引
previous():返回列表中ListIterator指向位置前面的元素
previousIndex():返回列表中ListIterator所需位置前面元素的索引
remove():从列表中删除next()或previous()返回的最后一个元素(有点拗口,意思就是对迭代器使用hasNext()方法时,删除ListIterator指向位置后面的元素;当对迭代器使用hasPrevious()方法时,删 除ListIterator指向位置前面的元素)
set(E e):从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改成指定元素e
Iterator 和 ListIterator 的不一样点
1.使用范围不一样,Iterator能够应用于全部的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。
2.ListIterator有add方法,能够向List中添加对象,而Iterator不能。
3.ListIterator和Iterator都有hasNext()和next()方法,能够实现顺序向后遍 历,可是ListIterator有hasPrevious()和previous()方法,能够实现逆向(顺序向前)遍历。Iterator不能够。
4.ListIterator能够定位当前索引的位置,nextIndex()和previousIndex()能够实现。Iterator没有此功能。
5.均可实现删除操做,可是ListIterator能够实现对象的修改,set()方法能够实现。Iterator仅能遍历,不能修改。
1.n个节点的二叉树一共有((2n)!)/(n! * (n+1)!)种
2.n层二叉树的第n层最多为2^(n-1)个
3.二叉树节点计算公式 N = n0+n1+n2,度为0的叶子节点比度为2的节点数多一个。N=1n1+2n2+1
4.对任何一棵二叉树T,若是其终端节点数为n0,度为2的节点数为n2,则n0=n2+1
5.具备n个节点的彻底二叉树的深度为log2(n) + 1
也就是当你看到heap相关的时候就确定是堆栈溢出了,此时若是代码没有问题的状况下,适当调整-Xmx和-Xms是能够避免的,不过必定是代码没有问题的前提,为何会溢出呢,要么代码有问题,要么访问量太多而且每一个访问的时间太长或者数据太多,致使数据释放不掉,由于垃圾回收器是要找到那些是垃圾才能回收,这里它不会认为这些东西是垃圾,天然不会去回收了;主意这个溢出以前,可能系统会提早先报错关键字为:
这种状况是当系统处于高频的GC状态,并且回收的效果依然不佳的状况,就会开始报这个错误,这种状况通常是产生了不少不能够被释放的对象,有多是引用使用不当致使,或申请大对象致使,可是java heap space的内存溢出有可能提早不会报这个错误,也就是可能内存就直接不够致使,而不是高频GC.
关键信息为:
java.lang.OutOfMemoryError: PermGen space
缘由:系统的代码很是多或引用的第三方包很是多、或代码中使用了大量的常量、或经过intern注入常量、或者经过动态代码加载等方法,致使常量池的膨胀,虽然JDK 1.5之后能够经过设置对永久带进行回收,可是咱们但愿的是这个地方是不作GC的,它够用就行,因此通常状况下今年少作相似的操做,因此在面对这种状况经常使用的手段是:增长-XX:PermSize和-XX:MaxPermSize的大小。
溢出关键字:
java.lang.OutOfMemoryError: Direct buffer memory
若是你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不作clear的时候就会出现相似的问题,常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,若是常规的应用程序你要将一个文件的内容输出到客户端须要经过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),而后再输出到直接内存由操做系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存能够直接由应用程序本身控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存,因此要注意了哦。
若是常常有相似的操做,能够考虑设置参数:-XX:MaxDirectMemorySize
溢出关键字:
java.lang.StackOverflowError
这个参数直接说明一个内容,就是-Xss过小了,咱们申请不少局部调用的栈针等内容是存放在用户当前所持有的线程中的,线程在jdk 1.4之前默认是256K,1.5之后是1M,若是报这个错,只能说明-Xss设置得过小,固然有些厂商的JVM不是这个参数,本文仅仅针对Hotspot VM而已;不过在有必要的状况下能够对系统作一些优化,使得-Xss的值是可用的。
溢出关键字:
java.lang.OutOfMemoryError: unable to create new native thread
上面第四种溢出错误,已经说明了线程的内存空间,其实线程基本只占用heap之外的内存区域,也就是这个错误说明除了heap之外的区域,没法为线程分配一块内存区域了,这个要么是内存自己就不够,要么heap的空间设置得太大了,致使了剩余的内存已经很少了,而因为线程自己要占用内存,因此就不够用了,说明了缘由,如何去修改,不用我多说,你懂的。
溢出关键字
java.lang.OutOfMemoryError: request {} byte for {}out of swap
这类错误通常是因为地址空间不够而致使。
六大类常见溢出已经说明JVM中99%的溢出状况,要逃出这些溢出状况很是困难,除非一些很怪异的故障问题会发生,好比因为物理内存的硬件问题,致使了code cache的错误(在由byte code转换为native code的过程当中出现,可是几率极低),这种状况内存 会被直接crash掉,相似还有swap的频繁交互在部分系统中会致使系统直接被crash掉,OS地址空间不够的话,系统根本没法启动,呵呵;JNI的滥用也会致使一些本地内存没法释放的问题,因此尽可能避开JNI;socket链接数据打开过多的socket也会报相似:IOException: Too many open files等错误信息。
是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;好比申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
是指程序在申请内存后,没法释放已申请的内存空间,一次内存泄露危害能够忽略,但内存泄露堆积后果很严重,不管多少内存,早晚会被占光。
memory leak会最终会致使out of memory!
6 0 000 0110
-6 1 000 0110 1 111 1001 1 111 1010
权值分别为从19,21,2,3,6,7,10,32的结点,构造一棵哈夫曼树,该树的带权路径长度是?
参数名 | 意义 |
---|---|
corePoolSize | 核心池的大小 |
maximumPoolSize | 池中容许的最大线程数,这个参数表示了线程池中最多能建立的线程数量 |
keepAliveTime | 当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。若是allowCoreThreadTimeout设置为true,则全部线程均会退出直到线程数量为0。 |
allowCoreThreadTimeout | 是否容许核心线程空闲退出,默认值为false。 |
ThreadFactory threadFactory | 执行程序建立新线程时使用的工厂 |
RejectedExecutionHandler handler | 因为超出线程范围和队列容量而使执行被阻塞时所使用的处理程序;ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,可是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,而后从新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 |
allowCoreThreadTimeout | 是否容许核心线程空闲退出,默认值为false。 |
SynchronousQueue | SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素。SynchronousQueue阻塞队列通常要求maximumPoolSizes为无界,避免线程拒绝执行操做 |
LinkedBlockingQueue | LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(因此在使用此阻塞队列时maximumPoolSizes就至关于无效了),每一个线程彻底独立于其余线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的状况下能够并行操做队列中的数据。 |
ArrayBlockingQueue | ArrayBlockingQueue是一个有界缓存等待队列,能够指定缓存队列的大小,当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中等待有空闲的线程时继续执行,当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSizes时,再有新的元素尝试加入ArrayBlockingQueue时会报错。 |
PriorityBlockingQueue | 和ArrayBlockingQueue同样内部使用数组实现,插入和获取数据使用同一把锁。不一样的是,无论有无指定长度,都会实现能够实现自动扩容;在构造函数需传入comparator,用于插入元素时继续排序,若没有传入comparator,则插入的元素必须实现Comparatable接口。 |
unit | 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性TimeUnit.DAYS;TimeUnit.HOURS;TimeUnit.MINUTES;TimeUnit.SECONDS; TimeUnit.MILLISECONDS;TimeUnit.MICROSECONDS;TimeUnit.NANOSECONDS; |
largestPoolSize | 用来记录线程池中曾经出现过的最大线程数 |
completedTaskCount | 用来记录已经执行完毕的任务个数 |
一、池中线程数小于corePoolSize,新任务都不排队而是直接添加新线程
二、池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程
三、池中线程数大于等于corePoolSize,workQueue已满,可是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务
四、池中线程数大于大于corePoolSize,workQueue已满,而且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务
volatile int runState; static final int RUNNING=0; static final int SHUTDOWN=1; static final int STOP=2; static final int TERMINATED3;
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
下面的几个static final变量表示runState可能的几个取值。
当建立线程池后,初始时,线程池处于RUNNING状态;
若是调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不可以接受新的任务,它会等待全部任务执行完毕;
若是调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,而且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,而且全部工做线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
定义: 即一个操做或者多个操做 要么所有执行而且执行的过程不会被任何因素打断,要么就都不执行。
原子性是拒绝多线程操做的,不管是多核仍是单核,具备原子性的量,同一时刻只能有一个线程来对它进行操做。简而言之,在整个操做过程当中不会被线程调度器中断的操做,均可认为是原子性。例如 a=1是原子性操做,可是a++和a +=1就不是原子性操做。Java中的原子性操做包括:
a. 基本类型的读取和赋值操做,且赋值必须是数字赋值给变量,变量之间的相互赋值不是原子性操做。
b.全部引用reference的赋值操做
c.java.concurrent.Atomic.* 包中全部类的一切操做
定义:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看获得修改的值。
在多线程环境下,一个线程对共享变量的操做对其余线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会当即被更新到主内存中,其余线程读取共享变量时,会直接从主内存中读取。固然,synchronize和Lock均可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁而后执行同步代码,而且在释放锁以前会将对变量的修改刷新到主存当中。所以能够保证可见性。
定义:即程序执行的顺序按照代码的前后顺序执行。
Java内存模型中的有序性能够总结为:若是在本线程内观察,全部操做都是有序的;若是在一个线程中观察另外一个线程,全部操做都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工做内存主主内存同步延迟”现象。
在Java内存模型中,为了效率是容许编译器和处理器对指令进行重排序,固然重排序不会影响单线程的运行结果,可是对多线程会有影响。Java提供volatile来保证必定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,能够经过synchronized和Lock来保证有序性,synchronized和Lock保证每一个时刻是有一个线程执行同步代码,至关因而让线程顺序执行同步代码,天然就保证了有序性。
二者区别:
1.首先synchronized是java内置关键字,在jvm层面,Lock是个接口;
2.synchronized没法判断是否获取锁的状态,Lock能够判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程当中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),不然容易形成线程死锁;
4.用synchronized关键字的两个线程1和线程2,若是当前线程1得到锁,线程2线程等待。若是线程1阻塞,线程2则会一直等待下去,而Lock锁就不必定会等待下去,若是尝试获取不到锁,线程能够不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(二者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少许的同步问题。
https://www.cnblogs.com/iyyy/p/7993788.html
1.volatile关键字解决的是变量在多个线程之间的可见性;而sychronized关键字解决的是多个线程之间访问共享资源的同步性。
2.volatile只能用于修饰变量,而synchronized能够修饰方法,以及代码块。(volatile是线程同步的轻量级实现,因此volatile性能比synchronized要好,而且随着JDK新版本的发布,sychronized关键字在执行上获得很大的提高,在开发中使用synchronized关键字的比率仍是比较大)
3.多线程访问volatile不会发生阻塞,而sychronized会出现阻塞。
4.volatile能保证变量在多个线程之间的可见性,但不能保证原子性;而sychronized能够保证原子性,也能够间接保证可见性,由于它会将私有内存和公有内存中的数据作同步。
5.线程安全包含原子性和可见性两个方面。
对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工做内存的值是最新的。
一句话说明volatile的做用:实现变量在多个线程之间的可见性。
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
3)Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;
4)经过Lock能够知道有没有成功获取锁,而synchronized却没法办到。
5)Lock能够提升多个线程进行读操做的效率(读写锁)。
在性能上来讲,若是竞争资源不激烈,二者的性能是差很少的,而当竞争资源很是激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。因此说,在具体使用时要根据适当状况选择。
多线程以及多进程改善了系统资源的利用率并提升了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。
死锁是指两个或两个以上的进程(线程)在运行过程当中因争夺资源而形成的一种僵局(Deadly-Embrace) ) ,若无外力做用,这些进程(线程)都将没法向前推动。
关于死锁的一些结论:
参与死锁的进程数至少为两个
参与死锁的全部进程均等待资源
参与死锁的进程至少有两个已经占有资源
死锁进程是系统中当前进程集合的一个子集
死锁会浪费大量系统资源,甚至致使系统崩溃。
饥饿(Starvation)指一个进程一直得不到资源。
死锁和饥饿都是因为进程竞争资源而引发的。饥饿通常不占有资源,死锁进程必定占有资源。
竞争不可抢占资源引发死锁
竞争可消耗资源引发死锁
进程推动顺序不当引发死锁
互斥条件:
进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时如有其余进程请求该资源,则请求进程只能等待。
不可剥夺条件:
进程所得到的资源在未使用完毕以前,不能被其余进程强行夺走,即只能由得到该资源的进程本身来释放(只能是主动释放)。
请求与保持条件:
进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其余进程占有,此时请求进程被阻塞,但对本身已得到的资源保持不放。
循环等待条件:
存在一种进程资源的循环等待链,链中每个进程已得到的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有
预防死锁:经过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的发生。
避免死锁:在资源的动态分配过程当中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生。
检测死锁:容许系统在运行过程当中发生死锁,但可设置检测机构及时检测死锁的发生,并采起适当措施加以清除。
解除死锁:当检测出死锁后,便采起适当措施将进程从死锁状态中解脱出来。
1.可重入锁
若是锁具有可重入性,则称做为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上代表了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,好比说method1,而在method1中会调用另一个synchronized方法method2,此时线程没必要从新去申请锁,而是能够直接执行方法method2。
class MyClass { public synchronized void method1() { method2(); } public synchronized void method2() { } }
上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而因为method2也是synchronized方法,假如synchronized不具有可重入性,此时线程A须要从新申请锁。可是这就会形成一个问题,由于线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而因为synchronized和Lock都具有可重入性,因此不会发生上述现象。
2.可中断锁
可中断锁:顾名思义,就是能够相应中断的锁。在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
若是某一线程A正在执行锁中的代码,另外一线程B正在等待获取该锁,可能因为等待时间过长,线程B不想等待了,想先处理其余事情,咱们可让它中断本身或者在别的线程中中断它,这种就是可中断锁。lockInterruptibly()方法。
3.公平锁
公平锁即尽可能以请求锁的顺序来获取锁。好比同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最早请求的线程)会得到该所,这种就是公平锁。
非公平锁即没法保证锁的获取是按照请求锁的顺序进行的。这样就可能致使某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它没法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认状况下是非公平锁,可是能够设置为公平锁。
咱们能够在建立ReentrantLock对象时,经过如下方式来设置锁的公平性:
ReentrantLock lock = new ReentrantLock(true);
若是参数为true表示为公平锁,为fasle为非公平锁。默认状况下,若是使用无参构造器,则是非公平锁。
另外在ReentrantLock类中定义了不少方法,好比:
1 isFair() //判断锁是不是公平锁 2 isLocked() //判断锁是否被任何线程获取了 3 isHeldByCurrentThread() //判断锁是否被当前线程获取了 4 hasQueuedThreads() //判断是否有线程在等待该锁
读操做不会发生冲突。
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。能够经过readLock()获取读锁,经过writeLock()获取写锁。
CAS 适合简单对象的操做,好比布尔值、整型值等;
CAS 适合冲突较少的状况,若是太多线程在同时自旋,那么长时间循环会致使 CPU 开销很大;
// 仍挑选以库存数做为乐观锁 //step1: 查询出商品信息 select (inventory) from items where id=100; //step2: 根据商品信息生成订单 insert into orders(id,item_id) values(null,100); //step3: 修改商品的库存 update items set inventory=inventory-1 where id=100 and inventory-1>0;
fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程当中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。fail-fast机制并不保证在不一样步的修改下必定会抛出异常,它只是尽最大努力去抛出,因此这种机制通常仅用于检测bug。在调用 next() 和 remove()时,都会执行 checkForComodification()。若 modCount 不等于 expectedModCount,
则抛出ConcurrentModificationException异常,产生fail-fast事件。 因此,若是想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,由于它的remove()方法不只会删除元素,还会维护一个标志,用来记录目前是否是可删除状态,例如,你不能连续两次调用它的remove()方法,调用以前至少有一次next()方法的调用。 采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程当中被修改),迭代器的快速失败行为应该仅用于检测程序错误。java.util.concurrent包下的容器都是安全失败,能够在多线程下并发使用,并发修改。CopyOnWriteArrayList中写操做须要大面积复制数组,因此性能确定不好,可是读操做由于操做的对象和写操做不是同一个对象,读之间也不须要加锁,读和写之间的同步处理只是在写完后经过一个简单的“=”将引用指向新的数组对象上来,这个几乎不须要时间,这样读操做就很快很安全,适合在多线程里使用,绝对不会发生ConcurrentModificationException,因此最后得出结论:CopyOnWriteArrayList适合使用在读操做远远大于写操做的场景里,好比缓存。
并发:不一样的代码块交替执行
并行:不一样的代码块同时执行
并发就是指代码逻辑上能够并行,有并行的潜力,可是不必定当前是真的以物理并行的方式运行。并发指的是代码的性质,并行指的是物理运行状态。
新建(NEW):新建立了一个线程对象。
可运行(RUNNABLE):线程对象建立后,其余线程(好比main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
运行(RUNNING):可运行状态(runnable)的线程得到了cpu 时间片(timeslice) ,执行程序代码。
阻塞(BLOCKED):阻塞状态是指线程由于某种缘由放弃了cpu 使用权,也即让出了cpu timeslice,暂时中止运行。直到线程进入可运行(runnable)状态,才有机会再次得到cpu timeslice 转到运行(running)状态。阻塞的状况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
(三). 其余阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程从新转入可运行(runnable)状态。
死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
//继承Thread class MyThread extends Thread{ //重写run方法 @Override public void run() { //任务内容.... System.out.println("当前线程是:"+Thread.currentThread().getName()); } } Thread thread = new MyThread(); //线程启动 thread.start();
好处:避免单继承
//实现Runnable接口 class MyTask implements Runnable{ //重写run方法 public void run() { //任务内容.... System.out.println("当前线程是:"+Thread.currentThread().getName()); } }
好处:减小处理器单元的闲置时间,增长吞吐量。
request 对象是 javax.servlet.httpServletRequest类型的对象。 该对象表明了客户端的请求信息,主要用于接受经过HTTP协议传送到服务器的数据。(包括头信息、系统信息、请求方式以及请求参数等)。request对象的做用域为一次请求。
response 表明的是对客户端的响应,主要是将JSP容器处理过的对象传回到客户端。response对象也具备做用域,它只在JSP页面内有效。
session 对象是由服务器自动建立的与用户请求相关的对象。服务器为每一个用户都生成一个session对象,用于保存该用户的信息,跟踪用户的操做状态。session对象内部使用Map类来保存数据,所以保存数据的格式为 “Key/value”。 session对象的value能够使复杂的对象类型,而不只仅局限于字符串类型。
application 对象可将信息保存在服务器中,直到服务器关闭,不然application对象中保存的信息会在整个应用中都有效。与session对象相比,application对象生命周期更长,相似于系统的“全局变量”。
out 对象用于在Web浏览器内输出信息,而且管理应用服务器上的输出缓冲区。在使用 out 对象输出数据时,能够对数据缓冲区进行操做,及时清除缓冲区中的残余数据,为其余的输出让出缓冲空间。待数据输出完毕后,要及时关闭输出流。
pageContext 对象的做用是取得任何范围的参数,经过它能够获取 JSP页面的out、request、reponse、session、application 等对象。pageContext对象的建立和初始化都是由容器来完成的,在JSP页面中能够直接使用 pageContext对象。
config 对象的主要做用是取得服务器的配置信息。经过 pageConext对象的 getServletConfig() 方法能够获取一个config对象。当一个Servlet 初始化时,容器把某些信息经过 config对象传递给这个 Servlet。 开发者能够在web.xml 文件中为应用程序环境中的Servlet程序和JSP页面提供初始化参数。
page 对象表明JSP自己,只有在JSP页面内才是合法的。 page隐含对象本质上包含当前 Servlet接口引用的变量,相似于Java编程中的 this 指针。
exception 对象的做用是显示异常信息,只有在包含 isErrorPage="true" 的页面中才能够被使用,在通常的JSP页面中使用该对象将没法编译JSP文件。excepation对象和Java的全部对象同样,都具备系统提供的继承结构。exception 对象几乎定义了全部异常状况。在Java程序中,能够使用try/catch关键字来处理异常状况; 若是在JSP页面中出现没有捕获到的异常,就会生成 exception 对象,并把 exception 对象传送到在page指令中设定的错误页面中,而后在错误页面中处理相应的 exception 对象。
# Get和post
GET在浏览器回退时是无害的,而POST会再次提交请求。
GET产生的URL地址能够被Bookmark,而POST不能够。
GET请求会被浏览器主动cache,而POST不会,除非手动设置。
GET请求只能进行url编码,而POST支持多种编码方式。
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
GET请求在URL中传送的参数是有长度限制的,而POST么有。
对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
GET比POST更不安全,由于参数直接暴露在URL上,因此不能用来传递敏感信息。
GET参数经过传递,POST放在Request body中。
世间万物均可以当作一个对象。每一个物体包括动态的行为和静态的属性,这些就构成了 一个对象。
类是对象的抽象,对象是类的具体,类是对象的模板,对象是类的实例
显示转换就是类型强转,把一个大类型的数据强制赋值给小类型的数据;隐式转换就是大范围的变量可以接受小范围的数据;隐式转换和显式转换其实就是自动类型转换和强制类型转换。
Java.lang
Java.io
Java.sql
Java.util
Java.awt
Java.net
Java.math
Equals
Hashcode
toString
wait
notify
clone
getClass
理论上说,java都是引用传递,对于基本数据类型,传递是值的副本,而不是值自己。对于对象类型,传递是对象的引用,当在一个方法操做操做参数的时候,其实操做的是引用所指向的对象。
Pow():幂运算
Sqrt():平方根
Round():四舍五入
Abs():求绝对值
Random():生成一个0-1的随机数,包括0不包括1
接口中声明全是public static final修饰的常量
接口中全部方法都是抽象方法
接口是没有构造方法的
接口也不能直接实例化
接口能够多继承
抽象类有构造方法,接口没有构造方法
抽象类只能单继承,接口能够多继承
抽象类能够有普通方法,接口中的全部方法都是抽象方法
接口的属性都是public static final修饰的,而抽象的不是
继承一个异常类,一般是RumtimeException或者Exception
Error和Exception都是java错误处理机制的一部分,都继承了Throwable类。
Exception表示的异常,异常能够经过程序来捕捉,或者优化程序来避免。
Error表示的是系统错误,不能经过程序来进行错误处理。
性质不一样
final为关键字;
finalize()为方法;
finally为区块标志,用于try语句中;
做用
final为用于标识常量的关键字,final标识的关键字存储在常量池中(在这里final常量的具体用法将在下面进行介绍);
finalize()方法在Object中进行了定义,用于在对象“消失”时,由JVM进行调用用于对对象进行垃圾回收,相似于C++中的析构函数;用户自定义时,用于释放对象占用的资源(好比进行I/0操做);
finally{}用于标识代码块,与try{}进行配合,不论try中的代码执行完或没有执行完(这里指有异常),该代码块之中的程序一定会进行;
抽象类:
抽象方法,只有行为的概念,没有具体的行为实现。使用abstract关键字修饰,没有方法体。子类必须重写这些抽象方法。
包含抽象方法的类,必定是抽象类。
抽象类只能被继承,一个类只能继承一个抽象类。
接口:
所有的方法都是抽象方法,属型都是常量
不能实例化,能够定义变量。
接口变量能够引用具体实现类的实例
接口只能被实现,一个具体类实现接口,必须实现所有的抽象方法
接口之间能够多实现
一个具体类能够实现多个接口,实现多继承现象
&是位运算符。&&是布尔逻辑运算符,在进行逻辑判断时用&处理的前面为false后面的内容仍需处理,用&&处理的前面为false再也不处理后面的内容。
· JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
· JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。
具体来讲 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了不少 Java 程序调试和分析的工具。简单来讲:若是你须要运行 Java 程序,只需安装 JRE 就能够了,若是你须要编写 Java 程序,须要安装 JDK。
== 解读
对于基本类型和引用类型 == 的做用效果是不一样的,以下所示:
· 基本类型:比较的是值是否相同;
· 引用类型:比较的是引用是否相同;
代码示例:
1. String x = "string"; 2. String y = "string"; 3. String z = new String("string"); 4. System.out.println(x==y); // true 5. System.out.println(x==z); // false 6. System.out.println(x.equals(y)); // true 7. System.out.println(x.equals(z)); // true
代码解读:由于 x 和 y 指向的是同一个引用,因此 == 也是 true,而 new String()方法则重写开辟了内存空间,因此 == 结果为 false,而 equals 比较的一直是值,因此结果都为 true。
equals 解读
equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了。
首先来看默认状况下 equals 比较一个有相同值的对象,代码以下:
1. class Cat { 2. public Cat(String name) { 3. this.name = name; 4. } 5. 6. private String name; 7. 8. public String getName() { 9. return name; 10. } 11. 12. public void setName(String name) { 13. this.name = name; 14. } 15.} 16. 17.Cat c1 = new Cat("王磊"); 18.Cat c2 = new Cat("王磊"); 19.System.out.println(c1.equals(c2)); // false
输出结果出乎咱们的意料,居然是 false?这是怎么回事,看了 equals 源码就知道了,源码以下:
1. public boolean equals(Object obj) { 2. return (this == obj); 3. }
原来 equals 本质上就是 ==。
那问题来了,两个相同值的 String 对象,为何返回的是 true?代码以下:
1. String s1 = new String("老王"); 2. String s2 = new String("老王"); 3. System.out.println(s1.equals(s2)); // true
一样的,当咱们进入 String 的 equals 方法,找到了答案,代码以下:
1. public boolean equals(Object anObject) { 2. if (this == anObject) { 3. return true; 4. } 5. if (anObject instanceof String) { 6. String anotherString = (String)anObject; 7. int n = value.length; 8. if (n == anotherString.value.length) { 9. char v1[] = value; 10. char v2[] = anotherString.value; 11. int i = 0; 12. while (n-- != 0) { 13. if (v1[i] != v2[i]) 14. return false; 15. i++; 16. } 17. return true; 18. } 19. } 20. return false; 21.}
原来是 String 重写了 Object 的 equals 方法,把引用比较改为了值比较。
总结 :== 对于基本类型来讲是值比较,对于引用类型来讲是比较的是引用;而 equals 默认状况下是引用比较,只是不少类从新了 equals 方法,好比 String、Integer 等把它变成了值比较,因此通常状况下 equals 比较的是值是否相等。
不对,两个对象的 hashCode() 相同,equals() 不必定 true。
代码示例:
1. String str1 = "通话"; 2. String str2 = "重地"; 3. System. out. println(String. format("str1:%d | str2:%d", str1. hashCode(),str2. hashCode())); 4. System. out. println(str1. equals(str2));
执行的结果:
1. str1:1179395 | str2:1179395 2. 3. false
代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,由于在散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不必定能得出键值对相等。
· final 修饰的类叫最终类,该类不能被继承。
· final 修饰的方法不能被重写。
· final 修饰的变量叫常量,常量必须初始化,初始化以后值就不能被修改。
等于 -1,由于在数轴上取值时,中间值(0.5)向右取整,因此正 0.5 是往上取整,负 0.5 是直接舍弃。
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、long、double,而 String 属于对象。
操做字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操做都会生成新的 String 对象,而后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 能够在原有对象的基础上进行操做,因此在常常改变字符串内容的状况下最好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,因此在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
\8. String str="i"与 String str=new String("i")同样吗?
不同,由于内存的分配方式不同。String str="i"的方式,Java 虚拟机会将其分配到常量池中;而 String str=new String("i") 则会被分到堆内存中。
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代码:
1. // StringBuffer reverse 2. StringBuffer stringBuffer = new StringBuffer(); 3. stringBuffer. append("abcdefg"); 4. System. out. println(stringBuffer. reverse()); // gfedcba 5. // StringBuilder reverse 6. StringBuilder stringBuilder = new StringBuilder(); 7. stringBuilder. append("abcdefg"); 8. System. out. println(stringBuilder. reverse()); // gfedcba
· indexOf():返回指定字符的索引。
· charAt():返回指定索引处的字符。
· replace():字符串替换。
· trim():去除字符串两端空白。
· split():分割字符串,返回一个分割后的字符串数组。
· getBytes():返回字符串的 byte 类型数组。
· length():返回字符串长度。
· toLowerCase():将字符串转成小写字母。
· toUpperCase():将字符串转成大写字符。
· substring():截取字符串。
· equals():字符串比较。
不须要,抽象类不必定非要有抽象方法。
示例代码:
1. abstract class Cat { 2. public static void sayHi() { 3. System. out. println("hi~"); 4. } 5. }
上面代码,抽象类并无抽象方法但彻底能够正常运行。
· 普通类不能包含抽象方法,抽象类能够包含抽象方法。
· 抽象类不能直接实例化,普通类能够直接实例化。
不能,定义抽象类就是让其余类继承的,若是定义为 final 该类就不能被继承,这样彼此就会产生矛盾,因此 final 不能修饰抽象类,以下图所示,编辑器也会提示错误信息:
· 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
· 构造函数:抽象类能够有构造函数;接口不能有。
· 实现数量:类能够实现不少个接口;可是只能继承一个抽象类。
· 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法能够是任意访问修饰符。
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
· BIO:Block IO 同步阻塞式 IO,就是咱们日常使用的传统 IO,它的特色是模式简单使用方便,并发处理能力低。
· NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端经过 Channel(通道)通信,实现了多路复用。
· AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操做基于事件和回调机制。
· Files. exists():检测文件路径是否存在。
· Files. createFile():建立文件。
· Files. createDirectory():建立文件夹。
· Files. delete():删除一个文件或目录。
· Files. copy():复制文件。
· Files. move():移动文件。
· Files. size():查看文件个数。
· Files. read():读取文件。
· Files. write():写入文件。
· Collection 是一个集合接口,它提供了对集合对象进行基本操做的通用接口方法,全部集合都是它的子类,好比 List、Set 等。
· Collections 是一个包装类,包含了不少静态方法,不能被实例化,就像一个工具类,好比提供的排序方法: Collections. sort(list)。
List、Set、Map 的区别主要体如今两个方面:元素是否有序、是否容许元素重复。
· 存储:HashMap 容许 key 和 value 为 null,而 Hashtable 不容许。
· 线程安全:Hashtable 是线程安全的,而 HashMap 是非线程安全的。
· 推荐使用:在 Hashtable 的类注释能够看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,若是须要多线程使用则用 ConcurrentHashMap 替代。
对于在 Map 中插入、删除、定位一个元素这类操做,HashMap 是最好的选择,由于相对而言 HashMap 的插入会更快,但若是你要对一个 key 集合进行有序的遍历,那 TreeMap 是更好的选择。
HashMap 基于 Hash 算法实现的,咱们经过 put(key,value)存储,get(key)来获取。当传入 key 时,HashMap 会根据 key. hashCode() 计算出 hash 值,根据 hash 值将 value 保存在 bucket 里。当计算出的 hash 值相同时,咱们称之为 hash 冲突,HashMap 的作法是用链表和红黑树存储相同 hash 值的 value。当 hash 冲突的个数比较少时,使用链表不然使用红黑树。
HashSet 是基于 HashMap 实现的,HashSet 底层使用 HashMap 来保存全部元素,所以 HashSet 的实现比较简单,相关 HashSet 的操做,基本上都是直接调用底层 HashMap 的相关方法来完成,HashSet 不容许重复的值。
· 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
· 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,由于 LinkedList 是线性的数据存储方式,因此须要移动指针从前日后依次查找。
· 增长和删除效率:在非首尾的增长和删除操做,LinkedList 要比 ArrayList 效率要高,由于 ArrayList 增删操做要影响数组内的其余数据的下标。
综合来讲,在须要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操做较多时,更推荐使用 LinkedList。
· 数组转 List:使用 Arrays. asList(array) 进行转换。
· List 转数组:使用 List 自带的 toArray() 方法。
代码示例:
1. // list to array 2. List<String> list = new ArrayList<String>(); 3. list. add("王磊"); 4. list. add("的博客"); 5. list. toArray(); 6. // array to list 7. String[] array = new String[]{"王磊","的博客"}; 8. Arrays. asList(array);
· 线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而 ArrayList 是非线程安全的。
· 性能:ArrayList 在性能方面要优于 Vector。
· 扩容:ArrayList 和 Vector 都会根据实际的须要动态的调整容量,只不过在 Vector 扩容每次会增长 1 倍,而 ArrayList 只会增长 50%。
· Array 能够存储基本数据类型和对象,ArrayList 只能存储对象。
· Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
· Array 内置方法没有 ArrayList 多,好比 addAll、removeAll、iteration 等方法只有 ArrayList 有。
· 相同点:都是返回第一个元素,并在队列中删除返回的对象。
· 不一样点:若是没有元素 poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
代码示例:
1. Queue<String> queue = new LinkedList<String>(); 2. queue. offer("string"); // add 3. System. out. println(queue. poll()); 4. System. out. println(queue. remove()); 5. System. out. println(queue. size());
Vector、Hashtable、Stack 都是线程安全的,而像 HashMap 则是非线程安全的,不过在 JDK 1.5 以后随着 Java. util. concurrent 并发包的出现,它们也有了本身对应的线程安全类,好比 HashMap 对应的线程安全类就是 ConcurrentHashMap。
能够使用 Collections. unmodifiableCollection(Collection c) 方法来建立一个只读集合,这样改变集合的任何操做都会抛出 Java. lang. UnsupportedOperationException 异常。
示例代码以下:
1. List<String> list = new ArrayList<>(); 2. list. add("x"); 3. Collection<String> clist = Collections. unmodifiableCollection(list); 4. clist. add("y"); // 运行时此行报错 5. System. out. println(list. size());
守护线程是运行在后台的一种特殊进程。它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。
runnable 没有返回值,callable 能够拿到有返回值,callable 能够看做是 runnable 的补充。
线程的状态:
· NEW 还没有启动
· RUNNABLE 正在执行中
· BLOCKED 阻塞的(被同步锁或者IO锁阻塞)
· WAITING 永久等待状态
· TIMED_WAITING 等待指定的时间从新被唤醒的状态
· TERMINATED 执行完成
· 类的不一样:sleep() 来自 Thread,wait() 来自 Object。
· 释放锁:sleep() 不释放锁;wait() 释放锁。
· 用法不一样:sleep() 时间到会自动恢复;wait() 能够使用 notify()/notifyAll()直接唤醒。
notifyAll()会唤醒全部的线程,notify()以后唤醒一个线程。notifyAll() 调用后,会将所有线程由等待池移到锁池,而后参与锁的竞争,竞争成功则继续执行,若是不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪个线程由虚拟机控制。
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 能够重复调用,而 start() 只能调用一次。
线程池建立有七种方式,最核心的是最后一种:
· newSingleThreadExecutor():它的特色在于工做线程数目被限制为 1,操做一个无界的工做队列,因此它保证了全部任务的都是被顺序执行,最多会有一个任务处于活动状态,而且不容许使用者改动线程池实例,所以能够避免其改变线程数目;
· newCachedThreadPool():它是一种用来处理大量短期工做任务的线程池,具备几个鲜明特色:它会试图缓存线程并重用,当无缓存线程可用时,就会建立新的工做线程;若是线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 做为工做队列;
· newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工做队列,任什么时候候最多有 nThreads 个工做线程是活动的。这意味着,若是任务数量超过了活动队列数目,将在工做队列中等待空闲线程出现;若是有工做线程退出,将会有新的工做线程被建立,以补足指定的数目 nThreads;
· newSingleThreadScheduledExecutor():建立单线程池,返回 ScheduledExecutorService,能够进行定时或周期性的工做调度;
· newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()相似,建立的是个 ScheduledExecutorService,能够进行定时或周期性的工做调度,区别在于单一工做线程仍是多个工做线程;
· newWorkStealingPool(int parallelism):这是一个常常被人忽略的线程池,Java 8 才加入这个建立方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序;
· ThreadPoolExecutor():是最原始的线程池建立,上面1-3建立方式都是对ThreadPoolExecutor的封装。
· RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
· SHUTDOWN:不接受新的任务提交,可是会继续处理等待队列中的任务。
· STOP:不接受新的任务提交,再也不处理等待队列中的任务,中断正在执行任务的线程。
· TIDYING:全部的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
· TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
· execute():只能执行 Runnable 类型的任务。
· submit():能够执行 Runnable 和 Callable 类型的任务。
Callable 类型的任务能够获取执行的返回值,而 Runnable 执行无返回值。
· 方法一:使用安全类,好比 Java. util. concurrent 下的类。
· 方法二:使用自动锁 synchronized。
· 方法三:使用手动锁 Lock。
synchronized 锁升级原理:在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,若是一致则能够直接使用此对象,若是不一致,则升级偏向锁为轻量级锁,经过自旋循环必定次数来获取锁,执行必定次数以后,若是尚未正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。
锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 以后优化 synchronized 的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的状况下,就会发生 AB 两个线程因为互相持有对方须要的锁,而发生的阻塞现象,咱们称为死锁。
· 尽可能使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时能够退出防止死锁。
· 尽可能使用 Java. util. concurrent 并发类代替本身手写锁。
· 尽可能下降锁的使用粒度,尽可能不要几个功能用同一把锁。
· 尽可能减小同步的代码块。
synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 Java 6 以前,monitor 的实现彻底是依靠操做系统内部的互斥锁,由于须要进行用户态到内核态的切换,因此同步操做是一个无差异的重量级操做,性能也很低。但在 Java 6 的时候,Java 虚拟机 对此进行了大刀阔斧地改进,提供了三种不一样的 monitor 实现,也就是常说的三种不一样的锁:偏向锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
· volatile 是变量修饰符;synchronized 是修饰类、方法、代码段。
· volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则能够保证变量的修改可见性和原子性。
· volatile 不会形成线程的阻塞;synchronized 可能会形成线程的阻塞。
反射是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
Java 序列化是为了保存各类对象在内存中的状态,而且能够把保存的对象状态再读出来。
如下状况须要使用 Java 序列化:
· 想把的内存中的对象状态保存到一个文件中或者数据库中时候;
· 想用套接字在网络上传送对象的时候;
· 想经过RMI(远程方法调用)传输对象的时候。
动态代理是运行时动态生成代理类。
动态代理的应用有 spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
JDK 原生动态代理和 cglib 动态代理。JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。
克隆的对象可能包含一些已经修改过的属性,而 new 出来的对象的属性都仍是初始化时候的值,因此当须要一个新的对象来保存当前对象的“状态”就靠克隆方法了。
· 实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
· 实现 Serializable 接口,经过对象的序列化和反序列化实现克隆,能够实现真正的深度克隆。
· 浅克隆:当对象被复制时只复制它自己和其中包含的值类型的成员变量,而引用类型的成员对象并无复制。
· 深克隆:除了对象自己被复制外,对象所包含的全部成员变量也将复制。
JSP 是 servlet 技术的扩展,本质上就是 servlet 的简易方式。servlet 和 JSP 最主要的不一样点在于,servlet 的应用逻辑是在 Java 文件中,而且彻底从表示层中的 html 里分离开来,而 JSP 的状况是 Java 和 html 能够组合成一个扩展名为 JSP 的文件。JSP 侧重于视图,servlet 主要用于控制逻辑。
· page:表明与一个页面相关的对象和属性。
· request:表明与客户端发出的一个请求相关的对象和属性。一个请求可能跨越多个页面,涉及多个 Web 组件;须要在页面显示的临时数据能够置于此做用域。
· session:表明与某个用户与服务器创建的一次会话相关的对象和属性。跟某个用户相关的数据应该放在用户本身的 session 中。
· application:表明与整个 Web 应用程序相关的对象和属性,它实质上是跨越整个 Web 应用程序,包括多个页面、请求和会话的一个全局做用域。
· 存储位置不一样:session 存储在服务器端;cookie 存储在浏览器端。
· 安全性不一样:cookie 安全性通常,在浏览器存储,能够被伪造和修改。
· 容量和个数限制:cookie 有容量限制,每一个站点下的 cookie 也有个数限制。
· 存储的多样性:session 能够存储在 Redis 中、数据库中、应用程序中;而 cookie 只能存储在浏览器中。
session 的工做原理是客户端登陆完成以后,服务器会建立对应的 session,session 建立完以后,会把 session 的 id 发送给客户端,客户端再存储到浏览器中。这样客户端每次访问服务器时,都会带着 sessionid,服务器拿到 sessionid 以后,在内存找到与之对应的 session 这样就能够正常工做了。
能够用,session 只是依赖 cookie 存储 sessionid,若是 cookie 被禁用了,能够使用 url 中添加 sessionid 的方式保证 session 能正常使用。
· 使用预处理 PreparedStatement。
· 使用正则表达式过滤掉字符中的特殊字符。
XSS 攻击:即跨站脚本攻击,它是 Web 程序中常见的漏洞。原理是攻击者往 Web 页面里插入恶意的脚本代码(css 代码、Javascript 代码等),当用户浏览该页面时,嵌入其中的脚本代码会被执行,从而达到恶意攻击用户的目的,如盗取用户 cookie、破坏页面结构、重定向到其余网站等。
预防 XSS 的核心是必须对输入的数据作过滤处理。
CSRF:Cross-Site Request Forgery(中文:跨站请求伪造),能够理解为攻击者盗用了你的身份,以你的名义发送恶意请求,好比:以你名义发送邮件、发消息、购买商品,虚拟货币转帐等。
防护手段:
· 验证请求来源地址;
· 关键操做添加验证码;
· 在请求地址添加 token 并验证。
· throw:是真实抛出一个异常。
· throws:是声明可能会抛出一个异常。
· final:是修饰符,若是修饰类,此类不能被继承;若是修饰方法和变量,则表示此方法和此变量不能在被改变,只能使用。
· finally:是 try{} catch{} finally{} 最后一部分,表示不论发生任何状况都会执行,finally 部分能够省略,但若是 finally 部分存在,则必定会执行 finally 里面的代码。
· finalize: 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法。
try-catch-finally 其中 catch 和 finally 均可以被省略,可是不能同时省略,也就是说有 try 的时候,必须后面跟一个 catch 或者 finally。
· NullPointerException 空指针异常
· ClassNotFoundException 指定类不存在
· NumberFormatException 字符串转换为数字异常
· IndexOutOfBoundsException 数组下标越界异常
· ClassCastException 数据类型转换异常
· FileNotFoundException 文件未找到异常
· NoSuchMethodException 方法不存在异常
· IOException IO 异常
· SocketException Socket 异常