知识点整理
一、常用方法
str.substring():字符串的截取,str.substring(a,b):获取字符串str中的(a,b)位置;不包括b处;
append(): StringBuilder中的append()方法,相当于“+”,将指定的字符串追加到此字符序列;
str.charAt(i):将字符串i位置的字符返回出来;
str.toCharArray():将字符串对象的字符转化为字符串数组;
stack.addLast():将给定元素插入到末尾;
public class Test {
public static void main(String[] args) {
LinkedList<Integer> stack = new LinkedList<Integer>();
stack.push(1);
stack.push(3);
stack.push(5);
stack.push(7);
stack.push(8);
stack.push(9);
stack.addLast(4);
System.out.println(stack);
stack.removeLast();
System.out.println(stack);
stack.addLast(12);
System.out.println(stack);
}
}

Arrays.sort()::从小到大对数组进行排序;
char[] numbers = String.valueOf(number).toCharArray();将一个字符串用数组接收
indexOf('a'):返回字符串中第一个字符a的下标;
String s = "01234560123456";
int a = s.indexOf('1'); // 返回第一个字符1的下标
int b = s.indexOf("23"); // 返回第一个字符串“23”的下标
int c = s.indexOf('1',5); // 以下标5开始,返回第一个字符1的下标
int d = s.indexOf("23",5); // 以下标5开始,返回第一个字符串“23”的下标
System.out.println(a + " " + b + " " + c + " "+ d);
//1 2 8 9
lastIndexOf('n'):返回最后一个字符n的下标
String s = "01234560123456";
int a = s.lastIndexOf('1'); // 返回最后一个字符1的下标
int b = s.lastIndexOf("23"); // 返回最后一个字符串“23”的下标
int c = s.lastIndexOf('1',5); // 以下标5为终点,返回最后一个字符1的下标
int d = s.lastIndexOf("23",5); // 以下标5为终点,返回最后一个字符串“23”的下标
System.out.println(a + " " + b + " " + c + " "+ d);
//8 9 1 2
str.split():对一个字符串进行拆分为多个字符串;
//第一种方法 str.split(String regex)
//分隔符不唯一,可以自定义;
//分隔符可以定义多个,使用符号"|",如",|+"就是使用","和"+"作为分隔符。这里还要注意,因为+、*、|、\等符号在正则表达示中有相应的不同意义。用的时候加上“\ ”或者“/”或者“[]”转义一下就可以了。
//返回字符串数组类型的数据
//举例
public class Asplit{
public static void main(String args[]){
//自定义一个字符串
String str = new String("13,235sdf+fnk,055s,fkd, ");
//注意这里字符串最后是空,下面会用到
//这里我们假设使用","作为分隔符
String[] newStr = str.split(",");
//想要看到最后的结果可以通过循环进行输出
for(int i = 0;i < newStr.length;i++){
System.out.println(newStr[i]);
}
//两个分隔符的情况 ","和"+"作为分隔符
//同样还是返回字符串数组类型的数据
//这里我实验之后发现给"+"加上"[]"之后才可以通过编译,得到正确的结果
String[] newStr2 = str.split(",|[+]");
for(int i = 0;i < newStr2.length;i++) {
System.out.println(newStr2[i]);
}
}
}

//第二种用法 str.split(String regex,int limit)
//limit有三种情况,大于0、小于0、等于0
//返回字符串数组类型的数据
//举例
public class Asplit{
public static void main(String args[]){
//自定义一个字符串
String str = new String("13,235sdf+fnk,055s,fkd, ");
//注意这里字符串最后是空,下面会用到
//同样还是这个数组
//
//第一种 limit>0
System.out.println("1111111111");
String[] newStr = str.split(",",2);
for(int i = 0;i < newStr.length;i++){
System.out.println(newStr[i]);
}
//第二种 limit=0
System.out.println("22222222222");
String[] newStr1 = str.split(",",0);
for(int i = 0;i < newStr1.length;i++){
System.out.println(newStr1[i]);
}
//第三种 limit<0
System.out.println("333333333");
String[] newStr2 = str.split(",",-1);
for(int i = 0;i < newStr2.length;i++){
System.out.println(newStr2[i]);
}
}

poll():获取并删除Queue中的第一个元素;
Math.max(A,B):获取A , B 两个数中的最大值;
String.valueOf():将基本数据形转换为字符串
(1)String.valueOf(boolean b) : 将 boolean 变量 b 转换成字符串
(2)String.valueOf(char c) : 将 char 变量 c 转换成字符串
(3)String.valueOf(char[] data) : 将 char 数组 data 转换成字符串
(4)String.valueOf(char[] data, int offset, int count) : 将 char 数组 data 中 由 data[offset] 开始取 count 个元素 转换成字符串
(5)String.valueOf(double d) : 将 double 变量 d 转换成字符串
(6)String.valueOf(float f) : 将 float 变量 f 转换成字符串
(7)String.valueOf(int i) : 将 int 变量 i 转换成字符串
(8)String.valueOf(long l) : 将 long 变量 l 转换成字符串
(9)String.valueOf(Object obj) : 将 obj 对象转换成 字符串, 等于 obj.toString()
getOrDefault(key,default):代表当哈希表包含键 key 时返回对应value ,不包含时返回默认值 default;
str.trim():删除字符串首尾空格
Arrays.copyOf(arr,k): 返回的是数组中新的数组对象,前k个;若k>arr.length,即后面用数组默认值填充;
PriorityQueue<Integer> p = new PriorityQueue<>();:小顶堆,即在队列中升序排列
PriorityQueue<Integer> q = new PriorityQueue<>((x, y) -> (y - x)):大顶堆,降序排列
Math.pow(a,b):返回的是a的b次方
queue.offer():将指定元素添加到此列表的尾部(最后一个元素)
add和offer区别:
两者都是往队列尾部插入元素,不同的是,当超出队列界限的时候,add()是抛出异常让你处理,而offer()是直接返回false。
leetcode一般用offer()。
同理
Queue 中 remove() 和 poll()有什么区别?
Queue 中 remove() 和 poll()都是用来从队列头部删除一个元素。
在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length):从指定的源数组(从指定位置开始)复制数组到目标数组的指定位置
源码:
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代码解释:
Object src : 原数组
int srcPos : 从元数据的起始位置开始
Object dest : 目标数组
int destPos : 目标数组的开始起始位置
int length : 要copy的数组的长度
Character.isLetterOrDigit(char c):判断字符是否是字母或数字
Character.toLowerCase():转化成小写
二、sql
order by():默认升序排列 desc:降序 asc:升序
order by 和 group by 的区别:
- order by 从英文里理解就是行的排序方式,默认的为升序。 order by 后面必须列出排序的字段名,可以是多个字段名。
- group by 从英文里理解就是分组。必须有“聚合函数”来配合才能使用,使用时至少需要一个分组标志字段。
注意:聚合函数是—sum()、count()、avg()等都是“聚合函数”
where和having的区别
where
- where是一个约束声明,使用where来约束来自数据库的数据;
- where是在结果返回之前起作用的;
- where中不能使用聚合函数;
having
- having是一个过滤声明;
- 在查询返回结果集以后,对查询结果进行的过滤操作;
- 在having中可以使用聚合函数。
执行顺序:where 早于 group by 早于 having
三、增强for循环
DK1.5引进了一种新的循环类型,即增强型for循环,可用于数组和集合
(集合中元素不能重复)
for(type element: arrays)
{
System.out.println(element);
}
其中type为arrays的类型(例如String,int,double,float),element是被声明的新的局部变量,对应的是数组或集合中的每个元素值。arrays即是要访问的数组名。
四
1:算法指的是解决问题的有限运算序列;
2:一个递归的定义可以用递归过程求解,也可以用非递归过程求解,但单从运行时间来看,通常递归过程比非递归过程较慢;
3:软件测试用例包括输入数据和预期输出结果;
4:区分白盒测试和黑盒测试的依据:是否能看到被测试源程序;
5:TCP/IP协议是Internet最基本的协议,其中应用层 的主要协议有Telnet、FTP、SMTP 等,是用来接收来自传输层的数据或者按不同应用要求与方式将数据传输至传输层;传输层 的主要协议有UDP、TCP ,是使用者使用平台和计算机信息网内部数据结合的通道,可以实现数据传输与数据共享;网络层 的主要协议有ICMP、IP、IGMP ,主要负责网络中数据包的传送等;而网络访问层 ,也叫网路接口层或数据链路层,主要协议有ARP、RARP ,主要功能是提供链路管理错误检测、对不同通信媒介有关信息细节问题进行有效处理等。
6:六大进程间通信方法:管道(匿名管道,命名管道)、信号、信号量、消息队列、共享内存、socket
7:堆栈溢出一般原因:①循环的递归调用 ②大数据结构的局部变量;
8:操作系统采用缓冲技术可减少对cpu的中断次数;
9:数据在内存中的逻辑结构分为: 集合结构、线性结构、树型结构、图形结构;
10:SQL中,子查询:嵌入到另一个查询语句之中的查询语句;
11:具有风险分析的软件生命周期模型是 螺旋模型。
12:length 和 length()区别
length:用于基本类型数组,得到数组容量
length():用于String类型的字符串中,用于获取字符串的长度
13:ifconfig可以支持的操作:修改网络设备信息、停止网络设备、修改网络硬件地址、绑定不同的IP地址到同一个网卡。
14:linux的vi编辑器中,
:w 保存文件但不退出vi
:w file 将修改另外保存到file中,不退出vi
:w! 强制保存,不推出vi
:wq 保存文件并退出vi
:wq! 强制保存文件,并退出vi
:q 不保存文件,退出vi
:q! 不保存文件,强制退出vi
:e! 放弃所有修改,从上次保存文件开始再编辑命令历史
15:如果CPU使用率居高,则代码可能存在死循环;可能出现内存泄漏;I/O等待CPU高,说明等待I/O的时间比较长。
16:http请求响应头部里关于缓存的描述:Cache-Control设优先级高于Expires设置;
17:使用Java命令行运行同一个jar包两次,会产生两个进程;
线程的死锁是指多个线程争抢资源造成的线程间互相等待的情况;
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
18:深拷贝的对象的值内存地址发生了改变;浅拷贝的对象的值等于原对象的值;
五、面经
1:常用的数据结构
常用有八种数据结构有:数组(Array)、栈(Stack)、队列(Queue)、链表(Linked List)、树(Tree)、图(Graph)、堆(Heap)、散列表(Hash)等;
2:数组和链表
数组
数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素,但如果要在数组中添加一个元素,则需要大量移动元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中;如果要删除一个元素,同样要移动大量元素去填掉被移动的元素。如果想要快速访问数据,很少增删数据,就用数组。
链表
链表中元素是在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针。如果要访问链表中的一个元素,需要从第一个元素开始,一直找到需要的元素位置,但是增删一个元素对于链表数据结构就很简单,只要修改元素中的指针就可以。
区别
- (1)存储位置上: 数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定;
- (2)存储空间上:链表存放的内存空间可以是连续的,也可以是不连续的,数组则是连续的一段内存空间。一般情况下存放相同多的数据数组占用较小的内存,而链表还需要存放其前驱和后继的空间。
- (3)长度的可变性:链表的长度是按实际需要可以伸缩的,而数组的长度是在定义时要给定的,如果存放的数据个数超过了数组的初始大小,则会出现溢出现象。
- (4)按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);
- (5)按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);
- (6)插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可;
3:合并两个二叉树
如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
注意:合并过程必须从两个树的根结点开始
递归法:在第一棵树上进行累加,使用前序遍历的思路
/** * Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
//在root1的基础上累加
if (root1 == null && root2 == null) {return null;}
if (root1 == null) {return root2;}
if (root2 == null) {return root1;}
root1.val = root1.val + root2.val;
root1.left = mergeTrees(root1.left, root2.left);
root1.right = mergeTrees(root1.right, root2.right);
return root1;
}
}
4:数据结构与算法十大算法
算法
常见的基本数据结构
常见的基本数据结构有链表、栈、队列、树
链表: 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列节点组成,这些节点不必在内存中连续。每个节点由数据部分Data和链部分Next,Next指向下一个节点,这样当添加或这删除时,只需改变相关节点的Next指向,效率很高;
栈和队列: 是比较特殊的线性表,栈是限制插入和删除只能在一端进行的表(先进后出); 队列只允许在front端进行行删除操作,在rear端进行插入操作(先进先出);
树: 树形结构是一类非常重要的非线性数据结构,主要以二叉树为主;
5:Vector的特性
其容量在需要时可以自动分配,可以在运行时高效的添加元素,本质上是数组形式的存储方式; 缺点是在插入或者删除一项时,需要线性时间,但是在尾部插入或者删除,是常数时间。
6:AQS
Java并发包(JUC)中提供了很多并发工具,这其中,很多我们耳熟能详的并发工具,譬如ReentrangLock、Semaphore,它们的实现都用到了一个共同的基类–AbstractQueuedSynchronizer,简称AQS。AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。
AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。状态信息通过procted类型的getState,setState,compareAndSetState进行操作 AQS支持两种同步方式:1.独占式2.共享式。这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock。总之,AQS为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。同步器的设计是基于模板方法模式的,一般的使用方式是这样:
- 1.使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
- 2.将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
7:AOP
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。 使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。
8:查看端口号、进程的指令;动态查看日志的指令;怎么判断一个端口存不存在?如果磁盘满了怎么处理?删除一个目录下的txt文件
- 查看全部端口号:netstat -an
- 查看单个端口号:netstat -an | grep 端口号
- 查看全部进程:ps -ef
- 查看单个进程号:ps -ef | grep 进程名
- 动态查看日志:tail -f
- 判断端口是否存在:netstat -an | grep 端口号 查看该端口是否已被占用
- 删除一个目录下的txt文件: rm -rf 文件.txt
磁盘满了怎么处理:
- df -h 查看是哪个挂在目录满了,常常是根目录/占满
- 快速定位一下应用日志大小情况,比如tomcat日志,应用系统自己的日志等。
- 如果能直观地看到日志文件过大,则酌情进行删除。有时候删除日志文件之后再df -h查看空间依然被占满,继续排查。 lsof file_name 查看文件占用进程情况,如果删除的日志正在被某个进程占用,则必须重启或者kill掉进程。
- 如果不能直观地排除出是某个日志多大的原因,就需要看一下指定目录下的文件和子目录大小情况,使用du命令。
9:在浏览器中输入一个网址的 运行过程
1、查询DNS,获取域名对应的IP。
-
(1)检查浏览器缓存、检查本地hosts文件是否有这个网址的映射,如果有,就调用这个IP地址映射,解析完成。
-
(2)如果没有,则查找本地DNS解析器缓存是否有这个网址的映射,如果有,返回映射,解析完成。
-
(3)如果没有,则查找填写或分配的首选DNS服务器,称为本地DNS服务器。服务器接收到查询时:
如果要查询的域名包含在本地配置区域资源中,返回解析结果,查询结束,此解析具有权威性。
如果要查询的域名不由本地DNS服务器区域解析,但服务器缓存了此网址的映射关系,返回解析结果,查询结束,此解析不具有权威性。 -
(4)如果本地DNS服务器也失效:
如果未采用转发模式(迭代),本地DNS就把请求发至13台根DNS,根DNS服务器收到请求后,会判断这个域名(如.com)是谁来授权管理,并返回一个负责该顶级域名服务器的IP,本地DNS服务器收到顶级域名服务器IP信息后,继续向该顶级域名服务器IP发送请求,该服务器如果无法解析,则会找到负责这个域名的下一级DNS服务器(如http://baidu.com)的IP给本地DNS服务器,循环往复直至查询到映射,将解析结果返回本地DNS服务器,再由本地DNS服务器返回解析结果,查询完成。
如果采用转发模式(递归),则此DNS服务器就会把请求转发至上一级DNS服务器,如果上一级DNS服务器不能解析,则继续向上请求。最终将解析结果依次返回本地DNS服务器,本地DNS服务器再返回给客户机,查询完成。
2、得到目标服务器的IP地址及端口号(http 80端口,https 443端口),会调用系统库函数socket,请求一个TCP流套接字。客户端向服务器发送HTTP请求报文:
- (1)应用层:客户端发送HTTP请求报文。
- (2)传输层:(加入源端口、目的端口)建立连接。实际发送数据之前,三次握手客户端和服务器建立起一个TCP连接。
- (3)网络层:(加入IP头)路由寻址。
- (4)数据链路层:(加入frame头)传输数据。
- (5)物理层:物理传输bit。
3、服务器端经过物理层→数据链路层→网络层→传输层→应用层,解析请求报文,发送HTTP响应报文。
4、关闭连接,TCP四次挥手。
5、客户端解析HTTP响应报文,浏览器开始显示HTML
10:TCP为什么可靠,哪些方法保证可靠?
-
[1] 确认和重传机制 建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是确认重传、流控的基础
传输过程中,如果Checksum校验失败、丢包或延时,发送端重传。 -
[2] 数据排序 TCP有专门的序列号SN字段,可提供数据re-order
-
[3] 流量控制 滑动窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量,发送方通过维持一个发送滑动窗口来确保不会发生由于发送方报文发送太快接收方无法及时处理的问题。
-
[4] 拥塞控制 TCP的拥塞控制由4个核心算法组成: “慢启动”(Slow Start) “拥塞避免”(Congestion avoidance) “快速重传 ”(Fast Retransmit) “快速恢复”(Fast Recovery)
11:多线程是什么
最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行的更快,无需做出任何特殊的调校。对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。 一个采用了多线程技术的应用程序可以更好地利用系统资源。其主要优势在于充分利用了CPU的空闲时间片,可以用尽可能少的时间来对用户的要求做出响应,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。更为重要的是,由于同一进程的所有线程是共享同一内存,所以不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。
12:Java内存区域划分
-
1.程序计数器: 可以看做是当前线程所执行的字节码的行号指示器。在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
每条线程都有一个独立的程序计数器,所以程序计数器是线程私有的内存区域。
如果线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果线程执行的是一个Native方法,计数器的值为空。
Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。 -
2.Java虚拟机栈: 描述Java方法执行的内存模型,每个方法执行的同时会创建一个栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。Java虚拟机栈是线程私有的,它的生命周期与线程相同。
局部变量表存放了编译时期可知的各种基本数据类型和对象引用。局部变量表所需的内存空间在编译时期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
Java虚拟机规范对这个区域规定了两种异常情况: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError
异常; 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常; -
3.本地方法栈: 本地方法栈与虚拟机栈的区别:虚拟机栈为虚拟机执行Java方法服务(也就是字节码),而本地方法栈为虚拟机使用到的Native方法服务。
Java虚拟机规范对这个区域规定了两种异常情况:StackOverflowError 和 OutOfMemoryError异常。 -
4.Java堆: Java堆是被所有的线程共享的一块内存区域,在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾回收器管理的主要区域,从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以Java堆可以细分为:新生代、老生代;从内存分配的角度看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(TLAB)。
Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
Java虚拟机规范规定,如果在堆上没有内存完成实例分配,并且堆上也无法再扩展时,将会抛出OutOfMemoryError异常。
Java堆内存的OOM异常: 内存泄露:指程序中一些对象不会被GC所回收,它始终占用内存,即被分配的对象引用链可达但已无用。
内存溢出:程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。 -
5.方法区: 被所有的线程共享的一块内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 不需要连续的内存和可以选择固定大小或者可扩展之外,还可以选择不实现垃圾回收。
Java虚拟机规范规定,当方法区无法满足内存分配的需求时,将抛出OutOfMemoryError异常。
13:GC Root(当前时刻存活的对象)可以是哪些?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象;
- 本地方法栈中JNI(即一般说的native方法)引用的对象;
- 方法区中的静态变量和常量引用的对象。
14:volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的;
- 禁止进行指令重排序。《深入理解Java虚拟机》中对volatile的描述:“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”lock前缀指令实际上相当于一个内存屏障(也称内存栅栏),内存屏障会提供3个功能:① 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;② 它会强制将对缓存的修改操作立即写入主存;③ 如果是写操作,它会导致其它CPU中对应的缓存行无效。因为volatile关键字无法保证操作的原子性。
通常来说,使用volatile必须具备以下两个条件:对变量的写操作不依赖当前值;该变量没有包含在具有其他变量的不变式中。
15:内存泄漏
当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。内存泄漏是造成应用程序OOM的主要原因之一。我们知道Android系统为每个应用程序分配的内存是有限的,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出从而导致应用Crash。 常见的内存泄漏:
-
1、单例造成的内存泄漏
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。 -
2、非静态内部类创建静态实例造成的内存泄漏
非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。 -
3、Handler造成的内存泄漏
-
4、线程造成的内存泄漏 如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。
-
5、资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。 -
6、使用ListView时造成的内存泄漏
-
7、集合容器中的内存泄露
-
8、WebView造成的泄露
避免内存泄漏:
①在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。
②对于需要在静态内部类中使用非静态外部成员变量(如:Context、View),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。
③对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。
④保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
⑤对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
1)将内部类改为静态内部类 2)静态内部类中使用弱引用来引用外部类的成员变量
16:如何防止数组越界
由于数组的元素个数默认情况下是不作为实参内容传入调用函数的,因此会带来数组访问越界的相关问题,防止数组越界:
- 检查传入参数的合法性;
- 可以用传递数组元素个数的方法,即:用两个实参,一个是数组名,一个是数组的长度。在处理的时候,可以判断数组的大小,保证自己不要访问超过数组大小的元素;
- 当处理数组越界时,打印出遍历数组的索引十分有帮助,这样我们就能够跟踪代码找到为什么索引达到了一个非法的值;
- Java中可以加入try{ } catch(){ }
17:HTTP用的什么连接
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。 但从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头有加入这行代码:Connection:keep-alive 在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的 TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接要客户端和服务端都支持长连接。
18:TCP的流量控制
滑动窗口机制: 滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。 举例: 发送和接受方都会维护一个数据帧的序列,这个序列被称作窗口。发送方的窗口大小由接受方确定,目的在于控制发送速度,以免接受方的缓存不够大,而导致溢出,同时控制流量也可以避免网络拥塞。图中的4,5,6号数据帧已经被发送出去,但是未收到关联的ACK,7,8,9帧则是等待发送。可以看出发送端的窗口大小为6,这是由接受端告知的(事实上必须考虑拥塞窗口cwnd,这里暂且考虑cwnd>rwnd)。此时如果发送端收到4号ACK,则窗口的左边缘向右收缩,窗口的右边缘则向右扩展,此时窗口就向前“滑动了”,即数据帧10也可以被发送。
19:设计模式是什么?常用的设计模式
设计模式
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码,让代码更容易被他人理解、保证代码可靠性。
常用的设计模式
-
(1)单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点,避免一个全局使用的类频繁的创建和销毁,节省系统资源,提高程序效率。
实现方式: 将被实现的类的构造方法设计成private的。 添加此类引用的静态成员变量,并为其实例化。
在被实现的类中提供公共的Create Instance函数,返回实例化的此类,就是2中的静态成员变量。
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,适用场景: 需要频繁实例化然后销毁的对象。
创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 有状态的工具类对象。 频繁访问数据库或文件的对象。 场景举例:
每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机;
Windows的TaskManager(任务管理器),不能打开两个windows task manager;
Windows的Recycle Bin(回收站),在整个系统运行过程中,回收站一直维护着仅有的一个实例;
网站的计数器,一般也是采用单例模式实现,否则难以同步; -
(2)策略模式:策略模式是把一个类中经常改变或者将来可能改变的部分提取出来作为一个接口,然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为。
实现方式: 提供公共接口或抽象类,定义需要使用的策略方法。(策略抽象类) 多个实现的策略抽象类的实现类。(策略实现类)
环境类,对多个实现类的封装,提供接口类型的成员量,可以在客户端中切换。 客户端调用环境类进行不同策略的切换。
Strategy:策略接口,用来约束一系列具体的策略算法。Context使用这个接口来调用具体的策略,实现定义的策略。
ConcreteStrategy:具体的策略实现,也就是具体的算法实现。
Context:上下文,负责与具体的策略交互,通常上下文会持有一个真正的策略实现。
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
一个系统需要动态地在几种算法中选择一种。 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。 -
(3)简单工厂模式:定义一个用于创建对象的接口或抽象类,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
实现方式: 抽象产品类(Product),是所创建的所有对象的父类,负责描述所有实例所共有的公共接口 多个具体的产品类(Concrete Product),具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。
工厂类(Creator),负责实现创建所有实例的内部逻辑 适用场景: 在任何需要生成复杂对象的地方,都可以使用工厂方法模式。
当需要系统有比较好的扩展性时,可以考虑工厂模式,不同的产品用不同的实现工厂来组装。 -
(4)装饰模式:允许向一个现有的对象添加新的功能,同时又不改变其结构,以在不使用创造更多子类的情况下,将对象的功能加以扩展。
实现方式: 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。
装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。 适用场景: 扩展一个类的功能。
动态增加功能,动态撤销。 -
(5)观察者模式:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
抽象主题(Subject)角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
抽象观察者(Observer)角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
具体主题(ConcreteSubject)角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
具体观察者(ConcreteObserver)角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。
适用场景: 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
20:分布式和集群的概念
分布式: 是指将不同的业务分布在不同的地方;
集群: 是指将几台服务器集中在一起,实现同一业务。
分布式中的每一个节点,都可以做集群,而集群并不一定就是分布式的。集群有组织性,一台服务器垮了,其他服务器可以顶上来,而分布式的每一个节点,都完成不同的业务,一个节点垮了,那个业务就不可访问了。
21:静态库和共享库的区别
静态库是在程序编译时期链接的,动态库是在程序运行时期链接的。
共享库如果10个应用程序共享,那么磁盘上只有一份共享的库文件,而且运行加载时在内存中只加载了标记了这一份库文件
而静态库,就是这10个应用程序都包含了这个库文件,那么10份的库文件都放在磁盘上了(占用磁盘空间),而且运行加载的时候占用内存空间(10份库文件都要加载);
共享库便于升级 ;只需要替换成新的共享库文件即可,而静态库则需要找到所有的用到该库的程序,把它们重新编译一遍。
