并发编程

 

注:关于join()方法,join()方法会让当前线程处于阻塞状态。比如:

public static void main(){
Thread t1 = new Thread(()->sout(“t1”));
Thread t2 = new Thread(()->sout(“t2”));
t1.start();
t1.join(); //join方法会阻塞main线程,等到t1的线程执行完后,main才会继续往下执行
t2.start();
}

查看join()方法源码发现,main的线程会一直检测t1的线程是否isAlive(),如果t1线程还存活,则一直执行wait()方法,但是我们发现后续并没有显式的执行notify()方法,这是因为在native方法里面,在线程执行结束的时候,会调用对象的notifyAll()方法,去唤醒等待该线程中对象的线程继续执行。

 

1.synchronized(作用域)

  • 对象锁:在同一个对象中可以达到线程互斥的效果

a.  synchronized直接修饰方法

b.  修饰某个对象

 

Object lock = new Object();  (该对象如果是类的成员变量,则为对象锁,                如果用static修饰,则会变成全局锁)

synchronized(lock)

Object lock = new Object(); 
public void method(){
    synchronized(lock)
}

 

  • 全局锁:在不同对象中也能实现互斥

synchronized(MyTest.class)

  • 代码块:缩小作用范围
public void method(){
    Object lock = new Object(); 
    if(xxx){
        synchronized(lock)
    }
}

monitorentermonitorexit

获得一个对象的监视器,如果获得了,则其他线程再无法获得,进入到synchronized队列等待,登exit了之后,其他线程才能尝试去获取。

synchronized:(非公平锁)

wait和notify:

执行wait方法后,会释放锁(sleep方法不释放锁),进入到等待队列,然后执行notify或者notifyall方法会将等待队列的线程进入到同步队列。

 

2.操作系统内存模型

因为总线锁,会锁住内存中数据,相当于会让多核变单核。所以有了缓存锁,缓存锁只针对获取共享变量的时候加锁,如果不同cpu访问的是不同的变量,则不加锁。

 

3.JMM和主内存交互过程

下面 8个操作都是原子的:
1)lock :作用于主内存的变量,它把一个标识为线程独占状态。

2)unlock :作用于主内存的 变量,他把一个处锁定状态的变量释放出来,释放后的变量才可以被其他线程使用。

3)read:作用于主内存变量,他把一个变量的值从主内存传输到线程的工作内存,以便随后的 load操作使用。

4)load:作用于工作内存的变量,他把 read操作从主内 存中得到的变量值放入工作内存的变量副本中。

5)use:作用于工作内存的变量,他把工作内存中一个变量的值传递给执行引擎每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这操作。

6)assign:作用于工作内存的变 量,他把一个从执行引擎接收到的值交付给工作内存的变量,每当虚拟机遇到一个给变赋值的字节码指令时执行这个操作。

7)store:作用于工作内存的变量,他把工作内存中一个值传送到主内存中,以便随后write使用。

8)write:作用于主内存的变量,他把store操作从工作内存中得到的变量值放入主内存的变量中。

 

4.volatile作用

保证内存可见性

禁止指令重排

volatile通过使用CPU的缓存锁(某个cpu核心修改共享变量后,会把变量刷新到主内存中,并且让其他核心缓存的该变量失效,重新从主内存去获取该变量),来保证可见性。volatile无法保证复合操作的原子性(i++),因为i++不是原子操作,多个核心在对i做操作时,有可能拿到i的旧的值进行操作。

cpu总线锁:当一个核心对某个变量做操作时,其他核心不允许对该变量进行操作

内存屏障

硬件: Load Barrier  Store Barrier                代表读屏障和写屏障

volatile的作用:

写操作之前插入 storestore屏障 , 在写操作之后插入storeload屏障

读操作之前插入loadload屏障、 在读操作之后插入loadstore屏障

JAVA指令:

load load    load1 ; load load; load2

load store    load1  loadstore store2

store store   store1 storestore store2

store load   store storeload load

 

指令重排:

对耗时的执行进行重新排序。比如a=1;b=2;   有可能b=2先执行。

无论如何重排,必须要保证as-if-serial语义 ,即:不管怎么重排序,单线程程序的执行结果不能改变。

单例模式下,doubleCheck检查后为什么引入临时变量赋值:

因为指令重排可能导致instance不为null,其他线程获得的是右边执行完3过程的内容 ,这时候的instance还没有初始化,所以就有可能引发问题。

 

MySQL底层

1.什么是索引

索引是帮助高效获取数据的数据结构

为什么不用hash:hash无法实现范围查找,比如搜索id>5

2.时间局部性原理和空间局部性原理

时间局部性原理:查找时,会缓存。比如先查找了itemID=1的,猜想你可能还会查找=1的,则会缓存

空间局部性原理:扩大范围查找。要查找itemID=1的,则同时可能会读取itemID=2或3 的。

3.三种存储方式可视化页面

https://www.cs.usfca.edu/~galles/visualization/BST.html

https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

4.BTree和B+TREE

BTree也就是B-树,是一种多路搜索树,对应节点都存储数据。B+树只有叶子节点存储数据。

 

5.MYISAM和INNODB

MYI:索引文件

MYD:数据文件

以name为索引的B+Tree类似于上图,每次找到对应节点,该节点记录地址,然后从对应地址取得数据返回。

MyISAM为非聚集索引(数据存储在一个地方,索引存储在另外一个地方)。

 

以id(主键)查找数据时,从根节点开始找到对应节点,返回数据。而且对应id>1这种查找(空间局部性原理)会比较方便。

以name为副索引查找时,会先找到主键,然后从根节点查找对应数据返回。

 

6.为什么要用自增ID做主键

插入:每申请一页内存后,能充分利用内存,用完后,再去申请下一块(因为自增,所以可以充分用连续的地址空间,如果是乱序的话,就不会一块用完才去申请下一块)

查找:数字大小比较 方便查找

RPC实现原理

 

serialVersionUID作用:

Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidClassException)

有序列化版本的情况下:

A->序列化———->用对应的结构B–>反序列化                           正常,内容相同

A->序列化———->用比A少字段的结构B–>反序列化                      缺少对应字段

A->序列化———->用比A多字段的结构B–>反序列化                     新增字段为默认值

 

 

 

远程调用时,有一个容器,负责管理。新的服务都注册到这个容器里面。

MySQL基础

1.给出一个表的细节

show columns from tbl_mendian_order_master;

等价于:describe tbl_mendian_order_master;

2.

show status;      //用于显示广泛的服务器状态信息

show grants;    //用来显示授权用户的安全权限

show errors;    //用来显示服务器错误信息

show warnings;    //用来显示服务器警告信息

 

3.不能部分使用distinct关键字

如:select  distinct groupID,shopID from table; 除非指定的两个列都不同,否则讲会展示所有满足条件的行(失去了过滤某一列重复数据的功能)。

 

4.limit语句

select * from table limit 3;

等价于:select * from table limit 0,3;  (从第0个开始,展示前3个)

等价于:select * from table limit 3 offset 0;(从第0行开始,取3个,MySQL5才支持)

 

5.ORDER BY默认是升序排列ASC

在字典排序总,A被视为和a相同,如果需要改变这种规则,需要请求数据库管理员的帮助。

 

6.MySQL在匹配字符串时,默认不区分大小写

where name = ‘Tree’  和  where name = ‘tree’  搜索相同

 

7.关于null

使用  = ‘null’   , <> ‘null’ , is not null  可以查出来不是null的数据

使用  is null  可以查询到匹配null的数据,但是使用  =’null’ 和 = null  查询不到内容

 

8.AND优先级高于OR

 

9.通配符

%表示任何字符出现任意次数(0->n次),%几乎可以匹配任何东西,但null例外

_ 下划线值匹配单个字符而不是多个字符

 

10.正则表达式

select * from table where name REGEXP ‘.000’;      //这会筛选出类似1000  或者2000这样的数据

. 是正则表达式语言中的一个特殊的字符,它表示匹配任意一个字符。

select * from table where name LIKE ‘1000’;   //不会返回值是1000的数据

select * from table where name REGEXP ‘1000’;  //会返回值是1000的数据

  • LIKE匹配整个列,如果被匹配的文本在列值中出现,LIKE将不会找到它,相应的行也不会被返回,除非使用了通配符。
  • REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现,则会被返回。

MySQL中的正则表达式匹配(从3.23.4版本后)不区分大小写,如果要区分大小写,可以使用BINARY关键字。

如:select * from table where name REGEXP BINARY ‘Henry’;

a.进行OR匹配

select * from country where Continent REGEXP ‘Asia|Africa|Europe’;

b.特殊字符匹配

如果要匹配特殊字符,必须用\\为前导。如:\\-     \\.   \\|    \\[     \\]   \\\                            (即_  .  |  [ ]  \)

select * from table where name REXEXP ‘\\.’;        //检索出name包含.的

  • 多数正则表达式使用单个反斜杠转义特殊字符,但MySQL要求两个反斜杠(MySQL自己解释一个,正则表达式解释另外一个)。

11.拼接字段

多数DBMS使用+或者||来实现拼接,MySQL则使用Concat()函数来实现。

SELECT CONCAT(RTRIM(shopID),'(‘,Trim(orderKey),’)’,LTRIM(shiftName)) AS NewName  FROM tbl_mendian_order_master  ;                                                                      //将对应几项作为别名NewName 输出

RTrim()可以去掉字符串右边的空格,LTrim()可以去掉左边的空格,Trim()可以去掉字符串两边的空格。

 

12.文本处理函数

1.RTrim()

作用:去掉串尾的空格来整理数据。

2.Upper()

作用:将文本转换为大写并返回。

3. Lower()

作用:将文本转换为小写并返回。

4.Length(str)

作用:返回串的长度。

5.Locate(substr,str)

参数:substr待查找的子串,str待查找的串,在str中查找substr第一次出现的位置,如果不存在则返回0。

另一种情况:Locate(substr,str,pos)。作用:返回子串 substr 在字符串 str 中的第 pos 位置后第一次出现的位置。如果 substr 不在 str 中返回 0。

6.Position(substr IN str)

作用:返回substr在str中第一次出现的位置。

7.SubString(str,pos)

作用:返回从第pos位置出现的子串的字符。

另一种情况:substring(str, pos, len)。作用:返回从pos位置开始长度为len的子串的字符。

8.Soundex()

返回串SOUNDEX值,SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。

select name from table where Soundex(name) = Soundex(‘Y Lie’);

可以返回Y Lee这样发音类似的记录。

 

13.数值处理函数

ABS(x) 返回x的绝对值

BIN(x) 返回x的二进制(OCT返回八进制,HEX返回十六进制)

EXP(x) 返回值e(自然对数的底)的x次方

GREATEST(x1,x2,…,xn) 返回集合中最大的值

LEAST(x1,x2,…,xn) 返回集合中最小的值

LN(x) 返回x的自然对数

LOG(x,y) 返回x的以y为底的对数

MOD(x,y) 返回x/y的模(余数)

PI() 返回pi的值(圆周率)

RAND() 返回0到1内的随机值,可以通过提供一个参数(种子)使RAND()随机数生成器生成一个指定的值。

FLOOR(x) 返回小于x的最大整数值,(去掉小数取整)

CEILING(x) 返回大于x的最小整数值,(进一取整)

ROUND(x,y) 返回参数x的四舍五入的有y位小数的值,(四舍五入)

TRUNCATE(x,y) 返回数字x截短为y位小数的结果

SIGN(x) 返回代表数字x的符号的值(正数返回1,负数返回-1,0返回0)

SQRT(x) 返回一个数的平方根

 

14.日期和时间处理函数

DATE_ADD(date,INTERVAL int keyword) 返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:SELECTDATE_ADD(CURRENT_DATE,INTERVAL 6 MONTH);

DATE_SUB(date,INTERVAL int keyword) 返回日期date加上间隔时间int的结果(int必须按照关键字进行格式化),如:SELECTDATE_SUB(CURRENT_DATE,INTERVAL 6 MONTH);

DATE_FORMAT(date,fmt) 依照指定的fmt格式格式化日期date值

FROM_UNIXTIME(ts,fmt) 根据指定的fmt格式,格式化UNIX时间戳ts

MONTHNAME(date) 返回date的月份名(英语月份,如October)

DAYNAME(date) 返回date的星期名(英语星期几,如Saturday)

NOW() 返回当前的日期和时间 如:2016-10-08 18:57:39

CURDATE()或CURRENT_DATE() 返回当前的日期

CURTIME()或CURRENT_TIME() 返回当前的时间

QUARTER(date) 返回date在一年中的季度(1~4)

WEEK(date) 返回日期date为一年中第几周(0~53)

DAYOFYEAR(date) 返回date是一年的第几天(1~366)

DAYOFMONTH(date) 返回date是一个月的第几天(1~31)

DAYOFWEEK(date) 返回date所代表的一星期中的第几天(1~7)

YEAR(date) 返回日期date的年份(1000~9999)

MONTH(date) 返回date的月份值(1~12)

DAY(date) 返回date的天数部分

HOUR(time) 返回time的小时值(0~23)

MINUTE(time) 返回time的分钟值(0~59)

SECOND(time) 返回time的秒值(0-59)

DATE(datetime) 返回datetime的日期值

TIME(datetime) 返回datetime的时间值

返回当前时间:

SELECT DATE_FORMAT(NOW(),’%Y%m%d%H%i%s’);

 

15.聚集函数

聚集函数运行在行组上,计算和返回单个值得函数。

AVG()                       //返回某列的平均值

AVG()忽略列值为null的行

COUNT()                      //返回某列的行数

count(*) 返回所有行的数目,不管是否为NULL

count(name) 返回对应列中有值得行进行计数,忽略NULL

MAX()                      //返回某列的最大值

MAX()忽略列值为null的行

MIN()                      //返回某列的最小值

MIN()忽略列值为null的行

SUM()                      //返回某列值之和

SUM()忽略列值为null的行

 

16.分组

select name from table group by name WITH ROLLUP;

使用WITH ROLLUP,最后会默认多一条分组的汇总信息(每一列数据的汇总)。

过滤分组(使用having子句):

select name from table group by name  HAVING COUNT(*) >=2;

 

17.内部联结

select name from a,b where a.id = b.id;

等价于:select name from a INNER JOIN b ON a.id = b.id;

 

18.自联结

//筛选出name是A的对象还有哪些school

select school from  table where id = (select id from table where name = ‘A’);

等价于:select t1.school FROM table t1, table t2 WHERE t1.id = t2.id AND t2.name = ‘A’;

 

19.四种连接

内连接:a和b表相关列的交集(两个表都有的)

select   a.*,b.*   from   a   inner   join   b     on   a.id=b.parent_id

左外连接:a全量,b对应a相应字段有的部分

select   a.*,b.*   from   a   left   join   b     on   a.id=b.parent_id

右外连接:b全量,a对应b相应字段有的部分

全连接:a和b的全量,对应部分没有的为null

select   a.*,b.*   from   a   full   join   b     on   a.id=b.parent_id

 

20.组合查询

SELECT * FROM a WHERE num >=5  UNION  SELECT * FROM a WHERE reportDate = 20180618;  //返回满足条件的记录(如果两次查询都有对应记录只显示一条)

SELECT * FROM a WHERE num >=5  UNION  ALL SELECT * FROM a WHERE reportDate = 20180618;    //返回满足条件的记录(可能会有重复的记录)

可以在最后用ORDER BY子句进行分组,放在最后,对返回所有结果进行排序(并不是只对最后那个select语句排序)。

 

21.全文本搜索

在创建表时,启用全文本搜索。

然后搜索时用Match()和Against()执行全文本搜索,Match()用来指定被搜索的列,Against()用来指定要使用的搜索表达式。

CREATE DATABASE test;
USE test;
CREATE TABLE fullTextTest
(
	id int NOT NULL AUTO_INCREMENT,
	name char(10) NOT NULL,
	testText  text NULL,
	PRIMARY KEY(id),
	FULLTEXT(testText)
)ENGINE = MyISAM;  //仅MyISAM引擎支持全文本搜索

SELECT testText FROM fullTextTest WHERE Match(testText) Against(‘rabbit’);   //返回testText字段包含rabbit字符串的记录。

全文本搜索的一个重要部分就是对结果排序,具有较高等级的行会先返回(越早出现对应字符串的行可能会越前返回,而like语句则无法保证)。

使用Match和Against会简历一个计算列,该列包含全文本搜索计算出的等级值,如果不包含对应字符串,则等级值为0,否则,文本中词靠前的记录的等级值会比词靠后的等级值高。

因为全文本搜索有索引,所以数组还相当快。

 

22.insert操作

insert into student(id,age,name) values (null,18,’tom’),(null,23,’hy’);

INSERT单条语句有多组值,每组用圆括号括起来。

  • 单条INSERT语句处理多个插入比使用多条语句快。

 

23.INSERT SELECT

insert into student(id,age,name) SELECT id,age,firstName FROM foreignStudent;

INSERT SELECT语句,不一定要求列名匹配,实际使用的是对应列的位置。

 

24.update

update在更新一行或者多行时,如果发生错误,则整个update操作会被取消。

如果即使发生错误也要继续进行更新,则使用IGNORE关键字。

但:以下语句只会更新id=1的内容,id=2的内容将不会更新。

 

25.DELETE

如果想从表中删除所有行,不要使用DELETE,可以使用TRUNCATE table语句,速度更快。truncate实际是删除了原来的表,并重新创建了一个表,而不是逐行删除。

 

26.last_insert_id()函数

last_insert_id()函数:获取自增主键上一次插入时候的值。

如果是手动指定主键为某个值,然后插入,则该函数返回的值不变(返回自增主键上次自己插入的值)。

 

 

27.引擎

show engines;    //查看MySQL引擎

show create table student;    //查看student表的信息

MyISAM:

数据库存储对应三个文件,一个索引文件,一个数据文件,一个表结构文件。叶子节点data域存放的是数据的地址。

MyISAM拥有较高的插入、查询速度,但不支持事务,支持表锁,不支持外键。

 

InnoDB:

数据库存储对应的两个文件,一个表结构文件,一个数据和索引文件。叶子节点存放的是数据。

支持事务安全表(ACID),支持行级锁和外键,InnoDB是默认的MySQL引擎。

 

MEMORY:

MEMORY存储引擎将表中的数据存储到内存中,MySQL中使用该引擎作为临时表,存放查询的中间结果。数据的处理速度很快但是安全性不高。

 

Archive

如果只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive。

 

28.ALTER

alter table student add column school varchar(50) not null default ” comment ‘学校’;

alter table student drop column school;

 

29.DROP TABLE test;

create table test(
id int not null AUTO_INCREMENT,
name varchar(20) NOT NULL DEFAULT '',
primary key (id)
)ENGINE = InnoDB;

DROP TABLE test;

 

30.RENAME

rename table test to test1,  student to student2;

 

31.VIEW

create view studentView AS select id,age from student where age != 18;

select * from studentView;

视图本身没有数据,对视图进行更新实际是更新其基表。

一般视图用于检索(SELECT)而不用于更新。

 

32.存储过程

a.创建存储过程

因为创建存储过程需要使用  ;分隔符,但是MySQL也把  ;作为分隔符,所以要创建存储过程,先使用DELIMITER  //  语句,将  //  作为新的语句分隔符。创建完存储过程后,再将  ;作为语句分隔符(恢复为原来的鳄鱼局分隔符)。

最后调用call getAvgAge();  可以使用存储过程并得到返回值。

除  \  符号外,任何字符都可以用作语句分隔符。

 

b.删除存储过程:

drop procudure getAvgAge;                 //后面没有括号

 

c.检查存储过程

show create procedure getAvgAge;

 

33.IN OUT INOUT

IN:传递给存储过程

OUT:从存储过程传出

INOUT:对存储过程传入和传出

所有MySQL变量都必须以  @  开始。

 

34.select … for update排他锁

开启事务,但是未提交:

另外对该条记录操作,过段时间会timeout:

如果在update后,立马到1处执行commit,则2处update也成功:

 

Java细节

1.关于构造器

如果编写程序时,类没有构造器,则编译器会自动创建一个默认构造器。

 

 class Bird{
        private int age;
}

此时可以直接用new Bird()创建一个对象。

但是假如自己已经定义过构造器,则编译器不会在创建默认构造器。

 class Bird{
        private int age;
        Bird(int age){
                this.age = age;
        }
}

那么此时用new Bird(),则编译器就会报错,没有找到对应的构造器。

 

2.this关键字

在调用方法时,编译器内部暗自把“所操作对象的引用”,也会作为参数传递给调用的方法。

this关键字只能在方法内部使用,表示对“调用方法的那个对象”的引用。

package com.buaa;

public class TestThis {
    int i = 0;
    TestThis increment(){
        i++;
        return this;
    }
    private void print(){
        System.out.println("Num i is:"+i);
    }
    public static void main(String[] args){
        new TestThis().increment().increment().print();
    }
}

结果:

  • 可以用this调用一个构造器,但是不能调用两个,此外,必须将构造器调用置于方法的最起始处,否则编译器会报错。除构造器外,编译器禁止在其他任何方法中调用构造器。

 

3.static的含义

static方法就是没有this的方法。

无论创建多少个对象,静态数据都只占用一份存储区域。所以static关键字不能应用于局部变量(编译器会报错),只能作用于域。

 

4.成员初始化

如果是方法的局部变量,不初始化,直接使用编译器会报错。

但是对于类的成员变量,new一个类的实例时,会给一个默认值。

public class Test {
    private int i;

    private void add() {
        int num;
        //num ++; 这里编译器会报错
    }

    public static void main(String[] args) {
        Test test = new Test();
        System.out.println(test.i++);
        System.out.println(test.i++);
    }
}

输出结果:

注:在调用构造方法时,先会去初始化成员变量,不管这些成员变量放在构造方法前或者后。(或者是在类实例化时,先去初始化其成员变量,然后才会调用其构造方法)

 

5.对象创建过程

以Dog类为例

a.首次创建对象时,Java解释器先查找类路径,找到对应Dog.class文件;

b.然后载入Dog.class文件,有关静态初始化的所有动作都会执行,所有静态初始化只在Class对象首次加载时进行一次;

c.当用new Dog()创建对象的时候,先在堆上为Dog对象分配足够的存储空间;

d.将这块存储空间清零,为Dog对象的所有基本类型数据设置默认值

e.执行所有字段定义处的初始化动作

f.执行构造器

 

6.数组赋值

int[] a = {1,2,3,4};

int[] b;

b = a;

数组赋值给另外一个数组其实真正做的只是复制了一个引用(修改一个的值,另外一个也会变)。

  • 数组的创建,是在运行时刻进行的。
        Random random = new Random();
        int[] arr;
        arr = new int[random.nextInt(10)];
        System.out.println(arr.length);

上面代码可以正确运行,并且输出数组的大小,数组中的值会被初始化为对应类型的默认值。

 

可变参数列表:

public class Test {
    static void printArray(Object[] array) {
        for (Object o : array) {
            System.out.print(o + " ");
        }
        System.out.println();
    }

    //可变参数
    static void printVarArray(Object... array) {
        for (Object o : array) {
            System.out.print(o + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Test.printArray(new Object[]{1, 2});
        Test.printArray(new Object[]{"a", 'b', 33});
        Test.printVarArray(1, 2);
        Test.printVarArray("a", 'b', 33);
    }

}

输出结果:

注:可变参数可以重载,如f(String… arr),f(Integer… arr),但是如果重载,并且在调用f()方法时,会编译错误,因为编译器不知道应该调用哪一个方法了。

 

7.enum

public class Test {

    public static void main(String[] args) {
        for (Type type : Type.values()) {
            System.out.println(type + ":" + type.ordinal());
        }
    }
}

enum Type {
    A, B, C, D
}

输出结果:

 

7.printf(String format, Object … args)方法

System.out.printf("a%s%d","b",3);

打印出:ab3

 

8.private修饰符

class AA{
    private AA(){
    }
}
class BB extends AA{//这里会报错,提示没有默认的构造方法
    
}

如上,在其他类中无法使用new AA()创建AA的一个实例,使用private会阻止别人直接访问该构造器。此外,默认构造器使用private,这将阻碍对该类的继承

  • 类不可以是private的,也不可以是protected(内部类除外,内部类可以是private或者protected)。所以类只能用包访问权限或者public。

 

 

HashMap源码阅读

1.HashMap存储结构是数组和链表结合的形式。

默认初始容量是16,负载因子是0.75

因为每次扩容都是翻倍,所以hashMap的大小是2的n次方。

2.当put一个<key,value>的node时,先计算key的hashCode,然后取hashCode的低16位和高16位做异或运算(充分利用每一位,让32位都能利用的到)。

然后如何确定数组的下标?将size的大小减1,然后和hashCode做与运算

精妙的地方:因为size的值(0000010000???0)是2的n次方,做减1操作后,其二进制会变为00???011111111111这种结构,然后再和前面运算得出的hashCode做与运算,这样可以保证算出的数肯定小于(size-1),那么计算出的这个数,也就可以作为数组的下标了。

 

源码阅读:

1.key对应的hash计算:

2.resize方法:

a.如果第一次put,则创建默认大小16,负载因子为0.75的table

b.如果hashMap的size已经到了最大,则threshold = Integer.MAX_VALUE;否则,扩大为原来两倍

c.当原来有数据进行resize时,如果原node的next没有东西,则直接将该node重新散列(相当于用原hash和新的容量做取余操作)

d.当next是红黑树时:

e.当next是链表时:

将整个链表移动到对应位置(其中loHead和hiHead用来区分是不是数组的第一个元素)

 

3.remove方法

如果移除的是红黑树上的节点,则用removeTreeNode方法删除对应节点;

如果移除的是数组上的节点,则将数组节点对应的链表上移(把根节点删掉,第二个节点放到数组里面);

如果移除的是链表中的节点,则将该节点上面的指针指到该节点下面的节点上。

 

4.put方法

a.如果没有初始化,则执行resize方法赋默认值,然后判断数组上对应节点是否为null,如果为null,直接将该节点置在该数组对应位置上

b.如果对应数组的位置的key值和hash值相同,则证明是用新的value覆盖旧的value,所以直接将该节点替换;否则如果是红黑树,调用红黑树的ptTreeVal方法;否则是链表,则调用列表的对应方法

c.如果是链表,将新的节点添加到原链表最后(链表遍历的过程中会判断是不是对应key重新赋值的操作,如果存在对应key则替换节点、退出),然后判断添加节点后是否链表的长度>=8,如果是,则将链表转为红黑树

 

关于concurrentHashMap:

多了segment(称为段或者槽),默认是16(相当于理论上同时最大可以有16个线程对concurrentHashMap进行写操作),不过可以设置,segment设置了值后,后续不允许修改,所以扩容对segment的数目没影响。

具体的数据结构:可以理解为segment数组的每个元素里面是一个hashMap,默认16个segment,每个segment中的数组大小是2.负载因子是0.75,所以超过1.5会进行扩容(也就是segment中插入第二个元素的时候会触发一次扩容)。扩容针对的是segment中的hashMap。

关于获取锁,使用的是cas,重试一定次数如果还失败的话会放弃锁的争用进入到阻塞队列。

关于put和get:先根据hash值得到segment的位置,然后再根据hash值得到对应数组的位置。

类似HashMap,也存在红黑树的转换。

为什么get过程不用加锁:因为get操作中需要用到的共享变量都是volatile的。

JVM相关

JVM、JRE、JDK关系图:

jvm运行时数据区(JVM内存模型):

类里面的常量(final修饰的变量)、静态变量(static修饰)存放在方法区,成员变量存放在堆区,局部变量存放在java栈的局部变量表中。

Java栈:存放基本数据类型、局部变量等,用完就消失。
堆:存放new创建的实例化对象及数组等,用完之后靠垃圾回收机制不定期自动消除。

本地方法栈(功能类似于Java栈):本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。

注:程序计数器是唯一不会抛出oom的区域,java栈和本地方法栈都会抛出 StackOverflowError和oom,java堆会抛出oom。

总结:JVM内存模型,是虚拟机运行时的内存模型,主要分为线程共享部分和线程私有部分。

线程共享:堆和方法区(非堆)。堆存放用户new的对象,方法区(非堆)存放java虚拟机运行时的对象,比如类信息,静态变量等。

线程私有:程序计数器、虚拟机栈、本地方法栈。其中虚拟机栈是程序运行时存放的一些栈帧,包括局部变量、动态链接等,而本地方法栈也类似,只不过存放的是本地方法运行时的一些栈帧。

  • Java栈:

局部变量表存放方法的局部变量(包含方法的参数),然后操作数栈用来保存操作数,动态链接(动态链接类似于抽象类Animal有两个实现类,Cat和Dog,animal实例什么时候用cat什么时候用dog,就是动态链接),出口就是方法的返回地址。

  • 运算流程:从局部变量表取数据入操作数栈,运算后出栈到局部变量表。

public void get(){

get();

}

如果是方法递归调用的话,每次都会有栈针入栈,最后超过XSS大小后,就会报StackOverFlowError.

  • JVM内存模型(堆区进行了分代)

 

  • JVM的内存模型和Java内存模型(JMM)的关系:

JVM内存模型主要是堆、方法区(非堆)、程序计数器、虚拟机栈、本地方法栈,JMM主要内容是主内存、工作内存等。

jmm中的主内存、工作内存与jvm中的Java堆、栈、方法区等并不是同一个层次的内存划分,这两者基本上是没有关系的,如果两者一定要勉强对应起来,那从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。

java==误用=

boolean x = true;
boolean y = true;
if(x=y){
    System.out.println("aaa");
}else {
    System.out.println("bbb");
}

输出:aaa

只有这一种情况,程序可以正常运行,对于其他的=  ,编译报错

CopyOnWriteArrayList

CopyOnWriteArrayList,用迭代器,不能remove,会报错。

如果想要add操作,可以使用for循环或者迭代器,如果要remove操作,只能用for循环。

Java数组默认值

public static void main(String[] args) {
        int[] a = new int[10];
        System.out.println(a[0]);
        String[] str = new String[10];
        System.out.println(str[0]);
        Integer[] i = new Integer[10];
        System.out.println(i[0]);
        float[] f = new float[10];
        System.out.println(f[0]);
        double[] d = new double[10];
        System.out.println(d[0]);
        char[] c = new char[10];
        System.out.println(c[0]);
        System.out.println(c[0] == ' ');
        System.out.println(c[0] == '\0');
        byte[] by = new byte[10];
        System.out.println(by[0]);
        System.out.println("\0".equals('\0'));
        System.out.println("\0");
        System.out.println('\0');
        System.out.println("finished");
    }


输出结果:


总结:int和byte数组,初始化内容是0,
String数组初始化内容是null;
double和float数组,初始化是0.0;
boolean数组,初始值为false;
char数组,初始化是\0 (代表空白,不等于null,不等于'')
"\0".equals('\0')为false