当前位置:网站首页 > Java基础 > 正文

java基础重点总结



方法重载特性
方法重载,所指的是将多个功能相似但是参数不同的方法。使用同一个方法名称,形成一系列方法,这种写法我们称之为方法重载。
形成方法重载需要满足以下的几个必要条件:
1. 必须在同一个作域
2. 方法名称相同
3. 返回值类型可以不同
4. 访问修饰符可以不同
5. main方法也可以重载
6. 参数列表需要不同,体现在数据类型、个数、顺序等三个方面。
- 基本数据类型
  - 数值类型
    - 整数类型:byte short int long
      - 默认情况下,Java语言将一个整数值,默认以int作为默认的数据类型。
      - 对于long类型值,建议习惯性的后面加一个大写或者小写的L。
    - 小数类型 float double
      - 默认情况下,Java语言将一个小数值,默认以double作为默认的数据类型。
    - 字符类型 char
    - 布尔类型 boolean
      - true
      - false
- 引用数据类型
- 区分:只要是属于8个基本数据类型的那么就是基本数据类型,否则一定就是引用数据类型。
- 原理不同:
  - 基本数据类型:值直接存储在栈空间中。
  - 引用数据类型:值存储在堆空间中,但是堆空间的地址存储在栈空间中。
方法重写
方法复写也被称之为“方法重写”,在子类中可以将父类的方法重新进行定义。以实现在子类中方法实现新的功能需要注意如下的几件事情:
1.重写方法的定义名称以及形参列表必须和父类被重写方法的定义完全一致
2.重写方法的访问修饰符必须要大于等于父类被重写方法的访问修饰符。
3.重写方法的返回值类型必须小于等于父类的返回值类型。
4.重写方法所抛出的异常类型要小于等于父类抛出的异常类型。
字符串的不可变特性
1. 因java.lang.String类中用于存储字符的容器使用final修饰,因此字符串的值一旦确定则无法改变。
2. 因字符串本身存储值无法改变,所以一旦进行改变的时候实际上是新创建了一个字符串。
# 乱码的发生成因
1. 编码字符集和解码字符集不对等。
2. 组成一个字符所需要的字节缺失。
3. 数字和英文字母永远不会乱码,因为英文字母和数字在任何的字符集中,编码结果都是一样的。
# 转换流

> InputStreamReader
>
> OutputStreamWriter

请注意:转换流的作用只有一个:将字节流转换为字符流。
1. 什么是线程安全?
   - 线程安全是指当多个线程访问同一个数据资源的时候,所发生的数据访问错误的情况。
2. 怎么实现线程安全
   1. 同步代码块
   2. 同步方法
java.util.ArrayList
•作为List接口的主要实现类,一般情况下我们如果希望将一些内容存储下来会优先考虑采用ArrayList。
•使用数组作为数据存储的载体,其原理也是数组。
•针对于元素的获取/遍历效率高。
•针对于元素的频繁增删改效率低。
java.util.LinkedList 
•作为List接口的次要实现类(次要并不代表不重要,只是不优先考虑使用)。
•使用双向链表作为数据存储的载体,其原理是双向链表。
•针对于元素的获取/遍历效率低。
•针对于元素的频繁增删改效率高。
如果我们需要存储一些数据,并且可能进行高频率的插入、移除、修改这一类操作那么会优先考虑使用
LinkedList。
ArrayList是非线程安全的。
ArrayList的扩容原理?
1、自动扩容的新容量的大小是原容量的1.5倍
2、一般来讲扩容后的最大容量为最大数组长度,即最大整数-8
3、若ArrayList中元素的数量大于最大数组长度,则扩容后的容量为最大整数
4、扩容将会产生一个新的长度更大的数组
5、我们可以通过直接调用ensureCapacity方法,手动对ArrayList进行扩容。且扩容后的容量至少为10。
ensureCapacityInternal(int minCapacity)
传入int类型的所需容量
|--判断底层elementData是否是空数组
|--是:扩容为默认的10个空间
|--非:使用所需容量
|--调用ensureExplicitCapacity(int minCapacity)
ensureExplicitCapacity(int minCa |--判断底层elementData是否已经满足所需容量
ensureExplicitCapacity(int minCapacity)
|--判断底层elementData是否已经满足所需容量
|--是:调用grow(int minCapacity)执行扩容
|--非:不进行扩容
grow(int minCapacity) |--记录底层elementData原有长度
|--在原有长度基础上计算出扩容后的新长度(原有容量的1.5倍)
|--判断此新长度是否不能满足所需容量
|--是:新长度直接采取所需容量值
|--判断新容量是否达到建议数组最大长度
|--是
|--判断所需容量是否超出建议数组最大长度
|--是:新容量采取Integer最大值
|--否:新容量采取建议数组最大长度
|--按照最终确定的新容量进行数组拷贝
Vector 和 ArrayList都是使用数组去进行数据存储的。
ArrayList不是线程安全的,Vector是线程安全的。
ArrayList扩容1.5倍,Vector是直接扩容2倍。

LinkedHashMap相比于HashMap在元素与元素之间又存在双向链表去维护着向后顺序,所以使用LinkedHashMap可以保证元素插入的顺序和迭代输出的顺序一致。
但是:LinkedHashMap元素存储依然是通过计算得来的没有按照特定顺序进行存储,所以LinkedHashMap本身也是无序的。
 java.util.HashMap 集合具有无序性和不可重复性,请表达在原理上无序性和不可重复性是怎么实现的?
元素添加原理
首先调用要添加元素key所在类的hashCode()获取到哈希值a
此哈希值经过某种算法计算之后得到在数组中的存放位置b
判断table中该位置是否已经有数据
 -->没有数据:此时Entry添加成功[情况1]
 -->已有数据(此位置上存在一个或者多个数据(以链表形式存在)):
-->比较当前key和已有数据的key的哈希值判断哈希值是否相等
 -->没有找到哈希值相同的:此时Entry添加成功[情况2]
 -->找到有相同哈希值的元素,调用key所在类的equals方法进行判断
 -->equals结果为false:此时Entry添加成功[情况3]
 -->equals结果为true:使用value去替换掉相同key的原有value值。
属性配置文件:Xxxx.properties
Collections类:
addAll:添加多个元素
fill:填充元素
copy:拷贝覆盖元素
max:获取最大元素
min:获取最小元素
replaceAll:替换指定元素到其他值
reverse:颠倒值
sort:排序
synchronizedXxxxx:返回线程安全的引用。
一、equals 和hashCode方法的区别与联系
1、equals方法用于比较对象的内容是否相等(覆盖以后)
2、hashcode方法只有在集合中用到
3、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。
4、将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
二、StringBuilder 和StringBuffer
在java中如果有大量拼接字符串的需求的话,应该使用StringBuilder和StringBuffer类,它们可以避免不必要的String对象产生(String被final修饰不可变),以提高程序性能,他们两者作用类似StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)
三、final的用法
(1)用来修饰数据,包括成员变量和局部变量,该变量只能被赋值一次且它的值无法被改变。对于成员变量来讲,我们必须在声明时或者构造方法中对它赋值;
(2)用来修饰方法参数,表示在变量的生存期中它的值不能被改变;
(3)修饰方法,表示该方法无法被重写;
(4)修饰类,表示该类无法被继承。

四、java中面向对象思想及特征
面向对象这种程序设计模式它将现实世界中的一切事物都看作是对象,它强调从对象出发,以对象为中心用人类的思维方式来认识和思考问题。每个对象都具有各自的状态特征(也可以称为属性)及行为特征(方法),java就是通过对象之间行为的交互来解决问题的。
封装:(面向对象编程核心思想):封装就是将对象的属性和行为特征包装到一个程序单元(即类)中,把实现细节隐藏起来,通过公用的方法来展现类对外提供的功能,提高了类的内聚性,降低了对象之间的耦合性。
继承:对原有类的拓展,指从已有的类中派生新的类,新的类能够吸收已有类的数据属性和行为,并能够扩展新的功能。
多态:是在继承的基础上。是指允许不同类的对象对同一消息做出响应,根据对象创建实例的不同调用不同的方法,本质是编译时和运行时才决定对象的行为。例如,子类类型的对象可以赋值给父类类型的引用变量,但运行时仍表现子类的行为特征(在内存中运行的,new的实例)。也就是说,同一种类型的对象执行同一个方法时可以表现出不同的行为特征。
方法重载:是指同一个类中的多个方法具有相同的名字,但这些方法具有不同的参数列表,即参数的数量或参数类型不能完全相同
方法重写:是存在子父类之间的,子类定义的方法与父类中的方法具有相同的方法名字,相同的参数表和相同的返回类型 ,子类函数的访问修饰权限不能少于父类的。
五、反射和注解
Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为Java 的反射机制。
1.反射的用处
通过反射运行配置文件内容
通过反射越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
通过反射获取并修改数组信息
2.注解
从 JDK5.0 开始,Java 增加了对元数据(MetaData)的支持,也就是Annotation(注释)
•  Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载, 运行时被读取,并执行相应的处理.通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息.
•  Annotation 可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的 “name=value”对中.
•  Annotation能被用来为程序元素(类,方法,成员变量等)设置元数据
@Documented –注解是否将包含在JavaDoc中 @Retention –什么时候使用该注解 @Target  –注解用于什么地方 @Inherited  –是否允许子类继承该注解
自定义 Annotation 定义新的 Annotation类型使用@interface关键字

六、集合框架
List:有序存放、允许重复、可以存放不同类型的对象
Set:无序存放、不允许重复、可以存放不同类型的对象
Map:映射,俗称键值对
TreeMap、hashMap、hashTable区别
TreeMap是有序的,HashMap和HashTable是无序的。
Hashtable的方法是同步的,HashMap的方法不是同步的。这是两者最主要的区别。
Hashtable是线程安全的,HashMap不是线程安全的。HashMap效率较高,Hashtable效率较低。
Hashtable不允许null值,HashMap允许null值(key和value都允许)
父类不同:Hashtable的父类是Dictionary,HashMap的父类是AbstractMap
Hashtable中hash数组默认大小是11,增加的方式是 old*2+1。
HashMap中hash数组的默认大小是16,而且一定是2的指数。

(二)LinkedList实现原理
LinkedList是List接口的双向链表非同步实现,并允许包括null在内的所有元素。
底层的数据结构是基于双向链表的,该数据结构我们称为节点
双向链表节点对应的类Entry的实例,Entry中包含成员变量:previous,next,element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
LinkedList是基于双向循环链表实现的,除了可以当做链表来操作外,它还可以当做栈、队列和双端队列来使用。
LinkedList同样是非线程安全的,只在单线程下适合使用。
LinkedList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了Cloneable接口,能被克隆。
LinkedList是基于链表实现的,因此插入删除效率高,查找效率低(虽然有一个加速动作)
(三)HashMap实现原理
1.HashMap的底层数据结构是什么?
    jdk1.2-1.7 数组+链表 jdk1.8 数组+链表+红黑树
2.HashMap中增删改查操作的底部实现原理是什么?
put数据
(1)第一步:调用put方法传入键值对
(2)第二步:使用hash算法计算hash值
(3)第三步:根据hash值确定存放的位置,判断是否和其他键值对位置发生了冲突
(4)第四步:若没有发生冲突,直接存放在数组中即可
(5)第五步:若发生了冲突,还要判断此时的数据结构是什么?
(6)第六步:若此时的数据结构是红黑树,那就直接插入红黑树中
(7)第七步:若此时的数据结构是链表,判断插入之后是否大于等于8
(8)第八步:插入之后大于8了,就要先调整为红黑树,在插入
(9)第九步:插入之后不大于8,那么就直接插入到链表尾部即可。
get数据
(1)先计算出key哈希值。
(2)通过哈希值定位到Key存在哪个数组下标。
(3)找到后看数组下标里面有没有节点。
(4)有节点的话区分节点数据是红黑树还是链表,然后分别使用对应数据结构的查找方法。
(5)根据查找的key和节点里面存的Key 值判断两个key是不是equals() ,equals则返回对应的节点,否则继续匹配下一个节点,直到匹配成功返回节点,或者没有节点配后返回null;
3.HashMap是如何实现扩容的?
先计算 新的hash表容量和新的容量阀值,然后初始化一个新的hash表,将旧的键值对重新映射在新的hash表里。如果在旧的hash表里涉及到红黑树,那么在映射到新的hash表中还涉及到红黑树的拆分负载因子=0.75越大则散列表的装填程度越高,也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低。反之,负载因子越小则链表中的数据量就越稀疏,此时会对空间造成烂费,但是此时索引效率高。
在外层遍历node数组,对于每一个table[j],判断该node扩容之后,是属于低位部分(原数组),还是高位部分(扩容部分数组)。判断的方式就是位与旧数组的长度,如果为0则代表的是低位数组,因为index的值小于旧数组长度,位与的结果就是0;相反,如果不为零,则为高位部分数组。低位数组,添加到以loHead为头的链表中,高位数组添加到以hiHead为头的数组中。链表遍历结束,分别设置新哈希表的index位置和(index+旧表长度)位置的值。非常的巧妙。
4.HashMap是如何解决hash冲突的?
拉链法,根据哈希值确定存储的位置,如果没有值直接存入,有值判断值是否相等,相等新值替换旧值,不相等存入链表尾部
5.HashMap为什么是非线程安的?
源码里面方法全部都是非线程安全的,你根本找不到synchronized这样的关键字。保证不了线程安全。于是出现了ConcurrentHashMap。
(四)HashTable实现原理
Hashtable是基于哈希表的Map接口的同步实现,不允许使用null值和null键
底层使用数组实现,数组中每一项是个单链表,即数组和链表的结合体
Hashtable在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
synchronized是针对整张Hash表的,即每次锁住整张表让线程独占
(五)ConcurrentHashMap实现原理
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。
它使用了多个锁来控制对hash表的不同段进行的修改,每个段其实就是一个小的hashtable,它们有自己的锁。只要多个并发发生在不同的段上,它们就可以并发进行。
ConcurrentHashMap在底层将key-value当成一个整体进行处理,这个整体就是一个Entry对象。Hashtable底层采用一个Entry[]数组来保存所有的key-value对,当需要存储一个Entry对象时,会根据key的hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据key的hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)
ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。
CAS算法:
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B
从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
ConcurrentHashMap是一个线程安全的Map集合,可以应对高并发的场景,保证线程安全。相比较HashTable,它的锁粒度更加的细化,因为HashTable的方法都是用Synchronized修饰的,效率灰常的底下。
1.8之前ConcurrentHashMap使用锁分段技术,将数据分成一段段的存储,每一个数据段配置一把锁,相互之间不影响,而1.8之后摒弃了Segment(锁段)的概念,启用了全新的实现,也就是利用CAS+Synchronized来保证并发更新的安全,底层采用的依然是数组+链表+红黑树。
(六)HashSet实现原理
HashSet由哈希表(实际上是一个HashMap实例)支持,不保证set的迭代顺序,并允许使用null元素。
基于HashMap实现,API也是对HashMap的行为进行了封装
HashSet是Set接口的典型实现,HashSet按照Hash算法来存储集合中的元素。存在以下特点:
不能保证元素的顺序,元素是无序的
HashSet不是同步的,需要外部保持线程之间的同步问题
集合元素值允许为null
(七)LinkedHashMap实现原理
LinkedHashMap继承于HashMap,底层使用哈希表和双向链表来保存所有元素,并且它是非同步,允许使用null值和null键。
基本操作与父类HashMap相似,通过重写HashMap相关方法,重新定义了数组中保存的元素Entry,来实现自己的链接列表特性。该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而构成了双向链接列表。
(八)LinkedHashSet
LinkedHashSet是具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序。
注意,此实现不是同步的。如果多个线程同时访问链接的哈希Set,而其中至少一个线程修改了该Set,则它必须保持外部同步。
对于LinkedHashSet而言,它继承与HashSet、又基于LinkedHashMap来实现的。
LinkedHashSet底层使用LinkedHashMap来保存所有元素,它继承与HashSet,其所有的方法操作上又与HashSet相同,因此LinkedHashSet 的实现上非常简单,只提供了四个构造方法,并通过传递一个标识参数,调用父类的构造器,底层构造一个LinkedHashMap来实现,在相关操作上与父类HashSet的操作相同,直接调用父类HashSet的方法即可。
(九)TreeMap实现原理
TreeMap实现了SotredMap接口,它是有序的集合。而且是一个红黑树结构,每个key-value都作为一个红黑树的节点。如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。这点会在接下来的代码中做说明,如果指定了比较器则按照比较器来进行排序。
(十)数组和链表
所谓数组,就是相同数据类型的元素按一定顺序排列的集合
所谓链表,链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
区别一:物理地址存储的连续性
数组的元素在内存中是连续存放的。
链表的元素在内存中不一定是连续存放的,通常是不连续的。
区别二:访问速度
数组的访问速度很快,因为数组可以根据数组可以根据下标进行快速定位。
链表的访问速度较慢,因为链表访问元素需要移动指针。
区别三:添加、删减元素速度 java基础重点总结
数组的元素增删速度较慢,因为需要移动大量的元素。
链表的元素增删速度较快,因为只需要修改指针即可。

七、快速失败和安全失败
1、快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
2、安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容**问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

八、抽象类和接口
抽象类:包含抽象方法的类或者被abstract关键字修饰的类
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:
  1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
  2)抽象类不能用来创建对象;
  3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
接口:英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数
抽象类和接口的区别
1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
接口中可以含有 变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。(jdk1.8之后default修饰的方法可以有具体的实现)
九、BIO、NIO、AIO
IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
BIO与NIO不同
1.BIO与NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者只使用少量的多线程,每个连接共用一个线程。
2.IO是面向流的,NIO是面向缓冲区的。
3.IO的各种流是阻塞的,NIO是非阻塞模式。

NIO的最重要的地方是当一个连接创建后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程就可以搞定,当这个线程中的多路复用器进行轮询的时候,发现连接上有请求的话,才开启一个线程进行处理,也就是一个请求一个线程模式。
AIO与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。  即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。  在JDK1.7中,这部分内容被称作NIO.2
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
十、Java中有几种类型的流?
答:字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不同于C#的是它只有一个维度一个方向。
 编程实现文件拷贝。(这个题目在笔试的时候经常出现,下面的代码给出了两种实现方案)
public static void fileCopy(String source, String target) throws IOException {
        try (InputStream in = new FileInputStream(source)) {
            try (OutputStream out = new FileOutputStream(target)) {
                byte[] buffer = new byte[4096];
                int bytesToRead;
                while((bytesToRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesToRead);
                }
            }
        }
    }
    public static void fileCopyNIO(String source, String target) throws IOException {
        try (FileInputStream in = new FileInputStream(source)) {
            try (FileOutputStream out = new FileOutputStream(target)) {
                FileChannel inChannel = in.getChannel();
                FileChannel outChannel = out.getChannel();
                ByteBuffer buffer = ByteBuffer.allocate(4096);
                while(inChannel.read(buffer) != -1) {
                    buffer.flip();
                    outChannel.write(buffer);
                    buffer.clear();
                }
            }
        }
}

十一、异常和错误
Error(错误)表示系统级的错误和程序不必处理的异常,一般是指与虚拟机相关的问题,是java运行环境中的内部错误或者硬件问题。比如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由Java虚拟机抛出的。
Exception(违例)表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。
Exception又分为运行时异常,受检查异常。
运行时异常,表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止程序,因此,编译器不检查这些异常。
受检查异常,是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理), 所以称之为受检查异常。

十二、内存溢出和内存泄漏
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存溢出的原因以及解决方法
引起内存溢出的原因有很多种,小编列举一下常见的有以下几种:
1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3.代码中存在死循环或循环产生过多重复的对象实体;
4.使用的第三方软件中的BUG;
5.启动参数内存值设定的过小
内存溢出的解决方案:
第一步,修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

内存泄露:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。memory leak会最终会导致out of memory!

1、静态集合类,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。简单而言,长生命周期的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
2、各种连接,如数据库连接、网络连接和IO连接等。在对数据库进行操作的过程中,首先需要建立与数据库的连接,当不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后,垃圾回收器才会回收对应的对象。否则,如果在访问数据库的过程中,对Connection、Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
3、变量不合理的作用域。一般而言,一个变量的定义的作用范围大于其使用范围,很有可能会造成内存泄漏。另一方面,如果没有及时地把对象设置为null,很有可能导致内存泄漏的发生。
public class UsingRandom {
    private String msg;
    public void receiveMsg(){
        readFromNet();// 从网络中接受数据保存到msg中
        saveDB();// 把msg保存到数据库中
    }
}
如上面这个伪代码,通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。
实际上这个msg变量可以放在receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间。
4、内部类持有外部类,如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
5、改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露

版权声明


相关文章:

  • java基础语法教学2025-04-01 15:02:06
  • 40个java基础教程2025-04-01 15:02:06
  • java基础按钮2025-04-01 15:02:06
  • java jvm基础2025-04-01 15:02:06
  • java基础书写2025-04-01 15:02:06
  • 成都基础java培训2025-04-01 15:02:06
  • java顺序结构基础2025-04-01 15:02:06
  • java基础游戏设计2025-04-01 15:02:06
  • java基础电路运算2025-04-01 15:02:06
  • 基础架构java2025-04-01 15:02:06