代码之旅

I love Coding !

synchronized 是Java中的关键字,作用是实现线程间的同步。synchronized 会对要同步的代码加锁,每次只会有一个线程进入同步代码块,从而保证线程安全。

阅读全文 »

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

阅读全文 »

编写线程安全的代码,核心在于对状态访问操作进行管理,特别是共享的和可变的状态的访问。

  • 共享意味着变量可以由多个线程进行访问;
  • 可变意味着变量的值在其生命周期内可变化

当有多个线程访问同一个可变的状态变量时,没有使用合适的同步,那么程序就会出现错误,有3种方法可以修复这个问题:

  • 不在线程之间共享状态变量
  • 将状态变量修改为不可变的变量
  • 在访问状态变量时使用同步
阅读全文 »

本文介绍基于一些数组的常用排序算法.

算法代码位置

  • 稳定: 如果一个排序算法可以保持相同值的元素,相对位置不变,那么我们可以称这个排序算法是稳定的。
  • 就地排序: 算法使用常量额外空间来生成输出(仅修改给定数组)。它仅通过修改列表中元素的顺序对列表进行排序。
  • 内部和外部排序
    • 当所有需要排序的数据不能一次放入内存时,排序称为外部排序。外部排序用于大量数据。
    • 当所有数据都放在内存中时,排序称为内部排序。
阅读全文 »

数组是使用最广泛的基础数据结构。数组是存放在连续内存空间上的相同类型数据的集合,可以方便的通过数字索引的方式获取到索引所对应的数据。需要注意的是数组的索引都是从0开始计算的。下图就是长度为3的字符数组的例子。

内存地址100110021003
数组元素‘a’‘b’‘c’
数组索引012

假设一个数组,其元素按照值的固定顺序排序(升序或降序),那么这种数组被称为有序数组。在实际的算法使用中常常会对数组先进行排序,然后在进行处理。

使用数组(Java)

下面介绍如何使用数组。

定义

Java中的数组声明很简单:

1
2
3
4
5
// 定义时初始化
int[] array = new int[]{0,0,0};
// 通过长度定义, 元素会被初始化为默认值
// int的默认值是0
int[] array = new int[3];

数组长度

数组提供了一个length参数,可以获取数组的长度。注意一旦创建数组的长度就不能再变化,只能通过重建新的数组扩容。

1
2
3
int[] array = new int[3];
int arrayLength = array.length;
assert arrayLength == 3

getter & setter

数组的一个最大特点是:可以进行随机访问。即数组可以根据下标,直接定位到某一个元素存放的位置。

1
2
3
4
5
6
int[] array = new int[3];
array[0] = 1;
array[1] = 2;
array[2] = 3;
int head = array[0];
assert head == 1

查找

数组的查找和删除就稍微麻烦一点。对于查找,需要遍历数组,将目标值和每个元素对比。

1
2
3
4
5
6
7
8
int search(int[] arr,int target) {
for (int i = 0; i < arr.length; i++) {
if(arr[i] == target){
return i;
}
}
throw new IllegalArgumentException("not found");
}

删除

对于删除,如果不知道索引的情况下,删除需要遍历数组,如果知道索引,可以通过指定索引设置 null的方式删除(基类数组通过设置默认值的方式删除)。

1
2
3
4
5
6
int[] array = new int[3];
array[0] = 1;
// -1 是默认值
array[0] = -1;
String[] strs = new String[3];
strs[0] = null;

多维数组

上面介绍的数组只有一个维度,称为一维数组,其数据元素也是单下标变量。但是在实际问题中,很多信息是二维或者是多维的,一维数组已经满足不了我们的需求,所以就有了多维数组。

chars012
0‘a’‘b’‘c’
1‘d’‘e’‘f’

二维数组是一个由 m 行 n 列数据元素构成的特殊结构,其本质上是以数组作为数据元素的数组,即 「数组的数组」。二维数组的第一维度表示行,第二维度表示列。例如上面的二维数组:

1
assert chars[0][1] == 'b'

可以将二维数组看做是一个矩阵,并处理矩阵的相关问题,比如转置矩阵、矩阵相加、矩阵相乘等等。

什么是算法?

从一个步骤开始,按照既定的顺序执行完所有的步骤,最终结束得到结果的一个过程。

  • 确定性,算法的每个步骤都是明确的,对结果的预期也是确定的
  • 有穷性,算法必须是由有限个步骤组成的过程,步骤的数量可以是几个,也可以是几百万个,但是必须有一个确定的结束条件
  • 可行性,一般来说我们期望算法得出的是正确的结果,这意味着算法的每个步骤都是可行的,只要有一个步骤不可行,算法就是失败的,或者不能被称为某种算法。
  • 输入和输出,算法总是要解决特定的问题,问题的来源就是算法的输入,期望的结果就是算法的输出。

程序 = 算法+ 数据结构.

将问题抽象为数学模型,输入输出方法和算法步骤是编写计算机算法程序的三大关键要素。对于非常复杂的问题,建立数学模型是很困难的事情,但是对简单的计算机算法而言,建立数学模型实际上就是设计合适的数据结构的问题,同时,输入输出方式和算法步骤的设计都是基于相应的数据结构设计的。

阅读全文 »

在java代码中,类型的加载,连接与初始化过程都是在程序运行期间完成的(类class文件信息在编译期间已经确定好)。

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载Loading、验证Verification、准备Preparation、解析Resolution、初始化Initialization、使用Using和卸载Unloading7个阶段。其中准备、验证、解析3个部分统称为连接Linking

加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始,而解析阶段则不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。

注意,本文的JDK版本是Java 1.8,在Java 9 引进模块化后,ClassLoader也有了一些变化。

阅读全文 »