赞
踩
总结: JDK 包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,就需要安装 JDK。
Java中数据类型分为基本数据类型和引用数据类型2种
基本类型和引用类型比较,== 的作用效果是不同的。
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));
equals 本质上就是 ==,Object类中定义的 equals 方法如下
public boolean equals(Object obj) {
return (this == obj);
}
总结:== 对于基本类型比较的是值,对于引用类型比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
不正确,两个对象的 hashCode() 相同,equals() 不一定 true。比如在 map 中,hashCode() 相等,只能说明这两个键值对的哈希值相同,不代表这两个键值对相等。
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));
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
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
拥有抽象方法(指没有方法体的方法,同时抽象方法还必须使用关键字abstract 做修饰)的类就是抽象类,抽象类要使用 abstract 关键字声明。
不需要,抽象类不一定非要有抽象方法,如下代码可以正常运行
public abstract class elephant {
String str = "apple";
public void test01(){
System.out.println("aaaa");
}
}
不能,定义抽象类就是让其他类继承的,如果定义为 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():写入文件
在 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();
Queue 中 remove() 和 poll() 都是用来从队列头部删除一个元素,在队列元素为空的情况下,remove() 方法会抛出 NoSuchElementException 异常,poll() 方法只会返回 null。
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
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()); } }
普通解释:
专业术语:
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是 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();
}
}
② 实现 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();
}
}
③ 通过 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 } }
线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
如果线程调用了对象的 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)
线程安全在三个方面体现:
在 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++ 一样的指针越界到其他进程的问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。