前言
在网上一搜索Java学习会弹出无数文章,但全都是培训机构的大纲文,实在鸡肋,根本没写一个字是在帮助菜鸟怎么认识Java的,所以我琢磨着写一篇真正意义上的Java从0到1的文章,帮0基础的小白和初学的财年打开Java的大门
如果观察过招聘网站上的Java相关岗位需求就会发现,抛开其它的经验能力等等,单纯从技术,或者说知识上来讲,可以发现一些共通的地方。
- Java基础
- 计算机基础
- 数据库,SQL/NoSQL
- 常用开源框架
- 分布式/微服务
- 中间件,缓存、消息中间件
学习任何一门编程语言,首先要学习的是基础语法,开启Java学习的第一步,当然就是深入掌握计算机基础、编程基础语法,面向对象,集合、IO流、线程、并发、异常及网络编程,这些我们称之为JavaSE基础。当你掌握了这些内容之后,你就可以做出诸如:电脑上安装的迅雷下载软件、QQ聊天客户端、考勤管理系统等桌面端软件。
而这篇文章要写的,就是Java基础,有人看的话后面可能也会继续写计算机基础、数据库和框架这些。
在文末我也整理了一份书单Java架构师学习核心书单,各位可以按照自己的学习进度去酌情购买,如果是学生或者最近手头紧,也没有关系,我给大家收集了电子档,可以自行点击蓝字领取。
话不多说,坐稳扶好,发车喽!
一、Java基础语法
关键字的概念与特征
概念:Java关键字是事先定义好的对Java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名、方法名、类名、包名和参数。
特征:
- 完全小写
- 在增强版记事本中(例如Notepad++)有特殊颜色
2、标识符的概念与规则
什么是标识符?
凡是可以由自己命名的地方都称为标识符.
例: 项目名 ,包名 ,类名 .方法名
命名规范
① 不可使用java关键字和保留字,但是可以包含关键字和保留字.
② 可以使用26个字母大小写,数字0-9,$和_.
③ 可以使用数字,但不可放在首位.
④ 长度理论上没有限制,但命名最好能反映出其作用,遵循”驼峰形式”,见命知意
⑤ 包名全部小写,类名全部大驼峰式
⑥ 变量名、方法名首字母小写,如果名称由多个单词组成,每个单词的首字母都要大写****
⑦ 常量(特指被 final 修饰的常量)名全部大写,如果是单个字母,需要大写,如果是多个单词,需要用下划线分开。并且都大写
例:写一个ajax的项目实例,
项目名:ajaxtest 包名:com.liu.ajax 类名:AjaxDemo
常量&变量
常量的概念:在程序运行期间。固定不变的量。
常量的分类:
- 字符常量:凡是用单引号引起来的单个字符,就做字符常量。例如:、‘b'、9’、‘中
- 字符串常量:凡是用双引号引起来的部分,叫做字符串常量。例如:"abc"、"Hello”、“123"
- 整数常量:直接写上的数字,没有小数点。例如:100、200、0、-250
- 浮点数常量:直接写上的数字,有小数点。例如:2.5、-3.14、0.0
- 布尔常量:只有两种取值 true | false
基本数据类型:
注意事项:
Java中的默认类型:整数类型是 int 、浮点类型是double
数据范围与字节数不一定相关,例如float数据范围比 long 更加广泛,但是float是4字节,1ong是8字节。
浮点型可能只是一个近似值,并非精确的值。
浮点数当中默认类型是double。如果一定要使用float类型,需要加上一个后缀F。
字符串不是基本类型,而是引用类型
如果是整数,默认为int类型,如果一定要使用long类型,需要加上一个后缀L。推荐使用大写字母后缀。
变量: 程序运行期间,内容可以改变的量。
创建一个变量并且使用的格式:
数据类型 变量名称; //创建了一个变量
变量名称 = 数据值; //赋值,将右边的数据值,赋值交给左边的变量
一步到位的格式: 数据类型 变量名称 = 数据值; //在创建一个变量的同时,立刻放入指定的数据值
注意事项:
1.如果创建多个变量,那么变量之间的名称不可以重复。
2.对于float和long类型来说,字母后缀F 和 L不要丢掉。
3.没有进行赋值的变量,不能直接使用;一定要赋值之后,才能使用。
**作用域 :**从定义变量的一行开始,一直到直接所属的大括号结束。
数据类型转换
当效据类型不一样时,就会发生数据类型转换
自动类型转换(隐式)
1.特点:代码不需要进行特殊处理,自动完成
2.规则:数据范围从小到大
long num1 = 100;
System.out.println(num1)
// 左边是long类型,右边是默认的int类型,左右不一样
// int-->long,符合了数据范围从小到大的要求
强制类型转换(显式)
1.特点:代码需要进行特殊的格式处理,不能自动完成
2.格式:范围小的类型范围小的变量名=(范围小的类型)原本范围大的数据
int num=(int)100L;
System.out.print1n(num);
//左边是int类型,右边是long类型,不一样
//格式范围小的类型 范围小的变量名 =(范围小的类型) 原本范围大的数据 ;
注意事项:
1.强制类型转换一般不推荐使用,因为有可能发生精度损失、数据溢出。
2.byte / short / char这三种类型都可以发生数学运算,例如加法“+”.
3.byte/short/char这三种类型在运算的时候,都会被首先向上提升成为int类型,然后再计算。符合ASCII编码表。
例3:
byte num4 = 40;//注意!右侧的数值大小不能超过左侧的类型范围
byte num5 = 50;
//当byte + byte,会先向上提升称为int类型,所以结果就是--> int + int ,返回类型也应该是int
int result1 = num4 + num5;
System.out.println(result1);//90
运算符 :进行特定操作的符号。例如:+
表达式:用运算符连起来的式子叫做表达式。例如:20+5 。 又例如:a+b
四则运算:加+ 减 - 乘 * 除 /
取模(取余数):%
只有对于整数的除法来说,取模运算符才有余数的意义。
int x = 10;
int y = 3;
int resultl = x / y ;
System.out.println(result1);//3
int result2 = x % y ;
System.out.println(result2);//余数,模,1
算术运算符
四则运算当中的加号“+”有常见的三种用法:
1.对于数值来说,+ 就是加法。
2.对于字符char类型来说,在计算之前,char会被提升成为int,然后再计算。 char类型字符,和int类型数字,之间的对照关系表:ASCII、Unicode
3.对于字符串String来说,+ 代表字符串连接操作。() 小括号的优先级最高
String str = "Java";
//String + int --> String
System.out.println(str2+20);//Java20
自增运算符:++
例如: a++; 先使用a值,再执行a = a + 1 ;
int a = 1;
int b = 2;
b = a++; //在这一步,a的值还没有改变,所以是将a=1的值赋给了b,然后再进行改变。
System.out.println("a:"+a); //a:2
System.out.println("b:"+b); //b:1
自减运算符:- -
例如: ++a; 先执行a = a + 1 ; 再使用a值。
int a = 1;
int b = 2;
b = --a;
System.out.println("a:"+a);
System.out.println("b:"+b);
a++ : 在变量值被使用之后才增加的值
++a : 在变量值在被使用之前增加它的值
- 操作符的工作原理与此相同,只是它所执行的是减值操作而非增值操作。
赋值运算符
赋值运算符是指为变量或常量指定数值的符号。如可以使用 “=” 将右边的表达式结果赋给左边的操作数。
Java 支持的常用赋值运算符,如下表所示:
比较运算符
比较运算符用于判断两个数据的大小,例如:大于、等于、不等于。比较的结果是一个布尔值( true 或 false )。
Java 中常用的比较运算符如下表所示:
注意事项:
1、 > 、 < 、 >= 、 <= 只支持左右两边操作数是数值类型
2、 == 、 != 两边的操作数既可以是数值类型,也可以是引用类型
逻辑运算符
主要用于进行逻辑运算,连接两个Boolean值,代表两个条件。
Java 中常用的逻辑运算符如下表所示:
我们可以从“投票选举”的角度理解逻辑运算符:
1、 与:要求所有人都投票同意,才能通过某议题
2、 或:只要求一个人投票同意就可以通过某议题
3、 非:某人原本投票同意,通过非运算符,可以使其投票无效
4、 异或:有且只能有一个人投票同意,才可以通过某议题
当使用逻辑运算符时,我们会遇到一种很有趣的 “短路” 现象 >> 如果根据左边已经可以判断得到最终结果,那么右边的代码将不再执行,从而节省一定的性能。
譬如:( 1 > 2 ) && ( 1 < 3 ) 中,如果能确定左边 1 > 2 运行结果为 false , 则系统就认为已经没有必要执行右侧的 1 < 3 啦。
条件运算符
条件运算符( ? : )也称为 “三元运算符”。
-
一元运算符:只需要一个数据就可以进行操作的运算符。例如:取反!、自增++、自减--
-
二元运算符:需要两个数据才可以进行操作的运算符。例如:加法+、赋值=
-
三元运算符:需要三个数据才可以进行操作的运算符。
语法形式:布尔表达式 ? 表达式1 :表达式2
运算过程:如果布尔表达式的值为 true ,则返回 表达式1 的值,否则返回 表达式2 的值
例如 :
因为,表达式 8>5 的值为 true ,所以,返回: 8大于5
方法 ♦ ♦
所谓方法,就是用来解决一类问题的代码的有序组合,是一个功能模块。
一般情况下,定义一个方法的语法是:
访问修饰符 返回值类型 方法名(参数列表){
方法体
}
其中:
1、 访问修饰符:方法允许被访问的权限范围, 可以是 public、protected、private 甚至可以省略 ,其中 public 表示该方法可以被其他任何代码调用
2、 返回值类型:方法返回值的类型,如果方法不返回任何值,则返回值类型指定为 void ; 如果方法具有返回值,则需要指定返回值的类型,并且在方法体中使用 return 语句返回值
3、 方法名:定义的方法的名字,必须使用合法的标识符
4、 参数列表:传递给方法的参数列表,参数可以有多个,多个参数间以逗号隔开,每个参数由参数类型和参数名组成,以空格隔开
根据方法是否带参、是否带返回值,可将方法分为四类:
无参无返回值方法
如果方法不包含参数,且没有返回值,我们称为无参无返回值的方法。
方法的使用分两步:
第一步,定义方法
例如:下面代码定义了一个方法名为 show ,没有参数,且没有返回值的方法,执行的操作为输出 “ welcome to imooc. ”
注意 :
1、 方法体放在一对大括号中,实现特定的操作
2、 方法名主要在调用这个方法时使用,需要注意命名的规范,一般采用第一个单词首字母小写,其它单词首字母大写的形式
第二步,调用方法
当需要调用方法执行某个操作时,可以先创建类的对象,然后通过 对象名.方法名(); 来实现
例如:在下面的代码中,我们创建了一个名为 hello 的对象,然后通过调用该对象的 show( ) 方法输出信息
运行结果为: welcome to imooc.
无参带返回值方法
如果方法不包含参数,但有返回值,我们称为无参带返回值的方法。
例如:下面的代码,定义了一个方法名为 calSum ,无参数,但返回值为 int 类型的方法,执行的操作为计算两数之和,并返回结果
在 calSum( ) 方法中,返回值类型为 int 类型,因此在方法体中必须使用 return 返回一个整数值
调用带返回值的方法时需要注意,由于方法执行后会返回一个结果,因此在调用带返回值方法时一般都会接收其返回值并进行处理。如下:第6行
运行结果为: 两数之和为:17
不容忽视的“小陷阱”:
1、 如果方法的返回类型为 void ,则方法中不能使用 return 返回值!
2、 方法的返回值最多只能有一个,不能返回多个值
3、 方法返回值的类型必须兼容,例如,如果返回值类型为 int ,则不能返回 String 型值
带参无返回值方法
有时方法的执行需要依赖于某些条件,换句话说,要想通过方法完成特定的功能,需要为其提供额外的信息才行。
例如,现实生活中电饭锅可以实现“煮饭”的功能,但前提是我们必须提供食材,如果我们什么都不提供,那就真是的“巧妇难为无米之炊”了。
我们可以通过在方法中加入参数列表接收外部传入的数据信息,参数可以是任意的基本类型数据或引用类型数据。
我们先来看一个带参数,但没有返回值的方法:
上面的代码定义了一个 show 方法,带有一个参数 name ,实现输出欢迎消息。
调用带参方法与调用无参方法的语法类似,但在调用时必须传入实际的参数值
例如:
运行结果为: 欢迎您,北游!
很多时候,我们把定义方法时的参数称为形参,目的是用来定义方法 需要传入参数的个数和类型;把调用方法时的参数称为实参,是传递给方法真正被处理的值。
一定不可忽视的问题:
1、 调用带参方法时,必须保证实参的数量、类型、顺序与形参一一对应
2、 调用方法时,实参不需要指定数据类型,如 []
3、 方法的参数可以是基本数据类型,如 int、double 等,也可以是引用数据类型,如 String、数组等
第10行,Array.toString()方法将数组转换为字符串输出
4、 当方法参数有多个时,多个参数间以逗号分隔
带参带返回值方法
如果方法既包含参数,又带有返回值,我们称为带参带返回值的方法。
例如:
下面的代码,定义了一个 show 方法,带有一个参数 name ,方法执行后返回一个 String 类型的结果
调用带参带返回值的方法:
运行结果为: 欢迎您,北游!
二、Java面向对象
面向对象
面向对象(Object Oriented)是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm),其基本思想是使用对象、类、继承、封装、多态等基本概念来进行程序设计。从现实世界中客观存在的事物(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。
对象
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。
类的实例化可生成对象,一个对象的生命周期包括三个阶段:生成、使用、消除。
当不存在对一个对象的引用时,该对象成为一个无用对象。Java的垃圾收集器自动扫描对象的动态内存区,把没有引用的对象作为垃圾收集起来并释放。当系统内存用尽或调用System.gc( )要求垃圾回收时,垃圾回收线程与系统同步运行。
类
类是具有相同属性和方法的一组对象的集合,它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和方法两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性和方法两个主要部分。
Java中的类实现包括两个部分:类声明和类体。
类声明
[public][abstract|final] class className [extends superclassName] [implements interfaceNameList]{……}
其中,修饰符public,abstract,final 说明了类的属性,className为类名,superclassName为类的父类的名字,interfaceNameList为类所实现的接口列表。
类体
class className{
[public | protected | private ] [static] [final] [transient] [volatile] type variableName;//成员变量
[public | protected | private ] [static] [final | abstract] [native] [synchronized] returnType methodName([paramList]) [throws exceptionList]{
statements
}//成员方法
}
成员变量限定词的含义:
- static: 静态变量(类变量)
- final: 常量;transient: 暂时性变量,用于对象存档,用于对象的串行化
- volatile: 贡献变量,用于并发线程的共享
方法的实现也包括两部分内容:方法声明和方法体。
方法声明
方法声明中的限定词的含义:
- static: 类方法,可通过类名直接调用
- abstract: 抽象方法,没有方法体
- final: 方法不能被重写
- native: 集成其它语言的代码
- synchronized: 控制多个并发线程的访问
方法声明包括方法名、返回类型和外部参数。其中参数的类型可以是简单数据类型,也可以是复合数据类型(又称引用数据类型)。 对于简单数据类型来说,java实现的是值传递,方法接收参数的值,但不能改变这些参数的值。如果要改变参数的值,则用引用数据类型,因为引用数据类型传递给方法的是数据在内存中的地址,方法中对数据的操作可以改变数据的值。
方法体
方法体是对方法的实现,它包括局部变量的声明以及所有合法的Java指令。方法体中声明的局部变量的作用域在该方法内部。若局部变量与类的成员变量同名,则类的成员变量被隐藏。 为了区别参数和类的成员变量,我们必须使用this。this用在一个方法中引用当前对象,它的值是调用该方法的对象。返回值须与返回类型一致,或者完全相同,或是其子类。当返回类型是接口时,返回值必须实现该接口。
构造方法
- 构造方法是一个特殊的方法。Java 中的每个类都有构造方法,用来初始化该类的一个对象。
- 构造方法具有和类名相同的名称,而且不返回任何数据类型。
- 重载经常用于构造方法。
- 构造方法只能由new运算符调用
面向对象的基本特性
封装
封装性就是尽可能的隐藏对象内部细节,对外形成一道边界,只保留有限的接口和方法与外界进行交互。封装的原则是使对象以外的部分不能随意的访问和操作对象的内部属性,从而避免了外界对对象内部属性的破坏。
可以通过对类的成员设置一定的访问权限,实现类中成员的信息隐藏。
- private:类中限定为private的成员,只能被这个类本身访问。如果一个类的构造方法声明为private,则其它类不能生成该类的一个实例。
- default:类中不加任何访问权限限定的成员属于缺省的(default)访问状态,可以被这个类本身和同一个包中的类所访问。
- protected:类中限定为protected的成员,可以被这个类本身、它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他的类访问。
- public:类中限定为public的成员,可以被所有的类访问。
继承
子类的对象拥有父类的全部属性与方法,称作子类对父类的继承。
- Java中父类可以拥有多个子类,但是子类只能继承一个父类,称为单继承。
- 继承实现了代码的复用。
- Java中所有的类都是通过直接或间接地继承java.lang.Object类得到的。
- 子类不能继承父类中访问权限为private的成员变量和方法。
- 子类可以重写父类的方法,即命名与父类同名的成员变量。
Java中通过super来实现对父类成员的访问,super用来引用当前对象的父类。super 的使用有三种情况:
- 访问父类被隐藏的成员变量,如:super.variable;
- 调用父类中被重写的方法,如:super.Method([paramlist]),super()调用父类构造方法;
- 调用父类的构造函数,如:super([paramlist]);
多态
对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或方法在父类及其各个子类中具有不同的语义。例如:"几何图形"的"绘图"方法,"椭圆"和"多边形"都是"几何图"的子类,其"绘图"方法功能不同。
Java的多态性体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态)。
- 编译时多态:在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。
- 运行时多态:由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。
重载(Overloading)
- 方法重载是让类以统一的方式处理不同数据类型的手段。
- 一个类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法。
- 返回值类型可以相同也可以不相同,无法以返回型别作为重载函数的区分标准。
重写(Overriding)
- 子类对父类的方法进行重新编写。如果在子类中的方法与其父类有相同的的方法名、返回类型和参数表,我们说该方法被重写 (Overriding)。
- 如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
- 子类函数的访问修饰权限不能低于父类的。
三、Java集合
集合的由来
通常,我们的程序需要根据程序运行时才知道创建多少个对象。但若非程序运行,程序开发阶段,我们根本不知道到底需要多少个数量的对象,甚至不知道它的准确类型。为了满足这些常规的编程需要,我们要求能在任何时候,任何地点创建任意数量的对象,而这些对象用什么来容纳呢?我们首先想到了数组,但是数组只能放统一类型的数据,而且其长度是固定的,那怎么办呢?集合便应运而生了!
集合是什么?
Java集合类存放于 java.util 包中,是一个用来存放对象的容器。
注意: ①、集合只能存放对象。比如你存一个 int 型数据 1放入集合中,其实它是自动转换成 Integer 类后存入的,Java中每一种基本类型都有对应的引用类型。
②、集合存放的是多个对象的引用,对象本身还是放在堆内存中。
③、集合可以存放不同类型,不限数量的数据类型。
Java 集合框架图
此图来源于:blog.csdn.net/u010887744/…
发现一个特点,上述所有的集合类,除了 map 系列的集合,即左边集合都实现了 Iterator 接口,这是一个用于遍历集合中元素的接口,主要hashNext(),next(),remove()三种方法。它的一个子接口 ListIterator 在它的基础上又添加了三种方法,分别是 add(),previous(),hasPrevious()。也就是说如果实现 Iterator 接口,那么在遍历集合中元素的时候,只能往后遍历,被遍历后的元素不会再被遍历到,通常无序集合实现的都是这个接口,比如HashSet;而那些元素有序的集合,实现的一般都是 LinkedIterator接口,实现这个接口的集合可以双向遍历,既可以通过next()访问下一个元素,又可以通过previous()访问前一个 元素,比如ArrayList。
还有一个特点就是抽象类的使用。如果要自己实现一个集合类,去实现那些抽象的接口会非常麻烦,工作量很大。这个时候就可以使用抽象类,这些抽象类中给我们提供了许多
现成的实现,我们只需要根据自己的需求重写一些方法或者添加一些方法就可以实现自己需要的集合类,工作量大大降低。
集合详解
①、Iterator:迭代器,它是Java集合的顶层接口(不包括 map 系列的集合,Map接口 是 map 系列集合的顶层接口)
Object next():返回迭代器刚越过的元素的引用,返回值是 Object,需要强制转换成自己需要的类型
boolean hasNext():判断容器内是否还有可供访问的元素
void remove():删除迭代器刚越过的元素
所以除了 map 系列的集合,我们都能通过迭代器来对集合中的元素进行遍历。
注意:我们可以在源码中追溯到集合的顶层接口,比如 Collection 接口,可以看到它继承的是类 Iterable
那这就得说明一下 Iterator 和 Iterable 的区别:
Iterable :存在于 java.lang 包中。
我们可以看到,里面封装了 Iterator 接口。所以只要实现了只要实现了Iterable接口的类,就可以使用Iterator迭代器了。
Iterator :存在于 java.util 包中。核心的方法next(),hasnext(),remove()。
这里我们引用一个Iterator 的实现类 ArrayList 来看一下迭代器的使用:暂时先不管 List 集合是什么,只需要看看迭代器的用法就行了
//产生一个 List 集合,典型实现为 ArrayList。
List list = new ArrayList();
//添加三个元素
list.add("Tom");
list.add("Bob");
list.add("Marry");
//构造 List 的迭代器
Iterator it = list.iterator();
//通过迭代器遍历元素
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
②、Collection:List 接口和 Set 接口的父接口
看一下 Collection 集合的使用例子:
//我们这里将 ArrayList集合作为 Collection 的实现类
Collection collection = new ArrayList();
//添加元素
collection.add("Tom");
collection.add("Bob");
//删除指定元素
collection.remove("Tom");
//删除所有元素
Collection c = new ArrayList();
c.add("Bob");
collection.removeAll(c);
//检测是否存在某个元素
collection.contains("Tom");
//判断是否为空
collection.isEmpty();
//利用增强for循环遍历集合
for(Object obj : collection){
System.out.println(obj);
}
//利用迭代器 Iterator
Iterator iterator = collection.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
由于 List 接口是继承于 Collection 接口,所以基本的方法如上所示。
1、List 接口的三个典型实现:
①、List list1 = new ArrayList();
底层数据结构是数组,查询快,增删慢;线程不安全,效率高
②、List list2 = new Vector();
底层数据结构是数组,查询快,增删慢;线程安全,效率低,几乎已经淘汰了这个集合
③、List list3 = new LinkedList();
底层数据结构是链表,查询慢,增删快;线程不安全,效率高
怎么记呢?我们可以想象:
数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要望某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。
链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
2、除此之外,List 接口遍历还可以使用普通 for 循环进行遍历,指定位置添加元素,替换元素等等。
//产生一个 List 集合,典型实现为 ArrayList
List list = new ArrayList();
//添加三个元素
list.add("Tom");
list.add("Bob");
list.add("Marry");
//构造 List 的迭代器
Iterator it = list.iterator();
//通过迭代器遍历元素
while(it.hasNext()){
Object obj = it.next();
//System.out.println(obj);
}
//在指定地方添加元素
list.add(2, 0);
//在指定地方替换元素
list.set(2, 1);
//获得指定对象的索引
int i=list.indexOf(1);
System.out.println("索引为:"+i);
//遍历:普通for循环
for(int j=0;j<list.size();j++){
System.out.println(list.get(j));
}
④、Set:典型实现 HashSet()是一个无序,不可重复的集合
1、Set hashSet = new HashSet();
①、HashSet:不能保证元素的顺序;不可重复;不是线程安全的;集合元素可以为 NULL;
②、其底层其实是一个数组,存在的意义是加快查询速度。我们知道在一般的数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较,此时的查询效率依赖于查找过程中比较的次数。而 HashSet 集合底层数组的索引和值有一个确定的关系:index=hash(value),那么只需要调用这个公式,就能快速的找到元素或者索引。
③、对于 HashSet: 如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
1、当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
1.1、如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置
1.2、如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较
1.2.1、hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
1.2.2、hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该约束这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
注意:每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象
对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同。
常见的 hashCode()算法:
2、Set linkedHashSet = new LinkedHashSet();
①、不可以重复,有序
因为底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
3、Set treeSet = new TreeSet();
TreeSet:有序;不可重复,底层使用 红黑树算法,擅长于范围查询。
如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口所以, 在其中不能放入 null 元素
必须放入同样类的对象.(默认会进行排序) 否则可能会发生类型转换异常.我们可以使用泛型来进行限制
Set treeSet = new TreeSet();
treeSet.add(1); //添加一个 Integer 类型的数据
treeSet.add("a"); //添加一个 String 类型的数据
System.out.println(treeSet); //会报类型转换异常的错误
自动排序:添加自定义对象的时候,必须要实现 Comparable 接口,并要覆盖 compareTo(Object obj) 方法来自定义比较规则
如果 this > obj,返回正数 1
如果 this < obj,返回负数 -1
如果 this = obj,返回 0 ,则认为这两个对象相等
* 两个对象通过 Comparable 接口 compareTo(Object obj) 方法的返回值来比较大小, 并进行升序排列
定制排序: 创建 TreeSet 对象时, 传入 Comparator 接口的实现类. 要求: Comparator 接口的 compare 方法的返回值和 两个元素的 equals() 方法具有一致的返回值
public class TreeSetTest {
public static void main(String[] args) {
Person p1 = new Person(1);
Person p2 = new Person(2);
Person p3 = new Person(3);
Set<Person> set = new TreeSet<>(new Person());
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set); //结果为[1, 2, 3]
}
}
class Person implements Comparator<Person>{
public int age;
public Person(){}
public Person(int age){
this.age = age;
}
@Override
/***
* 根据年龄大小进行排序
*/
public int compare(Person o1, Person o2) {
// TODO Auto-generated method stub
if(o1.age > o2.age){
return 1;
}else if(o1.age < o2.age){
return -1;
}else{
return 0;
}
}
@Override
public String toString() {
// TODO Auto-generated method stub
return ""+this.age;
}
}
当需要把一个对象放入 TreeSet 中,重写该对象对应的 equals() 方法时,应保证该方法与 compareTo(Object obj) 方法有一致的结果
以上三个 Set 接口的实现类比较:
共同点:1、都不允许元素重复
2、都不是线程安全的类,解决办法:Set set = Collections.synchronizedSet(set 对象)
不同点:
HashSet:不保证元素的添加顺序,底层采用 哈希表算法,查询效率高。判断两个元素是否相等,equals() 方法返回 true,hashCode() 值相等。即要求存入 HashSet 中的元素要覆盖 equals() 方法和 hashCode()方法
LinkedHashSet:HashSet 的子类,底层采用了 哈希表算法以及 链表算法,既保证了元素的添加顺序,也保证了查询效率。但是整体性能要低于 HashSet
TreeSet:不保证元素的添加顺序,但是会对集合中的元素进行排序。底层采用 红-黑 树算法(树结构比较适合范围查询)
⑤、Map:key-value 的键值对,key 不允许重复,value 可以
1、严格来说 Map 并不是一个集合,而是两个集合之间 的映射关系。
2、这两个集合没每一条数据通过映射关系,我们可以看成是一条数据。即 Entry(key,value)。Map 可以看成是由多个 Entry 组成。
3、因为 Map 集合即没有实现于 Collection 接口,也没有实现 Iterable 接口,所以不能对 Map 集合进行 for-each 遍历。
Map<String,Object> hashMap = new HashMap<>();
//添加元素到 Map 中
hashMap.put("key1", "value1");
hashMap.put("key2", "value2");
hashMap.put("key3", "value3");
hashMap.put("key4", "value4");
hashMap.put("key5", "value5");
//删除 Map 中的元素,通过 key 的值
hashMap.remove("key1");
//通过 get(key) 得到 Map 中的value
Object str1 = hashMap.get("key1");
//可以通过 添加 方法来修改 Map 中的元素
hashMap.put("key2", "修改 key2 的 Value");
//通过 map.values() 方法得到 Map 中的 value 集合
Collection<Object> value = hashMap.values();
for(Object obj : value){
//System.out.println(obj);
}
//通过 map.keySet() 得到 Map 的key 的集合,然后 通过 get(key) 得到 Value
Set<String> set = hashMap.keySet();
for(String str : set){
Object obj = hashMap.get(str);
//System.out.println(str+"="+obj);
}
//通过 Map.entrySet() 得到 Map 的 Entry集合,然后遍历
Set<Map.Entry<String, Object>> entrys = hashMap.entrySet();
for(Map.Entry<String, Object> entry: entrys){
String key = entry.getKey();
Object value2 = entry.getValue();
System.out.println(key+"="+value2);
}
System.out.println(hashMap);
Map 的常用实现类:
⑥、Map 和 Set 集合的关系
1、都有几个类型的集合。HashMap 和 HashSet ,都采 哈希表算法;TreeMap 和 TreeSet 都采用 红-黑树算法;LinkedHashMap 和 LinkedHashSet 都采用 哈希表算法和红-黑树算法。
2、分析 Set 的底层源码,我们可以看到,Set 集合 就是 由 Map 集合的 Key 组成。
四、Java IO流
流的概念和作用
- 流:代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象
- 流的本质:数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。
- 作用:为数据源和目的地建立一个输送通道
JavaIO所采用的模型
- Java的IO模型设计非常优秀,它使用Decorator(装饰者)模式,按功能划分Stream,您可以动态装配这些Stream,以便获得您需要的功能。
- 例如,您需要一个具有缓冲的文件输入流,则应当组合使用FileInputStream和BufferedInputStream。
IO流的分类
-
按数据流的方向分为 输入流、输出流
-
此输入、输出是相对于我们写的代码程序而言,
-
输入流:从别的地方(本地文件,网络上的资源等)获取资源 输入到 我们的程序中
-
输出流:从我们的程序中 输出到 别的地方(本地文件), 将一个字符串保存到本地文件中,就需要使用输出流。
-
按处理数据单位不同分为 字节流、字符流
* 1字符 = 2字节 、 1字节(byte) = 8位(bit) 、 一个汉字占两个字节长度
* 字节流:每次读取(写出)一个字节,当传输的资源文件有中文时,就会出现乱码
* 字符流:每次读取(写出)两个字节,有中文时,使用该流就可以正确传输显示中文
-
按功能不同分为 节点流、处理流
-
节点流:以从或向一个特定的地方(节点)读写数据。如FileInputStream
-
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装
-
4个基本的抽象流类型,所有的流都继承这四个
-
输入流 输出流 字节流 InputStream outputStream 字符流 Reader Writer
* inputStream:字节输入流
* 
* outputStream:字节输出流
* 
* Reader:字符输入流
* 
* Writer:字符输出流
-
-
总结流的分类
- 首先自己要知道是选择输入流还是输出流,这就要根据自己的情况而定,如果你想从程序写东西到别的地方,那么就选择输出流,反之用输入流
- 然后考虑你传输数据时,是选择使用字节流传输还是字符流,也就是每次传1个字节还是2个字节,有中文肯定就选择字符流了
- 前面两步就可以选出一个合适的节点流了,比如字节输入流inputStream,如果要在此基础上增强功能,那么就在处理流中选择一个合适的即可
-
字符流的由来: Java中字符是采用Unicode标准,一个字符是16位,即一个字符使用两个字节来表示。为此,JAVA中引入了处理字符的流。因为数据编码的不同,而有了对字符进行高效操作的流对象。本质其实就是基于字节流读取时,去查了指定的码表
IO流的特性
- 先进先出,最先写入输出流的数据最先被输入流读取到
- 顺序存取,可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(RandomAccessFile可以从文件的任意位置进行存取(输入输出)操作)
- 只读或只写,每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流
IO流常用到的五类一接口
- 在整个Java.io包中最重要的就是5个类和一个接口。5个类指的是File、OutputStream、InputStream、Writer、Reader;一个接口指的是Serializable.掌握了这些IO的核心操作那么对于Java中的IO体系也就有了一个初步的认识了。
* 主要的类如下:
1. File(文件特征与管理):File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法
2. InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征
3. OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征
4. Reader(文件格式操作):抽象类,基于字符的输入操作
5. Writer(文件格式操作):抽象类,基于字符的输出操作
6. RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作
7. 
Java IO流对象
1. 输入字节流InputStream
-
-
认识每个类的功能即作用
-
ByteArrayInputStream:字节数组输入流,该类的功能就是从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去,我们拿也是从这个字节数组中拿
-
PipedInputStream:管道字节输入流,它和PipedOutputStream一起使用,能实现多线程间的管道通信
-
FilterInputStream :装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类
-
BufferedInputStream:缓冲流,对处理流进行装饰,增强,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送。效率更高
-
DataInputStream:数据输入流,它是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型
-
FileInputSream:文件输入流。它通常用于对文件进行读取操作
-
File:对指定目录的文件进行操作,具体可以查看讲解File的博文。注意,该类虽然是在IO包下,但是并不继承自四大基础类
-
ObjectInputStream:对象输入流,用来提供对“基本数据或对象”的持久存储。通俗点讲,也就是能直接传输对象(反序列化中使用)
2. 输出字节流OutputStream
-
-
OutputStream 是所有的输出字节流的父类,它是一个抽象类
-
**ByteArrayOutputStream、FileOutputStream 是两种基本的介质流,它们分别向Byte 数组、和本地文件中写入数据。**PipedOutputStream 是向与其它线程共用的管道中写入数据
-
ObjectOutputStream 和所有FilterOutputStream 的子类都是装饰流(序列化中使用)
3. 字符输入流Reader
-
-
Reader 是所有的输入字符流的父类,它是一个抽象类
-
CharReader、StringReader 是两种基本的介质流,它们分别将Char 数组、String中读取数据。PipedReader 是从与其它线程共用的管道中读取数据
-
BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象
-
FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号
-
InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream 转变为Reader 的方法。我们可以从这个类中得到一定的技巧。Reader 中各个类的用途和使用方法基本和InputStream 中的类使用一致。后面会有Reader 与InputStream 的对应关系
4. 字符输出流Writer
-
-
Writer 是所有的输出字符流的父类,它是一个抽象类
-
CharArrayWriter、StringWriter 是两种基本的介质流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据
-
BufferedWriter 是一个装饰器为Writer 提供缓冲功能
-
PrintWriter 和PrintStream 极其类似,功能和使用也非常相似
-
OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似
5. 字节流和字符流使用情况:(重要)
- 字符流和字节流的使用范围:字节流一般用来处理图像,视频,以及PPT,Word类型的文件。字符流一般用于处理纯文本类型的文件,如TXT文件等,字节流可以用来处理纯文本文件,但是字符流不能用于处理图像视频等非文本类型的文件
字符流与字节流转换
-
转换流的作用,文本文件在硬盘中以字节流的形式存储时,通过InputStreamReader读取后转化为字符流给程序处理,程序处理的字符流通过OutputStreamWriter转换为字节流保存
-
转换流的特点:
- 其是字符流和字节流之间的桥梁
- 可对读取到的字节数据经过指定编码转换成字符
- 可对读取到的字符数据经过指定编码转换成字节
-
何时使用转换流?
- 当字节和字符之间有转换动作时
- 流操作的数据需要编码或解码时
-
具体的对象体现
- InputStreamReader:字节到字符的桥梁
- OutputStreamWriter:字符到字节的桥梁
- 这两个流对象是字符体系中的成员,它们有转换作用,本身又是字符流,所以在构造的时候需要传入字节流对象进来
- OutputStreamWriter(OutStreamout):将字节流以字符流输出
- InputStreamReader(InputStream in):将字节流以字符流输入
字节流和字符流的区别(重点)
-
字节流和字符流的区别:
- 字节流没有缓冲区,是直接输出的,而字符流是输出到缓冲区的。因此在输出时,字节流不调用colse()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据
- 结论:只要是处理纯文本数据,就优先考虑使用字符流。除此之外都使用字节流
System类对IO的支持
-
-
针对一些频繁的设备交互,Java语言系统预定了3个可以直接使用的流对象,分别是:
- System.in(标准输入),通常代表键盘输入
- System.out(标准输出):通常写往显示器
- System.err(标准错误输出):通常写往显示器
-
标准I/O
- Java程序可通过命令行参数与外界进行简短的信息交换,同时,也规定了与标准输入、输出设备,如键盘、显示器进行信息交换的方式。而通过文件可以与外界进行任意数据形式的信息交换
处理流BufferedReader,BufferedWriter,BufferedInputStream
- BufferedOutputsStream,都要包上一层节点流。也就是说处理流是在节点流的基础之上进行的,带有Buffered的流又称为缓冲流,缓冲流处理文件的输入输出的速度是最快的。所以一般缓冲流的使用比较多
什么是装饰者模式
-
用我自己的话来说,就是往一个添加更多的功能,而我们首先想到的是继承,继承就很好的符合了我们的要求,不管你想加多少层的功能,都可以使用继承一层层的实现,但是这带来了一个问题,一旦我需要改变我的需求,那么我就需要往源码中改东西,再就是在这个继承链中某个类做一些修改,这不符合我们的设计模式思想,所以就有了装饰者模式,装饰者中拥有被装饰者的实例,然后有什么具体的装饰我们都另写一个类来继承该装饰者,当我们需要该装饰时,就new出该类来,然后将其被装饰者当作参数传递进去
-
现在来看看一个具体的实例。比如,我们需要制作一份鸡腿堡,流程是怎样的呢
- 先有基本原料,也就是两块面包,这是不管做什么汉堡都需要的
- 做什么汉堡,取决于加什么材料,比如生菜,鸡肉等,所以根据材料来做汉堡,想做什么汉堡就加什么材料
- 所有材料加完之后,直接计算价格即可这样使用装饰者模式,是不是比一直使用继承方便的多的多呢?换一种汉堡,也不需要改源码,什么也不需要,希望你能够理解清楚其中的思想
-
其实处理流就是一个具体的装饰者,而节点流就是被装饰者
Scanner类
-
Java 5添加了java.util.Scanner类,这是一个用于扫描输入文本的新的实用程序。它是以前的StringTokenizer和Matcher类之间的某种结合。由于任何数据都必须通过同一模式的捕获组检索或通过使用一个索引来检索文本的各个部分。于是可以结合使用正则表达式和从输入流中检索特定类型数据项的方法。这样,除了能使用正则表达式之外,Scanner类还可以任意地对字符串和基本类型(如int和double)的数据进行分析。借助于Scanner,可以针对任何要处理的文本内容编写自定义的语法分析器
-
Scanner套接字节流或字符流:
- 字节流的套接:在Scanner的构造方法中Scanner(InputStream,InputStream只要经过适当的套接,总能获得你想要的流接口
- 字符流的套接:Scanner(Readable,你需要使用Java SE5中新加入的一个接口Readable,该接口表示“具有read()方法的某种东西”,查看Readable接口的API你可以发现你想要的带有Reader的类基本都在其中
序列化
-
将保存在内存中的对象数据转化为二进制数据流进行传输,任何对象都可以序列化
-
实现方法:实现java.io.Serializable接口
-
作用:把一个Java对象写入到硬盘或者传输到网路上面的其它计算机,这时我们就需要自己去通过java把相应的对象写成转换成字节流。对于这种通用的操作,我们为什么不使用统一的格式呢?没错,这里就出现了java的序列化的概念。在Java的OutputStream类下面的子类ObjectOutput-Stream类就有对应的WriteObject(Object object) 其中要求对应的object实现了java的序列化的接口
-
在使用tomcat开发JavaEE相关项目的时候,我们关闭tomcat后,相应的session中的对象就存储在了硬盘上,如果我们想要在tomcat重启的时候能够从tomcat上面读取对应session中的内容,那么保存在session中的内容就必须实现相关的序列化操作,还有jdbc加载驱动用的就是反序列化,将字符串变为对象
反序列化
-
将二进制数据换回原对象
-
构造:
-
ObjectInputStream(InputStream in)
-
方法:
-
Object readObject() 从 ObjectInputStream 读取对象
-
transient关键字(一个类某些属性不需要序列化)
-
以上序列化和反序列化实现了的对象序列化,但是可以发现,操作时是将整个对象的所有属性序列化,那么transient关键字可以将某些内容不需要保存,就可以通过transient关键字来定义
private transient string title;
此时title属性无法被序列化
小结
- inputStream类的功能不足被Scanner解决了
- OutputStream类的功能不足被PrintStream解决了
- Reader类功能不足被BufferReader解决了
- Writer类的功能不足被PrintWriter解决了
- 输出数据用printStream,printwriter读取数据用Scanner其次是bufferReader
五 Java线程、异常及网络编程
这里就不写了,线程跟网络编程的话一两句话写不完,篇幅所限,有时间的话我再单独写一篇关于线程和网络编程的。
六、Java架构师学习核心书单
书名 | 一句话评价 |
---|---|
《Java核心技术》 | 不用多说,Java领域最有影响力和价值的著作之一 |
《Java编程思想》 | 这个也不用多介绍了吧,永恒的经典 |
《Java并发编程的艺术》 | 国内作者写的Java并发书籍,比较简单易懂,广为流传的Java线程状态变化图就出自本书 |
《深入理解Java虚拟机》 | JVM,这一本就够了 |
《Effective Java》 | 和《Java编程思想》一样被称为神书,介绍了如何写健壮,高效的代码,当然阅读需要有一定的开发经验。 |
《Spring实战》 | Spring入门经典书籍 |
《Spring揭秘》 | 书比较老,但是概念和原理很清晰,看完之后,再看Spring5的源码也很不错 |
《深入浅出Spring Boot 2.x》 | 看过不少SpringBoot的书籍,个人感觉这本还不错,比较详细、全面 |
《MyBatis技术内幕》 | MyBatis源码解析的书不多,推荐这本,结合源码阅读更佳 |
《数据结构与算法分析》 | 国外数据结构与算法分析方面的经典教材,内容全面、缜密严格 |
《计算机网络》 | 还是忍不住推荐一本教材,比较全面、系统,但对非科班选手可能不友好 |
《图解HTTP》 | 这本不用多介绍了吧,很生动的讲解HTTP协议的书籍,阅读起来比较友好 |
《深入理解计算机系统》 | 评价非常不错的计算机操作系统书籍,但是这种黑皮书可能啃起来有些困难 |
《SQL必知必会》 | 非常受欢迎的MySQL入门书籍,也可以当作工具书来用 |
《高性能MySQL》 | MySQL领域的经典著作,进阶必看 |
《Redis 开发与运维》 | 从开发、运维两个角度总结了 Redis 实战经验,深入浅出地剖析底层实现,包含大规模集群开发与运维的实际案例 |
《Redis设计与实现》 | Redis进阶经典书籍 |
《SpringCloud微服务实战》 | 还不错的SpringCloud书籍 |
《Spring Cloud Alibaba 微服务原理与实战》 | Netflex的一些组件进入了维护的状态,Spring Cloud Alibaba在最近流行了起来(技术的变迁真的太快),这方面的书不多,这本是相对而言推荐的多一些的 |
《RabbitMQ实战指南》 | 简单清晰的RabbitMQ技术书籍 |
《Kafka权威指南》 | Kafka的好书还是不少的,这里推荐评分比较高的Kafka权威指南 |
《RocketMQ实战与原理解析》 | RocketMQ的书籍不多,这本书入门尚可,原理解析有些单薄了 |
除了以上列出的主要一些主要分类,这里再补充一些。
-
容器推荐《Docker从入门到事件》《Kubernetes 权威指南》;
-
Maven推荐《Maven实战》;
-
Git推荐《ProGit》;
-
Linux自然是《鸟哥的Linux私房菜》;
-
Elasticsearch推荐《Elasticsearch实战》;
-
设计模式推荐《设计模式之禅》。 以上推荐的书籍我都收集了电子档,学生党或者手头紧的朋友,可以点击领取
就写到这吧,头发掉的差不多了,如果能点个赞加个关注那真是再好不过的事了,下篇文章见
end