DBIS的21天学习打卡系列活动,此篇将记录这期间学习的Java知识。琦琦学长发钱了,冲啊!
感谢杀哥引路JavaGuide
5.1 Java基础概念与常识
今日五一,但打卡不能停,就简单先看下Java的基础概念与常识部分吧,嘿嘿。
其实Java的程序的特点就在于它的运行方式,一图以概之:JDK(Java Development Kit)是一套完整的Java开发包,它包含了JRE(Java Runtime Enviroment),还有javac编译器将我们写的.java文件编译成.class文件。这似乎还和C++将源代码编译成汇编代码类似。但Java牛B在它有一个Java虚拟机(JVM),它针对不同系统有特点的实现,可以将.class文件解释为机器码运行。JVM屏蔽了操作系统的差异,而.class字节码对不同的JVM是没有差别的,这样就是Java所说的“一次编译,到处运行”。而且前一步是编译,后一步是解释,所以Java“编译与解释并存”。
Oracle JDK和OpenJDK
OpenJDK是开源的参考模型,Oracle JDK是OpenJDK的一个实现,对OpenJDK进行了扩展和修复,性能更好更加稳定,但并不完全开源。
Java和C++区别
Java不提供指针直接访问内存,类不可以多继承,提供自动内存管理垃圾回收机制(GC),只支持方法重载不支持操作符重载。
5.2 基本语法
JavaGuide开始有点看不懂了,还是先转战菜鸟教程看看基础语法吧,尴尬了。
- 一个源文件中只能有一个 public 类,与文件名一致
- final修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的
- default同一包内可见,public对所有类可见,private同一类内可见(不能修饰类),protected同一包内和所有子类可见(不能修饰类)
- abstract 抽象类,抽象方法public abstract sample();
- synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。
- 序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
- volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
- instanceof 运算符用于操作对象实例,检查该对象是否是一个特定类型(类类型或接口类型)
各数据类型字节数与C++类似,不同的是char占两个字节,应该是UCS-2编码了。
Java关键字:
5.3 续
- Java增强for循环,有点类似于python的for . in .
1
2
3
4
5
6int [] numbers = {10, 20, 30, 40, 50};
for(int x : numbers ){
System.out.print( x );
System.out.print(",");
} - 装箱:将内置数据类型转为包装类。 拆箱:反之
- String类不可改变,要修改的话使用StringBuffer或StringBuilder类,StringBuilder不是线程安全的
- Arrays类的静态方法可以方便地操作数组
- Java有日期时间类Date,Calendar
- Jaava正则表达式Java.util.regex包
- 语法糖可变参数typeName... parameterName,只能是最后一个,相当于数组
- finalize(),对象析构前调用的方法
- 控制台输入,可以把System.in包装在一个BufferedReader对象中来创建一个字符流,然后使用read()读取字符,readline()读取字符串
1
2BufferedReader br = new BufferedReader(new
InputStreamReader(System.in)); - 读写文件FileInputStream和FileOutputStream
1
InputStream f = new FileInputStream("C:/java/hello");
- Scanner类来获取用户输入
1
2
3
4
5
6
7Scanner scan = new Scanner(System.in);
if (scan.hasNext()) {
String str1 = scan.next();
// String str2 = scan.nextLine();
System.out.println("输入的数据为:" + str1);
}
scan.close(); - Java异常使用try/catch捕获处理,throws声明函数要抛出的异常,throw抛出异常,自定义异常继承Exception或RuntimeException
- extends继承,不支持多继承,默认继承java.lang.Object;implements继承接口,支持继承多个接口。父类构造函数有参需要显式地使用super调用
- 重写与重载,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 多态的三个必要条件:继承、重写、父类引用指向子类对象
- 接口是抽象方法的集合。不能包含除static和final的成员变量,接口中的方法隐式的指定为public abstract,类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面,接口也可以使用extends继承。没有任何方法的接口称为标记接口,主要用于建立一个公共的父接口或向一个类添加数据类型(将类通过多态性变成接口类型)
- 枚举是一个特殊的类,常用在switch语句中,也可以有变量、方法和构造函数。
- package创建包,import导入包,编译时有多少个类和接口就有多少同名.class文件,CLASSPATH类文件路径
5.4 Java数据结构
- Bitset类,实现了一组可以单独设置和清除的位或标志,对布尔值操作比较方便
- 向量Vector,与数组类似,但大小能根据需要动态的变化
- Stack类是Vector的一个子类,实现了一个先进后出的栈
1
Stack<Integer> st = new Stack<Integer>()
- Hashtable实现了抽象类Dictionary,他和HashMap类相似,但支持同步
- Properties类继承与Hashtable,表示属性集,其中键值都是字符串
集合框架
集合框架围绕一组标准接口设计实现了高效的基本集合,其架构图如下所示。
Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射。Collection 接口又有 3 种子类型,List、Set 和 Queue,再下面是一些抽象类,最后是具体实现类,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
- ArrayList是可以动态修改的数组,与Vector类似,但是Vector是同步的,且包含很多不属于集合框架的传统方法 E: 泛型数据类型,用于设置 objectName 的数据类型,只能为引用数据类型(基本类型的包装类)
1
2
3import java.util.ArrayList;
ArrayList<E> objectName = new ArrayList<E>(); - LinkedList链表类似于ArrayList,增加和删除操作效率更高,查找和修改操作效率较低。
1
2
3
4
5import java.util.LinkedList;
LinkedList<E> list = new LinkedList<E>(); // 普通创建方法
LinkedList<E> list = new LinkedList(Collection<? extends E> c); // 使用集合创建链表
5.5 续
- HashSet是基于HashMap实现的不允许有重复元素的集合
- HashMap散列表,存储键值对
1
2
3import java.util.HashMap; // 引入 HashMap 类
HashMap<Integer, String> Sites = new HashMap<Integer, String>(); - 迭代器Iterator,常用next、hasNext、remove
- Java泛型,参数化类型,有界的参数类型使用extends后加上界,泛型类类名后面接
,类型通配符如List<?> 1
2
3
4
5
6
7
8
9
10
11
12public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
} - Java序列化使用ObjectInputStream和ObjectOutputStream,序列化的类必须实现java.io.Serializable接口
- Java网络编程,ServerSocket类和Socket类
- 创建线程:实现Runnable接口的类,重写run()方法,使用该类实例化一个Thread对象;创建一个类继承Thread类,重写run()方法,调用start()方法;创建实现Callable接口类的实例,用FutureTask包装,创建Thread对象启动线程
5.6 回到JavaGuide
- Java的泛型是伪泛型,编译期间所有泛型信息都会被擦掉,也就是类型擦除
- 对于基本数据类型来说,==比较的是值。对于引用数据类型来说,==比较的是对象的内存地址,equals()默认为Object类的equals()方法,等价于“==”比较两个对象,如果重写equals(),通常比较对象的属性值是否相等
- hashCode()返回对象的哈希码,可以快速确定对象在哈希表中的位置,重写equals时必须重写hashcode方法。
- Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在[0,127]范围的缓存数据,Boolean 直接返回 True Or False,所有整形包装对象值的比较实用equals方法(超过缓冲池的对象不会复用)
- 在一个静态方法内调用非静态成员非法
- java中只有值传递,一个方法不能让对象参数引用一个新的对象
- 重载和重写
- 面向过程性能比面向对象高,但面向对象易维护,易复用、易扩展
- 构造方法不能被重写但是可以被重载
5.8 续
- 面向对象三大特征:封装、继承、多态(父类的引用指向子类的实例)
- String对象不可变,StringBuffer可变且线程安全、StringBuider高效线程不安全
- native函数,使用其他语言(如C++)实现函数,并被编译成了DLL供Java调用
- 反射,在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应方法,可以让代码更加灵活,但也增加了安全问题
- try-catch-finally,当try和finally语句中都有return时,finally语句的内容将被执行,且finally语句的返回值会覆盖原始返回值
- 面对需要关闭的资源,使用try-with-resources
1
2
3
4
5
6
7try (Scanner scanner = new Scanner(new File("test.txt"))) {
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} - equals方法可能产生空指针异常(使用空指针调用),尽量使用常量或确定值调用
- 浮点数之间等值的判断可以使用BigDecimal
- 代理模式:使用代理对象来替代真实对象的访问,提供额外的功能操作。分为静态代理(将目标对象注入创建的代理类)和动态代理(JDK,CGLIB)
- 同步阻塞IO(BIO)、同步非阻塞IO(NIO)、异步IO(AIO)
5.9 续
Java容器
- comparable接口出自java.lang,有排序方法
compareTo(Object obj)
方法;comparator接口出自java.util,有compare(Object obj1, Object obj2)
,重写他们可以实现自定义排序 - HashMap非线程安全,HashTable线程安全,但基本淘汰,可用ConcurrentHashMap
- HashSet底层是基于HashMap实现的
- TreeMap和HashMap都继承自AbstractMap,但TreeMap还实现了NavigableMap和SortedMap接口,有对元素搜索可排序的能力
- HashMap底层的链表散列,当相同hashcode值对应的链表较长时,会转化为红黑树
- HashMap的长度是2的幂次方,在计算数组下标时可以将取模运算转化为位运算&,提高了计算效率
- Collections工具类排序、查找、替换同步控制操作
Java并发基础
- 线程共享进程的堆和方法区资源,线程有自己的程序计数器、虚拟机栈和本地方法栈(native方法),main线程执行main方法
- 使用多线程从单核来说可以提高CPU和IO设备的综合利用率,多核来说可以提高CPU利用率,提高系统的整体并发能力和性能
- 多线程可能导致内存泄露、死锁、线程不安全等问题
- 线程的生命周期和状态
- 上下文切换:任务从保存到再加载的过程
- 死锁的四个条件:互斥、请求与保持、不剥夺、循环等待
sleep()
方法没有释放锁,用于暂停执行,会自动苏醒;wait()
方法释放了锁,用于线程间通信,不会自动苏醒- 调用
start()
方法可可启动线程并使线程进入就绪状态,直接执行run()
方法不会以多线程方式执行
5.10 并发进阶
- synchronized关键字保证被它修饰的方法或代码块任意时刻只有一个线程执行
- synchronized关键字加倒static静态方法和synchronized(class)代码块上都是给class类上锁,加到实例方法上是给对象实例上锁
- 双重校验锁实现对象单例,volatile可禁止指令重排,保证多线程访问正确性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
} - synchronized同步语句块实际上是monitorenter和monitorexit指令,修饰方法使用了ACC_SYNCHRONIZED标识指明是同步方法
- synchronized和ReetrantLock都是可重入锁,前者依赖于JVM后者依赖于API且增加了一些高级功能
- volatile关键字除了防止JVM指令重排,还能保证变量的可见性
- 并发编程三个重要特性:原子性、可见性、有序性
- ThreadLocal类创建变量,使得每个线程都有该变量的本地副本,可以使用get()和set()方法获取和修改值
- Runnable接口不会返回结果或跑出异常,Callable接口可以
- execute()方法提交不需要返回值的任务,submit()方法提交需要返回值的任务,返回一个Future对象
- 三种类型的ThreadPoolExecutor:FixedThreadPool, SingleThreadExecutor, CachedThreadPool
- 线程池Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// MyRunnable.java
public class MyRunnable implements Runnable {
...
public void run(){
...
}
...
}
// ThreadPoolExecutorDemo.java
public class ThreadPoolExecutorDemo{
public static void main(String[] args){
ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
for (int i = 0; i < 10; i++) {
Runnable worker = new MyRunnable();
executor.execute(worker);
}
executor.shutdown();
}
} - 线程执行策略:
- Atomic原子类,保证数据的原子操作,如AtomicInteger,通过CAS(compare and swap) + volatile和native方法来保证原子操作
- AQS(AbstractQueuedSynchronizer)是一个用来构造锁和同步器的框架 > AQS 核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
- CountDownLatch,AQS的一个组件,允许count个线程阻塞在一个地方直至所有的线程任务都执行完毕
5.11 java内存区域
- StackOverFlowError:java虚拟机栈的内存大小不允许动态扩展,线程请求的栈深度大于虚拟机的最大深度;OutOfMemoryError:虚拟机栈的内存大小可以动态扩展,虚拟机栈无法申请到足够的内存空间
- 堆:几乎所有的对象实例以及数组都在这里分配内存(逃逸分析,栈上分配),从垃圾回收的角度,Java堆还可以细分为:新生代(Eden、From Survivor、To Survivor)和老年代
- 将永久代(hotspot中方法区的实现)替换为元空间,使用直接内存,受本机可用内存的限制,出现溢出的几率小
- 运行时常量池是方法区的一部分(JDK1.7字符串常量池从方法区拿到了堆)
- 直接内存并不是虚拟机运行时数据的一部分,使用NIO(New Input/Output)类操作
- 对象的创建过程,分配内存有指针碰撞和空闲列表两种,并发问题采用CAS+失败重试或TLAB(每个线程预分配内存)
- 在Hotspot虚拟机中,对象在内存中布局可分为对象头、实例数据和对齐填充三块区域
- 对象的访问方式有使用句柄和直接指针两种、
5.12 JVM垃圾回收
- Java对是垃圾收集器管理的主要区域,因此也被称作GC堆,从垃圾回收的角度,基本采用分代垃圾收集算法。
- 大多数情况下对象在新生代eden区分配,当eden区没有足够空间时,虚拟机将发起一次Minor GC;大对象直接进入老年代(字符串、数组),避免复制大对象降低效率;长期存活的对象将计入老年代
- 对象首先在Eden区域分配,在一次Minor GC后,如果对象还存活(且能被Survivor)容纳,则会进入s0或s1,并且对象年龄加1,当他的年龄增加到一定程度(默认15岁),就会被晋升到老年代中
- GC可分类为两大种:部分收集(Partial GC),整堆收集(Full GC)。部分收集又包括新生代收集(Minor GC/Young GC),老年代收集(Major GC/Old GC),混合收集(Mixed GC)
- 垃圾回收前的第一步就是判断哪些对象已经死亡
- 引用计数法很难解决对象之间的循环引用问题
- 强引用垃圾回收器绝不回收;软引用在内存够时不回收,内存不足会回收,可实现内存敏感的高速缓存;弱引用比软引用拥有更短暂的生命周期,一旦发现就会回收;虚引用不会决定对象的生命周期,主要用来跟踪对象被垃圾回收的活动,必须和引用队列联合使用,在回收对象内存前,虚引用会被加入到与之关联的引用队列中
- 不可达对象死亡至少要经历两次标记过程(是否有必要执行finalize方法)
- 字符串常量池在JDK1.7后被从方法区拿到了堆,如果字符串常量没有任何String对象引用,则为废弃常量
- 判断一个类是否为无用类:所有实例都已被回收,加载该类的ClassLoader已被回收,该类对应的java.lang.Class对象没有在任何地方被引用
- 垃圾收集算法
- 新生代可以使用“标记-复制”算法,老年代可以选择“标记-清除”或“标记-整理”算法
- 垃圾收集器
- CMS收集器目标是获取最短回收停顿时间,是HotSpot虚拟机第一款真正意义上的并发收集器,让垃圾收集线程与用户线程(基本上)同时工作,主要有初始标记、并发标记、重新标记、并发清除四个步骤
- G1(Garbage-First)收集器,面向服务器,主要针对配备多颗处理器及大容量内存的机器,有并行与并发、分代收集、空间整合(整体“标记-整理”)和可预测的停顿(根据允许的收集时间,优先选择回收价值最大的Region)的特点
5.13 JVM
JDK监控和故障处理
- jps (JVM Process Status):查看所有Java进程的启动类、传入参数和Java虚拟机参数等信息
- jstat (JVM Statistics Monitoring Tool):用于收集HotSpot虚拟机各方面的运行数据
- jinfo (Configuration Info for Java): 显示虚拟机配置信息,查看和调整虚拟机各项参数
- jmap (Memory Map for Java):生成堆转储快照
- jhat (JVM Heap Dump Browser):用于分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果
- jstack (Stack Trace for Java):生成虚拟机当前时刻的线程快照(每一条线程正在执行的方法堆栈的集合)
- JConsole:Java监视与管理控制台
- Visual VM:多合一故障处理工具
类文件结构
类的生命周期
- 验证过程有文件格式验证、元数据验证、字节码验证、符号引用验证
- 准备阶段为类变量(静态变量)分配内存并设置类变量初始值(默认零值)
- 解析阶段将符号引用替换为直接引用
- 在创建类的实例、访问类的静态变量、为类的静态变量赋值、调用类的静态方法时,会对类初始化
类加载器
- BootstrapClassLoader(启动类加载器):最顶层的类加载器,C++实现,负责加载
%JAVA_HOME%/lib
目录下的jar包和类或者或被-Xbootclasspath
参数指定的路径中的所有类 - ExtensionClassLoader(扩展类加载器):要负责加载目录
%JRE_HOME%/lib/ext
目录下的jar包和类,或被java.ext.dirs
系统变量所指定的路径下的jar包 - AppClassLoader(应用程序类加载器):面向用户的加载器,负责加载当前应用classpath下的所有jar包和类
- 双亲委派模型,避免类的重复加载
- 自定义自己的类加载器需要继承ClassLoader