网络2016 校园招聘java 笔试题及答案
作者:张超红
时间:20150920
说明:本试卷答案为本人所做,
仅作为参考,版本归本人所有,严禁随意外传。
1.在32 位机器中,一下结构体占多少字节()
Struct{
Char a;
Double b;
Int c;
Short d;
}
A.24 B.20 C.18 D.以上都不对
解释:char 1 个字节,double 8 个字节,int 4 个字节,short 2 字节,long 4 个字节,float 4
个字节。
1.网络协议采用()的方式来传输协议,在此字节序下从低地址到高地址0x12345678 的表
示形式为()
A.big_endian ,0x78 56 34 12 B.big_endiam,0x12 34 56 78
C.little_endian,0x78 56 34 D.little_endian,0x12 34 56 78
解释:所有网络协议也都是采用big endian 的方式来传输数据的。
2.上网时发现网络不能访问,qq 正常,出现此问题的可能原因是()
A.网线问题B.IP 地址冲突C.IP 地址冲突DNS 问题D.网关错误
3.某系统中的一个组件的某个函数中,有一个变量没有正确初始化,在()阶段容易发现问
题
A.单元测试B.集成测试C.确认测试D.系统测试
解释:单元测试的对象是软件设计的最小单位——模块。单元测试的依据是详细设描述,单
元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。单元测试
多采用白盒测试技术,系统内多个模块可以并行地进行测试。
4.栈的入栈序列是ABCDE,则栈的出栈序列不可能是()
A. DECBA B.ADEBC C.EDCBA D.ABCDE
解释:栈的数据进出特点是先进后出,假定原先入栈次序为1,2,3 的话,那么不会出现出栈顺序
为312 的情况(课本上应该有描述),所以本题中的c 选项eabcd 是不对的.(因为e a b 这种顺
序是不可能的)
5.已知一棵二叉树,如果先序遍历的节点顺序是ABDCGEF,中序遍历是DBGAEF,那么后
序遍历为()
A.CFHGEBDA B.CDFEGHBA C.DGCBEFA D.CFHGBA
6.执行以下语句的结果是()
Int a[2][3]={0,1,2,3,4,5};
Int *b=a[1];
*b=6;
B=a[0];
For(int i=0;i<6;++){
Printf(“%d”,b[i]);
}
A.0,1,2,3,4,5 B.0,6,2,3,4,5 C.0,1,2,6,4,5 D.编译错误
7.进程调度是从()选择一个进程投入运行
A.就绪队列B.等待队列C.作业后备队列D.提交队列
8.下列关于类和对象的叙述中,错误的是()
A.一个类只能有一个对象B.对象是类的具体实例
C.类是对某一类对象的抽象D.类和对象的关系是一种数据类型与变量的关系
解释:只要类可以声明对象那他就可以声明多个对象。而且这些多个对象占据不同的内存空
间。
另外new 对象就是在内存中分配空间存储变量的一个过程,不只是你说的初始化,先分配
空间然后初始化,如果空间分配不出来就直接异常了。而且分配内存和赋值是一起完成的。
9.如果存在一个基本有序的序列,按照那种排序方式最快()
A.快速排序B.冒泡排序C.归并排序D.插入排序
解释:快速排序在一般情况下的时间复杂度为O(nlogn),基本有序时,时间复杂度为O(n*n);
冒泡排序;冒泡排序比较稳定,基本都是n*n(虽然没有交换但是会比较),归并排序也是比较
稳定的,时间复杂度为O(nlogn);插入排序在基本有序的时候时间复杂度为O(n),一般为O(n)
10.在一个单链表中,已知指针q 指向的节点是指针p 指向的节点的前驱节点,若在q 和p
之间插入s 节点,则执行()
A.s->next=p->next; p->next=s; B.p->next=s->next ;s->next=p
C.q->next=s; s->next=p D.p->next=s; s->next=q
12. 25 匹马,5 条赛道,一匹马一个赛道,求决胜跑的最快的1,2,3 名至少需要多少场()
A.9 B.8 C.7 D.6
13.进程间的通信方式有()
A.管道B.信号量C.Socket D.互斥锁
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关
系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道(named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程
间的通信。
# 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主
要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标
识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受
限等缺点。
# 信号( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共
享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC 方式,它是针对
其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使
用,来实现进程间的同步和通信。
# 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用
于不同及其间的进程通信。
14.下面那些协议属于TCP/IP 模型的传输层()
A.http B.pop3 C.tcp D.ipx E.udp F.hdlc
15.多线程同步机制包括()
A.Critical Section B.Semaphore C.Pipe D.Mutex
事件
1、Event
用事件(Event)来同步线程是最具弹性的了。一个事件有两种状态:激发状态和未激
发状态。也称有信号状态和无信号状态。事件又分两种类型:手动重置事件和自动重置事件。
手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直
到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒“一个”等待
中的线程,然后自动恢复为未激发状态。所以用自动重置事件来同步两个线程比较理想。
MFC 中对应的类为CEvent.。CEvent 的构造函数默认创建一个自动重置的事件,而且处于
未激发状态。共有三个函数来改变事件的状态:SetEvent,ResetEvent 和PulseEvent。用事
件来同步线程是一种比较理想的做法,但在实际的使用过程中要注意的是,对自动重置事件
调用SetEvent 和PulseEvent 有可能会引起死锁,必须小心。
多线程同步-event
在所有的内核对象中,事件内核对象是个最基本的。它包含一个使用计数(与所有内核
对象一样),一个BOOL 值(用于指明该事件是个自动重置的事件还是一个人工重置的事件),
还有一个BOOL 值(用于指明该事件处于已通知状态还是未通知状态)。事件能够通知一个线
程的操作已经完成。有两种类型的事件对象。一种是人工重置事件,另一种是自动重置事件。
他们不同的地方在于:当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度
线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线
程。
当一个线程执行初始化操作,然后通知另一个线程执行剩余的操作时,事件使用得最频
繁。在这种情况下,事件初始化为未通知状态,然后,当该线程完成它的初始化操作后,它
就将事件设置为已通知状态,而一直在等待该事件的另一个线程在事件已经被通知后,就变
成可调度线程。
当这个进程启动时,它创建一个人工重置的未通知状态的事件,并且将句柄保存在一个
全局变量中。这使得该进程中的其他线程能够非常容易地访问同一个事件对象。程序一开始
创建了三个线程,这些线程在初始化后就被挂起,等待事件。这些线程要等待文件的内容读
入内存,然后每个线程都会访问这段文件内容。一个线程进行单词计数,另一个线程运行拼
写检查,第三个线程运行语法检查。这3 个线程函数的代码的开始部分都相同,每个函数
都调用WaitForSingleObject.,这将使线程暂停运行,直到文件的内容由主线程读入内存为
止。一旦主线程将数据准备好,它就调用SetEvent,给事件发出通知信号。这时,系统就
使所有这3 个辅助线程进入可调度状态,它们都获得了C P U 时间,并且可以访问内存块。
这3 个线程都必须以只读方式访问内存,否则会出现内存错误。这就是所有3 个线程能够同
时运行的唯一原因。如果计算机上配有三个以上CPU,理论上这个3 个线程能够真正地同
时运行,从而可以在很短的时间内完成大量的操作
如果你使用自动重置的事件而不是人工重置的事件,那么应用程序的行为特性就有很大
的差别。当主线程调用S e t E v e n t 之后,系统只允许一个辅助线程变成可调度状态。同
样,也无法保证系统将使哪个线程变为可调度状态。其余两个辅助线程将继续等待。已经变
为可调度状态的线程拥有对内存块的独占访问权。
让我们重新编写线程的函数,使得每个函数在返回前调用S e t E v e n t 函数(就像
Wi n M a i n 函数所做的那样)。
当主线程将文件内容读入内存后,它就调用SetEvent 函数,这样操作系统就会使这三
个在等待的线程中的一个成为可调度线程。我们不知道系统将首先选择哪个线程作为可调度
线程。当该线程完成操作时,它也将调用S e t E v e n t 函数,使下一个被调度。这样,三
个线程会以先后顺序执行,至于什么顺序,那是操作系统决定的。所以,就算每个辅助线程
均以读/写方式访问内存块,也不会产生任何问题,这些线程将不再被要求将数据视为只读
数据。
这个例子清楚地展示出使用人工重置事件与自动重置事件之间的差别。
P u l s e E v e n t 函数使得事件变为已通知状态,然后立即又变为未通知状态,这就像
在调用S e t E v e n t 后又立即调用R e s e t E v e n t 函数一样。如果在人工重置的事件上
调用P u l s e E v e n t 函数,那么在发出该事件时,等待该事件的任何一个线程或所有线
程将变为可调度线程。如果在自动重置事件上调用P u l s e E v e n t 函数,那么只有一个
等待该事件的线程变为可调度线程。如果在发出事件时没有任何线程在等待该事件,那么将
不起任何作用[2] 。
临界区
2、Critical Section
使用临界区域的第一个忠告就是不要长时间锁住一份资源。这里的长时间是相对的,视
不同程序而定。对一些控制软件来说,可能是数毫秒,但是对另外一些程序来说,可以长达
数分钟。但进入临界区后必须尽快地离开,释放资源。如果不释放的话,会如何?答案是不
会怎样。如果是主线程(GUI 线程)要进入一个没有被释放的临界区,呵呵,程序就会挂了!
临界区域的一个缺点就是:Critical Section 不是一个核心对象,无法获知进入临界区的线程
是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法
释放该临界资源。这个缺点在互斥器(Mutex)中得到了弥补。Critical Section 在MFC 中的相
应实现类是CcriticalSection。CcriticalSection::Lock()进入临界区,CcriticalSection::
UnLock()离开临界区。
互斥器
3、Mutex
互斥器的功能和临界区域很相似。区别是:Mutex 所花费的时间比Critical Section 多
的多,但是Mutex 是核心对象(Event、Semaphore 也是),可以跨进程使用,而且等待一个
被锁住的Mutex 可以设定TIMEOUT,不会像Critical Section 那样无法得知临界区域的情
况,而一直死等。MFC 中的对应类为CMutex。Win32 函数有:创建互斥体CreateMutex() ,
打开互斥体OpenMutex(),释放互斥体ReleaseMutex()。Mutex 的拥有权并非属于那个产
生它的线程,而是最后那个对此Mutex 进行等待操作(WaitForSingleObject 等等)并且尚
未进行ReleaseMutex()操作的线程。线程拥有Mutex 就好像进入Critical Section 一样,一
次只能有一个线程拥有该Mutex。如果一个拥有Mutex 的线程在返回之前没有调用
ReleaseMutex(),那么这个Mutex 就被舍弃了,但是当其他线程等待(WaitForSingleObject
等)这个Mutex 时,仍能返回,并得到一个WAIT_ABANDONED_0 返回值。能够知道一个
Mutex 被舍弃是Mutex 特有的。
信号量
4、Semaphore
信号量是最具历史的同步机制。信号量是解决producer/consumer 问题的关键要素。
对应的MFC 类是Csemaphore。Win32 函数CreateSemaphore()用来产生信号量。
ReleaseSemaphore()用来解除锁定。Semaphore 的现值代表的意义是可用的资源数,
如果Semaphore 的现值为1,表示还有一个锁定动作可以成功。如果现值为5,就表示还
有五个锁定动作可以成功。当调用Wait…等函数要求锁定,如果Semaphore 现值不为0,
Wait…马上返回,资源数减1。当调用ReleaseSemaphore()资源数加1,当然不会超过
初始设定的资源总数。
16.请写出下面二叉树的后序遍历节点顺序:DFGEBIHCA
17.linux 进程的三种状态(可执行状态)(可中断的睡眠状态)(不可中断的睡眠状态)
1.R (TASK_RUNNING)状态,可执行状态。
只有在该状态的进程才可能在CPU 上运行。而同一时刻可能有多个进程处于可执行状态,这
些进程的task_struct 结构(进程控制块)被放入对应CPU 的可执行队列中(一个进程最多
只能出现在一个CPU 的可执行队列中)。进程调度器的任务就是从各个CPU 的可执行队列中
分别选择一个进程在该CPU 上运行。
很多操作系统教科书将正在CPU 上执行的进程定义为RUNNING 状态、而将可执行但是尚未被
调度执行的进程定义为READY 状态,这两种状态在linux 下统一为TASK_RUNNING 状态。
2.S (TASK_INTERRUPTIBLE)状态,可中断的睡眠状态。
处于这个状态的进程因为等待某某事件的发生(比如等待socket 连接、等待信号量),而
被挂起。这些进程的task_struct 结构被放入对应事件的等待队列中。当这些事件发生时(由
外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。
通过ps 命令我们会看到,一般情况下,进程列表中的绝大多数进程都处于
TASK_INTERRUPTIBLE 状态(除非机器的负载很高)。毕竟CPU 就这么一两个,进程动辄几
十上百个,如果不是绝大多数进程都在睡眠,CPU 又怎么响应得过来。
3.D (TASK_UNINTERRUPTIBLE)状态,不可中断的睡眠状态。
与TASK_INTERRUPTIBLE 状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。不可
中断,指的并不是CPU 不响应外部硬件的中断,而是指进程不响应异步信号。
绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发
现,kill -9 竟然杀不死一个正在睡眠的进程了!于是我们也很好理解,为什么ps 命令看
到的进程几乎不会出现TASK_UNINTERRUPTIBLE 状态,而总是TASK_INTERRUPTIBLE 状态。
而TASK_UNINTERRUPTIBLE 状态存在的意义就在于,内核的某些处理流程是不能被打断的。
如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入
的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。在进程
对某些硬件进行操作时(比如进程调用read 系统调用对某个设备文件进行读操作,而read
系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用
TASK_UNINTERRUPTIBLE 状态对进程进行保护,以避免进程与设备交互的过程被打断,造成
设备陷入不可控的状态。这种情况下的TASK_UNINTERRUPTIBLE 状态总是非常短暂的,通过
ps 命令基本上不可能捕捉到。
linux 系统中也存在容易捕捉的TASK_UNINTERRUPTIBLE 状态。执行vfork 系统调用后,父
进程将进入TASK_UNINTERRUPTIBLE 状态,直到子进程调用exit 或exec
18.浏览器访问某页面,http 返回状态码为403 表示(资源不可用。服务器理解客户的请求,
但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。禁止访问:IIS 定义了许多
不同的403 错误,它们指明更为具体的错误原因)
19.快速排序法在序列已经有序的情况下的复杂度是(O(n*n))
编程题
1.给定一个序列,取出其中第二大的数,要求不对整个序列排序
package JAVA 语言基础笔试题3;
/**
*
* @author 超红谨防假冒
*/
public class 找出第二大的数{
public static void main(String[] args) {
int arr[] = { 1, 12, 32, 423, 1, 232, 111 };
GetSecondMaxNumber(arr, 7);
}
/**
* 思路:如果当前元素大于最大数max,则让第二大数等于原来的最大数max,
* 再把当前元素的值赋给
* max。如果当前的元素大于等于第二大数secondMax 的值而小于最大数max 的
* 值,则要把当前元素的值赋给secondMax。
*
* @param arr
* @param n
*/
static void GetSecondMaxNumber(int[] arr, int n) {
int i, max, second_max;
max = arr[0];
second_max = 0x80000000;
System.out.println("start");
for (i = 1; i < n; ++i) {
if (arr[i] > max) {
second_max = max;
max = arr[i];
} else if (arr[i] <= max && arr[i] >= second_max) {
second_max = arr[i];
}
}
System.out.println("第二大的数:" + second_max);
}
}
2.太简单的题目,在此不屑
3.给你一个单词a,如果通过交换单词中字母的顺序可以得到另外的单词b,那么单词a 是单
词b 的兄弟单词,现在给你一个字典,用户输入一个单词,让你根据字典找出有多少个兄弟
单词。请给出算法思路和代码实现。
思路:首先用全排列方法写出该单词的所有可能兄弟单词。如army 有4×3×2×1=24 种结
果(还没有优化有相同字符或大写字母的情况),然后用每一个'兄弟单词'去和字典中的单词匹
配,找到即为兄弟单词。下面给出全排列算法的代码,最直接的就是用递归了:
将字典中的和‘比较单词’首字母相同的单词取出存放到一个数组中,将每一个组合和字典
中的单词比较。首先比较单词长度,长度相同,则继续比较;否则,比较下一个单词。算法
如下:
package JAVA 语言基础笔试题3;
/**
*
* @author 超红谨防假冒
* :首先用全排列方法写出该单词的所有可能兄弟单词。
* 如army 有4×3×2×1=24 种结果(还没有优化有相同字符或大写字母的情况
* ),然后用每一个'兄弟单词'去和字典中的单词匹配,找到即为兄弟单词。
* 下面给出全排列算法的代码,最直接的就是用递归了:
*
* 将字典中的和‘比较单词’首字母相同的单词取出存放到一个数组中,
* 将每一个组合和字典中的单词比较。首先比较单词长度,长度相同,则继
续比较;否则
* ,比较下一个单词。算法
*/
public class 查找兄弟单词{
/**
* @param src
* @param start
* 起始位置索引
* @param end
* 结束位置索引
*/
public static void perm(String[] src, int start, int end) {
if (start == end) {
// 当只要求对数组中一个字母进行全排列时,只要按该数组输出即可
for (int i = 0; i <= end; i++) {
System.out.print(src[i]);
}
System.out.println();
} else {
// 多个字母全排列
for (int i = start; i <= end; i++) {
String temp = src[start];
// 交换数组第一个元素与后续的元素
src[start] = src[i];
src[i] = temp;
perm(src, start + 1, end);
// 后续元素递归全排列
temp = src[start];
// 将交换后的数组还原
src[start] = src[i];
src[i] = temp;
}
}
}
/**
* @param src
* 字典中的单词
* @param des
* 要比较的单词,因为要做大量比较,所以转化为字符数组
* @return
*/
public static boolean compare(String src, String[] des) {
int len = src.length();
if (len != des.length) {
// 如果长度不相等,肯定不是兄弟单词,则无需比较
return false;
}
int i = 1;
// i 等于1 是因为首字符已经相同,无需比较
while (i < len) {
if (des[i].equals(String.valueOf(src.charAt(i)))) {
i++;
continue;
}
return false;
}
return true;
}
}
4.通过键盘输入一串小写字母(a~z)组成的字符串。请编写一个字符串压缩程序,将字符串中
连续出席的重复字母进行压缩,并输出压缩后的字符串。
压缩规则:
1、仅压缩连续重复出现的字符。比如字符串"abcbc"由于无连续重复字符,压缩后的字符串
还是"abcbc"。
2、压缩字段的格式为"字符重复的次数+字符"。例如:字符串"xxxyyyyyyz"压缩后就成为
"3x6yz"。
package JAVA 语言基础笔试题3;
/**
*
* @author 超红
* 谨防假冒
*/
public class 字符串压缩{
public static void main(String[] args) {
String str="xxxyyyyyyz";
System.out.println(condense(str));
}
public static String condense(String str) {
char[] c = str.toCharArray();
//形成新的字符串
StringBuilder res = new StringBuilder();
int count = 1;// 记录重复的次数
char c1;
int length = c.length;
for (int i = 0; i < length; i++) {
c1 = c[i];
if (i < length - 1) {
if (c[i + 1] == c[i]) {
count++;
if (i != length - 2) {
continue;
}
}
if (count > 1) {
res.append(count).append(c1);
count = 1;// 上一次重复字母统计结束,计数器归为初始值
} else {
res.append(c[i]);
}
} else {
if (c[i] != c[i - 1]) {
res.append(c[i]);
}
}
}
return res.toString();
}
}