0%

声明:本文使用JDK1.8

关于ArrayList和LinkedList的区别可以看这里。

Vector

Vector也是实现List接口的,底层数据结构和 ArrayList 类似,也是一个动态数组存放数据。不过是在 add() 方法的时候使用 synchronize 进行同步写数据,但是开销较大,所以 Vector 是一个同步容器并不是一个并发容器。

初始化

Vector也提供了几种初始化方法,默认初始化为10。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//initialCapacity 初始容量;capacityIncrement 每次扩容时的增加量
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}

public Vector(int initialCapacity) {
this(initialCapacity, 0);
}

public Vector() {
this(10);
}

add方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1); //调用ensureCapacityHelper 方法,是否需要扩容
elementData[elementCount++] = e;
return true;
}

//参数 minCapacity 当前集合的位置加一
private void ensureCapacityHelper(int minCapacity) {
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

指定位置插入

Read more »

在Java这门面向对象编程语言中,有几个关键字是必须了解的,下面是我在学习的记录笔记。

static

在平时,我们调用一个类里面的属性或者方法的时候,需要new一个新的类,然后调用类的方法或者属性。
static表示全局或者静态的意思。可用来修饰成员变量或者方法。被static修饰的成员变量和方法独立于该类的任何对象,也就是说,它不依赖类特定的实例,就是被这个修饰的不需要实例化就可以使用,就可以被类的所有实例共享。
static代码块也叫静态代码块,在类中独立于类成员的static语句块,JVM加载时 会执行这些静态代码块。
在静态方法中,不能访问非静态方法或者是非静态变量。如代码中,准确的来说,只有被static修饰的变量和方法,才能不需要实例化直接使用,即使在static修饰的方法中,也不能直接调用没有实例化的非静态方法或者是非静态变量。

final

一旦使用了final,该变量就不可修改;如果方法被final修饰,那么这个方法就不可被子类重写;final类,是不能被子类继承的。这里有一篇博客讲的挺详细的。

  • final 修饰类,表示该类不可以被继承
  • final 修饰变量,分两种情况,如果修饰的是基本类型变量,那么只能被赋值一次,不能被赋值两次, 如果修饰的是引用类型变量,那么引用指向的内存地址将不可变,但是引用类型内的属性可以被修改
  • final 修饰方法,表示该方法不可以被子类重写,但是可以被子类继承使用

final的好处:

  • final关键字提高了性能。JVM和java的应用都会缓存final变量
  • final变量可以安全的在多线程环境下进行共享;
  • 使用final关键字,JVM会对方法、变量及类进行优化。

final修饰的变量是引用类型变量,那么引用指向的内存地址将不可变,但是引用类型内的属性可以被修改。

Read more »

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。

判断对象死亡

Java虚拟机中如何判断Java对象死亡,需要被回收的呢?

引用计数算法

给对象添加一个计数器,当被引用,就加1,当失效就减一,当计数器为零的时候就是对象不再引用。这种算法实现简单,判断效率也高。

首先,引用计数算法无法解决「循环引用无法回收」的问题,即两个对象互相引用,所以各对象的计数器的值都是 1,即使这些对象都成了垃圾(无外部引用),GC 也无法将它们回收。当然上面这一点还不是引用计数法最大的弊端,引用计数算法最大的问题在于:计数器值的增减处理非常繁重,譬如对根对象的引用,此外,多个线程之间共享对象时需要对计数器进行原子递增/递减,这本身又带来了一系列新的复杂性和问题,计数器对应用程序的整体运行速度的影响。

案例:Redis 中的对象,就是使用引用计数来判断对象是否应该被回收,在 redisObject 结构体中使用了 refcount 来计数。

可达性分析算法

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始往下搜索,搜索所有走过的路径称为引用链,当一个对象到GC Roots没有任何引用的时候,则证明该对象不可用。

引用

Read more »

Redis系列:

Redis官网
Redis中文官网

什么是Redis

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如字符串(string)、散列(hash)、列表(list)、集合(set)、有序集合(sorted set)与范围查询、bitmaps、 hyperloglogs 和地理空间(geospatial) 索引半径查询。Redis内置了复制(replication),Lua脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的磁盘持久化(persistence), 并通过 Redis Sentinel 和自动 Redis Cluster 提供高可用性(high availability)。

Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。

Redis 优势

  • 性能极高:Redis能读的速度是110000次/s,写的速度是81000次/s
  • 丰富的数据类型:Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作
  • 原子:Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,但不保证原子性
  • 丰富的特性:Redis还支持 publish/subscribe、通知、key 过期等特性

Redis 能做什么?

开发人员都只带Redis可以用来做缓存,除了缓存以外还可以做什么?
下面来自老钱的 Redis 深度历险:核心原理与应用实践

Read more »

最近在初步了解Docker,想把 Spring Boot 项目构建成镜像。先介绍下Docker的基本情况。

Docker是什么

Docker是一种开源的应用引擎,使用Go语言开发的。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。目前,Docker可以在容器内部快速自动化部署应用,并可以通过内核虚拟化技术(namespaces及cgroups等)来提供容器的资源隔离与安全保障等。由于Docker通过操作系统层的虚拟化实现隔离,所以Docker容器在运行时,不需要类似虚拟机(VM)额外的操作系统开销,提高资源利用率,并且提升诸如IO等方面的性能。
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker,就不用担心环境问题。

Spring Boot部署到docker

一个简单的springboot项目

创建springboot项目可以去官网入口上直接创建一个项目,或者在idea上面从创建,如果没有该选项,去idea设置里面插件里面去勾线,在重启idea。

在pom.xml中添加web依赖的jar包。

1
2
3
4
5
6
7
8
9
10
11
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

创建一个Controller.

Read more »

声明:本文使用JDK1.8

HashMap 是我们平时开发过程中使用最多的 Java 集合框架之一,它继承 AbstractMap,实现 Map 接口,是一种 key-value,并允许使用空值和空键。

1
2
public class HashMap<KV> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {

从结构实现来讲,HashMap 是 数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,相比于Java7,Node可以被扩展成TreeNode。

HashMap类加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//初始容量为16的大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量(1073741824)
static final int MAXIMUM_CAPACITY = 1 << 30;
//哈希表在其容量自动增加之前可以达到多满的一种尺度
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//链表中数据的临界值,如果达到8,就进行resize扩展,如果数组大于64则转换为树.
static final int TREEIFY_THRESHOLD = 8;
//如果链表的数据小于6,则从树转换为链表.
static final int UNTREEIFY_THRESHOLD = 6;
//如果数组的size大于64,则把链表进行转化为树
static final int MIN_TREEIFY_CAPACITY = 64;
transient Node<K,V>[] table;
//通过key计算hash值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

对于HashMap初始化的容量是16的原因,可以看这里:漫画:什么是HashMap?,
感觉讲的还是挺详细的。之所以选择 16 这个数字,是服务于从 key 映射到 index 的算法。index = HashCode(Key) & (length - 1), 如果 length 是2的幂的话,则 length - 1 就是全是 1的二进制数,比如 16 - 1 = 1111,这样相当于是坐落在长度为 length 的 hashMap 上的位置只和 HashCode 的后四位有关,这只要给出的 HashCode 算法本身分布均匀,算出的index就是分布均匀的。

可以了解下 漫画:高并发下的HashMap,不过这里的代码比较老。HashMap 在高并发下出现死锁,主要发生在 rehash 时,链表出现环链了。不过在Java8 中,对于链表超过8时,转化为红黑树,这有效的防止这个问题。

tableSizeFor

Read more »

本文是《深入理解Java虚拟机:JVM高级特性与最佳实践《第二版》读书笔记。

概述

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。
对于Java开发人员来说,不需要像C++程序员那样去管理内容回收的事情,Java虚拟机会干这些事情。

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。看下图:

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器,在分支、循环、跳转、异常、线程恢复等都依赖这个计数器。

由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

程序计数器是唯一一个没有 OOM 问题的区域,生命周期随线程的创建而创建,随线程结束而结束。

Read more »

声明:本文使用JDK1.8

先看下List在Collection中的框架图:

ArrayList源码分析

大家基本都知道ArrayList的底层是数组的数据结构,下面来看下它的随机访问、删除等的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
private static final int DEFAULT_CAPACITY = 10;//初始容量为10
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//ArrayList扩容函数方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 计算当前ArrayList大小
int oldCapacity = elementData.length;
//这里我们可以看出,ArrayList每次扩容是增加50%,oldCapacity >> 1是指往左移一位,也就是除以2
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);
}
//根据下标index获取元素值
public E get(int index) {
rangeCheck(index);//检查小标是否越界
return elementData(index);
}
//将index位置的值设为element,并返回原来的值
public E set(int index, E element) {
rangeCheck(index);

E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
//向数组中末尾增加一个元素
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
//向指定位置index处增加element
public void add(int index, E element) {
rangeCheckForAdd(index);

ensureCapacityInternal(size + 1); // Increments modCount!!
//将index以及index之后的数据复制到index+1的位置往后,即从index开始向后挪了一位
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//根据指定的index,删除元素
public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work

return oldValue;
}

LinkedList源码分析

来看下LinkedList的部分源码,底层是基于双向链表的数据结构。
定义:

1
2
3
4
5
6
package java.util;public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
}

方法:

Read more »

欢迎指正。

今天看到GET/POST/PUT,就想了解下这些的区别。HTTP方法有这几种:GET,POST,PUT和DELETE。

HTTP方法

GET

获取信息。GET请求必须是安全且幂等的,这意味着无论使用相同参数重复多少次,结果都是一样的。对于网上说的,GET请求有长度限制,其实,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。对GET的URL长度限制的是浏览器,这里的限制是URL,而不是参数的限制。

POST

请求URL中的资源对提供的实体执行某些操作。POST通常用于创建或者更新实体。POST把提交的数据则放置在是HTTP包的包体中。

PUT

将实体存储在URL中。PUT更新现有的实体,或者新增指定的资源(如果id不存在,新增一个含id资源)。PUT请求是幂等的。幂等性是PUT期望与POST请求之间的主要区别。

DELETE

Read more »

声明:本文使用JDK1.8

在Java中,对于字符串的操作有这三种:String、StringBuilder、StringBuffer。这三者的效率是:StringBuilder > StringBuffer > String。

1
2
3
4
5
6
7
8
9
String a = "abc";
a = a + "d";
System.out.println(a);
StringBuffer buffer = new StringBuffer();
buffer.append("a");
System.out.println(buffer);
StringBuilder builder = new StringBuilder();
builder.append("b");
System.out.println(builder);

String

先来看下String的源码,如图所示:

从图中我们可以看出,String 是由 char 数组构成的,而且有 final 关键字修饰,这说明 String 类型的对象是不可以改变的。那么,平时我们使用“+”来拼接字符串是什么实现的?

如上面的代码,首先创建一个 String 对象 a,再把“abc”赋值给它,后面Java虚拟机又创建了一个 String 对象 a,然后再把原来的 a 的值和 “d” 加起来再赋值给新的 a,而原来的a 就会被Java虚拟机的垃圾回收机制(GC)给回收掉了,所以,a 实际上并没有被更改,也就是前面说的 String 对象一旦创建之后就不可更改了。从这里可以看出,对于频繁操作的字符串,不建议使用 String 类型,这将会是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢。

对于 String 类型对象的“+”操作,通过在 StringBuilder 的 append 方法上面打断点,可以发现对于String的操作,其实是使用了 StringBuilderappend 操作,这个不是线程安全。详细可以看下面关于 StringBuilder 的源码。

Read more »