1.重定向和转发
转发过程:客户浏览器发送http请求—-》web服务器接受此请求–》调用内部的一个方法在容器内部完成请求处理和转发动作—-》将目标资源发送给客户。
整个过程只涉及一次浏览器和服务器之间的“请求-响应”,转发过程中的组件共享同一个请 求(request)和响应(response)对象。
RequestDispatcher对象封装了转发操作,通过request的getRequestDispatcher(String path)方法获得RequestDispatcher对象,通过forward(request, response)方法实现转发。
只能在一个应用内的组件进行,不可以转发给其他应用的地址
可以使用相对地址和绝对地址;绝对地址只需从应用名后面的的路径开始即可。
组件之间涉及数据传递可以通过request对象传递数据。
注:转发操作之前的response输出内容是没有意义的,因为转发操作之前会清空缓冲区,但是如果在转发之前的数据超过了缓冲区,或者调用了flush方法,响应内容输出到了客户端(响应信息已提交),此时执行转发操作会抛出运行时异常IllegalStateException。
重定向:
客户浏览器发送http请求—-》web服务器接受后发送302状态码响应及对应新的location给客户浏览器–》客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址—-》服务器根据此请求寻找资源并发送给客户
response的sendRedirect(String url)方法来实现,注意String类型的参数url表示重定向到的地址,需要注意的是,如果表示重定向到本应用内的一个绝对地址时,要从应用名前开始,例如:tst应用中的某个组件要重定向到本应用内的/jsp/somewhere.jsp,则重定向的绝对地址应该是:“/tst/jsp /somewhere.jsp”
区别:
转发是一次请求一次响应,重定向是两次请求两次响应
转发地址栏不会变,重定向地址栏会显示第二次请求的地址
转发只需要给出资源路径,不需要项目名,而重定向要全路径包括项目名
转发效率高,因为是一个请求。
2.两个文件都有一亿条url,然后得出重复的url
分析:我们先来看如果要把这些URL全部加载到内存中,需要多大的空间。
1MB = 2^20 = 10^6 = 100W
1GB = 2^30 = 10^9 = 10亿
1亿 = 0.1G * 64 Byte = 6.4G
明显是不可能全部加载到内存中的。我们可采用以下方法解决:
①采用布隆过滤器,位数组大小为m约为输入元素个数n的13倍,
元素个数:n=0.1G;
位数组大小:m = 0.1G * 13 = 1.3亿,即需要1.3亿bit位才能达到错误率0.01
而我们拥有的内存可容纳bit为 4G * 8 bit = 32G bit = 320 亿数据,,可以满足
②分别扫描A,B两个文件,根据hash(url) % k,如果k = 100,那么0.1g的url需要64m的空间,每个小文件需要64m,完全够;将url划分到不同的k个文件中,比如a0,a1,…,a99;b0,b1,…b99;这样处理后相同的url肯定在对应的小文件中,即a0&b0,a1&b1等。但是同一个文件中也会出现冲突,因此每个小文件中,即使url不同,也可能被放入同一个小文件里面;只需要找出100对文件中的相同url,对比a0,b0,,遍历a0,将url存放到hashset中,然后遍历b0,如果b0中某个url在hashset中,说明url在a,b中同时存在,输出即可。例如a0:a,b,c,d; b0:a,b;;就会输出a,b
3.辅助栈:对某个栈进行一系列的进栈,出栈的操作后,如何以o(1)的求出栈里面的最大值;
建立两个栈,一个是数据栈data,一个是辅助栈maxData;
进行push的时候,先对data进行push操作,如果maxData为空,直接push,如果data>=maxData,也push;
Pop的时候,date为空,直接return,如果不为空,且data的栈顶等于max的栈顶,那么就将maxData进行pop,并且data进行pop;
import java.util.Stack;
/** * 对某个栈进行一系列的进栈,出栈的操作后, * 如何以o(1)的求出栈里面的最大值; * @author 49692 * */ public class PopStackMax { public static void push(Stack<Integer> data, Stack<Integer> maxData, int d){ data.push(d); if(maxData.empty() || d >= maxData.peek()){ maxData.push(d); } }
public static void pop(Stack<Integer> data, Stack<Integer> maxData){ if(data.empty()){ return ; } if(data.peek() == maxData.peek()){ maxData.pop(); } data.pop(); }
public static int getMax(Stack<Integer> maxData){ if(maxData.isEmpty()){ return -1; } return maxData.peek(); } public static void main(String[] args) { Stack<Integer> data = new Stack<Integer>(); Stack<Integer> maxData = new Stack<Integer>(); push(data, maxData, 1); push(data, maxData, 2); push(data, maxData, 3); push(data, maxData, 4); pop(data, maxData); pop(data, maxData); push(data, maxData, 5); push(data, maxData, 6); pop(data, maxData); System.out.println(getMax(maxData)); } }
|
4.会议室预定
小刘的工作就是安排学校小礼堂的活动,每个时间最多安排一个活动。现在小刘有一些活动计划的时间表,他想尽可能的安排更多的活动,请问他该如何安排。
2
2
1 10
11 12
3
1 10
10 11
12 20
样例输出
1
2
import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Scanner;
/** * 会议室开会时间,起始时间和结束时间,时间不能重叠, * 各个活动顺利进行,最少需要多少个会议室; 2 2 1 10 11 12 3 1 10 10 11 12 20 样例输出 1 2
注意:如果上一个活动在t时间结束,下一个活动最早应该在t+1时间开始 定义两个类,会议室room和活动时间roomTime; 按照活动结束时间进行排序,创建活动的list和会议室的list; 然后遍历活动list,先添加第一个活动,然后贪心算法,遍历剩下每个活动 的开始时间与middleTime的大小,判断该添加哪个最合适的活动, 开始时间和middleTime最接近的 * @author 49692 * */ public class ArrangeRoom { public static void main(String[] args){ Scanner sc = new Scanner(System.in); int n = sc.nextInt(); List<RoomTime> list = new ArrayList<RoomTime>(); List<Room> rooms = new ArrayList<Room>(); for(int i = 0; i < n; i++){ int timeCount = sc.nextInt(); for(int j = 0; j < timeCount; j++){ RoomTime rt = new RoomTime(); rt.setStart(sc.nextInt()); rt.setEnd(sc.nextInt()); list.add(rt); } } Collections.sort(list); int current = -1; int roomCount = 0; greedySelect(list, rooms); for(int i = 0 ; i < rooms.size(); i++){ List<RoomTime> tls = rooms.get(i).getTimeList(); for(RoomTime rt : tls){ System.out.println(i + "-----start:" + rt.getStart() + ",,,end:" + rt.getEnd()); } } }
private static void greedySelect(List<RoomTime> list, List<Room> rooms) { // TODO Auto-generated method stub Room room = new Room(); int middleTime = list.get(0).getEnd(); room.addTime(list.get(0)); list.remove(0); rooms.add(room); for(int i = 0; i < list.size(); i++){ int index = -1; if((index = getBestTime(list, i, middleTime)) >= 0){ middleTime = list.get(index).getEnd(); room.addTime(list.get(index)); list.remove(index); } } if(list.size() != 0){ greedySelect(list, rooms); } }
private static int getBestTime(List<RoomTime> list, int index, int middleTime) { int min = -1; int minData = 100; int minEnd = 100; for(int i = index ; i < list.size(); i++){ int data = list.get(i).getStart() - middleTime; if(data >= 0 && minData >= data){ int end = list.get(i).getEnd(); if(minData > data){ minData = data; minEnd = end; min = i; }else{ if(minEnd > end){ minEnd = end; min = i; } } } } return min; } }
class RoomTime implements Comparable<RoomTime>{ private int start; private int end;
public int getStart() { return start; }
public void setStart(int start) { this.start = start; }
public int getEnd() { return end; }
public void setEnd(int end) { this.end = end; }
@Override public int compareTo(RoomTime o) { // TODO Auto-generated method stub return this.end - o.end; } }
class Room{ private List<RoomTime> timeList = new ArrayList<RoomTime>();
public List<RoomTime> getTimeList() { return timeList; }
public void setTimeList(List<RoomTime> timeList) { this.timeList = timeList; }
public int addTime(RoomTime rt){ timeList.add(rt); return timeList.size(); } }
|
5. 应对分布式缓存宕机
①缓存恢复前,如何保证系统正常有序的工作
②缓存恢复后,如何保证缓存在K/V恢复前,对应的服务会不会过载
缓存宕机后,并且未恢复数据前,调用方系统请求通过调用cache,发现cache系统状态不可用,则转向请求服务方系统(通过接口访问数据库):
针对缓存恢复前,系统的操作:
A:发现缓存不可用,不进一步调用服务端(数据库)的数据,直接返回预先设定的系统默认值
B:发现缓存不可用,调用方系统按照一定的比率或者概率,决定让部分请求调用数据库数据
C:调用服务方数据库之前,根据服务方反馈的状态,动态决定请求是否调用服务方数据,或者直接返回默认值
方案A:实施起来最容易,如果调用方法知道调用服务器数据库可能扛不住自己的全部流量,索性不请求数据库,等待缓存恢复后在继续访问。但这个方案的局限性在于,如果有写需求,这个方案就行不通了,因为很难简单的设定默认值,并且,如果缓存缺乏持久化,数据缓存的更新需要一次次调用来初始化的。
方案B:让一部分线程请求服务端数据库,保证数据能及时更新的同时,缓存也能持续初始化,至于让多少比例的流量请求服务端数据库,保守的做法就是保证调用方的并发不大于服务方的最大吞吐量。该方案之前需要做好性能的压力测试。
方案C:如果服务方系统运行正常,执行请求;如果服务方系统过载,则拒绝服务,保护服务方的同时,最大限度挖掘服务方性能。
针对缓存恢复后,保证k/v恢复前,对应服务会不会过载
如果缓存的key的数量较少,那么自然不用担心,很快就将缓存预热成功,也不会引起服务过载的情况出现,但是如果混村的数量流量不好估计的时候,不建议贸然将流量直接切过来,怕引起服务过载。
为了保证系统能顺利恢复,我们应该采用流控的方法,循序渐进的放开流量,让缓存逐步预热。流量控制刚开始可以为20%,然后50%,最后全量,放开一部分流量的时候,需要实时观察系统的状态,根据前端流量的变化和后端的承受能力,指定放开的节奏。
6.hash冲突的处理,为什么hashmap不安全,怎么解决
链式处理法,默认将entry放到table数组的index处,通过hash函数,计算hash值,通过hash值和length-1 相与,得到table的索引值index,如果已经有了一个entry对象,将新的entry对象放在table的index处,即头结点,,然后它的next是原有的entry对象。
loadFactor是用于计算threshold的,threshold=capacity*loadFactor;因此,如果loadFactor过大,会导致扩容的次数变少,即链的长度会增加,查询效率会下降;如果loadFactor过小,会导致扩容的次数变多,即数组的容量频繁增加,增加查询效率,但是内存空间会增加;因此取了一个0.75折中方案。
线程不安全体现在:添加元素和扩容方面
添加元素:根据hash值和length-1相与,确定index索引值,如果出现hash冲突,那么就采用拉链法;
如果线程1,2分别操作a1,a2;那么两者都读取到e1,然后线程先后将自己entry的next指向e1;此时总会有一个entry会被另一个覆盖;
扩容:新长度变为原来2倍,以新长度生成新数组,,然后将旧的数组拷贝到新数组中;如果线程1,2同时操作新数组newTab,那么最终只有一组数据会成功赋值到新数组中。
7.实现equals要注意哪些原则
1、使用==操作符检查“参数是否为这个对象的引用”,如果是,返回true。
2、使用instanceof操作符检查参数是否为正确的类型,如果不是,返回false。
3、把参数转换成正确的类型。
4、对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。
5、检查是否对称的(当x.equals(y)返回true时,y.equals(x)也必须返回true)、传递的(如果x.equals(y)返回true、y.equals(z)返回true、x.equals(z)也必须返回true)、一致的(参加比较的关键域没有被改变,x.equals(y)一直返回true)。
6、覆盖equals时总要覆盖hashCode,相等的对象必须具有相等的散列码。