总结: JDK 包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了不少 java 程序调试和分析的工具。简单来讲:若是你须要运行 java 程序,只需安装 JRE 就能够了,若是你须要编写 java 程序,就须要安装 JDK。java
Java中数据类型分为基本数据类型和引用数据类型2种web
基本类型和引用类型比较,== 的做用效果是不一样的。算法
基本类型:比较的是值是否相同设计模式
引用类型:比较的是引用是否相同数组
int x = 10;
int y = 10;
String a = "panda";
String b = "panda";
String c = new String("panda");
// true 基本类型比较值是否相同
System.out.println(x == y);
// true 引用类型比较引用是否相同,这里引用相同
System.out.println(a == b);
// false 引用不一样
System.out.println(a == c);
// true 引用不一样,String重写了equals,使其用值比较
System.out.println(a.equals(c));
复制代码
12345678910111213缓存
equals 本质上就是 ==,Object类中定义的 equals 方法以下安全
public boolean equals(Object obj) {
return (this == obj);
}
123
复制代码
总结:== 对于基本类型比较的是值,对于引用类型比较的是引用;而 equals 默认状况下是引用比较,只是不少类重写了 equals 方法,好比 String、Integer 等把它变成了值比较,因此通常状况下 equals 比较的是值是否相等。服务器
不正确,两个对象的 hashCode() 相同,equals() 不必定 true。好比在 map 中,hashCode() 相等,只能说明这两个键值对的哈希值相同,不表明这两个键值对相等。markdown
String str1 = "通话";
String str2 = "重地";
// str1: 1179395 | str2: 1179395
System.out.println(String.format("str1: %d | str2: %d",str1.hashCode(),str2.hashCode()));
// false
System.out.println(str1.equals(str2));
123456
复制代码
String 是字符串常量,每次操做都会生产新的对象,适用于少许字符串操做的状况;StringBuffer、StringBuilder 是字符串变量,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,因此在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。数据结构
不同,由于内存的分配方式不同。String str=“donkey”,java 虚拟机会将其分配到常量池中;而 String str=new String(“donkey”) 则会被分到堆内存中。
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("abcdefg");
System.out.println(stringBuffer.reverse()); // gfedcba
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abcdefg");
System.out.println(stringBuilder.reverse()); // gfedcba
123456
复制代码
String str = " app le ";
// indexOf(): 返回指定字符的索引
System.out.println(str.indexOf("a")); // 1
// charAt(): 返回指定索引处的字符
System.out.println(str.charAt(5)); // l
// replace(): 字符串替换
System.out.println(str.replace("pp", "cc")); // " acc le "
// trim(): 去除字符串两端空白
System.out.println(str.trim()); // "app le"
// split(): 分割字符串,返回一个分割后的字符串数组
String[] arr = str.split(" ");
// getBytes(): 返回字符串的 byte 类型数组
byte[] bytes = str.getBytes();
// length(): 返回字符串长度
System.out.println(str.length()); // 8
// toLowerCase(): 将字符串转成小写字母
System.out.println(str.toLowerCase()); // " app le "
// toUpperCase(): 将字符串转成大写字符
System.out.println(str.toUpperCase()); // " APP LE "
// substring(): 截取字符串
System.out.println(str.substring(2)); // "pp le "
// equals(): 字符串比较
System.out.println("apple".equals(str)); // false
1234567891011121314151617181920212223
复制代码
拥有抽象方法(指没有方法体的方法,同时抽象方法还必须使用关键字abstract 作修饰)的类就是抽象类,抽象类要使用 abstract 关键字声明。
不须要,抽象类不必定非要有抽象方法,以下代码能够正常运行
public abstract class elephant {
String str = "apple";
public void test01(){
System.out.println("aaaa");
}
}
123456
复制代码
不能,定义抽象类就是让其余类继承的,若是定义为 final 该类就不能被继承,这样彼此就会产生矛盾,因此 final 不能修饰抽象类。
接口能够理解为一种特殊的类,里面所有是由全局常量和公共的抽象方法所组成。接口是解决Java没法使用多继承的一种手段,可是接口在实际中更多的做用是制定标准。
字节流和字符流的区别:字节流按 8 位传输,以字节为单位输入输出数据;字符流按 16 位传输,以字符为单位输入输出数据。
// Files.exists():检测文件路径是否存在
Path path1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test");
System.out.println(Files.exists(path1, new LinkOption[]{LinkOption.NOFOLLOW_LINKS})); // true
// Files.createFile():建立文件
Path path2 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\a.txt");
try {
Path newFilw = Files.createFile(path2);
} catch (FileAlreadyExistsException e){
System.out.println("exists");
} catch (IOException e) {
System.out.println("other wrong");
}
// Files.createDirectory():建立文件夹
Path path3 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newDirectory");
try {
Path newDirectory = Files.createDirectory(path3);
} catch (FileAlreadyExistsException e) {
System.out.println("exists");
} catch (IOException e){
System.out.println("other wrong");
}
// Files.delete():删除一个文件或目录
try {
Files.delete(path2);
} catch (IOException e) {
e.printStackTrace();
}
// Files.copy():复制文件
Path source = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\b.txt");
Path target = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newb.txt");
try {
Files.copy(source,target);
} catch (FileAlreadyExistsException e) {
System.out.println("targetFile already exists");
} catch (IOException e){
System.out.println("other wrong");
}
// Files.move():移动文件
Path source1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\b.txt");
Path target1 = Paths.get("C:\\Users\\e-yangfangchao\\Desktop\\test\\newDirectory\\a.txt");
try {
Files.move(source1,target1);
} catch (FileAlreadyExistsException e) {
System.out.println("targetFile1 already exists");
} catch (IOException e){
System.out.println("other wrong");
}
// Files.size():查看文件个数
// Files.read():读取文件
// Files.write():写入文件
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
复制代码
在 Map 中作插入、删除和定位元素这类操做,HashMap 是最好的选择。假如你须要对一个有序的 key 集合进行遍历,TreeMap 是更好的选择。
HashMap概述: HashMap 是基于哈希表的Map接口的非同步实现。此实现提供全部可选的映射操做,并容许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构: HashMap 其实是一个“链表散列”的数据结构,即数组和链表的结合体。
当咱们往 HashMap 中 put 元素时,首先根据 key 的 hashcode 从新计算 hash 值,根据 hash 值获得这个元素在数组中的位置(下标),若是该数组在该位置上已经存放了其余元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最早加入的放入链尾。若是数组中该位置没有元素,就直接将该元素放到数组的该位置上。
Jdk1.8 中对 HashMap 的实现作了优化,当链表中的节点数据超过八个以后,该链表会转为红黑树来提升查询效率,从原来的 O(n) 到 O(logn)。
HashSet 实现 Set 接口,由哈希表(其实是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变;此类容许使用 null 元素;HashSet 中不容许有重复元素。
HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个 private static final Object PRESENT = new Object()
。HashSet跟HashMap同样,都是一个存放链表的数组。
ArrayList 底层基于动态数组,随机访问元素效率高,向集合尾部添加元素效率高,删除或者在其余位置添加元素效率低(须要移动数组);LinkedList 基于链表的动态数组,数据添加和删除效率高,只须要改变指针指向便可,可是访问数据的平均效率低,须要对链表进行遍历。
// List 转 数组
String[] strArr = {"apple","pear","banana","peach"};
List<String> list = Arrays.asList(strArr);
// 数组 转 List
String[] arr = (String[]) list.toArray();
12345
复制代码
Queue 中 remove() 和 poll() 都是用来从队列头部删除一个元素,在队列元素为空的状况下,remove() 方法会抛出 NoSuchElementException 异常,poll() 方法只会返回 null。
迭代器是一种设计模式,它是一个对象,它能够遍历并选择序列中的对象,而开发人员不须要了解该序列的底层结构。迭代器一般被称为“轻量级”对象,由于建立它的代价小。
Iterator 功能比较简单,而且只能单向移动
使用方法 iterator() 要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 继承。
使用 hasNext() 检查序列中是否还有元素
使用 next() 得到序列中的下一个元素
使用 remove() 将迭代器新返回的元素删除
Iterator 是 Java 迭代器最简单的实现,为 List 设计的 ListIterator 具备更多的功能,它能够从两个方向遍历 List,也能够从 List 中插入和删除元素。
public static void main(String[] args) {
// List
ArrayList<String> list = new ArrayList<>();
list.add("apple");
list.add("pear");
list.add("banana");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if ("apple".equals(s)){
iterator.remove();
}
}
list.forEach(item -> System.out.println(item));
// Map<key,value>
Map<String,String> map=new HashMap<>();
map.put("pig","猪");
map.put("cat","猫");
map.put("dog","狗");
Iterator<String> iterator1 = map.keySet().iterator();
Iterator<String> iterator2 = map.values().iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}
}
复制代码
1234567891011121314151617181920212223242526272829
普通解释:
专业术语:
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程当中拥有独立的内存单元,而多个线程共享内存资源,减小切换次数,从而效率更高。线程是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间能够并发执行。
守护线程(即 daemon thread),是个服务线程,准确地来讲就是服务其余的线程。它可以自我结束。若是 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。JVM 中的垃圾回收线程就是典型的守护线程,若是说没有守护线程,JVM 就永远不会退出了
① 继承 Thread 类建立线程
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("run task");
}
}
public class Demo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
123456789101112
复制代码
② 实现 Runnable 接口建立线程
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println("run task");
}
}
public class Demo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
12345678910111213
复制代码
③ 经过 Callable 和 Future 建立线程
public class MyThread implements Callable {
@Override
public Object call() {
System.out.println("run!");
return "run task success";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask<String>(myThread);
Thread thread = new Thread(futureTask);
thread.start();
// 得到子线程执行结束后的返回值
System.out.println(futureTask.get()); // run task success
}
}
1234567891011121314151617
复制代码
线程一般都有五种状态,建立、就绪、运行、阻塞和死亡。
若是线程调用了对象的 wait() 方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll() 方法(唤醒全部 wait 线程)或 notify() 方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了 notify 后只有一个线程会由等待池进入锁池,而 notifyAll 会将该对象等待池内的全部线程移动到锁池中,等待锁竞争。
优先级高的线程竞争到对象锁的几率大,倘若某线程没有竞争到该对象锁,它还会留在锁池中,惟有线程再次调用 wait() 方法,它才会从新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
每一个线程都是经过某个特定 Thread 对象所对应的方法 run() 来完成其操做的,方法 run() 称为线程体。经过调用 Thread 类的 start() 方法来启动一个线程。
线程池有5种状态:Running、ShutDown、Stop、Tidying、Terminated。
线程池各个状态切换框架图:
接收的参数不同
submit(Runnable task) submit(Runnable task,T result) submit(Callable task) execute(Runnable command) 1234
submit() 有返回值,而 execute() 没有。用到返回值的例子,好比说我有不少个作 validation 的task,我但愿全部的 task 执行完,而后每一个 task 告诉我它的执行结果,是成功仍是失败,若是是失败,缘由是什么。
submit()方便 Exception 处理,若是你在你的 task 里会抛出 checked 或者 unchecked exception,而你又但愿外面的调用者可以感知这些 exception 并作出及时的处理,那么就须要用到submit,经过捕获 Future.get 抛出的异常。
线程安全在三个方面体现:
在 Java 中,锁共有 4 种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争状况逐渐升级。锁能够升级但不能降级。
锁升级的图示过程:
死锁是指两个或两个以上的进程在执行过程当中,因为竞争资源或者因为彼此通讯而形成的一种阻塞的现象,若无外力做用,它们都将没法推动下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操做系统层面的一个错误,是进程死锁的简称,最先在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操做系统乃至整个并发程序设计领域最难处理的问题之一。
死锁的四个必要条件:
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之 一不知足,就不会发生死锁。
理解了死锁的缘由,尤为是产生死锁的四个必要条件,就能够最大可能地避免、预防和 解除死锁。因此,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确 定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的状况下占用资源。所以,对资源的分配要给予合理的规划。
线程局部变量是局限于线程内部的变量,属于线程自身全部,不在多个线程间共享。Java 提供ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。可是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别当心,在这种状况下,工做线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工做完成后没有释放,Java 应用就存在内存泄露的风险。
synchronized 能够保证方法或者代码块在运行时,同一时刻只有一个方法能够进入到临界区,同时它还能够保证共享变量的内存可见性。
Java 中每个对象均可以做为锁,这是 synchronized 实现同步的基础:
synchronized 是和 if、else、for、while 同样的关键字,ReentrantLock 是类,这是两者的本质区别。既然 ReentrantLock 是类,那么它就提供了比 synchronized 更多更灵活的特性,能够被继承、能够有方法、能够有各类各样的类变量,ReentrantLock 比 synchronized 的扩展性体如今几点上:
另外,两者的锁机制其实也是不同的:ReentrantLock 底层调用的是 Unsafe 的 park 方法加锁,synchronized 操做的应该是对象头中 mark word。
Atomic 包中的类基本的特性就是在多线程环境下,当有多个线程同时对单个(包括基本类型及引用类型)变量进行操做时,具备排他性,即当多个线程同时对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程能够向自旋锁同样,继续尝试,一直等到执行成功。
Atomic 系列的类中的核心方法都会调用 unsafe 类中的几个本地方法。咱们须要先知道一个东西就是Unsafe 类,全名为:sun.misc.Unsafe,这个类包含了大量的对 C 代码的操做,包括不少直接内存分配以及原子操做的调用,而它之因此标记为非安全的,是告诉你这个里面大量的方法调用都会存在安全隐患,须要当心使用,不然会致使严重的后果,例如在经过 unsafe 分配内存的时候,若是本身指定某些区域可能会致使一些相似 C++ 同样的指针越界到其余进程的问题。