Java 的源码由 Unicode 字符组成。Java 编程语言使用 UTF-16 编码表示 16 位代码单元序列中的文本。
关键字
abstract | continue | for | new | switch |
assert | default | if | package | synchronized |
boolean | do | goto(保留) | private | this |
break | double | implements | protected | throw |
byte | else | import | public | throws |
case | enum | instanceof | return | transient |
catch | extends | int | short | try |
char | final | interface | static | void |
class | finally | long | strictfp | volatile |
const(保留) | float | native | super | while |
_(下划线 Java 10) |
true
和false
不是关键字,而是布尔值。null
不是关键字,而是空值。var
不是关键字,而是具有特殊含义的标识符,作为局部变量声明的类型(Java 10)。
数值
// 下划线由 Java 1.7 支持
int bin = 0b0111_1111_1111_1111_1111_1111_1111_1111;
int oct = 0177_7777_7777; // 八进制不推荐使用
int dec = 2_147_483_647;
int hex = 0x7f_ff_ff_ff; // 十六进制每两位表一个字节
浮点数
Java 支持 IEEE 754 标准浮点数。
类型 | 符号位 | 指数位 | 有效位 |
---|---|---|---|
float(32bit) | 1 | 8 | 23 |
double(64bit) | 1 | 11 | 52 |
以 float
来说, 符号位就表正负, 指数位
2
2
7
=
2
128
=
3.40
E
+
38
2^{2^{7}} = 2^{128} = 3.40E+38
227=2128=3.40E+38 表最大值能有 38 位数字, 有效位
2
23
=
8388608
≈
1
0
6.92
2^{23} = 8388608 \approx 10^{6.92}
223=8388608≈106.92 表范围在 6~7 位。
最值
int min = Integer.MIN_VALUE; // -2147483648
int max = Integer.MAX_VALUE; // 2147483647
double minD = Double.MIN_VALUE; // 4.9E-324 非负数
字符串
字符串拼接, 一般情况下使用 +
进行拼接, 如:
// ldc "hello2world" (编译器优化)
String str1 = "hello2" + "world";
// ldc "hello2world"
String str2 = "hello2world";
// new class java/lang/StringBuilder
// invokespecial Method java/lang/StringBuilder."<init>":()V
// invokevirtual Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder
int i = 2;
String str3 = "hello" + i + "world";
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10; i++) {
// 如果使用 str4 += i;
// 那每次循环都会创建 StringBuilder 来拼接变量
builder.append(i);
}
String str4 = builder.toString(); // 0123456789
当我们在 for
循环中拼接字符串时, 需要使用 StringBuilder
类进行 append()
拼接,理由见上面。
StringBuffer
是线程安全的, 即当有多个线程进行一个字符串的拼接, 则使用它, 但一般都不会这么做。
文件
计算机文件(或称文件、电脑档案、档案),是存储在某种长期储存设备或临时存储设备中的一段数据流,并且归属于计算机文件系统管理之下。
// `C:\development\project\java\misc>java Main`
Path root = Paths.get("/");
System.out.println(root.toAbsolutePath());
// Output: C:\
Path path = Paths.get("");
System.out.println(path.toAbsolutePath());
// Output: C:\development\project\java\misc
资源
在 Java 的世界里,经常需要加载各种资源,如 Spring 框架的 application.xml
,或者是 web 项目的 web.xml
。
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 包所在的根目录
URL loaderResource = classLoader.getResource("resource.txt");
// 包所在的根目录
URL classResource = Resources.class.getResource("/resource.txt");
System.out.println(Objects.equals(loaderResource, classResource));
// Output: true
// 该类所在的包目录
URL classResource = Resources.class.getResource("");
集合
Java 的集合框架在 1.0 时就有提供,但只有 Dictionary
、Vector
、Stack
和 Properties
,并且都是并发安全的。
虽然这些类都非常有用,但是它们缺少一个核心的,统一的主题。由于这个原因,使用 Vector
类的方式和使用 Properties
类的方式有着很大不同。
List
ArrayList
ArrayList
以数组的方式实现了 List
、RandomAccess
接口,可以插入 null
,也支持随机访问。ArrayList
里面维护着一个数组。
// 维护的数组
transient Object[] elementData;
// 存储当前实例元素大小
private int size;
// 添加方法
public boolean add(E e) {
// 先确认容量是否足够,如果不够,则分配新的
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
// 增容方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
LinkedList
是以链表来实现 List
接口,在添加和移除方面比 ArrayList
快。
Vector
线程安全
Vector
的实现和 ArrayList
差不多,只不过每个公开方法都有 synchronized
关键字修饰。
CopyOnWriteArrayList
线程安全
CopyOnWriteArrayList
在写的时候加锁,并且直接复制原数组数据 +1 新元素,因此在读时候如有线程写入,读的是原数组数据。
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private boolean addIfAbsent(E e, Object[] snapshot) {
synchronized (lock) {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i]
&& Objects.equals(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
}
}
Set
HashSet
HashSet
是一个存储不重复元素且元素根据 hash 算法排序的集合。内部实现是 HashMap
。
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
CopyOnWriteArraySet
线程安全
CopyOnWriteArraySet
具体实现是内部维护一个 CopyOnWriteArrayList
,然后根据 addIfAbsent(E e)
方法实现 add(E e)
方法。
// 成员
private final CopyOnWriteArrayList<E> al;
public boolean add(E e) {
return al.addIfAbsent(e);
}
Map
HashMap
HashMap
是一个散列表,它存储的内容是键值对(key-value)映射。底层是由数组和链表实现,Java 1.8 后当链表长度增大到 8 后转为红黑树。
// 默认初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认扩容因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 树化容量
static final int TREEIFY_THRESHOLD = 8;
// 使用 remove() 容量下降到 6 则转为链表
static final int UNTREEIFY_THRESHOLD = 6;
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
ConcurrentHashMap
线程安全
ConcurrentHashMap
采用了分段锁技术,其中 Segment
继承于 ReentrantLock
。不会像 HashTable
那样不管是 put
还是 get
操作都需要做同步处理,理论上 ConcurrentHashMap
支持 CurrencyLevel (Segment
数组数量)的线程并发。每当一个线程占用锁访问一个 Segment
时,不会影响到其他的 Segment
。
Java 1.8 抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
也将 1.7 中存放数据的 HashEntry
改为 Node
,但作用都是相同的。
多线程
Java多线程-线程池ThreadPoolExecutor构造方法和规则
三大核心
原子性
原子性就和数据库事务的原子性差不多,一个操作中要么全部执行成功或者失败。
可见性
现代计算机中,由于 CPU 直接从主内存中读取数据的效率不高,所以都会对应的 CPU 高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。
顺序性
JVM 为了提高整体的效率,在保证最终结果和代码顺序执行结果一致的情况下可能进行指令重排。
J.U.C-AQS
java.util.concurrent
(J.U.C) 大大提高了并发性能,AQS 被认为是 J.U.C 的核心。
CountdownLatch
用来控制一个线程等待多个线程。
维护了一个计数器 cnt,每次调用 countDown()
方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await()
方法而在等待的线程就会被唤醒。
public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException {
final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("run..");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
// Output: run..run..run..run..run..run..run..run..run..run..end
CyclicBarrier
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和 CountdownLatch
相似,都是通过维护计数器来实现的。线程执行 await()
方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await()
方法而在等待的线程才能继续执行。
CyclicBarrier
和 CountdownLatch
的一个区别是,CyclicBarrier
的计数器通过调用 reset()
方法可以循环使用,所以它才叫做循环屏障。
CyclicBarrier
有两个构造函数,其中 parties 指示计数器的初始值,barrierAction 在所有线程都到达屏障的时候会执行一次。
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
public class CyclicBarrierExample {
public static void main(String[] args) {
final int totalThread = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalThread; i++) {
executorService.execute(() -> {
System.out.print("before..");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.print("after..");
});
}
executorService.shutdown();
}
}
// Output: before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..
Semaphore
Semaphore
类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
以下代码模拟了对某个服务的并发请求,每次只能有 3 个客户端同时访问,请求总数为 10。
public class SemaphoreExample {
public static void main(String[] args) {
final int clientCount = 3;
final int totalRequestCount = 10;
Semaphore semaphore = new Semaphore(clientCount);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < totalRequestCount; i++) {
executorService.execute(()->{
try {
semaphore.acquire();
System.out.print(semaphore.availablePermits() + " ");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executorService.shutdown();
}
}
// Output: 2 1 2 2 2 2 2 1 2 2
JDBC
基于 JDBC 技术的驱动程序可以做三件事
- 建立与数据源的连接
- 将查询和更新语句发送到数据源
- 处理结果
所在包
JDBC API 主要位于 JDK 中的 java.sql
包中,之后扩展的内容位于 javax.sql
包中,主要包括:
class DriverManager
: 负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。interface Driver
: 驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。interface Connection
: 数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。interface Statement
: 用以执行SQL查询和更新(针对静态SQL语句和单次执行)。interface PreparedStatement
: 用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。interface CallableStatement
: 用以调用数据库中的存储过程。class SQLException
: 代表在数据库连接的创建和关闭和SQL语句的执行过程中发生了例外情况(即错误)。
从根本上来说,JDBC是一种规范,它提供了一套完整的接口,允许对底层数据库进行可移植的访问,Java可以用于编写不同类型的可执行文件。