您好,欢迎来到保捱科技网。
搜索
您的当前位置:首页Java常用基础知识点总结(最全)

Java常用基础知识点总结(最全)

来源:保捱科技网
Java常⽤基础知识点总结(最全)Java基础知识点总结⼤纲

⼀、Java基础(语⾔、集合框架、OOP、设计模式等) ⼆、Java⾼级(JavaEE、框架、服务器、⼯具等) 三、多线程和并发 四、Java虚拟机 五、数据库(Sql、MySQL、Redis等) 六、算法与数据结构 七、计算机⽹络 ⼋、操作系统(OS基础、Linux等) 九、其他### ⼀、Java基础(语⾔、框架、OOP、设计模式等)1. HashMap和Hashtable的区别

Hashtable是基于陈旧的Dictionary的Map接⼝的实现,⽽HashMap是基于哈希表的Map接⼝的实现 从⽅法上看,HashMap去掉了Hashtable的contains⽅法 HashTable是同步的(线程安全),⽽HashMap线程不安全,效率上HashMap更快 HashMap允许空键值,⽽Hashtable不允许 HashMap的iterator迭代器执⾏快速失败机制,也就是说在迭代过程中修改集合结构,除⾮调⽤迭代器⾃⾝的remove⽅法,否则以其他任何⽅式的修改都将抛出并发修改异常。⽽Hashtable返回的Enumeration不是快速失败的。注:Fast-fail机制:在使⽤迭代器的过程中有其它线程修改了集合对象结构或元素数量,都将抛出ConcurrentModifiedException,但是抛出这个异常是不保证的,我们不能编写依赖于此异常的程序。2. java的线程安全

Vector、Stack、HashTable、ConcurrentHashMap、Properties3. java集合框架(常⽤)1234567101112

Collection - List - ArrayListCollection - List - LinkedListCollection - List - VectorCollection - List - Vector - StackCollection - Set - HashSetCollection - Set - TreeSetCollection - List - LinkedHashSetMap - HashMapMap - TreeMapMap - HashTableMap - LinkedHashMapMap - ConcurrentHashMap

3.1 List集合和Set集合

List中元素存取是有序的、可重复的;Set集合中元素是⽆序的,不可重复的。CopyOnWriteArrayList:COW的策略,即写时复制的策略。适⽤于读多写少的场景

Set集合元素存取⽆序,且元素不可重复。

HashSet不保证迭代顺序,线程不安全;LinkedHashSet是Set接⼝的哈希表和链接列表的实现,保证迭代顺序,线程不安全。TreeSet:可以对Set集合中的元素排序,元素以⼆叉树形式存放,线程不安全。3.2 ArrayList、LinkedList、Vector的区别⾸先它们均是List接⼝的实现。ArrayList、LinkedList的区别

1.随机存取:ArrayList是基于可变⼤⼩的数组实现,LinkedList是链接列表的实现。这也就决定了对于随机访问的get和set的操作,ArrayList要优于LinkedList,因为LinkedList要移动指针。

2.插⼊和删除:LinkedList要好⼀些,因为ArrayList要移动数据,更新索引。3.内存消耗:LinkedList需要更多的内存,因为需要维护指向后继结点的指针。

Vector从JDK 1.0起就存在,在1.2时改为实现List接⼝,功能与ArrayList类似,但是Vector具备线程安全。3.3 Map集合

Hashtable:基于Dictionary类,线程安全,速度快。底层是哈希表数据结构。是同步的。不允许null作为键,null作为值。

Properties:Hashtable的⼦类。⽤于配置⽂件的定义和操作,使⽤频率⾮常⾼,同时键和值都是字符串。

HashMap:线程不安全,底层是数组加链表实现的哈希表。允许null作为键,null作为值。HashMap去掉了contains⽅法。注意:HashMap不保证元素的迭代顺序。如果需要元素存取有序,请使⽤LinkedHashMapTreeMap:可以⽤来对Map集合中的键进⾏排序。ConcurrentHashMap:是JUC包下的⼀个并发集合。

3.4 为什么使⽤ConcurrentHashMap⽽不是HashMap或Hashtable?

HashMap的缺点:主要是同时put时,如果同时触发了rehash操作,会导致HashMap中的链表中出现循环节点,进⽽使得后⾯get的时候,会死循环,CPU达到100%,所以在并发情况下不能使⽤HashMap。让HashMap同步:Map m =

Collections.synchronizeMap(hashMap);⽽Hashtable虽然是同步的,使⽤synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率⾮常低下。因为当⼀个线程访问HashTable的同步⽅法时,其他线程访问HashTable的同步⽅法时,可能会进⼊阻塞或轮询状态。如线程1使⽤put进⾏添加元素,线程2不但不能使⽤put⽅法添加元素,并且也不能使⽤get⽅法来获取元素,所以竞争越激烈效率越低。

ConcurrentHashMap的原理:

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因在于所有访问HashTable的线程都必须竞争同⼀把锁,那假如容器⾥有多把锁,每⼀把锁⽤于锁容器其中⼀部分数据,那么当多线程访问容器⾥不同数据段的数据时,线程间就不会存在锁竞争,从⽽可以有效的提⾼并发访问效率,这就是ConcurrentHashMap所使⽤的锁分段技术,⾸先将数据分成⼀段⼀段的存储,然后给每⼀段数据配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段数据的时候,其他段的数据也能被其他线程访问。ConcurrentHashMap的结构:

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是⼀种可重⼊互斥锁ReentrantLock,在ConcurrentHashMap⾥扮演锁的⾓⾊,HashEntry则⽤于存储键值对数据。⼀个ConcurrentHashMap⾥包含⼀个Segment数组,Segment的结构和HashMap类似,是⼀种数组和链表结构, ⼀个Segment⾥包含⼀个HashEntry数组,每个HashEntry是⼀个链表结构的元素,当对某个HashEntry数组的数据进⾏修改时,必须⾸先获得它对应的Segment锁。ConcurrentHashMap的构造、get、put操作:

构造函数:传⼊参数分别为 1、初始容量,默认16 2、装载因⼦ 装载因⼦⽤于rehash的判定,就是当ConcurrentHashMap中的元素⼤于装载因⼦*最⼤容量时进⾏扩容,默认0.75 3、并发级别 这个值⽤来确定Segment的个数,Segment的个数是⼤于等于

concurrencyLevel的第⼀个2的n次⽅的数。⽐如,如果concurrencyLevel为12,13,14,15,16这些数,则Segment的数⽬为16(2的4次⽅)。默认值为static final int DEFAULT_CONCURRENCY_LEVEL = 16;。理想情况下ConcurrentHashMap的真正的并发访问量能够达到concurrencyLevel,因为有concurrencyLevel个Segment,假如有concurrencyLevel个线程需要访问Map,并且需要访问的数据都恰好分别落在不同的Segment中,则这些线程能够⽆竞争地⾃由访问(因为他们不需要竞争同⼀把锁),达到同时访问的效果。这也是为什么这个参数起名为“并发级别”的原因。默认16.初始化的⼀些动作:

初始化segments数组(根据并发级别得到数组⼤⼩ssize),默认16

初始化segmentShift和segmentMask(这两个全局变量在定位segment时的哈希算法⾥需要使⽤),默认情况下segmentShift为28,segmentMask为15

初始化每个Segment,这⼀步会确定Segment⾥HashEntry数组的长度.put操作:

1、判断value是否为null,如果为null,直接抛出异常。

2、key通过⼀次运算得到⼀个hash值。将得到hash值向右按位移动segmentShift位,然后再与segmentMask做&运算得到segment的索引j。即segmentFor⽅法

3、使⽤Unsafe的⽅式从Segment数组中获取该索引对应的Segment对象。向这个Segment对象中put值,这个put操作也基本是⼀样的步骤(通过&运算获取HashEntry的索引,然后set)。get操作:

1、和put操作⼀样,先通过key进⾏hash确定应该去哪个Segment中取数据。

2、使⽤Unsafe获取对应的Segment,然后再进⾏⼀次&运算得到HashEntry链表的位置,然后从链表头开始遍历整个链表(因为Hash可能会有碰撞,所以⽤⼀个链表保存),如果找到对应的key,则返回对应的value值,如果链表遍历完都没有找到对应的key,则说明Map中不包含该key,返回null。

定位Segment的hash算法:(hash >>> segmentShift) & segmentMask定位HashEntry所使⽤的hash算法:int index = hash & (tab.length - 1);注:

1.tab为HashEntry数组

2.ConcurrentHashMap既不允许null key也不允许null value3.5 Collection 和 Collections的区别

Collection是集合类的上级接⼝,⼦接⼝主要有Set 和List、Queue

Collections是针对集合类的⼀个辅助类,提供了操作集合的⼯具⽅法:⼀系列静态⽅法实现对各种集合的搜索、排序、线程安全化等操作。

3.6 Map、Set、List、Queue、Stack的特点与⽤法

Set集合类似于⼀个罐⼦,\"丢进\"Set集合⾥的多个对象之间没有明显的顺序。 List集合代表元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。 Stack是Vector提供的⼀个⼦类,⽤于模拟\"栈\"这种数据结构(LIFO后进先出) Queue⽤于模拟\"队列\"这种数据结构(先进先出 FIFO)。 Map⽤于保存具有\"映射关系\"的数据,因此Map集合⾥保存着两组值。3.7 HashMap的⼯作原理

HashMap维护了⼀个Entry数组,Entry内部类有key,value,hash和next四个字段,其中next也是⼀个Entry类型。可以将Entry数组理解为⼀个个的散列桶。每⼀个桶实际上是⼀个单链表。当执⾏put操作时,会根据key的hashcode定位到相应的桶。遍历单链表检查该key是否已经存在,如果存在,覆盖该value,反之,新建⼀个新的Entry,并放在单链表的头部。当通过传递key调⽤get⽅法时,它再次使⽤key.hashCode()来找到相应的散列桶,然后使⽤key.equals()⽅法找出单链表中正确的Entry,然后返回它的值。

3.8 Map的实现类的介绍

HashMap基于散列表来的实现,即使⽤hashCode()进⾏快速查询元素的位置,显著提⾼性能。插⼊和查询“键值对”的开销是固定的。可以通过设置容量和装载因⼦,以调整容器的性能。

LinkedHashMap, 类似于HashMap,但是迭代遍历它时,保证迭代的顺序是其插⼊的次序,因为它使⽤链表维护内部次序。此外可以在构造器中设定LinkedHashMap,使之采⽤LRU算法。使没有被访问过的元素或较少访问的元素出现在前⾯,访问过的或访问多的出现在后⾯。这对于需要定期清理元素以节省空间的程序员来说,此功能使得程序员很容易得以实现。

TreeMap, 是基于红⿊树的实现。同时TreeMap实现了SortedMap接⼝,该接⼝可以确保键处于排序状态。所以查看“键”和“键值对”时,所有得到的结果都是经过排序的,次序由⾃然排序或提供的Comparator决定。SortedMap接⼝拥有其他额外的功能,如:返回当前Map使⽤的Comparator⽐较强,firstKey(),lastKey(),headMap(toKey),tailMap(fromKey)以及可以返回⼀个⼦树的subMap()⽅法等。

WeakHashMap,表⽰弱键映射,WeakHashMap 的⼯作与正常的 HashMap 类似,但是使⽤弱引⽤作为 key,意思就是当 key 对象没有任何引⽤时,key/value 将会被回收。

ConcurrentHashMap, 在HashMap基础上分段锁机制实现的线程安全的HashMap。

IdentityHashMap 使⽤==代替equals() 对“键”进⾏⽐较的散列映射。专为解决特殊问题⽽设计。HashTable:基于Dictionary类的Map接⼝的实现,它是线程安全的。3.9 LinkedList 和 PriorityQueue 的区别

它们均是Queue接⼝的实现。拥有FIFO的特点,它们的区别在于排序⾏为。LinkedList ⽀持双向列表操作,PriorityQueue 按优先级组织的队列,元素的出队次序由元素的⾃然排序或者由Comparator⽐较器指定。3.10 线程安全的集合类。Vector、Hashtable、Properties和Stack、ConcurrentHashMap3.11 BlockingQueue

Java.util.concurrent.BlockingQueue是⼀个队列,在进⾏获取元素时,它会等待队列变为⾮空;当在添加⼀个元素时,它会等待队列中的可⽤空间。BlockingQueue接⼝是Java集合框架的⼀部分,主要⽤于实现⽣产者-消费者模式。我们不需要担⼼等待⽣产者有可⽤的空间,或消费者有可⽤的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,⽐如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。3.12 如何对⼀组对象进⾏排序

如果需要对⼀个对象数组进⾏排序,我们可以使⽤Arrays.sort()⽅法。如果我们需要排序⼀个对象列表,我们可以使⽤Collections.sort()⽅法。排序时是默认根据元素的⾃然排序(使⽤Comparable)或使⽤Comparator外部⽐较器。Collections内部使⽤数组排序⽅法,所有它们两者都有相同的性能,只是Collections需要花时间将列表转换为数组。4. ArrayList

⽆参构造 容量为10ArrayList(Collections c)构造包含指定collection的元素的列表ArrayList(int initialCapacity) 指定初始容量5. final关键字

final修饰的变量是常量,必须进⾏初始化,可以显⽰初始化,也可以通过构造进⾏初始化,如果不初始化编译会报错。

6. 接⼝与抽象类

6.1 ⼀个⼦类只能继承⼀个抽象类,但能实现多个接⼝6.2 抽象类可以有构造⽅法,接⼝没有构造⽅法6.3 抽象类可以有普通成员变量,接⼝没有普通成员变量

6.4 抽象类和接⼝都可有静态成员变量,抽象类中静态成员变量访问类型任意,接⼝只能public static final(默认)6.5 抽象类可以没有抽象⽅法,抽象类可以有普通⽅法,接⼝中都是抽象⽅法6.6 抽象类可以有静态⽅法,接⼝不能有静态⽅法

6.7 抽象类中的⽅法可以是public、protected;接⼝⽅法只有public abstract7. 抽象类和最终类

抽象类可以没有抽象⽅法, 最终类可以没有最终⽅法最终类不能被继承, 最终⽅法不能被重写(可以重载)8.异常

相关的关键字 throw、throws、try…catch、finally

throws ⽤在⽅法签名上, 以便抛出的异常可以被调⽤者处理throw ⽅法内部通过throw抛出异常try ⽤于检测包住的语句块, 若有异常, catch⼦句捕获并执⾏catch块9. 关于finally

finally不管有没有异常都要处理当try和catch中有return时,finally仍然会执⾏,finally⽐return先执⾏不管有⽊有异常抛出, finally在return返回前执⾏finally是在return后⾯的表达式运算后执⾏的(此时并没有返回运算后的值,⽽是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执⾏前确定的注意:finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值finally不执⾏的⼏种情况:程序提前终⽌如调⽤了System.exit, 病毒,断电10. 受检查异常和运⾏时异常

10.1 粉红⾊的是受检查的异常(checked exceptions),其必须被try…catch语句块所捕获, 或者在⽅法签名⾥通过throws⼦句声明。受检查的异常必须在编译时被捕捉处理,命名为Checked Exception是因为Java编译器要进⾏检查, Java也要进⾏检查, 以确保这个规则得到遵守。

常见的checked exception:ClassNotFoundException IOException FileNotFoundException EOFException10.2 绿⾊的异常是运⾏时异常(runtime exceptions), 需要程序员⾃⼰分析代码决定是否捕获和处理,⽐如空指针,被0除…常见的runtime exception:NullPointerException ArithmeticException ClassCastException IllegalArgumentExceptionIllegalStateException IndexOutOfBoundsException NoSuchElementException

10.3 ⽽声明为Error的,则属于严重错误,如系统崩溃、虚拟机错误、动态链接失败等,这些错误⽆法恢复或者不可能捕捉,将导致应⽤程序中断,Error不需要捕获。11. this & super

11.1 super出现在⽗类的⼦类中。有三种存在⽅式

1. super.xxx(xxx为变量名或对象名)意思是获取⽗类中xxx的变量或引⽤2. super.xxx(); (xxx为⽅法名)意思是直接访问并调⽤⽗类中的⽅法3. super() 调⽤⽗类构造注:super只能指代其直接⽗类

11.2 this() & super()在构造⽅法中的区别

1. 调⽤super()必须写在⼦类构造⽅法的第⼀⾏, 否则编译不通过2. super从⼦类调⽤⽗类构造, this在同⼀类中调⽤其他构造3. 均需要放在第⼀⾏4. 尽管可以⽤this调⽤⼀个构造器, 却不能调⽤2个5. this和super不能出现在同⼀个构造器中, 否则编译不通过6. this()、super()都指的对象,不可以在static环境中使⽤7. 本质this指向本对象的指针。super是⼀个关键字12. 修饰符⼀览

12345

修饰符 类内部 同⼀个包 ⼦类 任何地⽅private yes

default yes yesprotected yes yes yespublic yes yes yes yes

13. 构造内部类和静态内部类对象123456710111213

public class Enclosingone { public class Insideone {} public static class Insideone{}}

public class Test {

public static void main(String[] args) { // 构造内部类对象需要外部类的引⽤ Enclosingone.Insideone obj1 = new Enclosingone().new Insideone(); // 构造静态内部类的对象 Enclosingone.Insideone obj2 = new Enclosingone.Insideone(); }}

静态内部类不需要有指向外部类的引⽤。但⾮静态内部类需要持有对外部类的引⽤。⾮静态内部类能够访问外部类的静态和⾮静态成员。静态内部类不能访问外部类的⾮静态成员,只能访问外部类的静态成员。14. 序列化

声明为static和transient类型的数据不能被序列化, 反序列化需要⼀个⽆参构造函数序列化参见我的笔记15.正则表达式次数符号

12345

* 0或多次+ 1或多次0或1次{n} 恰n次{n,m} 从n到m次

其他符号符号 等价形式

1234567

\\d [0-9]\\D [^0-9] \\w [a-zA-Z_0-9]\\W [^a-zA-Z_0-9]\\s [\\\n\\r\\f]\\S [^\\\n\\r\\f]. 任何字符

边界匹配器⾏开头 ^⾏结尾 $单词边界 \\b

贪婪模式:最⼤长度匹配 ⾮贪婪模式:匹配到结果就好,最短匹配环视12345671011121314

字符 描述 匹配对象. 单个任意字符

[...] 字符组 列出的任意字符[^...] 未列出的任意字符^ caret ⾏的起始位置$ dollar ⾏的结束位置\\< 单词的起始位置\\> 单词的结束位置\\b 单词边界\\B ⾮单词边界

(?=Expression) 顺序肯定环视 成功,如果右边能够匹配(?!Expression) 顺序否定环视 成功,如果右边不能够匹配(?<=Expression) 逆序肯定环视 成功,如果左边能够匹配(?举例:北京市(海淀区)(朝阳区)(西城区)Regex: .*(?=\\()

模式和匹配器的典型调⽤次序1. 把正则表达式编译到模式中Pattern p = Pattern.compile(“a*b”);2. 创建给定输⼊与此模式的匹配器Matcher m = p.matcher(“aaab”);3. 尝试将整个区域与此模式匹配boolean b = m.matches();16. ⾯向对象的五⼤基本原则(solid)

1. S单⼀职责SRP:Single-Responsibility Principle

⼀个类,最好只做⼀件事,只有⼀个引起它的变化。单⼀职责原则可以看做是低耦合,⾼内聚在⾯向对象原则的引申,将职责定义为引起变化的原因,以提⾼内聚性减少引起变化的原因。2. O开放封闭原则OCP:Open-Closed Principle

软件实体应该是可扩展的,⽽不是可修改的。对扩展开放,对修改封闭3. L⾥⽒替换原则LSP:Liskov-Substitution Principle

⼦类必须能够替换其基类。这⼀思想表现为对继承机制的约束规范,只有⼦类能够替换其基类时,才能够保证系统在运⾏期内识别⼦类,这是保证继承复⽤的基础。

4. I接⼝隔离原则ISP:Interface-Segregation Principle

使⽤多个⼩的接⼝,⽽不是⼀个⼤的总接⼝

5. D依赖倒置原则DIP:Dependency-Inversion Principle

依赖于抽象。具体⽽⾔就是⾼层模块不依赖于底层模块,⼆者共同依赖于抽象。抽象不依赖于具体,具体依赖于抽象。

17. ⾯向对象设计其他原则1. 封装变化

2. 少⽤继承 多⽤组合

3. 针对接⼝编程 不针对实现编程4. 为交互对象之间的松耦合设计⽽努⼒

5. 类应该对扩展开发 对修改封闭(开闭OCP原则)6. 依赖抽象,不要依赖于具体类(依赖倒置DIP原则)7. 密友原则:只和朋友交谈(最少知识原则,迪⽶特法则)

说明:⼀个对象应当对其他对象有尽可能少的了解,将⽅法调⽤保持在界限内,只调⽤属于以下范围的⽅法:该对象本⾝(本地⽅法)对象的组件 被当作⽅法参数传进来的对象 此⽅法创建或实例化的任何对象8. 别找我(调⽤我) 我会找你(调⽤你)(好莱坞原则)9. ⼀个类只有⼀个引起它变化的原因(单⼀职责SRP原则)

18. null可以被强制转型为任意类型的对象19.代码执⾏次序

1. 多个静态成员变量, 静态代码块按顺序执⾏2. 单个类中: 静态代码 -> main⽅法 -> 构造块 -> 构造⽅法3. 构造块在每⼀次创建对象时执⾏4. 涉及⽗类和⼦类的初始化过程a.初始化⽗类中的静态成员变量和静态代码块b.初始化⼦类中的静态成员变量和静态代码块c.初始化⽗类的普通成员变量和构造代码块(按次序),再执⾏⽗类的构造⽅法(注意⽗类构造⽅法中的⼦类⽅法覆盖)d.初始化⼦类的普通成员变量和构造代码块(按次序),再执⾏⼦类的构造⽅法20. 数组复制⽅法

1. for逐⼀复制2. System.arraycopy() -> 效率最⾼native⽅法3. Arrays.copyOf() -> 本质调⽤arraycopy4. clone⽅法 -> 返回Object[],需要强制类型转换21. 多态

1. Java通过⽅法重写和⽅法重载实现多态2. ⽅法重写是指⼦类重写了⽗类的同名⽅法3. ⽅法重载是指在同⼀个类中,⽅法的名字相同,但是参数列表不同22. Java⽂件

.java⽂件可以包含多个类,唯⼀的就是:⼀个⽂件中只能有⼀个public类, 并且此public类必须与⽂件名相同。⽽且这些类和写在多个⽂件中没有区别。23. Java移位运算符java中有三种移位运算符

1. << :左移运算符,x << 1,相当于x乘以2(不溢出的情况下),低位补02. >> :带符号右移,x >> 1,相当于x除以2,正数⾼位补0,负数⾼位补13. >>> :⽆符号右移,忽略符号位,空位都以0补齐参见我的GitHub,24. 形参&实参

1. 形式参数可被视为local variable.形参和局部变量⼀样都不能离开⽅法。只有在⽅法中使⽤,不会在⽅法外可见。2. 形式参数只能⽤final修饰符,其它任何修饰符都会引起编译器错误。但是⽤这个修饰符也有⼀定的,就是在⽅法中不能对参数做任何修改。不过⼀般情况下,⼀个⽅法的形参不⽤final修饰。只有在特殊情况下,那就是:⽅法内部类。⼀个⽅法内的内部类如果使⽤了这个⽅法的参数或者局部变量的话,这个参数或局部变量应该是final。3. 形参的值在调⽤时根据调⽤者更改,实参则⽤⾃⾝的值更改形参的值(指针、引⽤皆在此列),也就是说真正被传递的是实参。25. IO流⼀览

26. 局部变量为什么要初始化

局部变量是指类⽅法中的变量,必须初始化。局部变量运⾏时被分配在栈中,量⼤,⽣命周期短,如果虚拟机给每个局部变量都初始化⼀下,是⼀笔很⼤的开销,但变量不初始化为默认值就使⽤是不安全的。出于速度和安全性两个⽅⾯的综合考虑,解决⽅案就是虚拟机不初始化,但要求编写者⼀定要在使⽤前给变量赋值。27. Java语⾔的鲁棒性

Java在编译和运⾏程序时,都要对可能出现的问题进⾏检查,以消除错误的产⽣。它提供⾃动垃圾收集来进⾏内存管理,防⽌程序员在管理内存时容易产⽣的错误。通过集成的⾯向对象的例外处理机制,在编译时,Java揭⽰出可能出现但未被处理的异常,帮助程序员正确地进⾏选择以防⽌系统的崩溃。另外,Java在编译时还可捕获类型声明中的许多常见错误,防⽌动态运⾏时不匹配问题的出现。28. Java语⾔特性

1. Java致⼒于检查程序在编译和运⾏时的错误2. Java虚拟机实现了跨平台接⼝3. 类型检查帮助检查出许多开发早期出现的错误4. Java⾃⼰操纵内存减少了内存出错的可能性5. Java还实现了真数组,避免了覆盖数据的可能29. 包装类的equals()⽅法不处理数据转型,必须类型和值都⼀样才相等。

30. ⼦类可以继承⽗类的静态⽅法!但是不能覆盖。因为静态⽅法是在编译时确定了,不能多态,也就是不能运⾏时绑定。31. Java语法糖

1. Java7的switch⽤字符串 - hashcode⽅法 switch⽤于enum枚举2. 伪泛型 - List原始类型3. ⾃动装箱拆箱 - Integer.valueOf和Integer.intValue4. foreach遍历 - Iterator迭代器实现5. 条件编译6. enum枚举类、内部类7. 可变参数 - 数组8. 断⾔语⾔9. try语句中定义和关闭资源32. Java 中应该使⽤什么数据类型来代表价格?

如果不是特别关⼼内存和性能的话,使⽤BigDecimal,否则使⽤预定义精度的 double 类型。33. 怎么将 byte 转换为 String?

可以使⽤ String 接收 byte[] 参数的构造器来进⾏转换,需要注意的点是要使⽤的正确的编码,否则会使⽤平台默认编码,这个编码可能跟原来的编码相同,也可能不同。

34. Java 中怎样将 bytes 转换为 long 类型?

String接收bytes的构造器转成String,再Long.parseLong

35. 我们能将 int 强制转换为 byte 类型的变量吗?如果该值⼤于 byte 类型的范围,将会出现什么现象?

是的,我们可以做强制转换,但是 Java 中 int 是 32 位的,⽽ byte 是 8 位的,所以,如果强制转化是,int 类型的⾼ 24 位将会被丢弃,byte 类型的范围是从 -128 到 127。

36. 存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为 C 么?如 C = © B;可以,向下转型。但是不建议使⽤,容易出现类型转型异常.37. 哪个类包含 clone ⽅法?是 Cloneable 还是 Object?

java.lang.Cloneable 是⼀个标⽰性接⼝,不包含任何⽅法,clone ⽅法在 object 类中定义。并且需要知道 clone() ⽅法是⼀个本地⽅法,这意味着它是由 c 或 c++ 或 其他本地语⾔实现的。38. Java 中 ++ 操作符是线程安全的吗?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差。还会存在竞态条件(读取-修改-写⼊)。

39. a = a + b 与 a += b 的区别

+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,⾸先会将它们提升到 int 类型,然后在执⾏加法操作。

12345

byte a = 127;byte b = 127;

b = a + b; // error : cannot convert from int to byteb += a; // ok

(因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)40. 我能在不进⾏强制转换的情况下将⼀个 double 值赋值给 long 类型的变量吗?

不⾏,你不能在没有强制类型转换的前提下将⼀个 double 值赋值给 long 类型的变量,因为 double 类型的范围⽐ long 类型更⼴,所以必须要进⾏强制转换。

41. 3*0.1 == 0.3 将会返回什么?true 还是 false?false,因为有些浮点数不能完全精确的表⽰出来。42. int 和 Integer 哪个会占⽤更多的内存?

Integer 对象会占⽤更多的内存。Integer 是⼀个对象,需要存储对象的元数据。但是 int 是⼀个原始类型的数据,所以占⽤的空间更少。43. 为什么 Java 中的 String 是不可变的(Immutable)?

Java 中的 String 不可变是因为 Java 的设计者认为字符串使⽤⾮常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。更详细的内容参见答案。

44. 我们能在 Switch 中使⽤ String 吗?

从 Java 7 开始,我们可以在 switch case 中使⽤字符串,但这仅仅是⼀个语法糖。内部实现在 switch 中使⽤字符串的 hash code。45. Java 中的构造器链是什么?

当你从⼀个构造器中调⽤另⼀个构造器,就是Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现。46. 枚举类

JDK1.5出现 每个枚举值都需要调⽤⼀次构造函数

48. 什么是不可变对象(immutable object)?Java 中怎么创建⼀个不可变对象?

不可变对象指对象⼀旦被创建,状态就不能再改变。任何修改都会创建⼀个新的对象,如 String、Integer及其它包装类。如何在Java中写出Immutable的类?要写出这样的类,需要遵循以下⼏个原则:

1)immutable对象的状态在创建之后就不能发⽣改变,任何对它的改变都应该产⽣⼀个新的对象。2)Immutable类的所有的属性都应该是final的。

3)对象必须被正确的创建,⽐如:对象引⽤在对象创建过程中不能泄露(leak)。

4)对象应该是final的,以此来⼦类继承⽗类,以避免⼦类改变了⽗类的immutable特性。

5)如果类中包含mutable类对象,那么返回给客户端的时候,返回该对象的⼀个拷贝,⽽不是该对象本⾝(该条可以归为第⼀条中的⼀个特例)

49. 我们能创建⼀个包含可变对象的不可变对象吗?

是的,我们是可以创建⼀个包含可变对象的不可变对象的,你只需要谨慎⼀点,不要共享可变对象的引⽤就可以了,如果需要变化时,就返回原对象的⼀个拷贝。最常见的例⼦就是对象中包含⼀个⽇期对象的引⽤。50. List和Set

List 是⼀个有序集合,允许元素重复。它的某些实现可以提供基于下标值的常量访问时间,但是这不是 List 接⼝保证的。Set 是⼀个⽆序集合。

51. poll() ⽅法和 remove() ⽅法的区别?

poll() 和 remove() 都是从队列中取出⼀个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。52. Java 中 LinkedHashMap 和 PriorityQueue 的区别是什么?

PriorityQueue 保证最⾼或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插⼊的顺序。当遍历⼀个PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插⼊的顺序。53. ArrayList 与 LinkedList 的区别?

最明显的区别是 ArrrayList 底层的数据结构是数组,⽀持随机访问,⽽ LinkedList 的底层数据结构书链表,不⽀持随机访问。使⽤下标访问⼀个元素,ArrayList 的时间复杂度是 O(1),⽽ LinkedList 是 O(n)。54. ⽤哪两种⽅式来实现集合的排序?

你可以使⽤有序集合,如 TreeSet 或 TreeMap,你也可以使⽤有顺序的的集合,如 list,然后通过 Collections.sort() 来排序。55. Java 中怎么打印数组?

你可以使⽤ Arrays.toString() 和 Arrays.deepToString() ⽅法来打印数组。由于数组没有实现 toString() ⽅法,所以如果将数组传递给System.out.println() ⽅法,将⽆法打印出数组的内容,但是 Arrays.toString() 可以打印每个元素。56. Java 中的 LinkedList 是单向链表还是双向链表?

是双向链表,你可以检查 JDK 的源码。在 Eclipse,你可以使⽤快捷键 Ctrl + T,直接在编辑器中打开该类。57. Java 中的 TreeMap 是采⽤什么树实现的?Java 中的 TreeMap 是使⽤红⿊树实现的。58. Java 中的 HashSet,内部是如何⼯作的?

HashSet 的内部采⽤ HashMap来实现。由于 Map 需要 key 和 value,所以所有 key 的都有⼀个默认 value。类似于HashMap,HashSet 不允许重复的 key,只允许有⼀个null key,意思就是 HashSet 中只允许存储⼀个 null 对象。59. 写⼀段代码在遍历 ArrayList 时移除⼀个元素?

该问题的关键在于⾯试者使⽤的是 ArrayList 的 remove() 还是 Iterator 的 remove()⽅法。这有⼀段⽰例代码,是使⽤正确的⽅式来实现在遍历的过程中移除元素,⽽不会出现 ConcurrentModificationException 异常的⽰例代码。60. 我们能⾃⼰写⼀个容器类,然后使⽤ for-each 循环吗?

可以,你可以写⼀个⾃⼰的容器类。如果你想使⽤ Java 中增强的循环来遍历,你只需要实现 Iterable 接⼝。如果你实现 Collection 接⼝,默认就具有该属性。

61. ArrayList 和 HashMap 的默认⼤⼩是多数?

在 Java 7 中,ArrayList 的默认⼤⼩是 10 个元素,HashMap 的默认⼤⼩是16个元素(必须是2的幂)。这就是 Java 7 中 ArrayList和 HashMap 类的代码⽚段:

12345

// from ArrayList.java JDK 1.7

private static final int DEFAULT_CAPACITY = 10;

//from HashMap.java JDK 7

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

62. 有没有可能两个不相等的对象有有相同的 hashcode?

有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。63. 两个相同的对象会有不同的的 hash code 吗?不能,根据 hash code 的规定,这是不可能的。. 我们可以在 hashcode() 中使⽤随机数字吗?不⾏,因为对象的 hashcode 值必须是相同的。

65. Java 中,Comparator 与 Comparable 有什么不同?

Comparable 接⼝⽤于定义对象的⾃然顺序,⽽ comparator 通常⽤于定义⽤户定制的顺序。Comparable 总是只有⼀个,但是可以有多个 comparator 来定义对象的顺序。

66. 为什么在重写 equals ⽅法的时候需要重写 hashCode ⽅法?

因为有强制的规范指定需要同时重写 hashcode 与 equal 是⽅法,许多容器类,如 HashMap、HashSet 都依赖于 hashcode 与 equals的规定。

67. “a==b”和”a.equals(b)”有什么区别?

如果 a 和 b 都是对象,则 a==b 是⽐较两个对象的引⽤,只有当 a 和 b 指向的是堆中的同⼀个对象才会返回 true,⽽ a.equals(b) 是进⾏逻辑⽐较,所以通常需要重写该⽅法来提供逻辑⼀致性的⽐较。例如,String 类重写 equals() ⽅法,所以可以⽤于两个不同对象,但是包含的字母相同的⽐较。

68. a.hashCode() 有什么⽤?与 a.equals(b) 有什么关系?

简介:hashCode() ⽅法是相应对象整型的 hash 值。它常⽤于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() ⽅法关系特别紧密。根据 Java 规范,两个使⽤ equal() ⽅法来判断相等的对象,必须具有相同的 hash code。1、hashcode的作⽤

List和Set,如何保证Set不重复呢?通过迭代使⽤equals⽅法来判断,数据量⼩还可以接受,数据量⼤怎么解决?引⼊hashcode,实际上hashcode扮演的⾓⾊就是寻址,⼤⼤减少查询匹配次数。2、hashcode重要吗

对于数组、List集合就是⼀个累赘。⽽对于hashmap, hashset, hashtable就异常重要了。3、equals⽅法遵循的原则

对称性 若x.equals(y)true,则y.equals(x)true⾃反性 x.equals(x)必须true传递性 若x.equals(y)true,y.equals(z)true,则x.equals(z)必为true⼀致性 只要x,y内容不变,⽆论调⽤多少次结果不变其他 x.equals(null) 永远false,x.equals(和x数据类型不同)始终false两者的关系

69. final、finalize 和 finally 的不同之处?

final 是⼀个修饰符,可以修饰变量、⽅法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。Java 技术允许使⽤finalize() ⽅法在垃圾收集器将对象从内存中清除出去之前做必要的清理⼯作。这个⽅法是由垃圾收集器在确定这个对象没有被引⽤时对这个对象调⽤的,但是什么时候调⽤ finalize 没有保证。finally 是⼀个关键字,与 try 和 catch ⼀起⽤于异常的处理。finally 块⼀定会被执⾏,⽆论在 try 块中是否有发⽣异常。

70. Java 中的编译期常量是什么?使⽤它⼜什么风险?

变量也就是我们所说的编译期常量,这⾥的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运⾏时不能改变。这种⽅式存在的⼀个问题是你使⽤了⼀个内部的或第三⽅库中的公有编译时常量,但是这个值后⾯被其他⼈改变了,但是你的客户端仍然在使⽤⽼的值,甚⾄你已经部署了⼀个新的jar。为了避免这种情况,当你在更新依赖 JAR ⽂件时,确保重新编译你的程序。

71. 说出⼏点 Java 中使⽤ Collections 的最佳实践这是我在使⽤ Java 中 Collectionc 类的⼀些最佳实践:

a)使⽤正确的集合类,例如,如果不需要同步列表,使⽤ ArrayList ⽽不是 Vector。b)优先使⽤并发集合,⽽不是对集合进⾏同步。并发集合提供更好的可扩展性。

c)使⽤接⼝代表和访问集合,如使⽤List存储 ArrayList,使⽤ Map 存储 HashMap 等等。d)使⽤迭代器来循环集合。e)使⽤集合的时候使⽤泛型。72. 静态内部类与顶级类有什么区别?

⼀个公共的顶级类的源⽂件名称与类名相同,⽽嵌套静态类没有这个要求。⼀个嵌套类位于顶级类内部,需要使⽤顶级类的名称来引⽤嵌套静态类,如 HashMap.Entry 是⼀个嵌套静态类,HashMap 是⼀个顶级类,Entry是⼀个嵌套静态类。73. Java 中,Serializable 与 Externalizable 的区别?

Serializable 接⼝是⼀个序列化 Java 类的接⼝,以便于它们可以在⽹络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化⽅式,成本⾼、脆弱⽽且不安全。Externalizable 允许你控制整个序列化过程,指定特定的⼆进制格式,增加安全机制。74. 说出 JDK 1.7 中的三个新特性?

虽然 JDK 1.7 不像 JDK 5 和 8 ⼀样的⼤版本,但是,还是有很多新的特性,如 try-with-resource 语句,这样你在使⽤流或者资源的时候,就不需要⼿动关闭,Java 会⾃动关闭。Fork-Join 池某种程度上实现 Java 版的 Map-reduce。允许 Switch 中有 String 变量和⽂本。菱形操作符(<>)⽤于泛型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码。另⼀个值得⼀提的特性是改善异常处理,如允许在同⼀个 catch 块中捕获多个异常。75. 说出 5 个 JDK 1.8 引⼊的新特性?

Java 8 在 Java 历史上是⼀个开创新的版本,下⾯ JDK 8 中 5 个主要的特性:Lambda 表达式,允许像对象⼀样传递匿名函数

Stream API,充分利⽤现代多核 CPU,可以写出很简洁的代码

Date 与 Time API,最终,有⼀个稳定、简单的⽇期和时间库可供你使⽤扩展⽅法,现在,接⼝中可以有静态、默认⽅法。

重复注解,现在你可以将相同的注解在同⼀类型上使⽤多次。

下述包含 Java ⾯试过程中关于 SOLID 的设计原则,OOP 基础,如类,对象,接⼝,继承,多态,封装,抽象以及更⾼级的⼀些概念,如组合、聚合及关联。也包含了 GOF 的问题。

76. 接⼝是什么?为什么要使⽤接⼝⽽不是直接使⽤具体类?

接⼝⽤于定义 API。它定义了类必须得遵循的规则。同时,它提供了⼀种抽象,因为客户端只使⽤接⼝,这样可以有多重实现,如 List 接⼝,你可以使⽤可随机访问的 ArrayList,也可以使⽤⽅便插⼊和删除的 LinkedList。接⼝中不允许普通⽅法,以此来保证抽象,但是Java 8 中你可以在接⼝声明静态⽅法和默认普通⽅法。77. Java 中,抽象类与接⼝之间有什么不同?

Java 中,抽象类和接⼝有很多不同之处,但是最重要的⼀个是 Java 中⼀个类只能继承⼀个类,但是可以实现多个接⼝。抽象类可以很好的定义⼀个家族类的默认⾏为,⽽接⼝能更好的定义类型,有助于后⾯实现多态机制参见第六条。

78. 除了单例模式,你在⽣产环境中还⽤过什么设计模式?

这需要根据你的经验来回答。⼀般情况下,你可以说依赖注⼊,⼯⼚模式,装饰模式或者观察者模式,随意选择你使⽤过的⼀种即可。不过你要准备回答接下的基于你选择的模式的问题。79. 你能解释⼀下⾥⽒替换原则吗?

严格定义:如果对每⼀个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象⽤o1替换o2时,程序P的⾏为没有变化,那么类型S是类型T的⼦类型。

通俗表述:所有引⽤基类(⽗类)的地⽅必须能透明地使⽤其⼦类的对象。也就是说⼦类可以扩展⽗类的功能,但不能改变⽗类原有的功能。它包含以下4层含义:

1. ⼦类可以实现⽗类的抽象⽅法,但不能覆盖⽗类的⾮抽象⽅法。2. ⼦类中可以增加⾃⼰特有的⽅法。3. 当⼦类的⽅法重载⽗类的⽅法时,⽅法的前置条件(即⽅法的形参)要⽐⽗类⽅法的输⼊参数更宽松。4. 当⼦类的⽅法实现⽗类的抽象⽅法时,⽅法的后置条件(即⽅法的返回值)要⽐⽗类更严格。80.什么情况下会违反迪⽶特法则?为什么会有这个问题?

迪⽶特法则建议“只和朋友说话,不要陌⽣⼈说话”,以此来减少类之间的耦合。81. 适配器模式是什么?什么时候使⽤?

适配器模式提供对接⼝的转换。如果你的客户端使⽤某些接⼝,但是你有另外⼀些接⼝,你就可以写⼀个适配去来连接这些接⼝。82. 构造器注⼊和 setter 依赖注⼊,那种⽅式更好?

每种⽅式都有它的缺点和优点。构造器注⼊保证所有的注⼊都被初始化,但是 setter 注⼊提供更好的灵活性来设置可选依赖。如果使⽤XML 来描述依赖,Setter 注⼊的可读写会更强。经验法则是强制依赖使⽤构造器注⼊,可选依赖使⽤ setter 注⼊。83. 依赖注⼊和⼯⼚模式之间有什么不同?

虽然两种模式都是将对象的创建从应⽤的逻辑中分离,但是依赖注⼊⽐⼯程模式更清晰。通过依赖注⼊,你的类就是 POJO,它只知道依赖⽽不关⼼它们怎么获取。使⽤⼯⼚模式,你的类需要通过⼯⼚来获取依赖。因此,使⽤ DI 会⽐使⽤⼯⼚模式更容易测试。84. 适配器模式和装饰器模式有什么区别?

虽然适配器模式和装饰器模式的结构类似,但是每种模式的出现意图不同。适配器模式被⽤于桥接两个接⼝,⽽装饰模式的⽬的是在不修改类的情况下给类增加新的功能。

85. 适配器模式和代理模式之前有什么不同?

这个问题与前⾯的类似,适配器模式和代理模式的区别在于他们的意图不同。由于适配器模式和代理模式都是封装真正执⾏动作的类,因此结构是⼀致的,但是适配器模式⽤于接⼝之间的转换,⽽代理模式则是增加⼀个额外的中间层,以便⽀持分配、控制或智能访问。86. 什么是模板⽅法模式?

模板⽅法提供算法的框架,你可以⾃⼰去配置或定义步骤。例如,你可以将排序算法看做是⼀个模板。它定义了排序的步骤,但是具体的⽐较,可以使⽤ Comparable 或者其语⾔中类似东西,具体策略由你去配置。列出算法概要的⽅法就是众所周知的模板⽅法。87. 什么时候使⽤访问者模式?

访问者模式⽤于解决在类的继承层次上增加操作,但是不直接与之关联。这种模式采⽤双派发的形式来增加中间层。88. 什么时候使⽤组合模式?

组合模式使⽤树结构来展⽰部分与整体继承关系。它允许客户端采⽤统⼀的形式来对待单个对象和对象容器。当你想要展⽰对象这种部分与整体的继承关系时采⽤组合模式。. 继承和组合之间有什么不同?

虽然两种都可以实现代码复⽤,但是组合⽐继承共灵活,因为组合允许你在运⾏时选择不同的实现。⽤组合实现的代码也⽐继承测试起来更加简单。

90. 描述 Java 中的重载和重写?

重载和重写都允许你⽤相同的名称来实现不同的功能,但是重载是编译时活动,⽽重写是运⾏时活动。你可以在同⼀个类中重载⽅法,但是只能在⼦类中重写⽅法。重写必须要有继承。91. OOP 中的 组合、聚合和关联有什么区别?

如果两个对象彼此有关系,就说他们是彼此相关联的。组合和聚合是⾯向对象中的两种形式的关联。组合是⼀种⽐聚合更强⼒的关联。组合中,⼀个对象是另⼀个的拥有者,⽽聚合则是指⼀个对象使⽤另⼀个对象。如果对象 A 是由对象 B 组合的,则 A 不存在的话,B⼀定不存在,但是如果 A 对象聚合了⼀个对象 B,则即使 A 不存在了,B 也可以单独存在。92. 给我⼀个符合开闭原则的设计模式的例⼦?

开闭原则要求你的代码对扩展开放,对修改关闭。这个意思就是说,如果你想增加⼀个新的功能,你可以很容易的在不改变已测试过的代码的前提下增加新的代码。有好⼏个设计模式是基于开闭原则的,如策略模式,如果你需要⼀个新的策略,只需要实现接⼝,增加配置,不需要改变核⼼逻辑。⼀个正在⼯作的例⼦是 Collections.sort() ⽅法,这就是基于策略模式,遵循开闭原则的,你不需为新的对象修改 sort()⽅法,你需要做的仅仅是实现你⾃⼰的 Comparator 接⼝。93. 什么时候使⽤享元模式(蝇量模式)?

享元模式通过共享对象来避免创建太多的对象。为了使⽤享元模式,你需要确保你的对象是不可变的,这样你才能安全的共享。JDK 中String 池、Integer 池以及 Long 池都是很好的使⽤了享元模式的例⼦。94. Java 中如何格式化⼀个⽇期?如格式化为 ddMMyyyy 的形式?

Java 中,可以使⽤ SimpleDateFormat 类或者 joda-time 库来格式⽇期。DateFormat 类允许你使⽤多种流⾏的格式来格式化⽇期。95. Java 中,怎么在格式化的⽇期中显⽰时区?pattern中加z yyyy-MM-dd HH:mm:ss.SSS Z

96. Java 中 java.util.Date 与 java.sql.Date 有什么区别?

java.sql.Date是针对SQL语句使⽤的,它只包含⽇期⽽没有时间部分,它们都有getTime⽅法返回毫秒数,⾃然就可以直接构建。

java.util.Date 是 java.sql.Date 的⽗类,前者是常⽤的表⽰时间的类,我们通常格式化或者得到当前时间都是⽤他,后者之后在读写数据库的时候⽤他,因为PreparedStament的setDate()的第2参数和ResultSet的getDate()⽅法的第2个参数都是java.sql.Date。97. Java 中,如何计算两个⽇期之间的差距?

12345678

public static int dateDiff(Date d1, Date d2) throws Exception {long n1 = d1.getTime();long n2 = d2.getTime();long diff = Math.abs(n1 - n2);diff /= 3600 * 1000 * 24;return diff;}

98. Java 中,如何将字符串 YYYYMMDD 转换为⽇期?SimpleDateFormat的parse⽅法

99. 说出⼏条 Java 中⽅法重载的最佳实践?

下⾯有⼏条可以遵循的⽅法重载的最佳实践来避免造成⾃动装箱的混乱。

a)不要重载这样的⽅法:⼀个⽅法接收 int 参数,⽽另个⽅法接收 Integer 参数。b)不要重载参数数量⼀致,⽽只是参数顺序不同的⽅法。c)如果重载的⽅法参数个数多于 5 个,采⽤可变参数。100. 说出 5 条 IO 的最佳实践

IO 对 Java 应⽤的性能⾮常重要。理想情况下,你应该在你应⽤的关键路径上避免 IO 操作。下⾯是⼀些你应该遵循的 Java IO 最佳实践:a)使⽤有缓冲区的 IO 类,⽽不要单独读取字节或字符b)使⽤ NIO 和 NIO2

c)在 finally 块中关闭流,或者使⽤ try-with-resource(Java7) 语句d)使⽤内存映射⽂件获取更快的 IO101. Object有哪些公⽤⽅法?

clone equals hashcode wait notify notifyall finalize toString getClass除了clone和finalize其他均为公共⽅法。11个⽅法,wait被重载了两次102. equals与==的区别

区别1. ==是⼀个运算符 equals是Object类的⽅法区别2. ⽐较时的区别

a. ⽤于基本类型的变量⽐较时:==⽤于⽐较值是否相等,equals不能直接⽤于基本数据类型的⽐较,需要转换为其对应的包装类型。b. ⽤于引⽤类型的⽐较时。==和equals都是⽐较栈内存中的地址是否相等 。相等为true 否则为false。但是通常会重写equals⽅法去实现对象内容的⽐较。

103. String、StringBuffer与StringBuilder的区别

第⼀点:可变和适⽤范围。String对象是不可变的,⽽StringBuffer和StringBuilder是可变字符序列。每次对String的操作相当于⽣成⼀个新的String对象,⽽对StringBuffer和StringBuilder的操作是对对象本⾝的操作,⽽不会⽣成新的对象,所以对于频繁改变内容的字符串避免使⽤String,因为频繁的⽣成对象将会对系统性能产⽣影响。

第⼆点:线程安全。String由于有final修饰,是immutable的,安全性是简单⽽纯粹的。StringBuilder和StringBuffer的区别在于StringBuilder不保证同步,也就是说如果需要线程安全需要使⽤StringBuffer,不需要同步的StringBuilder效率更⾼。104. switch能否⽤String做参数

Java1.7开始⽀持,但实际这是⼀颗Java语法糖。除此之外,byte,short,long,枚举,boolean均可⽤于switch,只有浮点型不可以。105. 封装、继承、多态封装:

1.概念:就是把对象的属性和操作(或服务)结合为⼀个独⽴的整体,并尽可能隐藏对象的内部实现细节。2.好处:

(1)隐藏内部实现细节。继承:

1.概念:继承是从已有的类中派⽣出新的类,新的类能吸收已有类的数据属性和⾏为,并能扩展新的能⼒2.好处:提⾼代码的复⽤,缩短开发周期。多态:

1.概念:多态(Polymorphism)按字⾯的意思就是“多种状态,即同⼀个实体同时具有多种形式。⼀般表现形式是程序在运⾏的过程中,同⼀种类型在不同的条件下表现不同的结果。多态也称为动态绑定,⼀般是在运⾏时刻才能确定⽅法的具体执⾏对象。2.好处:

1)将接⼝和实现分开,改善代码的组织结构和可读性,还能创建可拓展的程序。2)消除类型之间的耦合关系。允许将多个类型视为同⼀个类型。3)⼀个多态⽅法的调⽤允许有多种表现形式

106. Comparable和Comparator接⼝区别

Comparator位于包java.util下,⽽Comparable位于包java.lang下

如果我们需要使⽤Arrays或Collections的排序⽅法对对象进⾏排序时,我们需要在⾃定义类中实现Comparable接⼝并重写compareTo⽅法,compareTo⽅法接收⼀个参数,如果this对象⽐传递的参数⼩,相等或⼤时分别返回负整数、0、正整数。Comparable被⽤来提供对象的⾃然排序。String、Integer实现了该接⼝。

Comparator⽐较器的compare⽅法接收2个参数,根据参数的⽐较⼤⼩分别返回负整数、0和正整数。

Comparator 是⼀个外部的⽐较器,当这个对象⾃然排序不能满⾜你的要求时,你可以写⼀个⽐较器来完成两个对象之间⼤⼩的⽐较。⽤Comparator 是策略模式(strategy design pattern),就是不改变对象⾃⾝,⽽⽤⼀个策略对象(strategy object)来改变它的⾏为。107. 与Java集合框架相关的有哪些最好的实践

(1)根据需要选择正确的集合类型。⽐如,如果指定了⼤⼩,我们会选⽤Array⽽⾮ArrayList。如果我们想根据插⼊顺序遍历⼀个Map,我们需要使⽤TreeMap。如果我们不想重复,我们应该使⽤Set。

(2)⼀些集合类允许指定初始容量,所以如果我们能够估计到存储元素的数量,我们可以使⽤它,就避免了重新哈希或⼤⼩调整。(3)基于接⼝编程,⽽⾮基于实现编程,它允许我们后来轻易地改变实现。(4)总是使⽤类型安全的泛型,避免在运⾏时出现ClassCastException。

(5)使⽤JDK提供的不可变类作为Map的key,可以避免⾃⼰实现hashCode()和equals()。108. IO和NIO简述1、简述

在以前的Java IO中,都是阻塞式IO,NIO引⼊了⾮阻塞式IO。

第⼀种⽅式:我从硬盘读取数据,然后程序⼀直等,数据读完后,继续操作。这种⽅式是最简单的,叫阻塞IO。

第⼆种⽅式:我从硬盘读取数据,然后程序继续向下执⾏,等数据读取完后,通知当前程序(对硬件来说叫中断,对程序来说叫回调),然后此程序可以⽴即处理数据,也可以执⾏完当前操作在读取数据。2.流与块的⽐较

原来的 I/O 以流的⽅式处理数据,⽽ NIO 以块的⽅式处理数据。⾯向流 的 I/O 系统⼀次⼀个字节地处理数据。⼀个输⼊流产⽣⼀个字节的数据,⼀个输出流消费⼀个字节的数据。这样做是相对简单的。不利的⼀⾯是,⾯向流的 I/O 通常相当慢。

⼀个 ⾯向块 的 I/O 系统以块的形式处理数据。每⼀个操作都在⼀步中产⽣或者消费⼀个数据块。按块处理数据⽐按(流式的)字节处理数据要快得多。但是⾯向块的 I/O 缺少⼀些⾯向流的 I/O 所具有的优雅性和简单性。3.通道与流

Channel是⼀个对象,可以通过它读取和写⼊数据。通道与流功能类似,不同之处在于通道是双向的。⽽流只是在⼀个⽅向上移动(⼀个流必须是 InputStream 或者 OutputStream 的⼦类), ⽽通道可以⽤于读、写或者同时⽤于读写。4.缓冲区Buffer

在 NIO 库中,所有数据都是⽤缓冲区处理的。在 NIO 库中,所有数据都是⽤缓冲区处理的。Position: 表⽰下⼀次访问的缓冲区位置Limit: 表⽰当前缓冲区存放的数据容量。Capacity:表⽰缓冲区最⼤容量flip()⽅法:读写模式切换

clear⽅法:它将 limit 设置为与 capacity 相同。它设置 position 为 0。

⼆、Java⾼级(JavaEE、框架、服务器、⼯具等)

1. Servlet

1.1 Servlet继承实现结构

1234

Servlet (接⼝) --> init|service|destroy⽅法GenericServlet(抽象类) --> 与协议⽆关的ServletHttpServlet(抽象类) --> 实现了http协议⾃定义Servlet --> 重写doGet/doPost

1.2 编写Servlet的步骤1. 继承HttpServlet2. 重写doGet/doPost⽅法3. 在web.xml中注册servlet1.3 Servlet⽣命周期

1. init:仅执⾏⼀次,负责装载servlet时初始化servlet对象2. service:核⼼⽅法,⼀般get/post两种⽅式3. destroy:停⽌并卸载servlet,释放资源1.4 过程

1. 客户端request请求 -> 服务器检查Servlet实例是否存在 -> 若存在调⽤相应service⽅法2. 客户端request请求 -> 服务器检查Servlet实例是否存在 -> 若不存在装载Servlet类并创建实例 -> 调⽤init初始化 -> 调⽤service3. 加载和实例化、初始化、处理请求、服务结束1.5 doPost⽅法要抛出的异常:ServletExcception、IOException1.6 Servlet容器装载Servlet

1. web.xml中配置load-on-startup启动时装载2. 客户⾸次向Servlet发送请求3. Servlet类⽂件被更新后, 重新装载Servlet1.7 HttpServlet容器响应web客户请求流程1. Web客户向servlet容器发出http请求2. servlet容器解析Web客户的http请求3. servlet容器创建⼀个HttpRequest对象, 封装http请求信息4. servlet容器创建⼀个HttpResponse对象5. servlet容器调⽤HttpServlet的service⽅法, 把HttpRequest和HttpResponse对象作为service⽅法的参数传给HttpServlet对象6. HttpServlet调⽤httprequest的有关⽅法, 获取http请求信息7. httpservlet调⽤httpresponse的有关⽅法, ⽣成响应数据8. Servlet容器把HttpServlet的响应结果传给web客户1.8 HttpServletRequest完成的⼀些功能1. request.getCookie()2. request.getHeader(String s)3. request.getContextPath()4. request.getSession()12

HttpSession session = request.getSession(boolean create)返回当前请求的会话

1.9 HttpServletResponse完成⼀些的功能1. 设http响应头2. 设置Cookie3. 输出返回数据1.10 Servlet与JSP九⼤内置对象的关系JSP对象 怎样获得

1234567

1. out -> response.getWriter2. request -> Service⽅法中的req参数3. response -> Service⽅法中的resp参数4. session -> request.getSession5. application -> getServletContext6. exception -> Throwable7. page -> this

8. pageContext -> PageContext9. Config -> getServletConfig

exception是JSP九⼤内置对象之⼀,其实例代表其他页⾯的异常和错误。只有当页⾯是错误处理页⾯时,即isErroePage为 true时,该对象才可以使⽤。2. JSP

JSP的前⾝就是Servlet3. Tomcat

3.1 Tomcat容器的等级

Tomcat - Container - Engine - Host - Servlet - 多个Context(⼀个Context对应⼀个web⼯程)-Wrapper4. struts

1. struts可进⾏⽂件上传2. struts基于MVC模式3. struts让流程结构更清晰4. struts有许多action类, 会增加类⽂件数⽬5. Hibernate的7⼤⿎励措施

1. 尽量使⽤many-to-one, 避免使⽤单项one-to-many2. 灵活使⽤单项one-to-many3. 不⽤⼀对⼀, 使⽤多对⼀代替⼀对⼀4. 配置对象缓存, 不使⽤集合对象5. ⼀对多使⽤bag, 多对⼀使⽤set6. 继承使⽤显⽰多态7. 消除⼤表, 使⽤⼆级缓存6. Hibernate延迟加载

1. Hibernate2延迟加载实现:a)实体对象 b)集合(Collection)2. Hibernate3 提供了属性的延迟加载功能当Hibernate在查询数据的时候,数据并没有存在与内存中,当程序真正对数据的操作时,对象才存在与内存中,就实现了延迟加载,他节省了服务器的内存开销,从⽽提⾼了服务器的性能。3. hibernate使⽤Java反射机制,⽽不是字节码增强程序来实现透明性。4. hibernate的性能⾮常好,因为它是个轻量级框架。映射的灵活性很出⾊。它⽀持各种关系数据库,从⼀对⼀到多对多的各种复杂关系。7. Java 中,DOM 和 SAX 解析器有什么不同?

DOM 解析器将整个 XML ⽂档加载到内存来创建⼀棵 DOM 模型树,这样可以更快的查找节点和修改 XML 结构,⽽ SAX 解析器是⼀个基于事件的解析器,不会将整个 XML ⽂档加载到内存。由于这个原因,DOM ⽐ SAX 更快,也要求更多的内存,但不适合于解析⼤的XML ⽂件。

8. Java 中,Maven 和 ANT 有什么区别?

虽然两者都是构建⼯具,都⽤于创建 Java 应⽤,但是 Maven 做的事情更多,在基于“约定优于配置”的概念下,提供标准的Java 项⽬结构,同时能为应⽤⾃动管理依赖(应⽤中所依赖的 JAR ⽂件)。9. 解析XML不同⽅式对⽐DOM、SAX、JDOM、DOM4J

DOM DOM树驻留内存可以进⾏修改和写⼊,耗费内存。

步骤:创建DocumentBuilderFactory对象 -> 创建DocumentBuilder对象 -> Document document = db.parse(“xml”)

SAX 事件驱动模式获取⼀个SAXParserFactory⼯⼚的实例 -> 根据该实例获取SAXParser -> 创建Handler对象 -> 调⽤SAXParser的parse⽅法解析⽤于读取节点数据 不易编码 事件有顺序 很难同时访问xml的多处数据

JDOM创建⼀个SAXBuilder的对象 -> 创建⼀个输⼊流,加载xml⽂件 ->通过saxBuilder的build⽅法将输⼊流加载⾄saxBuilder并接收Document对象使⽤具体类⽽不使⽤接⼝

DOM4J通过SAXReader的read⽅法加载xml⽂件并获取document对象使⽤接⼝和抽象类,灵活性好,功能强⼤10. Nginx相关请参考我的

11. XML与JSON对⽐和区别XML

123

1)应⽤⼴泛,可扩展性强,被⼴泛应⽤各种场合2)读取、解析没有JSON快3)可读性强,可描述复杂结构

JSON

123456

1)结构简单,都是键值对

2)读取、解析速度快,很多语⾔⽀持3)传输数据量⼩,传输速率⼤⼤提⾼4)描述复杂结构能⼒较弱

JavaScript、PHP等原⽣⽀持,简化了读取解析。成为当前互联⽹时代普遍应⽤的数据结构。

三、多线程和并发

0. Java 中的 volatile 变量是什么

Java 语⾔提供了⼀种稍弱的同步机制,即volatile变量。但是volatile并不容易完全被正确、完整的理解。

⼀般来说,volatile具备2条语义,或者说2个特性。第⼀是保证volatile修饰的变量对所有线程的可见性,这⾥的可见性是指当⼀条线程修改了该变量,新值对于其它线程来说是⽴即可以得知的。⽽普通变量做不到这⼀点。第⼆条语义是禁⽌指令重排序优化,这条语义在JDK1.5才被修复。

关于第⼀点:根据JMM,所有的变量存储在主内存,⽽每个线程还有⾃⼰的⼯作内存,线程的⼯作内存保存该线程使⽤到的变量的主内存副本拷贝,线程对变量的操作在⼯作内存中进⾏,不能直接读写主内存的变量。在volatile可见性这⼀点上,普通变量做不到的原因正因如此。⽐如,线程A修改了⼀个普通变量的值,然后向主内存进⾏回写,线程B在线程A回写完成后再从主内存读取,新变量才能对线程B可见。其实,按照虚拟机规范,volatile变量依然有⼯作内存的拷贝,要借助主内存来实现可见性。但由于volatile的特殊规则保证了新值能⽴即同步回主内存,以及每次使⽤从主内存刷新,以此保证了多线程操作volatile变量的可见性。

关于第⼆点:先说指令重排序,指令重排序是指CPU采⽤了允许将多条指令不按规定顺序分开发送给相应的处理单元处理,但并不是说任意重排,CPU需要正确处理指令依赖情况确保最终的正确结果,指令重排序是机器级的优化操作。那么为什么volatile要禁⽌指令重排序呢,⼜是如何去做的。举例,DCL(双重检查加锁)的单例模式。volatile修饰后,代码中将会插⼊许多内存屏障指令保证处理器不发⽣乱序执⾏。同时由于Happens-before规则的保证,在刚才的例⼦中写操作会发⽣在后续的读操作之前。

除了以上2点,volatile还保证对于位long和double的读取是原⼦性的。因为在JMM中允许虚拟机对未被volatile修饰的位的long和double读写操作分为2次32位的操作来执⾏,这也就是所谓的long和double的⾮原⼦性协定。

基于以上⼏点,我们知道volatile虽然有这些语义和特性在并发的情况下仍然不能保证线程安全。⼤部分情况下仍然需要加锁。

除⾮是以下2种情况,1.运算结果不依赖变量的当前值,或者能够确保只有单⼀线程修改变量的值;2.变量不需要与其他的状态变量共同参与不变约束。1. volatile简述

Java 语⾔提供了⼀种稍弱的同步机制,即volatile变量.⽤来确保将变量的更新操作通知到其他线程,保证了新值能⽴即同步到主内存,以及每次使⽤前⽴即从主内存刷新。 当把变量声明为volatile类型后,编译器与运⾏时都会注意到这个变量是共享的。volatile修饰变量,每次被线程访问时强迫其从主内存重读该值,修改后再写回。保证读取的可见性,对其他线程⽴即可见。volatile的另⼀个语义是禁⽌指令重排序优化。但是volatile并不保证原⼦性,也就不能保证线程安全。2. Java 中能创建 volatile 数组吗?

能,Java 中可以创建 volatile 类型数组,不过只是⼀个指向数组的引⽤,⽽不是整个数组。我的意思是,如果改变引⽤指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 就不能起到之前的保护作⽤了。3. volatile 能使得⼀个⾮原⼦操作变成原⼦操作吗?

⼀个典型的例⼦是在类中有⼀个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量不是原⼦的,需要分成两步,如果⼀个线程正在修改该 long 变量的值,另⼀个线程可能只能看到该值的⼀半(前 32 位)。但是对⼀个 volatile 型的 long 或 double 变量的读写是原⼦。

4. volatile 禁⽌指令重排序的底层原理 指令重排序,是指CPU允许多条指令不按程序规定的顺序分开发送给相应电路单元处理。但并不是说任意重排,CPU需要能正确处理指令依赖情况以正确的执⾏结果。volatile禁⽌指令重排序是通过内存屏障实现的,指令重排序不能把后⾯的指令重排序到内存屏障之前。由内存屏障保证⼀致性。注:该条语义在JDK1.5才得以修复,这点也是JDK1.5之前⽆法通过双重检查加锁来实现单例模式的原因。

5. volatile 类型变量提供什么保证?

volatile 变量提供有序性和可见性保证,例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保⼀个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原⼦性,如读 位数据类型,像 long 和 double 都不是原⼦的,但 volatile 类型的 double 和 long 就是原⼦的。

volatile的使⽤场景:

1. 运算结果不依赖变量的当前值,或者能够确保只有单⼀的线程修改该值2. 变量不需要与其他状态变量共同参与不变约束

6. volatile的性能

volatile变量的读操作性能消耗和普通变量差不多,但是写操作可能相对慢⼀些,因为它需要在本地代码中插⼊许多内存屏障指令以确保处理器不发⽣乱序执⾏。⼤多数情况下,volatile总开销⽐锁低,但我们要注意volatile的语义能否满⾜使⽤场景。7. 10 个线程和 2 个线程的同步代码,哪个更容易写?

从写代码的⾓度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互独⽴的。但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更⼤的竞争,所以你需要利⽤同步技术,如锁分离,这要求更复杂的代码和专业知识。8. 你是如何调⽤ wait()⽅法的?使⽤ if 块还是循环?为什么?

wait() ⽅法应该在循环调⽤,因为当线程获取到 CPU 开始执⾏的时候,其他条件可能还没有满⾜,所以在处理前,循环检测条件是否满⾜会更好。下⾯是⼀段标准的使⽤ wait 和 notify ⽅法的代码:

1234567

// The standard idiom for using the wait methodsynchronized (obj) {

while (condition does not hold)

obj.wait(); // (Releases lock, and reacquires on wakeup)... // Perform action appropriate to condition}

参见 Effective Java 第 69 条,获取更多关于为什么应该在循环中来调⽤ wait ⽅法的内容。9. 什么是多线程环境下的伪共享(false sharing)?

伪共享是多线程系统(每个处理器有⾃⼰的局部缓存)中⼀个众所周知的性能问题。伪共享发⽣在不同处理器的上的线程对变量的修改依赖于相同的缓存⾏,如下图所⽰:

伪共享问题很难被发现,因为线程可能访问完全不同的全局变量,内存中却碰巧在很相近的位置上。如其他诸多的并发问题,避免伪共享的最基本⽅式是仔细审查代码,根据缓存⾏来调整你的数据结构。10. 线程的run⽅法和start⽅法

run⽅法

只是thread类的⼀个普通⽅法,若直接调⽤程序中依然只有主线程这⼀个线程,还要顺序执⾏,依然要等待run⽅法体执⾏完毕才可执⾏下⾯的代码。

start⽅法

⽤start⽅法来启动线程,是真正实现了多线程。调⽤thread类的start⽅法来启动⼀个线程,此时线程处于就绪状态,⼀旦得到cpu时间⽚,就开始执⾏run⽅法。

11. ReadWriteLock(读写锁)

写写互斥 读写互斥 读读并发, 在读多写少的情况下可以提⾼效率

12. resume(继续挂起的线程)和suspend(挂起线程)⼀起⽤13. wait与notify、notifyall⼀起⽤14. sleep与wait的异同点

sleep是Thread类的静态⽅法, wait来⾃object类sleep⽅法短暂停顿不释放锁, wait⽅法条件等待要释放锁,因为只有这样,其他等待的线程才能在满⾜条件时获取到该锁。wait, notify, notifyall必须在同步代码块中使⽤, sleep可以在任何地⽅使⽤都可以抛出InterruptedException15. 让⼀个线程停⽌执⾏异常 - 停⽌执⾏休眠 - 停⽌执⾏阻塞 - 停⽌执⾏16. ThreadLocal简介

16.1 ThreadLocal解决了变量并发访问的冲突问题

当使⽤ThreadLocal维护变量时,ThreadLocal为每个使⽤该变量的线程提供独⽴的变量副本,每个线程都可以独⽴地改变⾃⼰的副本,⽽不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)16.2 与synchronized同步机制的⽐较

⾸先,它们都是为了解决多线程中相同变量访问冲突问题。不过,在同步机制中,要通过对象的锁机制保证同⼀时间只有⼀个线程访问该变量。该变量是线程共享的, 使⽤同步机制要求程序缜密地分析什么时候对该变量读写, 什么时候需要锁定某个对象, 什么时候释放对象锁等复杂的问题,程序设计编写难度较⼤, 是⼀种“以时间换空间”的⽅式。⽽ThreadLocal采⽤了以“以空间换时间”的⽅式。17. 线程局部变量原理

当使⽤ThreadLocal维护变量时,ThreadLocal为每个使⽤该变量的线程提供独⽴的变量副本,每个线程都可以独⽴地改变⾃⼰的副本,⽽不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)

线程局部变量是局限于线程内部的变量,属于线程⾃⾝所有,不在多个线程间共享。Java 提供 ThreadLocal 类来⽀持线程局部变量,是⼀种实现线程安全的⽅式。但是在管理环境下(如 web 服务器)使⽤线程局部变量的时候要特别⼩⼼,在这种情况下,⼯作线程的⽣命周期⽐任何应⽤变量的⽣命周期都要长。任何线程局部变量⼀旦在⼯作完成后没有释放,Java 应⽤就存在内存泄露的风险。ThreadLocal的⽅法:void set(T value)、T get()以及T initialValue()。ThreadLocal是如何为每个线程创建变量的副本的:

⾸先,在每个线程Thread内部有⼀个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是⽤来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread⾥⾯,threadLocals为空,当通过ThreadLocal变量调⽤get()⽅法或者set()⽅法,就会对Thread类中的threadLocals进⾏初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程⾥⾯,如果要使⽤副本变量,就可以通过get⽅法在threadLocals⾥⾯查找。总结:

1. 实际的通过ThreadLocal创建的副本是存储在每个线程⾃⼰的threadLocals中的

2. 为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上⾯代码

中的longLocal和stringLocal;

3. 在进⾏get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调⽤set就能正常访问的话,必须重写initialValue()⽅法

18. JDK提供的⽤于并发编程的同步器

1. Semaphore Java并发库的Semaphore可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过acquire() 获取⼀个许可,如果没有就等待,⽽ release() 释放⼀个许可。2. CyclicBarrier 主要的⽅法就是⼀个:await()。await()⽅法每被调⽤⼀次,计数便会减少1,并阻塞住当前线程。当计数减⾄0时,阻塞解除,所有在此CyclicBarrier上⾯阻塞的线程开始运⾏。3. CountDownLatch 直译过来就是倒计数(CountDown)门闩(Latch)。倒计数不⽤说,门闩的意思顾名思义就是阻⽌前进。在这⾥就是指CountDownLatch.await() ⽅法在倒计数为0之前会阻塞当前线程。19. 什么是 Busy spin?我们为什么要使⽤它?

Busy spin 是⼀种在不释放 CPU 的基础上等待事件的技术。它经常⽤于避免丢失 CPU 缓存中的数据(如果线程先暂停,之后在其他CPU上运⾏就会丢失)。所以,如果你的⼯作要求低延迟,并且你的线程⽬前没有任何顺序,这样你就可以通过循环检测队列中的新消息来代替调⽤ sleep() 或 wait() ⽅法。它唯⼀的好处就是你只需等待很短的时间,如⼏微秒或⼏纳秒。LMAX 分布式框架是⼀个⾼性能线程间通信的库,该库有⼀个 BusySpinWaitStrategy 类就是基于这个概念实现的,使⽤ busy spin 循环 EventProcessors 等待屏障。20. Java 中怎么获取⼀份线程 dump ⽂件?

在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应⽤的 dump ⽂件。在 Windows 下,你可以按下 Ctrl +Break 来获取。这样 JVM 就会将线程的 dump ⽂件打印到标准输出或错误⽂件中,它可能打印在控制台或者⽇志⽂件中,具体位置依赖应⽤的配置。

21. Swing 是线程安全的?

不是,Swing 不是线程安全的。你不能通过任何线程来更新 Swing 组件,如 JTable、JList 或 JPanel,事实上,它们只能通过 GUI 或AWT 线程来更新。这就是为什么 Swing 提供 invokeAndWait() 和 invokeLater() ⽅法来获取其他线程的 GUI 更新请求。这些⽅法将更新请求放⼊ AWT 的线程队列中,可以⼀直等待,也可以通过异步更新直接返回结果。22. ⽤ wait-notify 写⼀段代码来解决⽣产者-消费者问题?

记住在同步块中调⽤ wait() 和 notify()⽅法,如果阻塞,通过循环来测试等待条件。23. ⽤ Java 写⼀个线程安全的单例模式(Singleton)?

当我们说线程安全时,意思是即使初始化是在多线程环境中,仍然能保证单个实例。Java 中,使⽤枚举作为单例类是最简单的⽅式来创建线程安全单例模式的⽅式。参见我整理的单例的⽂章

24. Java 中,编写多线程程序的时候你会遵循哪些最佳实践?这是我在写Java 并发程序的时候遵循的⼀些最佳实践:a)给线程命名,这样可以帮助调试。

b)最⼩化同步的范围,⽽不是将整个⽅法同步,只对关键部分做同步。c)如果可以,更偏向于使⽤ volatile ⽽不是 synchronized。

d)使⽤更⾼层次的并发⼯具,⽽不是使⽤ wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及Semeaphore。

e)优先使⽤并发集合,⽽不是对集合进⾏同步。并发集合提供更好的可扩展性。25. 说出⾄少 5 点在 Java 中使⽤线程的最佳实践。

这个问题与之前的问题类似,你可以使⽤上⾯的答案。对线程来说,你应该:a)对线程命名

b)将线程和任务分离,使⽤线程池执⾏器来执⾏ Runnable 或 Callable。c)使⽤线程池

26. 在多线程环境下,SimpleDateFormat 是线程安全的吗?

不是,⾮常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使⽤,除⾮是在对外线程安全的环境中使⽤,如将 SimpleDateFormat 在 ThreadLocal 中。如果你不这么做,在解析或者格式化⽇期的时候,可能会获取到⼀个不正确的结果。因此,从⽇期、时间处理的所有实践来说,我强⼒推荐 joda-time 库。27. Happens-Before规则

程序次序规则按控制流顺序先后发⽣

管程锁定规则⼀个unlock操作先⾏发⽣于后⾯对同⼀个锁的lock操作

volatile变量规则对⼀个volatile变量的写操作先⾏发⽣于后⾯对这个变量的读操作

线程启动规则start⽅法先⾏发⽣于线程的每⼀个动作

线程中断规则对线程的interrupt⽅法调⽤先⾏发⽣于被中断线程的代码检测到中断时间的发⽣

线程终⽌规则线程内的所有操作都先⾏发⽣于对此线程的终⽌检测

对象终结规则⼀个对象的初始化完成先⾏发⽣于它的finalize⽅法的开始

传递性如果A先⾏发⽣于操作B,B先⾏发⽣于操作C,则A先⾏发⽣于操作C28. 什么是线程

线程是操作系统能够进⾏运算调度的最⼩单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进⾏多处理器编程,可以使⽤多线程对运算密集型任务提速。⽐如,如果⼀个线程完成⼀个任务要100 毫秒,那么⽤⼗个线程完成改任务只需 10 毫秒。Java在语⾔层⾯对多线程提供了很好的⽀持。29. 线程和进程有什么区别从概念上:

进程:⼀个程序对⼀个数据集的动态执⾏过程,是分配资源的基本单位。线程:存在于进程内,是进程内的基本调度单位。共享进程的资源。从执⾏过程中来看:

进程:拥有独⽴的内存单元,⽽多个线程共享内存,从⽽提⾼了应⽤程序的运⾏效率。

线程:每⼀个独⽴的线程,都有⼀个程序运⾏的⼊⼝、顺序执⾏序列、和程序的出⼝。但是线程不能够独⽴的执⾏,必须依存在应⽤程序中,由应⽤程序提供多个线程执⾏控制。从逻辑⾓度来看:(重要区别)

多线程的意义在于⼀个应⽤程序中,有多个执⾏部分可以同时执⾏。但是,操作系统并没有将多个线程看做多个独⽴的应⽤,来实现进程的调度和管理及资源分配。

简⾔之,⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程。进程是资源分配的基本单位,线程共享进程的资源。30. ⽤ Runnable 还是 Thread

Java 不⽀持类的多重继承,但允许你调⽤多个接⼝。所以如果你要继承其他类,当然是实现Runnable接⼝好了。31. Java 中 Runnable 和 Callable 有什么不同

Runnable和 Callable 都代表那些要在不同的线程中执⾏的任务。Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增加的。它们的主要区别是 Callable 的 call () ⽅法可以返回值和抛出异常,⽽ Runnable 的 run ()⽅法没有这些功能。32. Java 中 CyclicBarrier 和 CountDownLatch 有什么不同

它们都是JUC下的类,CyclicBarrier 和 CountDownLatch 都可以⽤来让⼀组线程等待其它线程。区别在于CountdownLatch计数⽆法被重置。如果需要重置计数,请考虑使⽤ CyclicBarrier。33. Java 内存模型是什么

Java 内存模型规定和指引Java 程序在不同的内存架构、CPU 和操作系统间有确定性地⾏为。它在多线程的情况下尤其重要。Java内存模型对⼀个线程所做的变动能被其它线程可见提供了保证,它们之间是先⾏发⽣关系。这个关系定义了⼀些规则让程序员在并发编程时思路更清晰。

线程内的代码能够按先后顺序执⾏,这被称为程序次序规则。

对于同⼀个锁,⼀个解锁操作⼀定要发⽣在时间上后发⽣的另⼀个锁定操作之前,也叫做管程锁定规则。前⼀个对volatile的写操作在后⼀个volatile的读操作之前,也叫volatile变量规则。⼀个线程内的任何操作必需在这个线程的 start ()调⽤之后,也叫作线程启动规则。⼀个线程的所有操作都会在线程终⽌之前,线程终⽌规则。

⼀个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。a先⾏于b,b先⾏于c,传递性

34. 什么是线程安全?Vector 是⼀个线程安全类吗

如果你的代码所在的进程中有多个线程在同时运⾏,⽽这些线程可能会同时运⾏这段代码。如果每次运⾏结果和单线程运⾏的结果是⼀样的,⽽且其他的变量的值也和预期的是⼀样的,就是线程安全的。⼀个线程安全的计数器类的同⼀个实例对象在被多个线程使⽤的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和⾮线程安全的。Vector 是⽤同步⽅法来实现线程安全的,⽽和它相似的ArrayList 不是线程安全的。

35. Java 中什么是竞态条件? 举个例⼦说明。

竞态条件会导致程序在并发情况下出现⼀些 bugs。多线程对⼀些资源的竞争的时候就会产⽣竞态条件,如果⾸先要执⾏的程序竞争失败排到后⾯执⾏了,那么整个程序就会出现⼀些不确定的 bugs。这种 bugs 很难发现⽽且会重复出现,因为线程间的随机竞争。⼏类竞态条件check-and-act、读取-修改-写⼊、put-if-absent。36. Java 中如何停⽌⼀个线程

当 run () 或者 call () ⽅法执⾏完的时候线程会⾃动结束,如果要⼿动结束⼀个线程,你可以⽤ volatile 布尔变量来退出 run ()⽅法的循环或者是取消任务来中断线程。其他情形:异常 - 停⽌执⾏ 休眠 - 停⽌执⾏ 阻塞 - 停⽌执⾏37. ⼀个线程运⾏时发⽣异常会怎样

简单的说,如果异常没有被捕获该线程将会停⽌执⾏。Thread.UncaughtExceptionHandler 是⽤于处理未捕获异常造成线程突然中断情况的⼀个内嵌接⼝。当⼀个未捕获异常将造成线程中断的时候 JVM 会使⽤ Thread.getUncaughtExceptionHandler ()来查询线程的UncaughtExceptionHandler 并将线程和异常作为参数传递给 handler 的 uncaughtException ()⽅法进⾏处理。38. 如何在两个线程间共享数据?

通过共享对象来实现这个⽬的,或者是使⽤像阻塞队列这样并发的数据结构39. Java 中 notify 和 notifyAll 有什么区别

notify ()⽅法不能唤醒某个具体的线程,所以只有⼀个线程在等待的时候它才有⽤武之地。⽽ notifyAll ()唤醒所有线程并允许他们争夺锁确保了⾄少有⼀个线程能继续运⾏。

40. 为什么 wait, notify 和 notifyAll 这些⽅法不在 thread 类⾥⾯

⼀个很明显的原因是 JAVA 提供的锁是对象级的⽽不是线程级的。如果线程需要等待某些锁那么调⽤对象中的 wait ()⽅法就有意义了。如果 wait ()⽅法定义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait,notify 和 notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对象。41. 什么是 FutureTask?

在 Java 并发程序中 FutureTask 表⽰⼀个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等⽅法。只有当运算完成的时候结果才能取回,如果运算尚未完成 get ⽅法将会阻塞。⼀个 FutureTask 对象可以对调⽤了 Callable 和 Runnable 的对象进⾏包装,由于 FutureTask 也是调⽤了 Runnable 接⼝所以它可以提交给 Executor 来执⾏。42. Java 中 interrupted 和 isInterruptedd ⽅法的区别interrupted是静态⽅法,isInterruptedd是⼀个普通⽅法

如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调⽤interrupted⽅法,第⼀次会返回true。然后,当前线程的中断状态被⽅法内部清除了。第⼆次调⽤时就会返回false。如果你刚开始⼀直调⽤isInterrupted,则会⼀直返回true,除⾮中间线程的中断状态被其他操作清除了。也就是说isInterrupted 只是简单的查询中断状态,不会对状态进⾏修改。43. 为什么 wait 和 notify ⽅法要在同步块中调⽤

如果不这么做,代码会抛出 IllegalMonitorStateException异常。还有⼀个原因是为了避免 wait 和 notify 之间产⽣竞态条件。44. 为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满⾜结束条件的情况下退出。因此,当⼀个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在 notify ⽅法调⽤之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使⽤ wait ⽅法效果更好的原因。45. Java 中的同步集合与并发集合有什么区别

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,不过并发集合的可扩展性更⾼。在 Java1.5 之前程序员们只有同步集合来⽤且在多线程并发的时候会导致争⽤,阻碍了系统的扩展性。Java1.5加⼊了并发集合像 ConcurrentHashMap,不仅提供线程安全还⽤锁分离和内部分区等现代技术提⾼了可扩展性。它们⼤部分位于JUC包下。46. 什么是线程池? 为什么要使⽤它?

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,⽽且⼀个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若⼲线程来响应处理,它们被称为线程池,⾥⾯的线程叫⼯作线程。从 JDK1.5 开始,Java API 提供了Executor 框架让你可以创建不同的线程池。⽐如单线程池,每次处理⼀个任务;数⽬固定的线程池或者是缓存线程池(⼀个适合很多⽣存期短的任务的程序的可扩展线程池)。47. 如何写代码来解决⽣产者消费者问题?

在现实中你解决的许多线程问题都属于⽣产者消费者模型,就是⼀个线程⽣产任务供其它线程进⾏消费,你必须知道怎么进⾏线程间通信来解决这个问题。⽐较低级的办法是⽤ wait 和 notify 来解决这个问题,⽐较赞的办法是⽤ Semaphore 或者 BlockingQueue 来实现⽣产者消费者模型。48.如何避免死锁?

死锁是指两个或两个以上的进程在执⾏过程中,因争夺资源⽽造成的⼀种互相等待的现象,若⽆外⼒作⽤,它们都将⽆法推进下去。这是⼀个严重的问题,因为死锁会让你的程序挂起⽆法完成任务,死锁的发⽣必须满⾜以下四个条件:互斥条件:⼀个资源每次只能被⼀个进程使⽤。

请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。不剥夺条件:进程已获得的资源,在末使⽤完之前,不能强⾏剥夺。循环等待条件:若⼲进程之间形成⼀种头尾相接的循环等待资源关系。

避免死锁最简单的⽅法就是阻⽌循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以⼀定的顺序(升序或降序)做操作来避免死锁。49. Java 中活锁和死锁有什么区别?

活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是⼀种特殊的饥饿。⼀个现实的活锁例⼦是两个⼈在狭⼩的⾛廊碰到,两个⼈都试着避让对⽅好让彼此通过,但是因为避让的⽅向都⼀样导致最后谁都不能通过⾛廊。简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执⾏。50. 怎么检测⼀个线程是否拥有锁

在 java.lang.Thread 中有⼀个⽅法叫 holdsLock,当且仅当当前线程拥有某个具体对象的锁时它返回true。51. 你如何在 Java 中获取线程堆栈

在 Linux 下,你可以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java 应⽤的 dump ⽂件。在 Windows 下,你可以按下 Ctrl +Break 来获取。这样 JVM 就会将线程的 dump ⽂件打印到标准输出或错误⽂件中,它可能打印在控制台或者⽇志⽂件中,具体位置依赖应⽤的配置。

52.Java 中 synchronized 和 ReentrantLock 有什么不同

Java 在过去很长⼀段时间只能通过 synchronized 关键字来实现互斥,它有⼀些缺点。⽐如你不能扩展锁之外的⽅法或者块边界,尝试获取锁时不能中途取消等。Java 5 通过 Lock 接⼝提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与synchronized 相同的并发性和内存语义且它还具有可扩展性。53.有三个线程 T1,T2,T3,怎么确保它们按顺序执⾏

可以⽤线程类的 join ()⽅法。具体操作是在T3的run⽅法中调⽤t2.join(),让t2执⾏完再执⾏t3;T2的run⽅法中调⽤t1.join(),让t1执⾏完再执⾏t2。这样就按T1,T2,T3的顺序执⾏了54.Thread 类中的 yield ⽅法有什么作⽤

Yield ⽅法可以暂停当前正在执⾏的线程对象,让其它有相同优先级的线程执⾏。它是⼀个静态⽅法⽽且只保证当前线程放弃 CPU 占⽤⽽不能保证使其它线程⼀定能占⽤ CPU,执⾏ yield的线程有可能在进⼊到暂停状态后马上⼜被执⾏。55.Java 中 ConcurrentHashMap 的并发度是什么

ConcurrentHashMap 把实际 map 划分成若⼲部分来实现它的可扩展性和线程安全。这种划分是使⽤并发度获得的,它是ConcurrentHashMap 类构造函数的⼀个可选参数,默认值为 16,这样在多线程情况下就能避免争⽤。56.Java 中 Semaphore是什么

JUC下的⼀种新的同步类,它是⼀个计数信号。从概念上讲,Semaphore信号量维护了⼀个许可集合。如有必要,在许可可⽤前会阻塞每⼀个 acquire,然后再获取该许可。每个 release添加⼀个许可,从⽽可能释放⼀个正在阻塞的获取者。但是,不使⽤实际的许可对象,Semaphore 只对可⽤许可的号码进⾏计数,并采取相应的⾏动。信号量常常⽤于多线程的代码中,⽐如数据库连接池。57.如果你提交任务时,线程池队列已满。会发会⽣什么?

这个问题问得很狡猾,许多程序员会认为该任务会阻塞直到线程池队列有空位。事实上如果⼀个任务不能被调度执⾏那么ThreadPoolExecutor’s submit ()⽅法将会抛出⼀个 RejectedExecutionException 异常。58.Java 线程池中 submit () 和 execute ()⽅法有什么区别

两个⽅法都可以向线程池提交任务,execute ()⽅法的返回类型是 void,它定义在 Executor 接⼝中, ⽽ submit ()⽅法可以返回持有计算结果的 Future 对象,它定义在 ExecutorService 接⼝中,它扩展了 Executor 接⼝,其它线程池类像 ThreadPoolExecutor 和ScheduledThreadPoolExecutor 都有这些⽅法。59.什么是阻塞式⽅法?

阻塞式⽅法是指程序会⼀直等待该⽅法完成期间不做其他事情,ServerSocket 的 accept ()⽅法就是⼀直等待客户端连接。这⾥的阻塞是指调⽤结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。此外,还有异步和⾮阻塞式⽅法在任务完成前就返回。60.Swing 是线程安全的吗?

你可以很肯定的给出回答,Swing 不是线程安全的。你不能通过任何线程来更新 Swing 组件,如 JTable、JList 或 JPanel,事实上,它们只能通过 GUI 或 AWT 线程来更新。这就是为什么 Swing 提供 invokeAndWait() 和 invokeLater() ⽅法来获取其他线程的 GUI 更新请求。这些⽅法将更新请求放⼊ AWT 的线程队列中,可以⼀直等待,也可以通过异步更新直接返回结果。61.Java 中 invokeAndWait 和 invokeLater 有什么区别

这两个⽅法是 Swing API 提供给 Java 开发者⽤来从当前线程⽽不是事件派发线程更新 GUI 组件⽤的。InvokeAndWait ()同步更新 GUI组件,⽐如⼀个进度条,⼀旦进度更新了,进度条也要做出相应改变。如果进度被多个线程跟踪,那么就调⽤ invokeAndWait ()⽅法请求事件派发线程对组件进⾏相应更新。⽽ invokeLater ()⽅法是异步调⽤更新组件的。62.Swing API 中那些⽅法是线程安全的?

虽然Swing不是线程安全的但是有⼀些⽅法是可以被多线程安全调⽤的。如repaint (), revalidate ()。 JTextComponent 的 setText ()⽅法和 JTextArea 的 insert () 和 append () ⽅法也是线程安全的。63.如何在 Java 中创建 Immutable 对象

Immutable 对象可以在没有同步的情况下共享,降低了对该对象进⾏并发访问时的同步化开销。可是 Java 没有@Immutable 这个注解符,要创建不可变类,要实现下⾯⼏个步骤:通过构造⽅法初始化所有成员、对变量不要提供 setter ⽅法、将所有的成员声明为私有的,这样就不允许直接访问这些成员、在 getter ⽅法中,不要直接返回对象本⾝,⽽是克隆对象,并返回对象的拷贝。.Java 中的 ReadWriteLock 是什么?

⼀般⽽⾔,读写锁是⽤来提升并发程序性能的锁分离技术的成果。Java 中的 ReadWriteLock 是 Java 5 中新增的⼀个接⼝,⼀个ReadWriteLock 维护⼀对关联的锁,⼀个⽤于只读操作⼀个⽤于写。在没有写线程的情况下⼀个读锁可能会同时被多个读线程持有。写锁是独占的,你可以使⽤ JDK 中的 ReentrantReadWriteLock 来实现这个规则,它最多⽀持 65535 个写锁和 65535 个读锁。65.多线程中的忙循环是什么?

忙循环就是程序员⽤循环让⼀个线程等待,不像传统⽅法 wait (), sleep () 或 yield () 它们都放弃了 CPU 控制,⽽忙循环不会放弃CPU,它就是在运⾏⼀个空循环。这么做的⽬的是为了保留 CPU 缓存,在多核系统中,⼀个等待线程醒来的时候可能会在另⼀个内核运⾏,这样会重建缓存。为了避免重建缓存和减少等待重建的时间就可以使⽤它了。66.volatile 变量和 atomic 变量有什么不同

volatile 变量和 atomic 变量看起来很像,但功能却不⼀样。volatile 变量可以确保先⾏关系,即写操作会发⽣在后续的读操作之前, 但它并不能保证原⼦性。例如⽤ volatile 修饰 count 变量那么 count++ 操作并不是原⼦性的。⽽ AtomicInteger 类提供的 atomic ⽅法可以让这种操作具有原⼦性如 getAndIncrement ()⽅法会原⼦性的进⾏增量操作把当前值加⼀,其它数据类型和引⽤变量也可以进⾏相似操作。

67.如果同步块内的线程抛出异常会发⽣什么?

⽆论你的同步块是正常还是异常退出的,⾥⾯的线程都会释放锁,所以对⽐锁接⼝我更喜欢同步块,因为它不⽤我花费精⼒去释放锁,该功能可以在 finally block ⾥释放锁实现。68.如何在 Java 中创建线程安全的 Singleton5种,急加载,同步⽅法,双检锁,静态内部类,枚举69.如何强制启动⼀个线程?

这个问题就像是如何强制进⾏ Java 垃圾回收,⽬前还没有觉得⽅法,虽然你可以使⽤ System.gc ()来进⾏垃圾回收,但是不保证能成功。在 Java ⾥⾯没有办法强制启动⼀个线程,它是被线程调度器控制着且 Java 没有公布相关的 API。70.Java 中的 fork join 框架是什么?

fork join 框架是 JDK7 中出现的⼀款⾼效的⼯具,Java 开发⼈员可以通过它充分利⽤现代服务器上的多处理器。它是专门为了那些可以递归划分成许多⼦模块设计的,⽬的是将所有可⽤的处理能⼒⽤来提升程序的性能。fork join 框架⼀个巨⼤的优势是它使⽤了⼯作窃取算法,可以完成更多任务的⼯作线程可以从其它线程中窃取任务来执⾏。71.Java 多线程中调⽤ wait () 和 sleep ()⽅法有什么不同?

Java 程序中 wait 和 sleep 都会造成某种形式的暂停,它们可以满⾜不同的需要。wait ()⽅法意味着条件等待,如果等待条件为真且其它线程被唤醒时它会释放锁,⽽ sleep ()⽅法仅仅释放 CPU 资源或者让当前线程短暂停顿,但不会释放锁。72.可重⼊锁

可重⼊锁:如果当前线程已经获得了某个监视器对象所持有的锁,那么该线程在该⽅法中调⽤另外⼀个同步⽅法也同样持有该锁。

12345678

public synchrnozied void test() { xxxxxx; test2();}

public synchronized void test2() { yyyyy;}

在上⾯代码段中,执⾏ test ⽅法需要获得当前对象作为监视器的对象锁,但⽅法中⼜调⽤了 test2 的同步⽅法。

如果锁是具有可重⼊性的话,那么该线程在调⽤ test2 时并不需要再次获得当前对象的锁,可以直接进⼊ test2 ⽅法进⾏操作。

如果锁是不具有可重⼊性的话,那么该线程在调⽤test2前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得。

如果锁是不具有可重⼊性特点的话,那么线程在调⽤同步⽅法、含有锁的⽅法时就会产⽣死锁。73. 同步⽅法和同步代码块

同步⽅法默认⽤this或者当前类class对象作为锁;

同步代码块可以选择以什么来加锁,⽐同步⽅法要更细颗粒度,我们可以选择只同步会发⽣同步问题的部分代码⽽不是整个⽅法。

四、Java虚拟机

0. 对哪些区域回收

Java运⾏时数据区域:程序计数器、JVM栈、本地⽅法栈、⽅法区和堆。

由于程序计数器、JVM栈、本地⽅法栈3个区域随线程⽽⽣随线程⽽灭,对这⼏个区域内存的回收和分配具有确定性。⽽⽅法区和堆则不⼀样,程序需要在运⾏时才知道创建哪些对象,对这部分内存的分配是动态的,GC关注的也就是这部分内存。1. 主动GC

调⽤system.gc() Runtime.getRuntime.gc()2. 垃圾回收

释放那些不在持有任何引⽤的对象的内存3. 怎样判断是否需要收集

引⽤计数法:对象没有任何引⽤与之关联(⽆法解决循环引⽤)ext:Python使⽤引⽤计数法

可达性分析法:通过⼀组称为GC Root的对象为起点,从这些节点向下搜索,如果某对象不能从这些根对象的⼀个(⾄少⼀个)所到达,则判定该对象应当回收。ext:可作为GCRoot的对象:虚拟机栈中引⽤的对象。⽅法区中类静态属性引⽤的对象,⽅法区中类常量引⽤的对象,本地⽅法栈中JNI引⽤的对象4.对象的⾃我救赎

即使在可达性算法中判定为不可达时,也并⾮⼀定被回收。对象存在⾃我救赎的可能。要真正宣告对象的死亡,需要经历2次标记的过程。如果对象经过可达性分析法发现不可达时,对象将被第⼀次标记被进⾏筛选,筛选的条件是此对象是否有必要执⾏finalize⽅法。如果对象没有重写finalize⽅法或finalize⽅法已经被JVM调⽤过,则判定为不需要执⾏。

如果对象被判定为需要执⾏finalize⽅法,该对象将被放置在⼀个叫做F-Queue的队列中,JVM会建⽴⼀个低优先级的线程执⾏finalize⽅法,如果对象想要完成⾃我救赎需要在finalize⽅法中与引⽤链上的对象关联,⽐如把⾃⼰也就是this赋值给某个类变量。当GC第⼆次对F-Queue中对象标记时,该对象将被移出“即将回收”的集合,完成⾃我救赎。简⾔之,finalize⽅法是对象逃脱死亡命运的最后机会,并且任何对象的finalize⽅法只会被JVM调⽤⼀次。5.垃圾回收算法

Mark-Sweep法:标记清除法 容易产⽣内存碎⽚,导致分配较⼤对象时没有⾜够的连续内存空间⽽提前出发GC。这⾥涉及到另⼀个问题,即对象创建时的内存分配,对象创建内存分配主要有2种⽅法,分别是指针碰撞法和空闲列表法。指针碰撞法:使⽤的内存在⼀侧,空闲的在另⼀侧,中间使⽤⼀个指针作为分界点指⽰器,对象内存分配时只要指针向空闲的移动对象⼤⼩的距离即可。

空闲列表法:使⽤的和空闲的内存相互交错⽆法进⾏指针碰撞,JVM必须维护⼀个列表记录哪些内存块可⽤,分配时从列表中找出⼀个⾜够的分配给对象,并更新列表记录。所以,当采⽤Mark-Sweep算法的垃圾回收器时,内存分配通常采⽤空闲列表法。

Copy法:将内存分为2块,每次使⽤其中的⼀块,当⼀块满了,将存活的对象复制到另⼀块,把使⽤过的那⼀块⼀次性清除。显然,Copy法解决了内存碎⽚的问题,但算法的代价是内存缩⼩为原来的⼀半。现代的垃圾收集器对新⽣代采⽤的正是Copy算法。但通常不执⾏1:1的策略,HotSpot虚拟机默认Eden区Survivor区8:1。每次使⽤Eden和其中⼀块Survivor区。也就是说新⽣代可⽤内存为新⽣代内存空间的90%。

Mark-Compact法:标记整理法。它的第⼀阶段与Mark-Sweep法⼀样,但不直接清除,⽽是将存活对象向⼀端移动,然后清除端边界以外的内存,这样也不存在内存碎⽚。

分代收集算法:将堆内存划分为新⽣代,⽼年代,根据新⽣代⽼年代的特点选取不同的收集算法。因为新⽣代对象⼤多朝⽣⼣死,⽽⽼年代对象存活率⾼,没有额外空间进⾏分配担保,通常对新⽣代执⾏复制算法,⽼年代执⾏Mark-Sweep算法或Mark-Compact算法。6.垃圾收集器

通常来说,新⽣代⽼年代使⽤不同的垃圾收集器。新⽣代的垃圾收集器有Serial(单线程)、ParNew(Serial的多线程版本)、

ParallelScavenge(吞吐量优先的垃圾收集器),⽼年代有SerialOld(单线程⽼年代)、ParallelOld(与ParallelScavenge搭配的多线程执⾏标记整理算法的⽼年代收集器)、CMS(标记清除算法,容易产⽣内存碎⽚,可以开启内存整理的参数),以及当前最先进的垃圾收集器G1,G1通常⾯向服务器端的垃圾收集器,在我⾃⼰的Java应⽤程序中通过-XX:+PrintGCDetails,发现⾃⼰的垃圾收集器是使⽤了ParallelScavenge + ParallelOld的组合。7. 不同垃圾回收算法对⽐

标记清除法(Mark-Sweeping):易产⽣内存碎⽚复制回收法(Copying):为了解决Mark-Sweep法⽽提出,内存空间减⾄⼀半标记压缩法(Mark-Compact):为了解决Copying法的缺陷,标记后移动到⼀端再清除分代回收法(GenerationalCollection):新⽣代对象存活周期短,需要⼤量回收对象,需要复制的少,执⾏copy算法;⽼年代对象存活周期相对长,回收少量对象,执⾏mark-compact算法.新⽣代划分:较⼤的eden区 和 2个survivor区8. 内存分配

新⽣代的三部分 |Eden Space|From Space|To Space|,对象主要分配在新⽣代的Eden区⼤对象直接进⼊⽼年代

⼤对象⽐如⼤数组直接进⼊⽼年代,可通过虚拟机参数-XX:PretenureSizeThreshold参数设置长期存活的对象进⼊⽼年代

ext:虚拟机为每个对象定义⼀个年龄计数器,如果对象在Eden区出⽣并经过⼀次MinorGC仍然存活,将其移⼊Survivor的To区,GC完成标记互换后,相当于存活的对象进⼊From区,对象年龄加1,当增加到默认15岁时,晋升⽼年代。可通过-XX:MaxTenuringThreshold设置

GC的过程:GC开始前,对象只存在于Eden区和From区,To区逻辑上始终为空。对象分配在Eden区,Eden区空间不⾜,发起MinorGC,将Eden区所有存活的对象复制到To区,From区存活的对象根据年龄判断去向,若到达年龄阈值移⼊⽼年代,否则也移⼊To区,GC完成后Eden区和From区被清空,From区和To区标记互换。对象每在Survivor区躲过⼀次MinorGC年龄加⼀。MinorGC将重复这样的过程,直到To区被填满,To区满了以后,将把所有对象移⼊⽼年代。

动态对象年龄判定 suvivor区相同年龄对象总和⼤于suvivor区空间的⼀半,年龄⼤于等于该值的对象直接进⼊⽼年代

空间分配担保 在MinorGC开始前,虚拟机检查⽼年代最⼤可⽤连续空间是否⼤于新⽣代所有对象总空间,如果成⽴,MinorGC可以确保是安全的。否则,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,继续查看⽼年代最⼤可⽤连续空间是否⼤于历次晋升到⽼年代对象的平均⼤⼩,如果⼤于则尝试MinorGC,尽管这次MinorGC是有风险的。如果⼩于,或者HandlerPromotionFailure设置不允许,则要改为FullGC。

新⽣代的回收称为MinorGC,对⽼年代的回收成为MajorGC⼜名FullGC

9. 关于GC的虚拟机参数GC相关

-XX:NewSize和-XX:MaxNewSize 新⽣代⼤⼩-XX:SurvivorRatio Eden和其中⼀个survivor的⽐值-XX:PretenureSizeThreshold ⼤对象进⼊⽼年代的阈值-XX:MaxTenuringThreshold 晋升⽼年代的对象年龄收集器设置

-XX:+UseSerialGC:设置串⾏收集器-XX:+UseParallelGC:设置并⾏收集器

-XX:+UseParalledlOldGC:设置并⾏年⽼代收集器-XX:+UseConcMarkSweepGC:设置并发收集器堆⼤⼩设置-Xmx:最⼤堆⼤⼩

-Xms:初始堆⼤⼩(最⼩内存值)-Xmn:年轻代⼤⼩

-XXSurvivorRatio:3 意思是Eden:Survivor=3:2-Xss栈容量垃圾回收统计信息

-XX:+PrintGC 输出GC⽇志

-XX:+PrintGCDetails 输出GC的详细⽇志10. ⽅法区的回收

⽅法区通常会与永久代划等号,实际上⼆者并不等价,只不过是HotSpot虚拟机设计者⽤永久代实现⽅法区,并将GC分代扩展⾄⽅法区。永久代垃圾回收通常包括两部分内容:废弃常量和⽆⽤的类。常量的回收与堆区对象的回收类似,当没有其他地⽅引⽤该字⾯量时,如果有必要,将被清理出常量池。判定⽆⽤的类的3个条件:

1.该类的所有实例都已经被回收,也就是说堆中不存在该类的任何实例2.加载该类的ClassLoader已经被回收

3.该类对应的java.lang.Class对象没有在任何地⽅被引⽤,⽆法在任何地⽅通过反射访问该类的⽅法。当然,这也仅仅是判定,不代表⽴即卸载该类。11. JVM⼯具命令⾏

1. jps(jvm processor status)虚拟机进程状况⼯具2. jstat(jvm statistics monitoring)统计信息监视3. jinfo(configuration info for java)配置信息⼯具4. jmap(memory map for java)Java内存映射⼯具5. jhat(JVM Heap Analysis Tool)虚拟机堆转储快照分析⼯具6. jstack(Stack Trace for Java)Java堆栈跟踪⼯具7. HSDIS:JIT⽣成代码反汇编可视化

1. JConsole(Java Monitoring and Management Console):Java监视与管理控制台2. VisualVM(All-in-one Java Troubleshooting Tool):多合⼀故障处理⼯具12. JVM内存结构1. 堆:新⽣代和年⽼代2. ⽅法区(⾮堆):持久代, 代码缓存, 线程共享3. JVM栈:中间结果,局部变量,线程隔离4. 本地栈:本地⽅法(⾮Java代码)5. 程序计数器 :线程私有,每个线程都有⾃⼰独⽴的程序计数器,⽤来指⽰下⼀条指令的地址6. 注:持久代Java8消失, 取代的称为元空间(本地堆内存的⼀部分)13. JVM的⽅法区

与堆⼀样,是线程共享的区域。⽅法区中存储:被虚拟机加载的类信息,常量,静态变量,JIT编译后的代码等数据。参见我是⼀个JavaClass。

14. Java类加载器

⼀个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职:1. Bootstrap ClassLoader(引导类加载器) 负责加载java基础类,主要是 %JRE_HOME/lib/⽬录下的rt.jar、resources.jar、charsets.jar等2. Extension ClassLoader(扩展类加载器) 负责加载java扩展类,主要是 %JRE_HOME/lib/ext⽬录下的jar等3. App ClassLoader(系统类加载器) 负责加载当前java应⽤的classpath中的所有类。classloader 加载类⽤的是全盘负责委托机制。 所谓全盘负责,即是当⼀个classloader加载⼀个Class的时候,这个Class所依赖的和引⽤的所有 Class也由这个classloader负责载⼊,除⾮是显式的使⽤另外⼀个classloader载⼊。所以,当我们⾃定义的classloader加载成功了com.company.MyClass以后,MyClass⾥所有依赖的class都由这个classLoader来加载完成。15. 位 JVM 中,int 的长度是多⼤?

Java 中,int 类型变量的长度是⼀个固定值,与平台⽆关,都是 32 位。意思就是说,在 32 位 和 位 的Java 虚拟机中,int 类型的长度是相同的。

16. Serial 与 Parallel GC之间的不同之处?

Serial 与 Parallel 在GC执⾏的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执⾏ GC 的时候只有⼀个线程,⽽ parallel 收集器使⽤多个 GC 线程来执⾏。17.Java 中 WeakReference 与 SoftReference的区别?

Java中⼀共有四种类型的引⽤。StrongReference、 SoftReference、 WeakReference 以及 PhantomReference。StrongReference:Java 的默认引⽤实现, 它会尽可能长时间的存活于 JVM 内,当没有任何对象指向它时将会被GC回收SoftReference:尽可能长时间保留引⽤,直到JVM内存不⾜,适合某些缓存应⽤

WeakReference:顾名思义, 是⼀个弱引⽤, 当所引⽤的对象在 JVM 内不再有强引⽤时, 下⼀次将被GC回收

PhantomReference:它是最弱的⼀种引⽤关系,也⽆法通过PhantomReference取得对象的实例。仅⽤来当该对象被回收时收到⼀个通知

虽然 WeakReference 与 SoftReference 都有利于提⾼ GC 和 内存的效率,但是 WeakReference ,⼀旦失去最后⼀个强引⽤,就会被GC 回收,⽽ SoftReference 会尽可能长的保留引⽤直到 JVM 内存不⾜时才会被回收(虚拟机保证), 这⼀特性使得 SoftReference ⾮常适合缓存应⽤。

18. WeakHashMap 是怎么⼯作的?

WeakHashMap 的⼯作与正常的 HashMap 类似,但是使⽤弱引⽤作为 key,意思就是当 key 对象没有任何引⽤时,key/value 将会被回收。

19. JVM 选项 -XX:+UseCompressedOops 有什么作⽤?为什么要使⽤?

当你将你的应⽤从 32 位的 JVM 迁移到 位的 JVM 时,由于对象的指针从 32 位增加到了 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量⽐内存⼩很多)的数据产⽣不利的影响。因为,迁移到 位的 JVM 主要动机在于可以指定最⼤堆⼤⼩,通过压缩 OOP 可以节省⼀定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使⽤ 32 位的 OOP,⽽不是 位的OOP。

20. 怎样通过 Java 程序来判断 JVM 是 32 位 还是 位?

你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。21. 32 位 JVM 和 位 JVM 的最⼤堆内存分别是多数?

理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会⽐这个⼩很多。不同操作系统之间不同,如 Windows 系统⼤约1.5 GB,Solaris ⼤约 3GB。 位 JVM允许指定最⼤的堆内存,理论上可以达到 2^,这是⼀个⾮常⼤的数字,实际上你可以指定堆内存⼤⼩到 100GB。甚⾄有的 JVM,如 Azul,堆内存到 1000G 都是可能的。22. JRE、JDK、JVM 及 JIT 之间有什么不同?

JRE 代表 Java 运⾏时(Java run-time),是运⾏ Java 应⽤所必须的。JDK 代表 Java 开发⼯具(Java development kit),是 Java程序的开发⼯具,如 Java 编译器,它也包含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运⾏ Java 应⽤。JIT代表即时编译(Just In Time compilation),当代码执⾏的次数超过⼀定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利⼤幅度提⾼ Java 应⽤的性能。23. 解释 Java 堆空间及 GC?

当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的⼀部分⽤于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的⼀个后台进程,回收⽆效对象的内存⽤于将来的分配。24. 你能保证 GC 执⾏吗?

不能,虽然你可以调⽤ System.gc() 或者 Runtime.getRuntime().gc(),但是没有办法保证 GC 的执⾏。25. 怎么获取 Java 程序使⽤的内存?堆使⽤的百分⽐?

可以通过 java.lang.Runtime 类中与内存相关⽅法来获取剩余的内存,总内存及最⼤堆内存。通过这些⽅法你也可以获取到堆使⽤的百分⽐及堆内存的剩余空间。Runtime.freeMemory() ⽅法返回剩余空间的字节数,Runtime.totalMemory() ⽅法总内存的字节数,Runtime.maxMemory() 返回最⼤内存的字节数。26. Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,使⽤⽬的也不同。栈常⽤于保存⽅法帧和局部变量,⽽对象总是在堆上分配。栈通常都⽐堆⼩,也不会在多个线程之间共享,⽽堆被整个 JVM 的所有线程共享。27. JVM调优

使⽤⼯具Jconsol、VisualVM、JProfiler等堆信息查看

可查看堆空间⼤⼩分配(年轻代、年⽼代、持久代分配)提供即时的垃圾回收功能垃圾监控(长时间监控回收情况)

查看堆内类、对象信息查看:数量、类型等对象引⽤情况查看

有了堆信息查看⽅⾯的功能,我们⼀般可以顺利解决以下问题:年⽼代年轻代⼤⼩划分是否合理内存泄漏

垃圾回收算法设置是否合理线程监控

线程信息监控:系统线程数量。

线程状态监控:各个线程都处在什么样的状态下Dump线程详细信息:查看线程内部运⾏情况死锁检查热点分析

CPU热点:检查系统哪些⽅法占⽤的⼤量CPU时间

内存热点:检查哪些对象在系统中数量最⼤(⼀定时间内存活对象和销毁对象⼀起统计)快照

系统两个不同运⾏时刻,对象(或类、线程等)的不同

举例说,我要检查系统进⾏垃圾回收以后,是否还有该收回的对象被遗漏下来的了。那么,我可以在进⾏垃圾回收前后,分别进⾏⼀次堆情况的快照,然后对⽐两次快照的对象情况。内存泄漏检查年⽼代堆空间被占满持久代被占满堆栈溢出线程堆栈满系统内存被占满

28. Java中有内存泄漏吗?

内存泄露的定义: 当某些对象不再被应⽤程序所使⽤,但是由于仍然被引⽤⽽导致垃圾收集器不能释放。

内存泄漏的原因:对象的⽣命周期不同。⽐如说对象A引⽤了对象B. A的⽣命周期⽐B的要长得多,当对象B在应⽤程序中不会再被使⽤以后, 对象 A 仍然持有着B的引⽤. (根据虚拟机规范)在这种情况下GC不能将B从内存中释放。这种情况很可能会引起内存问题,倘若A还持有着其他对象的引⽤,那么这些被引⽤的(⽆⽤)对象也不会被回收,并占⽤着内存空间。甚⾄有可能B也持有⼀⼤堆其他对象的引⽤。这些对象由于被B所引⽤,也不会被垃圾收集器所回收,所有这些⽆⽤的对象将消耗⼤量宝贵的内存空间。并可能导致内存泄漏。怎样防⽌:

1、当⼼集合类, ⽐如HashMap, ArrayList等,因为这是最容易发⽣内存泄露的地⽅.当集合对象被声明为static时,他们的⽣命周期⼀般和整个应⽤程序⼀样长。29. OOM解决办法

内存溢出的空间:Permanent Generation和Heap Space,也就是永久代和堆区1、永久代的OOM解决办法有2种:

a.通过虚拟机参数-XX:PermSize和-XX:MaxPermSize调整永久代⼤⼩b.清理程序中的重复的Jar⽂件,减少类的重复加载2、堆区的溢出

发⽣这种问题的原因是java虚拟机创建的对象太多,在进⾏垃圾回收之间,虚拟机分配的到堆内存空间已经⽤满了,与Heap Space的size有关。解决这类问题有两种思路:

1. 检查程序,看是否存在死循环或不必要地重复创建⼤量对象,定位原因,修改程序和算法。2. 通过虚拟机参数-Xms和-Xmx设置初始堆和最⼤堆的⼤⼩

30. DirectMemory直接内存

直接内存并不是Java虚拟机规范定义的内存区域的⼀部分,但是这部分内存也被频繁使⽤,⽽且也可能导致OOM异常的出现。

JDK1.4引⼊了NIO,这是⼀种基于通道和缓冲区的⾮阻塞IO模式,它可以使⽤Native函数库分配直接堆外内存,然后通过⼀个存储在Java堆中的DirectByteBuffer对象作为这块内存的引⽤进⾏操作,使得在某些场合显著提⾼性能,因为它避免了在Java堆和本地堆之间来回复制数据。

31. Java 中堆和栈有什么不同

每个线程都有⾃⼰的栈内存,⽤于存储本地变量,⽅法参数和栈调⽤,⼀个线程中存储的变量对其它线程是不可见的。⽽堆是所有线程共享的⼀⽚公⽤内存区域。对象都在堆⾥创建,为了提升效率线程会从堆中弄⼀个缓存到⾃⼰的栈,如果多个线程使⽤该变量就可能引发问题,这时 volatile 变量就可以发挥作⽤了,它要求线程从主存中读取变量的值。32. 双亲委派模型中的⽅法

findLoadedClass(),LoadClass(),findBootstrapClassOrNull(),findClass(),resolveClass()33. IO模型

⼀般来说 I/O 模型可以分为:同步阻塞,同步⾮阻塞,异步阻塞,异步⾮阻塞 四种IO模型同步阻塞 IO :

在此种⽅式下,⽤户进程在发起⼀个 IO 操作以后,必须等待 IO 操作的完成,只有当真正完成了 IO 操作以后,⽤户进程才能运⾏。 JAVA传统的 IO 模型属于此种⽅式!同步⾮阻塞 IO:

在此种⽅式下,⽤户进程发起⼀个 IO 操作以后可返回做其它事情,但是⽤户进程需要时不时的询问 IO 操作是否就绪,这就要求⽤户进程不停的去询问,从⽽引⼊不必要的 CPU 资源浪费。其中⽬前 JAVA 的 NIO 就属于同步⾮阻塞 IO 。

异步阻塞 IO :

此种⽅式下是指应⽤发起⼀个 IO 操作以后,不等待内核 IO 操作的完成,等内核完成 IO 操作以后会通知应⽤程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问 IO 是否完成,那么为什么说是阻塞的呢?因为此时是通过 select 系统调⽤来完成的,⽽select 函数本⾝的实现⽅式是阻塞的,⽽采⽤ select 函数有个好处就是它可以同时监听多个⽂件句柄,从⽽提⾼系统的并发性!异步⾮阻塞 IO:

在此种模式下,⽤户进程只需要发起⼀个 IO 操作然后⽴即返回,等 IO 操作真正的完成以后,应⽤程序会得到 IO 操作完成的通知,此时⽤户进程只需要对数据进⾏处理就好了,不需要进⾏实际的 IO 读写操作,因为 真正的 IO读取或者写⼊操作已经由 内核完成了。⽬前 Java7的AIO正是此种类型。

BIO即同步阻塞IO,适⽤于连接数⽬较⼩且固定的架构,这种⽅式对服务器资源要求⽐较⾼,并发局限于应⽤中,JDK1.4之前的唯⼀选择,但程序直观、简单、易理解。

NIO即同步⾮阻塞IO,适⽤于连接数⽬多且连接⽐较短的架构,⽐如聊天服务器,并发局限于应⽤中,编程⽐较复杂,JDK1.4开始⽀持。AIO即异步⾮阻塞IO,适⽤于连接数⽬多且连接⽐较长的架构,如相册服务器,充分调⽤OS参与并发操作,编程⽐较复杂,JDK1.7开始⽀持

34. 类加载器按照层次,从顶层到底层,分别加载哪些类?

启动类加载器:负责将存放在JAVA_HOME/lib下的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。启动类加载器⽆法被Java程序直接引⽤。

扩展类加载器:这个加载器负责加载JAVA_HOME/lib/ext⽬录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使⽤扩展类加载器

应⽤程序类加载器:这个加载器是ClassLoader中getSystemClassLoader()⽅法的返回值,所以⼀般也称它为系统类加载器。它负责加载⽤户类路径(Classpath)上所指定的类库,可直接使⽤这个加载器,如果应⽤程序没有⾃定义⾃⼰的类加载器,⼀般情况下这个就是程序中默认的类加载器实现⾃⼰的加载器

只需要继承ClassLoader,并覆盖findClass⽅法。

在调⽤loadClass⽅法时,会先根据委派模型在⽗加载器中加载,如果加载失败,则会调⽤⾃⼰的findClass⽅法来完成加载

五、数据库(Sql、MySQL、Redis等)

1. Statement1.1 基本内容

Statement是最基本的⽤法, 不传参, 采⽤字符串拼接,存在注⼊漏洞PreparedStatement传⼊参数化的sql语句, 同时检查合法性, 效率⾼可以重⽤, 防⽌sql注⼊CallableStatement接⼝扩展PreparedStatement,⽤来调⽤存储过程BatchedStatement⽤于批量操作数据库,BatchedStatement不是标准的Statement类12

public interface CallableStatement extends PreparedStatement public interface PreparedStatement extends Statement

1.2 Statement与PrepareStatement的区别

创建时的区别12

Statement statement = conn.createStatement();

PreparedStatement preStatement = conn.prepareStatement(sql);

执⾏的时候12

ResultSet rSet = statement.executeQuery(sql);ResultSet pSet = preStatement.executeQuery();

由上可以看出,PreparedStatement有预编译的过程,已经绑定sql,之后⽆论执⾏多少遍,都不会再去进⾏编译,⽽ statement 不同,如果执⾏多遍,则相应的就要编译多少遍sql,所以从这点看,preStatement 的效率会⽐ Statement要⾼⼀些

安全性PreparedStatement是预编译的,所以可以有效的防⽌SQL注⼊等问题

代码的可读性和可维护性PreparedStatement更胜⼀筹2. 游标

3. 列出 5 个应该遵循的 JDBC 最佳实践

有很多的最佳实践,你可以根据你的喜好来例举。下⾯是⼀些更通⽤的原则:a)使⽤批量的操作来插⼊和更新数据

b)使⽤ PreparedStatement 来避免 SQL 异常,并提⾼性能c)使⽤数据库连接池

d)通过列名来获取结果集,不要使⽤列的下标来获取4. 数据库索引的实现

数据库系统还维护着满⾜特定查找算法的数据结构,这些数据结构以某种⽅式引⽤(指向)数据,这样就可以在这些数据结构上实现⾼级查找算法。这种数据结构,就是索引。B树:

⼀棵m阶B树(balanced tree of order m)是⼀棵平衡的m路搜索树。它或者是空树,或者是满⾜下列性质的树:1、根结点⾄少有两个⼦⼥;

2、每个⾮根节点所包含的关键字个数 j 满⾜:┌m/2┐ - 1 <= j <= m - 1;

3、除根结点以外的所有结点(不包括叶⼦结点)的度数正好是关键字总数加1,故内部⼦树个数 k 满⾜:┌m/2┐ <= k <= m ;4、所有的叶⼦结点都位于同⼀层。

由于B-Tree的特性,在B-Tree中按key检索数据的算法⾮常直观:⾸先从根节点进⾏⼆分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进⾏查找,直到找到节点或找到null指针,前者查找成功,后者查找失败。

⼀个度为d的B-Tree,设其索引N个key,则其树⾼h的上限为logd((N+1)/2),检索⼀个key,其查找节点个数的渐进复杂度为O(logdN)。从这点可以看出,B-Tree是⼀个⾮常有效率的索引数据结构。B+树:

B-Tree有许多变种,其中最常见的是B+Tree,例如MySQL就普遍使⽤B+Tree实现其索引结构。

B+树是B树的变形,它把所有的data都放在叶⼦结点中,只将关键字和⼦⼥指针保存于内结点,内结点完全是索引的功能。与B-Tree相⽐,B+Tree有以下不同点:1、每个节点的指针上限为2d⽽不是2d+1。

2、内节点不存储data,只存储key;叶⼦节点存储data不存储指针。

⼀般在数据库系统或⽂件系统中使⽤的B+Tree结构都在经典B+Tree的基础上进⾏了优化,增加了顺序访问指针。在B+Tree的每个叶⼦节点增加⼀个指向相邻叶⼦节点的指针

例如图4中如果要查询key为从18到49的所有数据记录,当找到18后,只需顺着节点和指针顺序遍历就可以⼀次性访问到所有数据节点,极⼤提到了区间查询效率。为什么B树(B+树)?

⼀般来说,索引本⾝也很⼤,不可能全部存储在内存中,因此索引往往以索引⽂件的形式存储的磁盘上。这样的话,索引查找过程中就要产⽣磁盘I/O消耗,相对于内存存取,I/O存取的消耗要⾼⼏个数量级,所以评价⼀个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。这涉及到磁盘存取原理、局部性原理和磁盘预读。

先从B-Tree分析,根据B-Tree的定义,**可知检索⼀次最多需要访问h个节点。数据库系统的设计者巧妙利⽤了磁盘预读原理,将⼀个节点的⼤⼩设为等于⼀个页,这样每个节点只需要⼀次I/O就可以完全载⼊。**为了达到这个⽬的,在实际实现B-Tree还需要使⽤如下技巧:每次新建节点时,直接申请⼀个页的空间,这样就保证⼀个节点物理上也存储在⼀个页⾥,加之计算机存储分配都是按页对齐的,就实现了⼀个node只需⼀次I/O。

B-Tree中⼀次检索最多需要h-1次I/O(根节点常驻内存),渐进复杂度为O(h)=O(logdN)。⼀般实际应⽤中,出度d是⾮常⼤的数字,通常超过100,因此h⾮常⼩(通常不超过3)。综上所述,⽤B-Tree作为索引结构效率是⾮常⾼的。

⽽红⿊树这种结构,h明显要深的多。由于逻辑上很近的节点(⽗⼦)物理上可能很远,⽆法利⽤局部性,所以红⿊树的I/O渐进复杂度也为O(h),效率明显⽐B-Tree差很多。

⾄于B+Tree为什么更适合外存索引,原因和内节点出度d有关。

由于B+Tree内节点去掉了data域,因此可以拥有更⼤的出度,拥有更好的性能。

六、

1. ⼆叉搜索树:(Binary Search Tree⼜名:⼆叉查找树,⼆叉排序树)它或者是⼀棵空树,或者是具有下列性质的⼆叉树: 若它的左⼦树不空,则左⼦树上所有结点的值均⼩于它的根结点的值;若它的右⼦树不空,则右⼦树上所有结点的值均⼤于它的根结点的值;它的左、右⼦树也分别为⼆叉搜索树。2. RBT红⿊树

⼆叉搜索树:(Binary Search Tree⼜名:⼆叉查找树,⼆叉排序树)它或者是⼀棵空树,或者是具有下列性质的⼆叉树: 若它的左⼦树不空,则左⼦树上所有结点的值均⼩于它的根结点的值;若它的右⼦树不空,则右⼦树上所有结点的值均⼤于它的根结点的值;它的左、右⼦树也分别为⼆叉搜索树。

红⿊树是⼀棵⼆叉搜索树,它在每个结点上增加⼀个存储位来表⽰结点的颜⾊,可以是RED或BLACK。通过对任何⼀条从根到叶⼦的简单路径上各个结点的颜⾊进⾏约束,红⿊树没有⼀条路径会⽐其他路径长出2倍,所以红⿊树是近似平衡的,使得红⿊树的查找、插⼊、删除等操作的时间复杂度最坏为O(log n),但需要注意到在红⿊树上执⾏插⼊或删除后将不在满⾜红⿊树性质,恢复红⿊树的属性需要少量(O(log

n))的颜⾊变更(实际是⾮常快速的)和不超过三次树旋转(对于插⼊操作是两次)。虽然插⼊和删除很复杂,但操作时间仍可以保持为 O(log n)次。具体如何保证?引出红⿊树的5个性质。红⿊树的5个性质:满⾜以下五个性质的⼆叉搜索树1. 每个结点或是红⾊的或是⿊⾊的2. 根结点是⿊⾊的3. 每个叶结点是⿊⾊的4. 如果⼀个结点是红⾊的,则它的两个⼦结点是⿊⾊的5. 对于每个结点,从该结点到其后代叶结点的简单路径上,均包含相同数⽬的⿊⾊结点插⼊操作:

由于性质的约束,插⼊的结点都是红⾊的。插⼊时性质1、3始终保持。破坏性质2当且仅当当前插⼊结点为根节点。变⼀下颜⾊即可。如果是破坏性质4或5,则需要旋转和变⾊来继续满⾜红⿊树的性质。下⾯说⼀说插⼊的⼏种情况,约定当前插⼊结点为N,其⽗结点为P,叔叔为U,祖⽗为G

情形1:树空,直接插⼊违反性质1,将红⾊改⿊。情形2:N的⽗结点为⿊,不必修改,直接插⼊

从情形3开始的情形假定N结点的⽗结点P为红⾊,所以存在G,并且G为⿊⾊。且N存在⼀个叔叔结点U,尽管U可能为叶结点。情形3:P为红,U为红(G结点⼀定存在且为⿊)这⾥不论P是G的左孩⼦还是右孩⼦;不论N是P的左孩⼦还是右孩⼦。

⾸先把P、U改⿊,G改红,并以G作为⼀个新插⼊的红结点重新进⾏各种情况的检查,若⼀路检索⾄根节点还未结束,则将根结点变⿊。情形4:P为红,U为⿊或不存在(G结点⼀定存在且为⿊),且P为G的左孩⼦,N为P的左孩⼦(或者P为G的右孩⼦,N为P的右孩⼦,保证同向的)。

P、G右旋并将P、G变相反⾊。因为P取代之前⿊G的位置,所以P变⿊可以理解,⽽G变红是为了不违反性质5。

情形5:P为红,U为⿊或不存在,且P为G的左孩⼦,N为P的右孩⼦(或P为G的右孩⼦,N为P的左孩⼦,保证是反向的),对N,P进⾏⼀次左旋转换为情形4

删除操作⽐插⼊复杂⼀些,但最多不超过三次旋转可以让红⿊树恢复平衡。其他

⿊⾼从某个结点x出发(不含x)到达⼀个叶结点的任意⼀条简单路径上的⿊⾊结点个数称为该结点的⿊⾼。红⿊树的⿊⾼为其根结点的⿊⾼。⼀个具有n个内部结点的红⿊树的⾼度h<=2lg(n+1)结点的属性(五元组):color key left right p动态集合操作最坏时间复杂度为O(lgn)3. 排序算法

稳定排序:插⼊排序、冒泡排序、归并排序、基数排序插⼊排序[稳定]

适⽤于⼩数组,数组已排好序或接近于排好序速度将会⾮常快

复杂度:O(n^2) - O(n) - O(n^2) - O(1)[平均 - 最好 - 最坏 - 空间复杂度]归并排序[稳定]采⽤分治法

复杂度:O(nlogn) - O(nlgn) - O(nlgn) - O(n)[平均 - 最好 - 最坏 - 空间复杂度]冒泡排序[稳定]

复杂度:O(n^2) - O(n) - O(n^2) - O(1)[平均 - 最好 - 最坏 - 空间复杂度]基数排序 分配+收集[稳定]

复杂度: O(d(n+r)) r为基数d为位数 空间复杂度O(n+r)树排序[不稳定]

应⽤:TreeSet的add⽅法、TreeMap的put⽅法不⽀持相同元素,没有稳定性问题复杂度:平均最差O(nlogn)堆排序(就地排序)[不稳定]

复杂度:O(nlogn) - O(nlgn) - O(nlgn) - O(1)[平均 - 最好 - 最坏 - 空间复杂度]快速排序[不稳定]

复杂度:O(nlgn) - O(nlgn) - O(n^2) - O(1)[平均 - 最好 - 最坏 - 空间复杂度]栈空间0(lgn) - O(n)选择排序[不稳定]

复杂度:O(n^2) - O(n^2) - O(n^2) - O(1)[平均 - 最好 - 最坏 - 空间复杂度]希尔排序[不稳定]

复杂度 ⼩于O(n^2) 平均 O(nlgn) 最差O(n^s)[1九⼤内部排序算法代码及性能分析参见我的4. 查找与散列4.1 散列函数设计

直接定址法:f(key) = a*key+b

简单、均匀,不易产⽣冲突。但需事先知道关键字的分布情况,适合查找表较⼩且连续的情况,故现实中并不常⽤

除留余数法:f(key) = key mod p (p<=m) p取⼩于表长的最⼤质数 m为表长DJBX33A算法(time33哈希算法hash = hash*33+(unsigned int)str[i];

平⽅取中法 折叠法 更多…4.2 冲突处理

闭散列(开放地址⽅法):要求装填因⼦a较⼩,闭散列⽅法把所有记录直接存储在散列表中

线性探测:易产⽣堆积现象(基地址不同堆积在⼀起)⼆次探测:f(key) = (f(key)+di) % m di=12,-12,22,-22…可以消除基本聚集随机探测:f(key) = (f(key)+di),di采⽤随机函数得到,可以消除基本聚集双散列:避免⼆次聚集开散列(链地址法):原地处理

同义词记录存储在⼀个单链表中,散列表中⼦存储单链表的头指针。优点:⽆堆积 事先⽆需确定表长 删除结点易于实现 装载因⼦a>=1,缺点:需要额外空间5. 跳表

为什么选择跳表?

⽬前经常使⽤的平衡数据结构有:B树,红⿊树,AVL树,Splay Tree, Treep等。想象⼀下,给你⼀张草稿纸,⼀只笔,⼀个编辑器,你能⽴即实现⼀颗红⿊树,或者AVL树出来吗? 很难吧,这需要时间,要考虑很多细节,要参考⼀堆算法与数据结构之类的树,还要参考⽹上的代码,相当⿇烦。

⽤跳表吧,跳表是⼀种随机化的数据结构,⽬前开源软件 Redis 和 LevelDB 都有⽤到它,它的效率和红⿊树以及 AVL 树不相上下,但跳表的原理相当简单,只要你能熟练操作链表,就能去实现⼀个 SkipList。

跳跃表是⼀种随机化数据结构,基于并联的链表,其效率可⽐拟于⼆叉查找树(对于⼤多数操作需要O(log n)平均时间),并且对并发算法友好。

Skip list(跳表)是⼀种可以代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定⼀个数据的向上攀升与否,是⼀种“空间来换取时间”的⼀个算法,在每个节点中增加了指向下⼀层的指针,在插⼊、删除、查找时可以忽略⼀些不可能涉及到的结点,从⽽提⾼了效率。在Java的API中已经有了实现:分别是

ConcurrentSkipListMap(在功能上对应HashTable、HashMap、TreeMap) ;ConcurrentSkipListSet(在功能上对应HashSet)Skip list的性质

(1) 由很多层结构组成,level是通过⼀定的概率随机产⽣的(2) 每⼀层都是⼀个有序的链表,默认是升序(3) 最底层(Level 1)的链表包含所有元素

(4) 如果⼀个元素出现在Level i 的链表中,则它在Level i 之下的链表也都会出现

(5) 每个节点包含两个指针,⼀个指向同⼀链表中的下⼀个元素,⼀个指向下⾯⼀层的元素时间复杂度O(lgn) 最坏O(2lgn)Java实现参见我的GitHub Repo6. AVL树1.LL型

在某⼀节点的左孩⼦的左⼦树上插⼊⼀个新的节点,使得该节点不再平衡。

举例 A B Ar Bl Br 在Bl下插⼊N,执⾏⼀次右旋即可,即把B变为⽗结点,原来的根节点A变为B的左孩⼦,B的右⼦树变为A的左⼦树。2.RR型

与LL型是对称的,执⾏⼀次左旋即可。3.LR型

指在AVL树某⼀结点左孩⼦的右⼦树上插⼊⼀个结点,使得该节点不在平衡。这时需要两次旋转,先左旋再右旋。4.RL型

与LR对称,执⾏⼀次右旋,再执⾏⼀次左旋。

删除

1、被删的节点是叶⼦节点

将该节点直接从树中删除,并利⽤递归的特点和⾼度的变化,反向推算其⽗节点和祖先节点是否失衡。2、被删的节点只有左⼦树或只有右⼦树

将左⼦树(右⼦树)替代原有节点的位置,并利⽤递归的特点和⾼度的变化,反向推算⽗节点和祖先节点是否失衡。3、被删的节点既有左⼦树⼜有右⼦树

找到被删节点的左⼦树的最右端的节点,将该结点的的值赋给待删除结点,再⽤该结点的左孩⼦替换它本来的位置,然后释放该结点,并利⽤递归特点,反向推断⽗节点和祖⽗节点是否失衡。7. ⼀致性Hash第⼀:简单介绍

⼀致性哈希算法是分布式系统中常⽤的算法。⽐如,⼀个分布式的存储系统,要将对象存储到具体的节点上,如果采⽤普通的hash⽅法,将数据映射到具体的节点上,如key%N,N是机器节点数。

1、考虑到⽐如⼀个服务器down掉,服务器结点N变为N-1,映射公式必须变为key%(N-1)2、访问量加重,需要添加服务器结点,N变为N+1,映射公式变为hash(object)%(N+1)

当出现1,2的情况意味着我们的映射都将⽆效,对服务器来说将是⼀场灾难,尤其是对缓存服务器来说,因为缓存服务器映射的失效,洪⽔般的访问都将冲向后台服务器。第⼆点:hash算法的单调性

Hash 算法的⼀个衡量指标是单调性,单调性是指如果已经有⼀些内容通过哈希分派到了相应的缓冲中,⼜有新的缓冲加⼊到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲中去,⽽不会被映射到旧的缓冲集合中的其他缓冲区。

consistent hash 也是⼀种hash 算法,简单的说,在移除 / 添加⼀个结点时,它能够尽可能⼩的改变已存在的映射关系,尽可能的满⾜单调性的要求。

第三点:将对象和服务器结点分别映射到环型空间

通常的⼀致性哈希做法是将 value 映射到⼀个 32 位的 key 值,也即是 0~2^32-1 次⽅的数值空间;我们可以将这个空间想象成⼀个⾸(0 )尾( 2^32-1 )相接的圆环。

我们可以通过hash函数将我们的key映射到环型空间中,同时根据相同的哈希算法把服务器也映射到环型空间中,顺便提⼀下服务器或者某个计算节点的 hash 计算,⼀般的⽅法可以使⽤机器的 IP 地址或者机器名作为 hash 输⼊。第四点:将对象映射到服务器

在这个环形空间中,如果沿着顺时针⽅向从对象的 key 值出发,直到遇见⼀个 服务器结点,那么就将该对象存储在这个服务器结点上,因为对象和服务器的hash 值是固定的,因此这个 cache 必然是唯⼀和确定的。

这时候考察某个服务器down机或者需要添加服务器结点,也就是移除和添加的操作,我们只需要⼏个对象的映射。第五点:虚拟结点

Hash 算法的另⼀个指标是平衡性 (Balance)。平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利⽤。

对于上述的做法,可能导致某些对象都映射到某个服务器,使得分布不平衡。为此可以采⽤“虚拟结点”的做法。

“虚拟结点”( virtual node )是实际节点在 hash 空间的复制品,⼀实际结点对应了若⼲个“虚拟节点”,这个对应个数也成为“复制个数”,“虚拟节点”在 hash 空间中以 hash 值排列。引⼊“虚拟结点”会让我们的映射分布更为平衡⼀些。引⼊“虚拟结点”前:Hash(“192.168.1.1”);

引⼊“虚拟结点”后:Hash(“192.168.1.1#1”);Hash(“192.168.1.1#2”);8. 如何判断链表是否有环

⽅法1:快慢指针法 2.设两个⼯作指针p、q,p总是向前⾛,但q每次都从头开始⾛,对于每个节点,看p⾛的步数是否和q⼀样。⽐如p从A⾛到D,⽤了4步,⽽q则⽤了14步。因⽽步数不等,出现⽭盾,存在环。9. 熟悉哪些算法?

[哈希算法] ⼀致性哈希 time33哈希 FNV1_32_HASH[排序算法] 快速排序[搜索算法] DFS BFS[最⼩⽣成树算法] Kruskal Prim[最短路径算法] Dijkstra Floyed七、

1.停⽌等待协议

停⽌等待协议是最基本的数据链路层协议,它的⼯作原理是这样的。

在发送端,每发送完⼀帧就停⽌发送,等待接收端的确认,如果收到确认就发送下⼀帧。

在接收端,每收到⼀个⽆差错的帧,就把这个帧交付上层并向发送端发送确认。若该帧有差错,就丢弃,其他什么也不做。其他细节:

停⽌等待协议为了可靠交付,需要对帧进⾏编号,由于每次只发送⼀帧,所以停⽌等待协议使⽤1个⽐特编号,编号0和1

停⽌等待协议会出现死锁现象(A等待B的确认),解决办法,启动超时计时器,超时计时器有⼀个重传时间。重传时间⼀般选择略⼤于“正常情况下从发完数据帧到收到确认帧所需的平均时间”。2.滑动窗⼝协议

再说滑动窗⼝之前,先说下连续ARQ,连续ARQ⼜称Go-back-N ARQ,意思是当出现差错必须重传时,要向回⾛N个帧,然后再开始重传,也就意味着只要有⼀帧出现差错,即使已经正确的帧也需要重传,⽩⽩浪费时间,增⼤开销。为此,应该对发送出去但未被确认的帧的数⽬加以,这就是滑动窗⼝协议。滑动窗⼝指收发两端分别维护⼀个发送窗⼝和接收窗⼝,发送窗⼝有⼀个窗⼝值Wt,窗⼝值Wt代表在没有收到对⽅确认的情况下最多可以发送的帧的数⽬。当发送的帧的序号被接收窗⼝正确收下后,接收端向前滑动并向发送端发去确认,发送端收到确认后,发送窗⼝向前滑动。收发两端按规律向前推进。

连续ARQ和选择重传ARQ均是窗⼝⼤于1的滑动窗⼝协议,⽽停⽌等待协议相当于收发两端窗⼝等于1。滑动窗⼝指接收和发送两端的窗⼝按规律不断向前推进,是⼀种流量控制的策略。3.Http1.0和Http1.1的区别

1.HTTP/1.0协议使⽤⾮持久连接,即在⾮持久连接下,⼀个tcp连接只传输⼀个Web对象。

2.HTTP/1.1默认使⽤持久连接(然⽽,HTTP/1.1协议的客户机和服务器可以配置成使⽤⾮持久连接)。在持久连接下,不必为每个Web对象的传送建⽴⼀个新的连接,⼀个连接中可以传输多个对象。4.Post和Get的区别

1.安全性上说:get的⽅式是把数据在地址栏中明⽂的形式发送,URL中可见,POST⽅式对⽤户是透明的,安全性更⾼。2.数据量说:Get传送的数据量较⼩,⼀般不能⼤于2KB,POST传送的数据量更⼤。3.适⽤范围说:查询⽤Get,数据添加、修改和删除建议Post5.TCP/IP体系各层功能及协议

TCP/IP体系共有四个层次,分别为⽹络接⼝层Host-to-Network Layer, ⽹际层 Internet Layer, 传输层Transport Layer,应⽤层Application Layer。

5.1 ⽹络接⼝层 -> 接收和发送数据报

主要负责将数据发送到⽹络传输介质上以及从⽹络上接收TCP/IP数据报,相当于OSI参考模型的物理层和数据链路层。在实际中,先后流⾏的以太⽹、令牌环⽹、ATM、帧中继等都可视为其底层协议。它将发送的信息组装成帧并通过物理层向选定⽹络发送,或者从⽹络上接收物理帧,将去除控制信息后的IP数据报交给⽹络层。5.2 ⽹际层 -> 数据报封装和路由寻址

⽹际层主要功能是寻址和对数据报的封装以及路由选择功能。这些功能⼤部分通过IP协议完成,并通过地址解析协议ARP、逆地址解析协议RARP、因特⽹控制报⽂协议ICMP、因特⽹组管理协议IGMP从旁协助。所以IP协议是⽹络层的核⼼。

⽹际协议IP:IP协议是⼀个⽆连接的协议,主要负责将数据报从源结点转发到⽬的结点。也就是说IP协议通过对数据报中源地址和⽬的地址进⾏分析,然后进⾏路由选择,最后再转发到⽬的地。需要注意的是:IP协议只负责对数据进⾏转发,并不对数据进⾏检查,也就是说,它不负责数据的可靠性,这样设计的主要⽬的是提⾼IP协议传送和转发数据的效率。ARP:该协议负责将IP地址解析转换为计算机的物理地址。

虽然我们使⽤IP地址进⾏通信,但IP地址只是主机在抽象的⽹络层中的地址。最终要传到数据链路层封装成MAC帧才能发送到实际的⽹络。因此不管使⽤什么协议最终需要的还是硬件地址。

每个主机拥有⼀个ARP⾼速缓存(存放所在局域⽹内主机和路由器的IP地址到硬件地址的映射表)举例:A发送B

(1)A在⾃⼰的ARP⾼速缓存中查到B的MAC地址,写⼊MAC帧发往此B

(2)没查到,A向本局域⽹⼴播ARP请求分组,内容包括⾃⼰的地址映射和B的IP地址

(3)B发送ARP响应分组,内容为⾃⼰的IP地址到物理地址的映射,同时将A的映射写⼊⾃⼰的ARP⾼速缓存(单播的⽅式)注:ARP Cache映射项⽬具有⼀个⽣存时间。RARP:将计算机物理地址转换为IP地址

ICMP:该协议主要负责发送和传递包含控制信息的数据报,这些控制信息包括了哪台计算机出现了什么错误,⽹络路由出现了什么错误等内容。

5.3 传输层 -> 应⽤进程间端到端的通信

传输层主要负责应⽤进程间“端到端”的通信,即从某个应⽤进程传输到另⼀个应⽤进程,它与OSI参考模型的传输层功能类似。传输层在某个时刻可能要同时为多个不同的应⽤进程服务,因此传输层在每个分组中必须增加⽤于识别应⽤进程的标识,即端⼝。TCP/IP体系的传输层主要包含两个主要协议,即传输控制协议TCP和⽤户数据报协议UDP。TCP协议是⼀种可靠的、⾯向连接的协议,保证收发两端有可靠的字节流传输,进⾏了流量控制,协调双⽅的发送和接收速度,达到正确传输的⽬的。UDP是⼀种不可靠的、⽆连接的协议,其特点是协议简单、额外开销⼩、效率较⾼,不能保证可靠传输。

传输层提供应⽤进程间的逻辑通信。它使应⽤进程看见的就好像是在两个运输层实体间⼀条端到端的逻辑通信信道。

当运输层采⽤TCP时,尽管下⾯的⽹络是不可靠的,但这种逻辑通信信道相当于⼀条全双⼯的可靠信道。可以做到报⽂的⽆差错、按序、⽆丢失、⽆重复。

注:单单⾯向连接只是可靠的必要条件,不充分。还需要其他措施,如确认重传,按序接收,⽆丢失⽆重复。熟知端⼝:

1234567

20 FTP数据连接 21 FTP控制连接 22 SSH 23 TELNET 25 SMTP 53 DNS 69 TFTP80 HTTP161 SNMP

UDP重要UDP的优点:

1.发送之前⽆需建⽴连接,减⼩了开销和发送数据的时延

2.UDP不使⽤连接,不使⽤可靠交付,因此主机不需要维护复杂的参数表、连接状态表3.UDP⽤户数据报只有8个字节的⾸部开销,⽽TCP要20字节。

4.由于没有拥塞控制,因此⽹络出现拥塞不会使源主机的发送速率降低(IP电话等实时应⽤要求源主机以恒定的速率发送数据是有利的)Table,使⽤TCP和UDP的应⽤

应⽤名字转换⽂件传送路由选择协议IP地址配置⽹络管理远程⽂件服务器

IP电话流式多媒体通信

电⼦邮件远程终端接⼊万维⽹⽂件传送

应⽤层协议

DNSTFTPRIPBOOTTP,DHCP

SNMPNFS专⽤协议专⽤协议SMTPTELNETHTTPFTP

运输层协议

UDPUDPUDPUDPUDPUDPUDPUDPTCPTCPTCPTCP

注:TFTP:Trivial File Transfer ProtocolUDP的过程(以TFTP举例):

1.服务器进程运⾏着,等待TFTP客户进程的服务请求。客户端TFTP进程启动时,向操作系统申请⼀个临时端⼝号,然后操作系统为该进程创建2个队列,

⼊队列和出队列。只要进程在执⾏,2个队列⼀直存在。

2.客户进程将报⽂发送到出队列中。UDP按报⽂在队列的先后顺序发送。在传送到IP层之前给报⽂加上UDP⾸部,其中⽬的端⼝后为69,然后发给IP层。

出队列若溢出,则操作系统通知应⽤层TFTP客户进程暂停发送。

3.客户端收到来⾃IP层的报⽂时,UDP检查报⽂中⽬的端⼝号是否正确,若正确,放⼊⼊队列队尾,客户进程按先后顺序⼀⼀取⾛。若不正确,UDP丢弃该报⽂,并请ICMP发送”端⼝不可达“差错报⽂给服务器端。⼊队列可能会溢出,若溢出,UDP丢弃该报⽂,不通知对⽅。服务器端类似。

UDP⾸部:源端⼝ - ⽬的端⼝ - 长度 - 检验和,每个字段22字节。

注:IP数据报检验和只检验IP数据报的⾸部,⽽UDP的检验和将⾸部和数据部分⼀起都检验。TCP重要细节:

TCP报⽂段是⾯向字节的数据流。TCP⾸部:20字节固定⾸部

确认⽐特ACK,ACK=1 确认号字段才有效;同步⽐特SYN:SYN=1 ACK=0表⽰⼀个连接请求报⽂段;终⽌⽐特FIN,FIN=1时要求释放连接。

窗⼝:将TCP收发两端记为A和B,A根据TCP缓存空间的⼤⼩确定⾃⼰的接收窗⼝⼤⼩。并在A发送给B的窗⼝字段写⼊该值。作为B的发送窗⼝的上限。意味着B在未收到A的确认情况下,最多发送的字节数。

选项:最⼤报⽂段长度MSS,MSS告诉对⽅TCP:我的缓存所能接收的报⽂段的数据字段的最⼤长度是MSS个字节。若主机未填写,默认为536字节。

TCP的可靠是使⽤了序号和确认。当TCP发送⼀个报⽂时,在⾃⼰的重传队列中存放⼀个副本。若收到确认,删除副本。TCP使⽤捎带确认。

TCP报⽂段的发送时机:1.维持⼀个变量等于MSS,发送缓存达到MSS就发送 2.发送端应⽤进程指明要发送,即TCP⽀持的PUSH操作。3.设定计时器

TCP的拥塞控制:TCP使⽤慢开始和拥塞避免算法进⾏拥塞控制慢开始和拥塞避免

接收端根据⾃⾝资源情况控制发送端发送窗⼝的⼤⼩。每个TCP连接需要维持⼀下2个状态变量:

接收端窗⼝rwnd(receiver window):接收端根据⽬前接收缓存⼤⼩设置的窗⼝值,是来⾃接收端的流量控制

拥塞窗⼝cwnd(congestion window):是发送端根据⾃⼰估计的⽹络拥塞程度设置的窗⼝值,是来⾃发送端的流量控制发送端的窗⼝上限值=Min(rwnd, cwnd)

慢开始算法原理:主机刚开始发送数据时,如果⽴即将较⼤的发送窗⼝的全部字节注⼊⽹络,由于不清楚⽹络状况,可能会引起拥塞。通常的做法是将cwnd设置为1个MSS,每收到⼀个确认,将cwnd+1,由⼩到⼤逐步增⼤cwnd,使分组注⼊⽹络的速率更加合理。为了防⽌拥塞窗⼝增长引起⽹络拥塞,还需设置⼀个状态变量ssthresh,即慢开始门限。

慢开始门限:ssthresh,当cwnd < ssthresh,执⾏慢开始算法;cwnd > ssthresh,改⽤拥塞避免算法。 cwnd = ssthresh时,都可以。拥塞避免算法使发送端的拥塞窗⼝每经过⼀个RTT增加⼀个MSS(⽽不管在此期间收到多少ACK),这样,拥塞窗⼝cwnd按线性规律增长,拥塞窗⼝此时⽐慢开始增长速率缓慢很多。这⼀过程称为加法增⼤,⽬的在于使拥塞窗⼝缓慢增长,防⽌⽹络过早拥塞。

⽆论是慢开始还是拥塞避免,只要发送端发现⽹络出现拥塞(根据是没有按时收到ACK或者收到重复ACK),就将慢开始门限ssthresh设置为拥塞窗⼝值的⼀半并将拥塞窗⼝cwnd置为1,重新执⾏慢开始算法。这⼀过程称为乘法减⼩。⽬的在于迅速减少主机发送到⽹络中的分组数,使得发⽣拥塞的路由器有⾜够时间把队列中积压的分组处理完毕。上述TCP确认都是通过捎带确认执⾏的。快重传和快恢复

上述的慢开始和拥塞避免算法是早期TCP使⽤的拥塞控制算法。因为有时TCP连接会在重传时因等待重传计时器的超时时间⽽空闲。为此在快重传中规定:只要发送端⼀连收到三个重复的ACK,即可断定分组丢失,不必等待重传计数器,⽴即重传丢失的报⽂。

与快重传搭配使⽤的还有快恢复:当不使⽤快恢复时,发送端若发现⽹络拥塞就将拥塞窗⼝降为1,然后执⾏慢开始算法,这样的缺点是⽹络不能很快恢复到正常状态。快恢复是指当发送端收到3个重复的ACK时,执⾏乘法减⼩,ssthresh变为拥塞窗⼝值的⼀半。但是cwnd不是置为1,⽽是ssthresh+3xMSS。若收到的重复ACK

为n(n > 3),则cwnd=ssthresh+n*MSS.这样做的理由是基于发送端已经收到3个重复的ACK,它表明已经有3个分组离开了⽹络,它们不在消耗⽹络的资源。

注意的是:在使⽤快恢复算法时,慢开始算法只在TCP连接建⽴时使⽤。TCP的重传机制

每发送⼀个报⽂段,就对这个报⽂段设置⼀次计时器。新的重传时间=γ*旧的重传时间。TCP连接建⽴和释放的过程

SYN置1和FIN的报⽂段要消耗⼀个序号。

客户端连接状态变迁:CLOSED -> 主动打开,发送SYN=1 -> SYN_SENT -> 收到服务器的SYN=1和ACK时,发送三次握⼿的最后⼀个ACK-> ESTABLISHED -> 数据传送 -> 主动关闭 -> 发送FIN=1,等待确认ACK的到达 -> FIN_WAIT_1 -> 收到确认ACK后 -> FIN_WAIT_2-> 收到服务器发送的FIN=1报⽂,响应,发送四次挥⼿的的最后⼀个确认ACK -> 进⼊TIME_WAIT状态-> 经过2倍报⽂寿命,TCP删除连接记录 -> 回到CLOSED状态

客户端状态:CLOSED - SYN_SENT- ESTABLISHED - FIN_WAIT_1 - FIN_WAIT_2 - TIME_WAIT - CLOSED

服务器端连接状态变迁:CLOSED -> 被动打开 -> LISTEN -> 收到SYN=1的报⽂,发送SYN=1和确认ACK -> 进⼊SYN_RCVD -> 收到三次握⼿

的最后⼀个确认ACK -> ESTABLISHED -> 数据传送 -> 数据传送完毕,收到FIN=1 -> 发送确认ACK并进⼊CLOSED_WAIT -> 发送FIN=1给客户端 -> LAST_ACK

-> 收到客户端四次挥⼿的最后⼀个确认ACK -> 删除连接记录 -> 回到CLOSED状态

服务器端:CLOSED - LISTEN - SYN_RCVD - ESTABLISHED - CLOSED_WAIT - LAST_ACK - CLOSED5.4 应⽤层

应⽤层位于TCP/IP体系结构的最⾼⼀层,也是直接为应⽤进程服务的⼀层,即当不同的应⽤进程数据交换时,就去调⽤应⽤层的不同协议实体,让这些实体去调⽤传输层的TCP或者UDP来进⾏⽹络传输。具体的应⽤层协议有,SMTP 25、DNS 53、HTTP 80、FTP 20数据端⼝ 21控制端⼝、TFTP 69、TELNET 23、SNMP 161等5.5 ⽹络的划分

按⽹络拓扑结构:总线、星型、环型、树型、⽹状结构和混合型。按覆盖范围:局域⽹、城域⽹、⼴域⽹按传播⽅式:⼴播⽹络和点对点⽹络

⼴播式⽹络是指⽹络中的计算机使⽤⼀个共享信道进⾏数据传播,⽹络中的所有结点都能收到某⼀结点发出的数据信息。单播:⼀对⼀的发送形式。

组播:采⽤⼀对⼀组的发送形式,将数据发送给⽹络中的某⼀组主机。⼴播:采⽤⼀对所有,将数据发送给⽹络所有⽬的结点。

点对点⽹络中两个结点间的通信⽅式是点对点的。如果两台计算机之间没有直连的线路,则需要中间结点的接收、存储、转发直⾄⽬的结点。

6. TCP的三次握⼿和四次挥⼿的过程以客户端为例

连接建⽴(三次握⼿):⾸先Client端发送连接请求报⽂SYN并进⼊SYN_SENT状态,Server收到后发送ACK+SYN报⽂,并为这次连接分配资源。Client端接收到Server端的SYN+ACK后发送三次握⼿的最后⼀个ACK,并分配资源,连接建⽴。

连接释放(四次挥⼿):假设Client端发起断开连接请求,⾸先发送FIN=1,等待确认ACK的到达 -> FIN_WAIT_1 -> 收到Server端的确认ACK后时 -> FIN_WAIT_2

->收到服务器发送的FIN=1报⽂,响应,发送四次挥⼿的的最后⼀个确认ACK ->进⼊TIME_WAIT状态-> 经过2倍报⽂寿命,TCP删除连接记录 -> 回到CLOSED状态7. 为什么连接建⽴是三次握⼿,⽽连接释放要四次挥⼿?

因为当Server端收到Client端发送的SYN连接请求报⽂后,可以直接发送SYN+ACK报⽂,其中ACK⽤来应答,SYN⽤来同步。但是关闭连接时,当Server端收到FIN报⽂后,并不会⽴即关闭socket,所以先回复⼀个ACK,告诉Client端“你的FIN我收到了”,只有等Server端的所有报⽂发送完了,Server端才发送FIN报⽂,因此不能⼀起发送,故需要四次挥⼿。8. 为什么TIME_WAIT状态需要2MSL(最⼤报⽂段⽣存时间)才能返回Closed状态?

这是因为虽然双⽅都同意关闭连接了,⽽且四次挥⼿的报⽂也都协调发送完毕。但是我们必须假想⽹络是不可靠的,⽆法保证最后发送的ACK报⽂⼀定被对⽅收到,因此处于LAST_ACK状态下的

Server端可能会因未收到ACK⽽重发FIN,所以TIME_WAIT状态的作⽤就是⽤来重发可能丢失的ACK报⽂。9. Http报⽂格式

Http请求报⽂格式:1.请求⾏ 2.Http头 3.报⽂主体

请求⾏由三部分组成,分别是请求⽅法,请求地址,Http版本

Http头:有三种,分别为请求头(request header),普通头(General Header)和实体头(entity header)。Get⽅法没有实体头。报⽂主体:只在POST⽅法请求中存在。Http响应报⽂:1.状态⾏ 2.Http头 3.返回内容

状态⾏:第⼀部分为Http版本,第⼆部分为响应状态码 第三部分为状态码的描述

其中第三部分为状态码的描述,信息类100-199 响应成功200-299 重定向类300-399 客户端错误400-499 服务器端错误500-599常见的123456710111213

100 continue 初始请求已接受,客户端应继续发送请求剩余部分200 OK

202 Accepted 已接受,处理尚未完成 301 永久重定向302 临时重定向400 Bad Request401 Unauthorized403 Forbidden 资源不可⽤404 Not Found

500 Internal Server Error 服务器错误502 Bad Gateway

503 Service Unavailable 服务器负载过重

504 Gateway Timeout 未能及时从远程服务器获得应答

Http头:响应头(Response Header),普通头(General Header)和实体头(Entity Header)返回内容:即Http请求的信息,可以是HTML也可以是图⽚等等。10. Http和Https的区别

Https即Secure Hypertext Transfer Protocol,即安全超⽂本传输协议,它是⼀个安全通信信道,基于Http开发,⽤于在客户机和服务器间交换信息。它使⽤安全套接字层SSL进⾏信息交换,是Http的安全版。Https协议需要到CA申请证书,⼀般免费证书很少,需要交费。

Http是超⽂本传输协议,信息是明⽂传输,https则是具有安全性的tls/ssl加密传输协议。http是80端⼝,https是443端⼝

11. 浏览器输⼊⼀个URL的过程

1. 浏览器向DNS服务器请求解析该URL中的域名所对应的IP地址2. 解析出IP地址后,根据IP地址和默认端⼝80和服务器建⽴TCP连接

3. 浏览器发出Http请求,该请求报⽂作为TCP三次握⼿的第三个报⽂的数据发送给服务器4. 服务器做出响应,把对应的请求资源发送给浏览器5. 释放TCP连接6. 浏览器解析并显⽰内容

12. 中间⼈攻击

中间⼈获取server发给client的公钥,⾃⼰伪造⼀对公私钥,然后伪造⾃⼰让client以为它是server,然后将伪造的公钥发给client,并拦截client发给server的密⽂,⽤伪造的私钥即可得到client发出去的内容,最后⽤真实的公钥对内容加密发给server。解决办法:数字证书,证书链,可信任的中间⼈13. 差错检测

误码率:传输错误的⽐特与传输总⽐特数的⽐率

CRC是检错⽅法并不能纠错,FCS(Frame Check Sequence)是冗余码。计算冗余码(余数R)的⽅法:先补0(n个)再对⽣成多项式取模。

CRC只能表⽰以接近1的概率认为它没有差错。但不能做到可靠传输。可靠传输还需要确认和重传机制。⽣成多项式P(X):CRC-16,CRC-CCITT,CRC-3214. 数据链路层的协议

停⽌等待协议 - 连续ARQ - 选择重传ARQ - PPP - 以太⽹协议- 帧中继 - ATM - HDLC15. 截断⼆进制指数退避算法

是以太⽹⽤于解决当发⽣碰撞时就停⽌发送然后重发再碰撞这⼀问题。

截断⼆进制指数退避算法:基本退避时间为2τ k=min{重传次数,10} r=random(0~2^k-1) 重传所需时延为r倍的基本退避时间

⼋、操作系统(OS基础、Linux等)1. 并发和并⾏

“并⾏”是指⽆论从微观还是宏观,⼆者都是⼀起执⾏的,也就是同⼀时刻执⾏⽽“并发”在微观上不是同时执⾏的。是在同⼀时间间隔交替轮流执⾏2. 进程间通信的⽅式

管道( pipe ):管道是⼀种半双⼯的通信⽅式,数据只能单向流动,⽽且只能在具有亲缘关系的进程间使⽤。进程的亲缘关系通常是指⽗⼦进程关系。

有名管道 (named pipe) : 有名管道也是半双⼯的通信⽅式,但是它允许⽆亲缘关系进程间的通信。

信号量( semophore ) :信号量是⼀个计数器,可以⽤来控制多个进程对共享资源的访问。它常作为⼀种锁机制,防⽌某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同⼀进程内不同线程之间的同步⼿段。

消息队列( message queue ) 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载⽆格式字节流以及缓冲区⼤⼩受限等缺点。

信号 ( sinal ) : 信号是⼀种⽐较复杂的通信⽅式,⽤于通知接收进程某个事件已经发⽣。共享内存( shared memory )

共享内存就是映射⼀段能被其他进程所访问的内存,这段共享内存由⼀个进程创建,但多个进程都可以访问。共享内存是最快的 IPC ⽅式,它是针对其他进程间通信⽅式运⾏效率低⽽专门设计的。它往往与其他通信机制,如信号量配合使⽤,来实现进程间的同步和通信。

套接字( socket ) :套接字也是⼀种进程间通信机制,与其他通信机制不同的是,它可⽤于不同机器间的进程通信。3. LinuxIO模型1、阻塞IO模型

以socket为例,在进程空间调⽤recvfrom,其系统调⽤知道数据包到达且被复制到应⽤进程的缓冲区或者发⽣错误才返回,在此期间⼀直等待,进程从调⽤recvfrom开始到它返回的整段时间内都是被阻塞的,因此称为阻塞IO2、⾮阻塞IO模型

应⽤进程调⽤recvfrom,如果缓冲区没有数据直接返回EWOULDBLOCK错误。⼀般对⾮阻塞IO进⾏轮询,以确定是否有数据到来。3、IO多路复⽤模型

Linux提供select/poll,通过将⼀个或多个fd传递给select或poll系统调⽤,阻塞在select上。select/poll顺序扫描fd是否就绪。4、信号驱动IO

开启套接字接⼝信号驱动IO功能,并通过系统调⽤sigaction执⾏信号处理函数。当数据准备就绪时,为该进程⽣成SIGIO信号,通过信号回调通知应⽤程序调⽤recvfrom来读取数据,并通知主函数处理数据。5、异步IO

告知内核启动某个操作,并让内核在整个操作完成后通知我们。它与信号驱动IO的区别在于信号驱动IO由内核通知我们何时可以开始IO操作。⽽异步IO模型由内核通知我们IO操作已经完成。

九、其他

1. 开源软件有哪些?

Eclipse、Linux及其Linux下的⼤多数软件、Git等。

Apache下的众多软件:Lucene、Velocity、Maven、⾼性能Java⽹络框架MINA、版本控制系统SVN、应⽤服务器Tomcat、Http服务器Apache、MVCStruts、持久层框架iBATIS、Apache SPARK、ActiveMQ2. 开源协议

MIT:相对宽松。适⽤:JQuery

Apache:相对宽松与MIT类似的协议,考虑有专利的情况。适⽤:Apache服务器、SVN

GPL:GPLV2和GPLV3,如果你在乎作品的传播和别⼈的修改,希望别⼈也以相同的协议分享出来。

LGPL:主要⽤于⼀些代码库。衍⽣代码可以以此协议发布(⾔下之意你可以⽤其他协议),但与此协议相关的代码必需遵循此协议。BSD:较为宽松的协议,包含两个变种BSD 2-Clause 和BSD 3-Clause,两者都与MIT协议只存在细微差异。

上⾯各协议只是针对软件或代码作品,如果你的作品不是代码,⽐如视频,⾳乐,图⽚,⽂章等,共享于公众之前,也最好声明⼀下协议以保证⾃⼰的权益不被侵犯,CC协议。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- baoaiwan.cn 版权所有 赣ICP备2024042794号-3

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务