百词斩一面9.17
链接:https://www.nowcoder.com/discuss/111305
目录
tcp和udp的区别,http是基于tcp还是udp.http响应码
Top k问题。有个大文件,记录形式为: 用户昵称 背单词的次数。查找里面10个背单词数量最的人。如果文件1t,内存只有2g怎么做
java.try catch finally问题,常见运行时异常,创建线程的方法,synchronized实现,使用方法
数据库。数据库事务是什么.redis里面的命令,redis持久化怎么做
Linux系统.Linux根目录下有哪些目录,各自存放些什么内容,交换分区的意义,文件添加可执行权限,分用户添加权限,超大日志文件里面的内容查找,如何使用nginx反向代理(简历上写了熟悉Linux开发环境)
Linux根目录下有哪些目录,各自存放些什么内容:
/bin:存放最常用命令(二进制可执行命令)
/boot:启动Linux的核心文件;
/dev:设备文件
/home:用户主目录
/etc:存放各种配置文件
/lib:系统最基本的动态链接共享库(标准程序设计库)
/mnt:一般是空的,用来临时挂载别的文件系统。
/proc:虚拟目录,是内存的映射。
/etc/rc.d: 启动的配置文件和脚本
tcp和udp的区别,http是基于tcp还是udp.http响应码
是基于tcp。HTTP客户端发起一个请求,建立一个到服务器指定端口(默认是80端口)的tcp连接。HTTP服务器则在那个端口监听客户端发送过来的请求。一旦收到请求,服务器(向客户端)发回一个状态行,比如“HTTP/1.1 200 OK"和(相应的)消息,消息的消息体可能是请求的文件、错误消息或者其他一些信息。HTTP使用tcp而不是udp的原因在于打开一个网页必须传送很多数据,而tcp协议提供传输控制,按顺序组织数据和错误纠正。
响应吗:http://tool.oschina.net/commons?type=5
二叉树的遍历,递归和循环方法。
先序遍历——按照根节点-左孩子-右孩子“”的顺序进行访问
void pre_traverse(BTree pTree)
{
if(pTree){
printf("%c",pTree->data);
if(pTree->pLchild)
pre_traverse(pTree->pLchild);
if(pTree->pRchild)
pre_traserve(pTree->pRchild);
}
}
中序遍历——按照“左孩子-根节点-右孩子”的顺序进行访问
void in_traverse(BTree pTree){
if(pTree){
if(pTree->pLchild)
in_traverse(pTree->pLchild);
printf("%c",pTree->data);
if(pTree->pRchild)
in_traverse(pTree->pRchild);
}
}
后序遍历——按照“左孩子-右孩子-根节点”的顺序进行访问
void beh_traverse(BTree pTree){
if(pTree){
if(pTree->pLchild)
beh_traverse(pTree->pLchild);
if(pTree->pRchild)
beh_traverse(pTree->pRchild);
printf("%c",pTree->data);
}
}
非递归如下:
前序遍历
void pre_traverse(BTree pTree)
{
PSTACK stack = create_stack(); //创建一个空栈
BTree node_pop; //用来保存出栈节点
BTree pCur = pTree; //定义用来指向当前访问的节点的指针
//直到当前节点pCur为NULL且栈空时,循环结束
while(pCur || !is_empty(stack))
{
//从根节点开始,输出当前节点,并将其入栈,
//同时置其左孩子为当前节点,直至其没有左孩子,及当前节点为NULL
printf("%c ", pCur->data);
push_stack(stack,pCur);
pCur = pCur->pLchild;
//如果当前节点pCur为NULL且栈不空,则将栈顶节点出栈,
//同时置其右孩子为当前节点,循环判断,直至pCur不为空
while(!pCur && !is_empty(stack))
{
pCur = getTop(stack);
pop_stack(stack,&node_pop);
pCur = pCur->pRchild;
}
}
}
中序遍历
void in_traverse(BTree pTree)
{
PSTACK stack = create_stack(); //创建一个空栈
BTree node_pop; //用来保存出栈节点
BTree pCur = pTree; //定义指向当前访问的节点的指针
//直到当前节点pCur为NULL且栈空时,循环结束
while(pCur || !is_empty(stack))
{
if(pCur->pLchild)
{
//如果pCur的左孩子不为空,则将其入栈,并置其左孩子为当前节点
push_stack(stack,pCur);
pCur = pCur->pLchild;
}
else
{
//如果pCur的左孩子为空,则输出pCur节点,并将其右孩子设为当前节点,看其是否为空
printf("%c ", pCur->data);
pCur = pCur->pRchild;
//如果为空,且栈不空,则将栈顶节点出栈,并输出该节点,
//同时将它的右孩子设为当前节点,继续判断,直到当前节点不为空
while(!pCur && !is_empty(stack))
{
pCur = getTop(stack);
printf("%c ",pCur->data);
pop_stack(stack,&node_pop);
pCur = pCur->pRchild;
}
}
}
}
后序遍历
void beh_traverse(BTree pTree)
{
PSTACK stack = create_stack(); //创建一个空栈
BTree node_pop; //用来保存出栈的节点
BTree pCur; //定义指针,指向当前节点
BTree pPre = NULL; //定义指针,指向上一各访问的节点
//先将树的根节点入栈
push_stack(stack,pTree);
//直到栈空时,结束循环
while(!is_empty(stack))
{
pCur = getTop(stack); //当前节点置为栈顶节点
if((pCur->pLchild==NULL && pCur->pRchild==NULL) ||
(pPre!=NULL && (pCur->pLchild==pPre || pCur->pRchild==pPre)))
{
//如果当前节点没有左右孩子,或者有左孩子或有孩子,但已经被访问输出,
//则直接输出该节点,将其出栈,将其设为上一个访问的节点
printf("%c ", pCur->data);
pop_stack(stack,&node_pop);
pPre = pCur;
}
else
{
//如果不满足上面两种情况,则将其右孩子左孩子依次入栈
if(pCur->pRchild != NULL)
push_stack(stack,pCur->pRchild);
if(pCur->pLchild != NULL)
push_stack(stack,pCur->pLchild);
}
}
}
Top k问题。有个大文件,记录形式为: 用户昵称 背单词的次数。查找里面10个背单词数量最的人。如果文件1t,内存只有2g怎么做
大数据下的2个Top K场景:
1、 1亿个数字中找出最大或最小的前100个数字(考虑判重和内存空间限制)?
思路: 考虑将数字分散存储在不同文件中, 然后依次读取各个文件, 例如1~10000存到1.txt、 2~20000存到2.tx、以此类推;
如果不判重,可以将前100个数字做成最大堆或最小堆的数据结构(找最大的Top100用最小堆, 找最小的Top100用最大堆), 然后依次遍历所有数字, 符合条件时替换根节点后并重新构建堆。堆的缺点是允许有重复数字!!!
如果要判重,则创建一个100个空间的空数组, 遍历1亿个数字依次插入值(按照升序), 使用二分查找算法判断新值在数组中的位置并插入, 该数组最多容纳100个值。 当有101个值时先判重, 如果重复继续向后遍历, 如果值大于数组最小值则插入到指定位置,数组第一个元素移出数组, 因为数组是连续的,所有可以用内存拷贝方式赋值,只有新插入的值要单独赋值到对应的下标(原理类似于android.util.SparseArray)。 因内存拷贝可认为不占用时间, 该思路的总会时间复杂度是O(1亿*log100), log100是二分查找的复杂度。
2、 1亿个搜索关键词找出次数最多的前100个搜索词(考虑内存空间限制)?
思路: 核心是“分治”、“归并”和哈希, 第一次遍历将关键词散列到不同的文件中(散列算法是性能关键,哈希函数的性能直接影响散列的结果, 尽量避免“数据倾斜”), 同一个关键词一定会散列到同一个文件, 理想情况是所有关键词均匀散列到不同的文件中(即分治思想,将大文件分解为小问题)。
读取每个文件并记录各关键词的次数, 做个冒泡排序, 从每个文件中排序出前100的关键词;
取第一个文件的记录结果, 和第二个文件做“合并”, 即200个结果中排序出前100个关键词, 然后依次合并第三个、第四个。。。。第N个文件(将子结果合并为总结果)。
PS: 大道同源, 我想到2个类似的场景。
1、 电脑里有众多的文件,但我们还是能从众多文件中找到你想要的。 这是为什么呢? 因为使用了文件夹, 按照文件类型保存在各个层级的目录里。 这个目录层级跟”在1亿个搜索词中找出频率最高的100个“的解决思路是一样的, 即将关键词分散存储到不同的文件里(也可以使用层级目录, 目录越深分散的粒度越细, 到底分几个文件/目录层级其实是时间空间的权衡)
2、 数据库用户表里要存储几亿个记录,该怎么办? 跟上面管理电脑文件的问题类似, 可以按地域、年龄、姓名等等因素将数据存储到N张表里, 术语叫做”横向切割“。
名词解释:
1、”数据倾斜“是大数据里的一个概念,就是数据集中到几个文件、处理器, 没均匀分散到各个处理单元。说白了就是忙的忙死,闲的闲死。
2、”分治“就是将大问题分解为小问题, 处理每个小问题并得到结果, 然后将所有子结果汇总成最终结果。
3、”横向切割“是数据库的一个概念, 就是将表记录分散存储到不同的表里,每个表里的记录都是完整的。 还有个“纵向切割”,就是将分拆表的列为多个表, 即一条记录要存到多张表里且每个表只存几个属性。
4、“哈希”即散列, 就是通过哈希值找到存储位置, 理想情况下时间复杂度O(1)。
---------------------
代码如下:https://blog.csdn.net/brycegao321/article/details/79629155
java.try catch finally问题,常见运行时异常,创建线程的方法,synchronized实现,使用方法
最近在抽时间看面试题,很多面试题都提出了写出java常见的5个运行时异常。现在来总结一下,
java运行时异常是可能在java虚拟机正常工作时抛出的异常。
java提供了两种异常机制。一种是运行时异常(RuntimeExepction),一种是检查式异常(checked execption)。
检查式异常:我们经常遇到的IO异常及sql异常就属于检查式异常。对于这种异常,java编译器要求我们必须对出现的这些异常进行catch 所以 面对这种异常不管我们是否愿意,只能自己去写一堆catch来捕捉这些异常。
运行时异常:我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。
RuntimeExecption在java.lang包下,
下面是由java虚拟机提供的运行时异常
AnnotationTypeMismatchException,
ArithmeticException,
ArrayStoreException,
BufferOverflowException,
BufferUnderflowException,
CannotRedoException,
CannotUndoException,
ClassCastException,
CMMException,
ConcurrentModificationException,
DOMException,
EmptyStackException,
EnumConstantNotPresentException,
EventException,
IllegalArgumentException,
IllegalMonitorStateException,
IllegalPathStateException,
IllegalStateException,
ImagingOpException,
IncompleteAnnotationException,
IndexOutOfBoundsException,
JMRuntimeException,
LSException,
MalformedParameterizedTypeException,
MirroredTypeException,
MirroredTypesException,
MissingResourceException,
NegativeArraySizeException,
NoSuchElementException,
NoSuchMechanismException,
NullPointerException,
ProfileDataException,
ProviderException,
RasterFormatException,
RejectedExecutionException,
SecurityException,
SystemException,
TypeConstraintException,
TypeNotPresentException,
UndeclaredThrowableException,
UnknownAnnotationValueException,
UnknownElementException,
UnknownTypeException,
UnmodifiableSetException,
UnsupportedOperationException,
WebServiceException
看到这么多异常,想要找出我们常见的5中运行时异常是非常容易的。
例如:ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
还有IO操作的BufferOverflowException异常
synchronized 的主要作用为:
1.确保线程互斥访问同步代码
2.共享变量变更能即时可见
3.有效解决重排序问题
synchronized 可修饰:普通方法,静态方法,对象。
synchronized 实现原理: 可以观察以下代码反编译之后的结果
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
关于这两条指令,我们直接参考jvm规范:
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
大概意思是:每一个对象都有一个监视器。当监视器被拥有时,监视器就被锁定了。当线程执行到monitorenter时,试图获取监视器的所有权。
,1.如果监视器的计数器为0,线程获取监视器,并将计数器+1
2.如果这个线程已经拥有该监视器,可以继续执行代码,计数器+1
3.如果有其他线程试图进入,必须等到计数器为0时才可以。
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。
再来看一下同步方法的实现原理,代码和反编译代码分别如下:
package com.paddx.test.concurrent;
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
数据库。数据库事务是什么.redis里面的命令,redis持久化怎么做
什么是事务?
事务是由一个或多个sql语句组成的一个整体,如果所有语句执行成功那么修改将会全部生效,如果一条sql语句将销量+1,下一条再+1,倘若第二条失败,那么销量将撤销第一条sql语句的+1操作,只有在该事务中所有的语句都执行成功才会将修改加入数据库中。
事务的特性:
原子性
原子性是指事务所有操作要么全部成功,要不全部失败回滚,因此事务的操作如果成功就必须全部完全应用刀数据库,如果操作失败这不饿能对数据库有任何的影响。
一致性
一致性是指事务必须使数据库从一个一致性的状态变换到另一个一致性状态,也就是说一个事务执行之前客执行之后必须处于一致性状态
拿转账来说,假设用户A和用户B两者的前加起来一共是5000,那么不管A和B之间怎么转账两个用户的钱相加起来来应该还的是5000,这就是事务的一致性。
隔离性
隔离性是当多个用户并发访问数据库时,比如操作同一张表的时候,数据库为每一个用户开启事务,不会被其他操作所干扰
,并发事务之间要相互隔离。
持久性
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。