0%

如果本文有错,希望在下面的留言区指正。

最近在看书的时候,看到说使用 BloomFilter 来进行判断一个元素是否最有可能属于一个集合,或者它是绝对不属于这个集合。BloomFilter 不适合“零错误”的场合,只能在能容忍地错误率的场合下使用,BloomFilter 通过极少的错误换取了存储空间的极大节省。

在Java中并不提供 BloomFilter 集合框架,使用者需要导入google guava jar包,提供了 BloomFilter。

BloomFilter 是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。

原理

BloomFilter 的数据结构是由两部分构成:

  • 一堆散列函数
  • 一个位数组

如下所示,定义一个10位的数组:
[0,0,0,0,0,0,0,0,0,0]

在添加元素,先对添加的元素使用 k 个hash函数,来计算出 k 个在数组中的位置,然后,将这些位置的 bit 置为 1。

例如,把输入的x经过两次hash,给出的位置分别是0和4,将这两个位置bit置为1。

阅读全文 »

如果本文有错,希望在下面的留言区指正。

TCP/IP 五层模型如图所示:

面向连接的 TCP

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个 TCP 连接必须要经过三次”对话”才能建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。

三次握手

TCP 建立连接如下图所示:

四次挥手

阅读全文 »

声明:如果本文有错误,希望指出。

虽然学习和使用 Java 好多年了,但是对于多态的整体概念还是有点模糊。今天在看之前写的关于反射博文时,突然想到了 Java 的三大特性——多态。其他两个分别是封装、继承。只是想起多态的继承、重写,对于别的有点模糊了,于是就像自己整理下。

多态

多态指的是对象的多种形态。继承是多态的实现基础。当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

多态的分类:

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
public class PolymorphicCase {

public static void main(String[] args) {
show(new Cat());
show(new Dog());

//向上转型
Animal animal = new Cat();
animal.eat();//调用的是Cat的 eat
//当使用多态方式调用方法时,首先检查父类中是否有该方法,
// 如果没有,则编译错误;如果有,再去调用子类的同名方法
animal.work();//编译报错
//向下转型
Cat cat = (Cat) animal;
cat.work();//调用的是Cat的work
}

public static void show(Animal animal) {
animal.eat();
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.work();
}
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.work();
}
}
}

abstract class Animal {
abstract void eat();
}

class Cat extends Animal {

@Override
void eat() {
System.out.println("猫吃鱼....");
}

public void work() {
System.out.println("猫抓老鼠....");
}
}

class Dog extends Animal {

@Override
void eat() {
System.out.println("狗吃骨头....");
}

public void work() {
System.out.println("look door....");
}
}

向上转型

Animal animal = new Cat(); 对于这个对象的创建,就是体现了向上转型。

子类引用的对象转换为父类类型成为向上转型。通俗的说就是将子类对象转化为父类对象,此处对象可以是接口。

向下转型

阅读全文 »

最近在看阿里出的一本书 —— 《码出高效 Java 开发手册》,这本书不是之前那本关于 Java 开发注意规范的手册,这本书内容还是不错的,干货满满。

在看书的时候,就看到了关于上面讲的知识点,于是想到之前在看 Java 一些源码时候,也看到一些关于这个泛型的写法,当时还没注意,比如下面关于 ThreadLocal 源码是看到的。

这样以后看 Java 的源码就能看懂这些泛型的写法了。

是 Java 泛型中的 “通配符“和”边界”的概念。 - : 是指“上界通配符”。 A super B 表示 A 是 B 的父类或者祖先,在 B 上面。 - :是指“下界通配符”。A extends B 表示 A 是 B 的子类或者孙类,在 B 的下面。 两种语法,但是两者的区别非常微妙。简单来说,是 Get first,适用于消费集合元素为主的场景;是 Put First,适用于生产集合元素为主的场景。 # 可以赋值给任何 T 及 T 子类的集合,上界为T,取出来的类型带有泛型限制,向上强制转型为T。null可以表示任何类型,所以除nul外,任何元素都不得添加进集合内。 # 可以赋值给任何 T 及 T 的父类集合,下界为T。在生活中,投票选举类似于 的操作。选举代表时,你只能往里投选票,取数据时,根本不知道是谁的票,相当于泛型丢失。有人说,这只是一种生活场景,在系统设计中。很难有这样的情形。再举例说明一下,我们在填写对主管的年度评价时,提交后若想再次访问之前的链接修改评价,就会被告之:“您已经完成对主管的年度反馈,谢谢参与。" extends的场景是put功能受限,而 super的场景是get功能受限。 # 集合和泛型 下面的代码和说明出自《码出高效,Java开发手册》
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
public static void main(String[] args) {

//第一阶段:泛型出现之前的集合定义方式
List a1 = new ArrayList();
a1.add(new Object());
a1.add(new Integer(111));
a1.add(new String("Hello World"));

for (Object o : a1){
System.out.println(o);
}

//第二段:把a1 引用赋值给a2,注意a2与a1的区别是增加了泛型限制<Object>
List<Object> a2 = a1;
a2.add(new Object());
a2.add(new Integer(222));
a2.add(new String("hello a2a2"));

//第三段:把a1引用赋值给a3,注意a3与a1的区别是增加了泛型<Integer>
List<Integer> a3 = a1;
a3.add(new Integer(333));
//下方两行编译出错,不允许增加非 Integer类型进入集合
a3.add(new Object());
a3.add(new String("hello a3a3"));

//第四段:把a1引用赋值给a4,a1与a4的区别是增加了通配符
List<?> a4 = a1;
a1.remove(0);
a4.clear();
//编译出错。不允许增加任何元素
a4.add(new Object());

}
第一段说明:在定义List之后,毫不犹豫地往集合里装入三种不同的对象:Object、 Integer 和 String,遍历没有问题,但是贸然以为里边的元素都是 Integer,使用强制转化,则抛出 ClassCastException异常。 第二段说明:把a赋值给a2,a2是 List类型的,也可以再往里装入三种不同的对象。很多程序员认为List和List是完全相同的,至少从目前这两段来看是这样的。 第三段说明:由于泛型在JDK5之后才出现,考虑到向前兼容,因此历史代码有时需要赋值给新泛型代码,从编译器角度是允许的。这种代码似乎有点反人类,在实际故障案例中经常出现。 第四段说明:问号在正则表达式中可以匹配任何字符,List

称为通配符集合。它可以接受任何类型的集合引用赋值,不能添加任何元素,但可以 remove 和 clear,并非 immutable集合。List<?>一般作为参数来接收外部的集合,或者返回一个不知道具体元素类型的集合。

Reference

  • 《码出高效:Java开发手册》
阅读全文 »

欢迎指正。

队列同步器(AbstractQueuedSynchronizer),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态, 通用过内置的 FIFO 队列来,可以用于构建锁或者其他同步装置,完成资源获取线程的排队工作。

同步器的主要使用方法是继承、子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态镜像更改,这时就需要使用同步器提供的方法(getState()、setState(in newSate) 和 compareAndSetState(int expect, int update))来进行操作。

AQS 是一个抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作,提供独占、共享、中断等特性。

基于AQS构建的同步器:

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • ReentrantReadWriteLock
  • SynchronusQueue
  • FutureTask

AQS 核心知识

队列同步器的接口与实例

重写同步器指定的方法是,需要使用同步器提供的3个方法来访问或者修稿同步状态:

  • getState():获取当前同步状态
  • setState(in newSate):设置当前同步状态
  • ompareAndSetState(int expect, int update):使用 CAS 设置当前状态,该方法能够保证状态设置的原子性。
阅读全文 »

如果有不正确的,欢迎指正。

String,StringBuilder,StringBuffer源码分析
上面这篇是之前写的关于 String 的一些介绍,有兴趣可以看下。

今天重新翻看 《Effective Java》这本书的时候,看到第五条:避免创建不必要的对象。文中是以 String s = new String(“stringette”); 拿来举例的。看到这里,就想起以前看一些面经中看到的一个问题,对于上面的的语句,在运行时涉及几个String实例?

对于上面的答案: 两个,一个是字符串 “abc” 锁在常量池中的实例,另一个是通过 new String(String) 创建并初始化、内容与 “abc” 相同的实例。

但是对下面的代码:

1
2
3
4
5
6
7
8
public class StringDemo {

public static void main(String[] args) {
String s1 = "abc";
String s = new String("abc");
System.out.println(s == s1);
}
}

字节码 ldc:将常量值从常量池中取出压入栈中。

通过查看上面代码的字节码的第0行和第7行可以看出,该字符串变量的值已经确定了,并没有重新创建一个变量,而是从缓冲区中取出,同时让该变量指向该字符串值。

阅读全文 »

Java 提供了很多同步工具,最近看Java锁的时候,看到了 CountDownLatch 和 Semaphore,同时了解到还有 CyclicBarrier,就想对它们做一个一定的了解。

CountDownLatch

CountDownLatch 是在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。使用一个计数器进行实践,计数器初始值为线程的数量。每当一个线程完成后,计数器的值就会减一。当计数器的值为0时,便是所有线程都完成了任务,然后等待的线程就可以恢复执行任务了。

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。

初始化

1
CountDownLatch countDownLatch = new CountDownLatch(1);

CountDownLatch 是一次性的,计数值在构建的时候就已经初始化完成了。

await

提供两种方法是当前线程处于等待状态

1
2
3
4
5
6
7
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
阅读全文 »

声明:如果本文有错误,希望指出。

在之前的一篇博文中简单介绍了 Java 中的一些锁:Java 中各种锁
最近在极客时间上买了杨晓峰的《Java核心技术36讲》,今天看到关于标题的东西,于是想记录下自己的学习。
Synchronized 和 ReentrantLock 这两个都是可重入锁,指的是同一线程在外层函数获取锁之后,内层函数仍然可以获取该锁,且不受影响。

Synchronized 可重入测试源码

Synchronized

1
2
3
4
5
6
7
8
9
10
11
12
public class SynchronizedTest {

public synchronized void doSth() {
System.out.println("doSth");
}

public void method() {
synchronized (SynchronizedTest.class) {
System.out.println("method");
}
}
}

使用javac SynchronizedTest.javajavap -c -s -v -l SynchronizedTest 命令查看编译后的Test.class的字节码,可以很容易看出在代码块上和方法上是有区别的。代码块上使用 monitorenter/monitorexit 指令来实现的;方法上是使用 ACC_SYNCHRONIZED。详情看下面的图片,是编译后的字节码:

在 Java6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要用户状态到内核状态的切换,所有同步操作是一个重量级操作。

现在的JVM提供三种不同的 Monitor 实现方式:

  • 偏向锁
  • 轻量级锁
  • 重量级锁
阅读全文 »

如果有误,欢迎批评指正。友情提示,本文有点长。
SpringBoot 版本 2.0.5.RELEASE。

在学习 SpringBoot 时,会在启动项里面看到在类名上面有一个注解 @@SpringBootApplication。前几天在一个公众号中看到关于一道面试题,题目类似于:知道 @SpringBootApplication 的原理吗?

突然发现自己对于 SpringBoot 还是处在‘知其然而不知所以然’的状态。于是就去官网和网上查看一些资料,于是有了这篇文章。

SpringBoot 启动项

1
2
3
4
5
6
7
@SpringBootApplication
public class ApplicationStartup {

public static void main(String[] args) {
SpringApplication.run(ApplicationStartup.class, args);
}
}

@SpringBootApplication

看下 @SpringBootApplication 注解的详情

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
//...
}

从注解的源码中可以看到它被 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 等注解修饰。

@SpringBootConfiguration

阅读全文 »

如果有错,希望指出

动态代理在Java中有着广泛的应用,比如 Spring Aop、Hibernate 数据查询、RPC、Java 注解对象的获取。静态代理的代理关系在编译时确定,而动态代理的代理关系是在编译期确定的。主要了解:JDK Proxy 和 cglib 动态代理。

动态代理可以提供另一个对象的访问,同时可以隐藏实际对象的具体实例。

静态代理

再说动态代理之前,先了解下静态代理。

静态代理是代理模式实现方式之一,在程序运行前,由程序员创建或者特定工具自动生成源代码并对其编译生成 .class 文件。静态代理的实现需要三步:

  • 定义业务接口
  • 实现业务接口
  • 定义代理类并实现业务接口

最后就可以直接通过客户顿进行调用静态代理了。

阅读全文 »