举个例子:
《大话设计模式》中大鸟给小菜讲的故事非常经典:
“三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军船连成一片,眼看就要灭掉东吴,统一天下,曹操大悦,于是大宴众文武,在酒席间,曹操诗性大发,不觉吟道:‘喝酒唱歌,人生真爽……’于是一臣子速命印刷工匠刻版印刷,以便流传天下。”
“样版出来给曹操一看,曹操感觉不妥,说道:‘喝酒唱歌,此话过俗,应改为‘对酒当歌’较好!’于是此臣就命工匠重新来过。工匠眼看连夜刻版之工,彻底白费,心中叫苦不迭。只得照办。”
“样版再次出来请曹操过目,曹操细细一品,觉得还是不好,说:‘人生真爽‘太过直接,应改问语才够意境,因此应改为‘对酒当歌,人生几何……’当臣子转告工匠之时,工匠晕倒……”
“这里面问题出在哪里?”
“就是因为三国时期活字印刷还未发明,所以要改字的时候,就必须要整个刻板全部重新刻。”
“说得好!如果是有了活字印刷,则只需更改四个字就可,其余工作都未白做。岂不妙哉。
一、只需要更改要改的字,此为可维护;
二、这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
三、此诗若要加字,只需另刻字加入即可,这是可扩展;
四、字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,这就是灵活性好。”
“而在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。”
- 封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。- 继承
提高代码复用性;继承是多态的前提。- 多态
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。
- 单一职责原则:一个类只负责一个功能
- 开闭原则:对象**(**类,模块,函数等等)对于扩展开放,对于修改关闭
- 里氏代换原则:能够使用到父类的地方都能够使用子类对象,就是子类可以替换父类出现在父类能够出现的任何地方
- 依赖倒置原则:高层模块不应该依赖低层模块,它们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
- 接口隔离原则:客户端不应该依赖它不需要的接口(功能),类与类之间的依赖应该建立在最小的接口上
- 迪米特法则: 一个对象应当对其它对象有尽可能少的了解,只和朋友通信,不和陌生人说话,所以迪米特法则又叫做最少知识原则
高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一职责原则。
模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。
对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。
举个最简单点的例子来区分 面向过程和面向对象
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。他的缺点在于不易维护,不易复用,不易拓展
有一天你想吃鱼香肉丝了,怎么办呢?你有两个选择
- 自己买材料,肉,鱼香肉丝调料,蒜苔,胡萝卜等等然后切菜切肉,开炒,盛到盘子里。这就是面向过程
- 去饭店,张开嘴:老板!来一份鱼香肉丝!这是面向对象。
首先你不需要知道鱼香肉丝是怎么做的,降低了耦合性。如果你突然不想吃鱼香肉丝了,想吃羊肉泡馍,对于1你可能不太容易了,还需要重新买菜,买调料什么的。对于2,太容易了,大喊:老板!那个鱼香肉丝换成羊肉泡馍吧,提高了可维护性。总的来说就是降低耦合,提高维护性!
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。
面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,不管我们的事,我们会用就可以了。
面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素
- 缺点:不易维护、不易复用、不易扩展
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
面向对象的编程方式使得每一个类都只做一件事。面向过程会让一个类越来越全能,就像一个管家一样做了所有的事。而面向对象像是雇佣了一群人,每个人只一件事
- new —> 调用了构造函数
- 反射,调用Class类的new Instance方法(创建实例) —> 调用了构造函数
- 反序列化 —> 没有调用构造函数
- 调用clone方法(克隆)—> 没有调用构造函数
整数类型
长整型 long —> Long
短整型 short —> Short
整型 int —> Integer
字节型 byte —> Byte
浮点类型
单精度 float —> Float
双精度 double —> Double
布尔型 boolean —> Boolean
字符型 char —> Character
访问等级比较
public > protected > default > private
继承的时候子类不能比父类的访问权限小
(简单的理解,你可以在代码定义一个父类变量,传一个子类进去。那么运行时,所有父类的方法必须可以被正确运行。所以子类如果把权限降低了,肯定无法访问了。)
- public(公共): 被public修饰的成员可在任意类中使用。可以理解为public基本不存在访问权限。
- protected(受保护): 被protected修饰的成员可在本类所在包中或者本类的子类中使用。protected不能修饰类(外部类)。接口及接口的成员变量和成员方法不能声明为 protected。
- default(默认 一般省略不写): 被default修饰的成员可在本类所在包中使用。可以用来修饰变量、方法、类、接口。
- private(私有): 被private修饰的成员只能在本类中使用,其他类不能调用。private不能修饰类(外部类)
- public
(1) public是公共的,被public修饰的成员可以被所有类访问到。
(2) public修饰的成分
public修饰类,在一个java文件中只能有一个类被声明为public,而且一旦有一个类为public,这个java文件的文件名就必须要和这个被public所修饰的类的类名相同,否则不能通过编译。一个类作为外部类只能被public或者默认访问修饰符修饰,如果作为内部类则可以被四种访问修饰符修饰。
public修饰变量和方法,被public所修饰的成员可以在任何类中都能被访问到,通过操作该类的对象能随意访问public成员。
public在类的继承中,被public所修饰的不同名成员可以被所有的子类继承,同名成员会被覆写。- protected
(1) protected是受保护的,受到该类所在的包所保护。
(2) 被protected所修饰的成员会被位于同一package中所有类访问到。被protected所修饰的成员会被该类的所有子类继承下来。- default
被default修饰的成员可在本类所在包中使用。可以用来修饰变量、方法、类、接口。
(1) 默认的(友好的),不写任何的访问修饰符。
(2) 会被位于同一package中所有类访问到。被friendly修饰的成员只能被给类所在同一package中的子类所继承下来,也就是同一package中的子类才能访问到父类中friendly修饰的成员。- private
private是私有的,被private修饰的成员只能在本类中使用,其他类不能调用。private不能修饰类(外部类)
- 一处编译,多处运行(Java 虚拟机实现平台⽆关性)
- 健全的安全体系
- 兼容不同平台
- 自带内存管理机制
- 支持多线程(C++ 语⾔没有内置的多线程机制,因此必须调⽤操作系统的多线程功能来进⾏多线程程序设计)
- 属于解释型语言(C语言是编译型语言,C中有指针的概念,java取消了指针的概念)
- 都是面向对象的语言,都支持封装、继承和多态
- Java不提供指针来直接访问内存,程序内存更加安全
- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
- Java有自动内存管理机制,不需要程序员手动释放无用内存
- JDK:java 开发工具。是功能⻬全的 Java SDK。它拥有 JRE 所拥有的⼀切,还有编译器(javac)和⼯具(如 javadoc 和 jdb)。它能够创建和编译程序
- JRE: java运行时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的⼀些基础构件
- JVM:Java 虚拟机,是运⾏ Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果
简单理解:JDK=JRE(包含JVM+核心类库(JavaAPI))+开发工具
- Lambda表达式
简化了匿名委托的使用,让你让代码更加简洁,优雅
语法:() -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符- 新时间日期Api
LocalDate
LocalTime
LocalDateTime
旧的时间日期类缺点
新的在java.time包下
- 对于日期的计算困难
- 线程安全问题
- 接口中的默认方法和静态方法
- 函数式接口
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持- 方法引用和构造器调用
- Stream 流
Stream 就好像一个高级的迭代器,但只能遍历一次,在流的过程中,对流中的元素执行一些操作,比如 过滤掉长度大于 10 的字符串、获取每个字符串的首字母 等
流的操作可以分为两种类型:
- 中间操作,可以有多个,每次返回一个新的流,可进行链式操作。
- 终端操作,只能有一个,每次执行完,这个流也就用光光了,无法执行下一个操作,因此只能放在最后
有且仅有一个抽象方法的接口,就是函数式接口,并且还提供了注解:@FunctionalInterface
提供类型:Supplier<T>接口
特点:只出不进,作为方法/构造参数、方法返回值
消费类型:Consumer<T>接口
特点:只进不出,作为方法/构造参数
断定类型:Predicate<T>接口
特点:boolean类型判断,作为方法/构造参数
转换类型:Function<T>接口
特点:有输入,有输出
接口指系统对外提供的所有服务(Interface定义的实实在在的接口,即接口类型)【只能有抽象方法】 的入口,是一个抽象类型。一个类通过实现接口的方式,从而继承接口的抽象方法;一个类只能继承一个类,但可以继承多个接口;一个接口不能实现另一个接口,但可以继承多个其他接口
JDK1.8之前:
- 接口中的变量都是静态变量(public static final),必须显式初始化
- 接口中所有方法默认都是public abstract
- 接口没有构造方法,不可以被实例化,但可以被实现(常作为类型使用,也就是父类引用指向子类对象)
- 实现类必须实现接口的所有的方法
- 实现类可以实现多个接口(java中的多继承)
JDK1.8之后:
- 接口里可以有(default关键字修饰的)默认方法(方法体)了
默认方法可以被继承,通过实例调用,可以有多个。如果一个类实现了多个接口,多个接口都定义了多个同样的默认方法时就需要实现类覆盖重写接口中的默认方法,不然会报错,可以使用super来调用指定接口的默认方法。- 接口里可以声明(并且可以提供实现)静态方法
接口中的静态方法必须是public的,public修饰符可以省略,static修饰符不能省略,静态方法不能被继承即覆盖,所以只被具体所在的接口调用,接口中静态方法可以有多个。
简单来说:JDK1.8之前接口只能有抽象⽅法,JDK1.8之后接口中可以有不同⽅法,但是⽅法必须⽤static或者default来修饰
相同点:
- 都代表系统的抽象层;
- 都不能被实例化;
- 都能包含抽象方法(用于描述系统提供的服务,不必提供具体实现)
不同点:
- 一个类只能继承一个直接父类,但可以实现多个接口;
- 抽象类有构造器,接口没有构造器;
- 抽象类中可以有普通⽅法,接口中有没有要区分JDK版本;
- 抽象类中成员变量⾃⼰定义权限修饰,接口成员变量默认为public static final;
- 从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
接口的多种不同的实现方式即为多态。
多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术。
我们在程序中定义的引用变量所指向的具体类型和通过该引用变量的方法调用在编程的时候并不确定,当处于运行期间才确定。就是这个引用变量究竟指向哪一个实例对象,在编译期间是不确定的,只有运行期才能确定,这样不用修改源码就可以把变量绑定到不同的类实例上,让程序拥有了多个运行状态,这就是多态。
replace() // 字符串的替换
String str1 = "hello word";
String str2 = str1.replace("hello", "hi"); //输出 hi word
replaceall() // 字符串全部替换
String str1 = "hello word";
String str2 = str1.replaceAll("o", "1"); //把字符串中的o全部替换为1 输出 hell1 w1rd
replaceFirst() // 把字符串中第一个字符替换为X
split(String regex) // 字符串拆分
String str1 = "你见过洛杉矶1凌晨四点的1样子吗?";
String[] strs = str1.split("1");
// 通过1进行拆分
for(String s : strs){
System.out.println("s------"+s);
}
substring(int beginIndex) // 字符串截取
String str1 = "abcdef";
//截取下标为2(含2)
String str2 = str1.substring(2); // cdef
substring(int beginIndex, int endIndex) // 字符串截取,从下标beginIndex开始截取到endIndex
String str1 = "abcdefghi";
// 从下标为2的字符开始截取,截取到下标为4的字符(含2不含4)
String str2 = str1.substring(2,4); // cd
contains() // 字符串查找
String str = "Hello Word";
boolean result1 = str.contains("l");// 输出 true
boolean result2 = str.contains("s");// 输出 false
endsWith() // 是否以指定后缀结束
String str = "Hello Word";
boolean result1 = str.endsWith("d");// 输出 true
boolean result2 = str.endsWith("s");// 输出 false
String.format("%.2f", 0.1356);// 四舍五入(保留两位小数)
startsWith(String prefix) // 是否以指定的字符开始
startsWith(String prefix, int offIndex) // 判断指定字符是否在offIndex索引位置上
indexOf(String str) // 返回指定字符在字符串中第一次出现处的索引位置,如果此字符串中没有这个字符,则返回-1
indexOf(String str, int fromIndex) // 返回从 fromIndex 位置开始查找指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1
lastindexOf(String str) // 返回指定字符在此字符串中最后一次出现的索引位置,如果此字符串中没有这个字符,则返回 -1
lastindexOf(String str, int fromIndex) // 从fromIndex个字符中找,返回指定字符在此字符串中最后一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1
charAt(int index) // 返回指定索引处的字符。索引范围为从 0 到 length() - 1
int hashCode() // 返回此字符串的哈希码
equals() // 字符串比较
equalsIgnoreCase() // 字符串比较,不区分大小写
getBytes() // 使用平台的默认字符集将字符串编码为 byte 序列,并将结果存储到一个新的 byte 数组中
toCharArray() // 将字符串转换为字符数组
length() // 字符串长度
toLowerCase() // 大写转小写
toUpperCase() // 小写转大写
trim() // 去掉前后空格(中间保留)
concat() // 字符串连接(一般用+)
intern() // 将内容保存到对象池中
isEmpty() // 判断是否是空字符串
- String是不可变字符序列,因为String类是被final修饰的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量的内存空间
- String Buffer是可变类,线程安全,多线程操作字符串,效率较低
- String Builder也是可变类,非线程安全,单线程操作字符串,效率较快
运行速度:
StringBuilder > StringBuffer > String
适用场景:
- String:适用于少量的字符串操作的情况
- StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
- StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
- throws:
跟在方法声明后面,后面跟的是异常类名,可以跟多个异常类名,用逗号隔开,表示抛出异常,由该方法的调用者来处理,throws表示有出现异常的可能性,并不一定出现这些异常- throw:
用在方法体内,后面跟的是异常类对象名,只能抛出一个异常对象名,表示抛出异常,由该方法体内的语句来处理,throw则是抛出了异常,执行throw一定出现了某种异常
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法写在 finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,该方法一般由垃圾回收器来调用,当我们调用System.gc() 方法的时候,由垃圾回收器调用finalize(),回收垃圾,对一个对象是否可回收做的最后判断
- 变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域
- 成员变量:方法外部,类内部定义的变量
- 局部变量:类的方法中的变量
- 成员变量:针对整个类有效
- 局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
- 成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中
- 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放
- 成员变量:随着对象的创建而存在,随着对象的消失而消失
- 局部变量:当方法调用完,或者语句结束后,就自动释放
- 成员变量:有默认初始值
- 局部变量:没有默认初始值,使用前必须赋值
在使用变量时需要遵循的原则为:就近原则,首先在局部范围找,有就使用;接着在成员位置找
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用表的类是否已被加载,解析和初始化过。如果没有,那必须先执行相应的类加载过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
- == 是比较对象的地址值,即判断是否是同一个对象(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址))
- equals重写,这里比较的是内容是否相同
- equals在Object中,比较的是是否为同一对象,等价于通过“==”比较这两个对象
- ==指引用是否相同
- equals指值是否相同
String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。
当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。
hashCode() 的作用是获取哈希码,也称为散列码;
它实际上是返回一个int整数
这个哈希码的作用是确定该对象在哈希表中的索引位置
hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数
equals它的作用也是判断两个对象是否相等如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,等同“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
- 若重写了equals(Object obj)方法,则有必要重写hashCode()方法
- 若两个对象equals(Object obj)返回true,则hashCode()也返回相同的哈希码值
- 若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的哈希码值
- 若两个对象hashCode()返回相同哈希码值,则equals(Object obj)不一定返回true
- 若两个对象hashCode()返回不同哈希码值,则equals(Object obj)一定返回false
- 同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题
- break:跳出总上一层循环,不再执行循环(结束当前的循环体)
- continue:跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
- return:程序返回,不再执行下面的代码(结束当前的方法 直接返回)
- 方法的重载(Overload):一个类中有多个同名方法,但是参数个数或参数类型不同
- 方法的重写(Override):在继承关系中,子类对从父类继承过来的方法进行改变,变成自己的方法
构造器不能被继承,因此不能被重写,但可以被重载
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
两者区别:并发是交替执行,并行是同时执行
- 并发:多个任务在同一个CPU核上,按细分的时间片轮流执行,从逻辑上看任务是同时执行的;
- 并行:多个处理器或多核处理器同时处理多个任务。
- 进程:进程是运行在操作系统上的一个应用程序
- 线程:线程就是进程中的一个任务
例如:
- 打开微信聊天工具就是开启了一个进程
- 在微信中和其中的一个人聊天就会开启一个线程
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止
关系:线程是进程的基本执行单元,一个进程的所有任务都在线程中执行;进程要想执行任务,必须得有线程。
- 同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间;
- 同一进程内的线程共享本进程的资源,而进程间的资源是独立的。
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源.
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
线程执行开销小,但不利于资源的管理和保护;
而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移
- 包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址;基本类型不是
- 包装类型是引用的传递;基本类型是值的传递
- 声明方式不同:
基本数据类型不需要new关键字;
包装类型需要new在堆内存中进行new来分配内存空间- 存储位置不同:
基本数据类型直接将值保存在值栈中;
包装类型是把对象放在堆中,然后通过对象的引用来调用他们- 初始值不同:
int的初始值为0 、 boolean的初始值为false
包装类型的初始值为null- 使用方式不同:
基本数据类型直接赋值使用就好;
包装类型是在集合中使用,比如collection和Map
不能。
因为是用final修饰的,Integer 继承 Number 抽象类,实现了 Comparable 接口。
Number 类是常用数字类型类的公共父类,它规定了其子类(通常就是数字类)必须提供将其值转换成 int、long、float、double、byte、short 类型数据的能力。实现 Comparable 接口自然是为了比较大小。
另外,Integer 类型也是最终类,不可被继承(事实上,常用数据类型的封装类都是 final 类)。
详解
- 开放地址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
- 再哈希法
- 拉链法(Java hashmap就是这么做的)
- 建立一个公共溢出区
- 静态变量前要加static关键字,而实例变量前则不加。
- 实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。
- 静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。
- 实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
关于static和非static变量的区别
- static 修饰的变量称为类变量或全局变量或成员变量,在类被加载的时候成员变量即被初始化,与类关联,只要类存在,static变量就存在。非static修饰的成员变量是在对象new出来的时候划分存储空间,是与具体的对象绑定的,该成员变量仅为当前对象所拥有的。
- static修饰的变量在加载的时候先于main方法加载在内存中的数据共享区-------方法区,而非static的变量在加载的时候,是要创建变量才加载在堆内存中的。
- 一个static变量单独划分一块存储空间,不与具体的对象绑定在一起,该存储空间被类的各个对象所共享。static变量值在方法区加载一次,而非static在创建对象时会加载很多次。每次创建都会拷贝一份。
- 对象在引用成员变量是直接通过类名.变量名调用,对象在引用实例变量时只能通过对象名.变量名调用。
- 在类中调用成员变量时直接调用或者以类名.变量名方式调用,实例变量则用this或者直接调用。
关于static方法和非static方法的区别
- static修饰的方法也和static一样。先于main方法被加载到方法区,以便共享使用。
- 静态的static方法中不能使用this或者super关键字,因为static方法是先于对象创建之前就已经加载的方法,是属于类的方法,而this和super指向的是本类的对象或者父类的对象,非静态的方法是属于对象的,方法里可以用this和super。
- static方法可以用对象.方法名来调用,也可以用类名.方法名来调用。而非静态的方法只能创建对象后时调用。
- static方法是加载一次,被所有的对象所共享。而非静态方法是有多少个对象就拷贝多少次,每个对象只能调用自己的拷贝的方法。
- 对象调用非静态的方法时,不考虑线程安全性的问题,而调用静态方法时,要考虑安全性的问题。因为静态方法只有一份。而对象的方法是自己有自己的。
- 同一个类中,静态方法中只能访问类中的静态成员。而非静态方法可以访问非静态的方法(使用类名调用,或者创创建本类的对象调用)
int num = 10;
String str = “hello”;
对于基本类型的数据变量,他的值是直接存储在变量中
str是一个引用类型的变量,变量中保存的是我们实际的对象在堆内存中的地址,而我们真实的对象,其实是存储在堆空间中的
num=20
str=“java”
- 对于基本类型 num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
- 对于引用类型str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)
如上图所示,“hello” 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)
- 值传递(pass by value)指的是在把实参传递给形参的时候,将实际的参数复制一份传递给函数的形参,也就是说,在函数内部对形参的修改,不会改变原来实参的值。
- 引用传递(pass by reference)指的是在实际的函数调用时,将实际参数的地址传递给函数的形式参数,也就是说其实形参和实参保存的是堆中同一个对象的地址。如果在函数中对堆中的对象属性进行修改,实参对应的此对象的属性也会改变。
- 基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
- 对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
字符流和字节流
字节流继承inputStream和OutputStream
字符流继承自InputSteamReader和OutputStreamWriter
数据source:就是需要读取,可以使用两个体系:InputStream、Reader;
数据destination:就是需要写入,可以使用两个体系:OutputStream、Writer;
多线程知识点总结
- 继承Thread类
- 实现Rannable接口
- 实现Callable接口
- 线程池中获取
- 悲观锁:每次操作都加锁,其他线程阻塞。synchronized、ReentrantLock
- 乐观锁:操作数据时不会上锁,在更新时会判断此期间有没有线程去更新这个数据。版本号机制和CAS算法实现。
- 互斥锁:某一资源同时只允许一个访问者进行访问,具有唯一性和排它性。
- 读写锁:读写锁管理一组锁,一个是只读锁,一个是写锁。每次只有一个写线程,但是同时有多个线程并发读。
- 公平锁:先到先得,lock new ReentrantLock(true)
- 非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待;性能比公平锁高。synchronized,lock new ReentrantLock(false);
- 自旋锁:线程没有获取到锁时不被直接挂起,而是执行一个盲循环,这个忙循环就是所谓的自旋锁。为了减少线程被挂起的几率,因为线程的挂起和唤醒也是消耗资源的操作。
- 分段锁:是一种锁的设计,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
JDK1.6,引入4种锁的状态:无锁,偏向锁,轻量级锁,重量级锁
- 无锁就是乐观锁。
- 偏向锁:偏向于第一个访问锁的线程。不存在多线程操作,不需要重复获取锁。
- 轻量级锁:线程竞争激烈,偏向锁就会升级为轻量级锁,通过自旋方式等待上一个线程释放锁。CAS
- 重量级锁:线程并发进一步加剧,线程的自旋超过一定次数,或者一个线程持有锁,一个线程在自旋,又来了第三个线程访问时,轻会变重,除了此时拥有锁的线程以外,其他线程都阻塞。
Java集合详解(List、Map、Set)
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
- 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
加锁
- New
尚未启动的线程的线程状态- Runnable
可运行线程的线程状态,等待CPU调度- Blocked
线程阻塞等待监视器锁定的线程状态(处于synchronized同步代码块或方法中被阻塞)- Waiting
等待线程的线程状态
(不带超时的方式:Object.wait、Thread.join、LockSupport.park)- Timed Waiting
具有指定等待时间的等待线程的线程状态
(带超时的方式:Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUnitl)- Terminated
终止线程的线程状态。(线程正常完成执行或出现异常)
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
- 单例设计模式:如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案 —枚举
- 工厂设计模式:工厂模式主要是为创建对象提供了接口。—beanfactory
- 策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换。此模式让算法的变化独立于使用算法的客户。— 一件事情,有很多方案可以实现。
- 观察者模式:观察者模式又被称作发布/订阅模式,定义了对象间一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
- 迭代器模式:迭代器模式提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
- 模版方法模式:模板方法模式定义一个操作中的算法的骨架,将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。
举个例子:普通B/S模式(同步)AJAX技术(异步)
- 同步:提交请求 -> 等待服务器处理 -> 处理完毕返回 这个期间客户端浏览器不能干任何事
- 异步:请求通过事件触发 -> 服务器处理(这是浏览器仍然可以作其他事情) -> 处理完毕
- 同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
- 异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。
同步
- 优点:同步是按照顺序一个一个来,不会乱掉,更不会出现上面代码没有执行完就执行下面的代码
- 缺点:是解析的速度没有异步的快;
异步
- 优点:异步是接取一个任务,直接给后台,在接下一个任务,一直一直这样,谁的先读取完先执行谁的
- 缺点:没有顺序 ,谁先读取完先执行谁的 ,会出现上面的代码还没出来下面的就已经出来了,会报错;
Java排序算法
char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,那么,这个char型变量中就不能存储这个特殊汉字。
补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节。
- 引用计数器法:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
- 可达性分析算法:从 GC Roots作为起点,引用链作为路径。当一个对象到 GC Roots没有任何引用链相连时,则证明此对象是可以被回收的。
标记清除
首先标记出所有需要回收的对象,在标记完成后统一回收掉被标记的对象 (老年代)
- 优点:实现简单,不需要对象进行移动
- 缺点:第一个是执行效率不稳定,第二个是内存空间的碎片化问题
标记复制
将可用内存按容量划分为大小相等的两块,每次使用其中的一块,这一块用完了就将还存活者的对象复制到另一块上面 (新生代)
- 优点:按顺序分配内存即可,实现简单、运行高效,不用考虑内存碎片
- 缺点:内存缩小到原来的一半.
(eden区和幸存区大小比例是8:1)
标记整理
让所有存活的对象都移向内存空间的一端,然后直接清理掉边界以外的内存 (老年代)
- 优点:解决了标记-清理算法存在的内存碎片问题
- 缺点:仍需要进行局部对象移动,一定程度上降低了效率
释放和重用资源是垃圾回收算法的具体实现
- Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
- ParNew收集器 (复制算法):新生代收并行集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
- Parallel Scavenge收集器 (复制算法):新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
- Serial Old收集器 (标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本;
- Parallel Old收集器 (标记-整理算法):老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
- CMS(Concurrent Mark Sweep)收集器(标记-清除算法):老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间
- G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片
- 页面提交请求到servlet
- 容器创建请求和响应对象
- 容器根据URL查找具体的servlet
- 容器加载并实例化servlet
- 执行servlet的初始化方法(init())
- 容器创建一个新的线程处理该请求
- 容器调用servlet的service(服务方法)方法(同时将请求和响应对象作为参数)
- servlet调用模型的业务逻辑进行处理
- servlet将处理结果保存到指定对象中(request、session、application)
- servlet将请求转发给相应的jsp(处理完成后,会调用destroy()方法(销毁方法))
- jsp动态生成响应(使用响应对象向流写入html或其他内容)
- 容器将响应(通过web服务器)返回给客户端
- 浏览器解析并渲染返回的html文件