PHP面试题

13次阅读

共计 21543 个字符,预计需要花费 54 分钟才能阅读完成。

词法结构

题 1 php 区分大小写吗?

分析:用户定义的类和函数、内置的结构以及关键字例如 echo,while,class 是不区分大小写的,但是变量和常量区分大小写,例如 $user,$User,$USER 是 3 个不同的变量。

表达式和操作符

题 1.
<?php

$a = 3;
$b = 5;
if($a = 5 || $b = 7) {
    $a++;
    $b++;
}

echo '$a =',$a,', $b =',$b;

分析:该题考察 php 中操作符的优先级,根据 php 手册,逻辑运算符 || 的优先级要高于赋值运算符 =,因此if 条件实际的顺序应该是 if($a = (5 || $b = 7))$a 的值是bool 类型的,由于 5true,所以会执行 $a++; $b++;,由于$abool类型的,所以 $a++ 的结果仍然是 1,用var_dump() 打印 $a 的结果是 boolean true$b 的结果是 6
所以本题最后的结果是$a = 1, $b = 6

题 2 不使用比较运算符求两个数中较大者
function max($a, $b) {return ($a + $b + abs($a -$b)) / 2;
}
题 3 求下面的程序输出什么
echo '1+5='. 1 + 5,PHP_EOL;
echo '1+5='. 5 + 1,PHP_EOL;
echo '5+1='. 5 + 1,PHP_EOL;
echo '5+1='. 1 + 5,PHP_EOL;

分析:此题隐式转换和操作符优先级。.+ 的优先级是相同的。字符串 '1+5='. 1 会转换成1, 所以第一行会输出运算结果6,以此类推,后面 3 行输出2610


变量

题 1
<?php
 
$a =  array(1);
$b = & $a[0]; 
$c = $a;
$c[0]++;
 
echo $c[0].$a[0];

分析:本题考察变量的引用。在 赋值数组 的时候,如果 = 右边的数据存在引用,那边赋值的新数组对应的元素也是引用,所以改变 $c[0] 的值 也会同时改变 $a[0] 的值,因此这段代码输出的结果是 22
如果没有第 2$b = & $a,那么输出的结果是 21
如果是 $a 是普通变量,例如

<?php

$a = 1;
$b = & $a;
$c = $a;
$c++;

echo $c,$a;

输出的结果也是21

题 2
<?php

$a = 1; 
$x = &$a; 
$b = $a++; 

$b$x 的值
分析:此题考察变量的引用赋值以及递增运算符(++$a, $a++, --$a, $a--
由于 $b = $a++;,因此 $b等于 1,同时$a 递增 1,即此时$a 等于 2,由于$x$a的引用,即 $x$a的别名,指向的是同一个内容,因此 $x 等于2

题 3 以下代码输出什么
$a = 2;
$b = &$a;
unset($a);
echo $b;

分析:此题同样考察变量的引用。在引用赋值之后,$a$b 有相同的值但不同的名称,销毁其中任何一个变量,不会影响其值的其他别名。

题 4 PHP 如何管理内存,它的垃圾收集机制是怎样的

分析:
php 使用 引用计数 写时复制 来管理内存。写时复制保证了变量间复制值不浪费内存,引用计数保证了不再需要时将内存释放给操作系统。
当将一个变量的值复制到另一个变量时,php 没有为复制值使用更多的内存,相反,它会更新 符号表(符号表是一个将变量名映射到内存中变量值所在地址的数组)来说明这两个变量拥有相同的内存块,如果后来修改了任意一个副本,php 将分配所学的内存来进行复制,例如:

$username = ['dee', 'lee', 'john];
$users = $username; // 数组不被复制
$user[1] = 'jack';  // 值改变,数组被复制

通过延迟分配和复制,php 在很多情形下节省了时间和内存,这就是写时复制。符号表中每一个值都有一个引用计数器,它的数值表示获取那片内存的方式的数目。在上例中,在 $username$users初始化赋值后,数组指向了符号表,$username 和 $user 的引用计数器的值为 2,即那块内存有两种方式可以获得:通过 $username 和 $users。当 $users\[1\]被改变时,php 为 $users 创建了一个新的数组,此时 $username 和 $users 的引用计数器的值各位 1.
当一个变量离开作用域,例如函数参数和局部变量到达函数末尾时,它的值的引用计数减 1。当一个变量在其他内存空间被赋值时,旧值的引用计数减 1。当引用计数的值为 0 时,他的内存被释放,这就是引用计数。

题 5 PHP 中变量的生命周期

分析:
局部变量:为其所在函数被调用的整个过程。当局部变量所在的函数结构结束时,局部变量的生命周期也随之结束。
全局变量:为其所在“.php”脚本文件被调用的整个过程。当全局变量所在脚本文件结束调用时,全局变量的生命周期结束
静态变量:有时某个定义函数结束后,我们希望该函数内的变量仍然存在,就需要将这个变量声明为静态变量(static)。

题 6 请说说你对 PHP 中引用类型和值类型的理解

分析:
值类型:大部分变量类型,如字符串,整型,浮点型,数组等,赋值时会在内存中创建一个新的变量。
引用类型:引用就是创建变量的别名。赋值之后两个变量拥有相同的变量但是不同的名称。对象的赋值不会创建新的变量。引用的优点是,当大的字符串或数组进行赋值时可以节省资源。

题 7 交换两个变量的值,不使用新的变量

分析:
方法① 使用异或运算符

<?php

$a = 4;
$b = 3;
$a = $a ^ $b;
$b = $a ^ $b;
$a = $a ^ $b;

方法②
使用 list() 函数

<?php

$a = 'memcached';
$b = 'redis';
list($a, $b) = array($b, $a);
题 7 用最短的代码找出 3 个数字中的最大值

分析:可以使用三元运算

<?php

($a = ($a > $b ? $a : $b)) > $c ? $a : $c;

不够短?还可以使用 max() 函数:

<?php

$max = max(array($a, $b, $c));
题 8 以下代码会输出?
$A="Hello"; 
function print_A() {
      $A= "php mysql !!";
      global$A; 
      echo$A;
}
echo$A;
print_A();

分析:HelloHello
第一次执行 echo $A; ,$A 的值是 Hello, 第二次在方法中赋值的$A 是局部变量,global $A声明全局变量 $A , 则在函数中echo $A 由于是全局变量所以值还是 Hello。如果本题在globle $A 后执行 $A = "php mysql" 结果就是Hellophp mysql!! 了。


函数

题 1 include, include_once, require, require_once 这几个函数有什么区别?

分析:
includerequire 这两个关键字都是包含并运行指定文件,不同的是 include 在引入不存的文件时产生一个警告且脚本还会继续执行,而 require 则会导致一个致命性错误且脚本停止执行
include_once语句与 include 类似,唯一的区别是如果该文件中已经被加载成功了,则后续再次加在同一个文件的操作会被忽略,require_oncerequire 的区别也是如此。

题 2 isset()、empty()与 is_null 的区别

分析:
isset()函数检测变量是否设置,如果变量存在并且值不是 NULL 则返回 TRUE,否则返回 FALSE。
empty()函数检查一个变量是否为空,当变量存在,并且是一个非空非零的值时返回 FALSE 否则返回 TRUE
is_null()函数检测变量是否为 NULL,如果变量是 null 则返回 TRUE,否则返回 FALSE。

题 3 php 中传值与传引用的区别

按值传递 :函数范围内对值的任何改变在函数外部都会被忽略
引用传递:函数范围内对值的任何改变在函数外部也能反映出这些修改

题 4 用 php 写出显示客户端 IP、服务器端 IP 的代码和网页地址

客户端 IP:$_SERVER["REMOTE_ADDR"]
服务器端 IP:$_SERVER["SERVER_ADDR"]
网页地址:$_SERVER["REQUEST_URI"]
当前脚本的执行路径:$_SERVER["SCRIPT_FILENAME"]或者 __FILE__
当前脚本的名称:$_SERVER["PHP_SELF"]或者 $_SERVER["SERIPT_NAME"]
链接到前一页的 URL 地址:$_SERVER["HTTP_REFERER"]

题 5 echo,print(),print_r(),printf(),sprintf(),var_dump()有什么区别

echo是语言结构,用于输出字符串到页面
print()函数发送一个值(它的参数)给浏览器
print_r()函数可以智能地显示传给它的参数,可以打印字符串和数字,也可以打印数组和对象
printf()函数可以通过替换模版中的值输出字符串
var_dump()print_r() 更适合调试,它用更适合阅读的格式显示所有 php 的值

题 6 php 中对数组序列化和反序列化的函数,把 utf- 8 转换成 gbk 的函数

serializeunserializeiconv("utf-8","gbk",$strs)

题 7 strlen()与 mb_strlen 的作用分别是什么?

strlen()无法正确处理中文字符串的占位,对于 gb2312 得到的是汉字个数的 2 倍,utf8得到的是汉字个数的 3

mb_strlen()就很好的解决了这个问题,它的第二个参数就是设置字符编码的

题 7 exit 和 return 有什么区别

分析:
脚本执行到 exit 语句时,就会停止执行
return语句一般用于某个函数退出返回,或者囧啊本停止执行

字符串

题 1 使用 5 种方式获取一个文件的扩展名

分析:
可以使用 php 内置的函数、数组函数、字符串函数来实现。
1. 内置函数 pathinfo(),在返回的数组中键为extension 的值就是扩展名

<?php

$filename = 'pic.20160626.jpg';

$pathinfo = pathinfo($filename);
var_dump($pathinfo['extension']); // jpg

2. 同样使用内置函数pathinfo(),带上第二个参数PATHINFO_EXTENSION

$ext = pathinfo($filename, PATHINFO_EXTENSION);
var_dump($ext);

3. 数组函数explode()+count()

$arr = explode('.', $filename);
var_dump($arr[count($arr) - 1]); // jpg

4. 字符串函数 strrpos()+substr():strrpos() 用于计算指定字符串在目标字符串中最后一次出现的位置

$position = strrpos($filename, '.');
var_dump(substr($filename, $position + 1)); // jpg

5. 字符串函数 strrchr():strrchr() 查找指定字符在字符串中的最后一次出现,并该函数返回字符串的一部分

var_dump(strrchr($filename, '.')); // .jpg
题 2 给任意一段 URL,取出该 URL 中包含的扩展名

分析:
类似于上一题。也可以使用内置函数 parse_url()+basename() 来解析 URL:

<?php

$url = 'http://www.sina.cn/index.php?username=dee';

$parse_url = parse_url($url);
$basename = basename($parse_url['path']); // index.php
$arr = explode('.', $basename);
$ext = array_pop($arr); // php
题 3 PHP 中单引号和双引号有什么区别?哪个速度更快?

分析:
1. 用单引号括起来的变量不能被解析
2. 在单引号括起来的字符串中只有单引号 ’ 和反斜线܀要用反斜线转义;用双引号括起来的变量能够被解析,有双引号、换行符、回车、制表符、大括号、中括号等需要转义。
3. 如果包含的字符串中含有变量,使用单引号更快,因为单引号不会解析变量,而双引号解析变量需要时间。

数组

题 1 写个函数用来对二维数组排序

解析:
情况一、如果是针对二维数组中每个元素的某个值进行排序,例如

$arr = [['name'=>'dee','age'=>28],
    ['name'=>'emperor', 'age'=>27],
    ['name'=>'Lee', 'age'=>20],
    ['name'=>'Arshavin', 'age'=>33],
    ['name'=>'Totti', 'age'=>40],
    ['name'=>'K6', 'age'=>27],
];

按照 age 的值从小到大进行排序
解法① 可以使用 usort() 函数

$arr = [['name'=>'dee','age'=>28],
    ['name'=>'emperor', 'age'=>27],
    ['name'=>'Lee', 'age'=>20],
    ['name'=>'Arshavin', 'age'=>33],
    ['name'=>'Totti', 'age'=>40],
    ['name'=>'K6', 'age'=>27],
];

usort($arr, function($a, $b){return $a['age'] >= $b['age'] ? 1 : 0;
});

var_dump($arr);

解法② 使用 array_multisort() 函数

$arr = [['name'=>'dee','age'=>28],
    ['name'=>'emperor', 'age'=>27],
    ['name'=>'Lee', 'age'=>20],
    ['name'=>'Arshavin', 'age'=>33],
    ['name'=>'Totti', 'age'=>40],
    ['name'=>'K6', 'age'=>27],
];

$age = [];
foreach($arr as $val) {$age[] = $val['age'];
}
array_multisort($age, SORT_ASC, $arr);

var_dump($arr);

情况二、如果是针对二维数组中每个元素的某几个值进行排序,例如

$arr = [['name'=>'dee','age'=>28, 'height'=>180],
    ['name'=>'emperor', 'age'=>27, 'height'=>181],
    ['name'=>'Lee', 'age'=>20, 'height'=>170],
    ['name'=>'Arshavin', 'age'=>33, 'height'=>173],
    ['name'=>'Totti', 'age'=>40, 'height'=>183],
    ['name'=>'Jonh', 'age'=>27, 'height'=>170],
];

要根据 age 从小到到,age相同的按 height 从大到小排序:

$arr = [['name'=>'dee','age'=>28, 'height'=>180],
    ['name'=>'emperor', 'age'=>27, 'height'=>181],
    ['name'=>'Lee', 'age'=>20, 'height'=>170],
    ['name'=>'Arshavin', 'age'=>33, 'height'=>173],
    ['name'=>'Totti', 'age'=>40, 'height'=>183],
    ['name'=>'Jonh', 'age'=>27, 'height'=>170],
];

$age = $height = [];
foreach($arr as $val) {$age[] = $val['age'];
    $height[] = $val['height'];
}
array_multisort($age, SORT_ASC, $height, SORT_DESC, $arr);

var_dump($arr);

情况三、如果是针对多维数组中每个元素的某几个值进行排序,例如

$arr = [['name'=>'dee','info'=>['subinfo'=>['age'=>28, 'height'=>180]]],
    ['name'=>'emperor', 'info'=>['subinfo'=>['age'=>27, 'height'=>181]]],
    ['name'=>'Lee', 'info'=>['subinfo'=>['age'=>20, 'height'=>170]]],
    ['name'=>'Arshavin', 'info'=>['subinfo'=>['age'=>33, 'height'=>173]]],
    ['name'=>'Totti', 'info'=>['subinfo'=>['age'=>40, 'height'=>183]]],
    ['name'=>'Jonh', 'info'=>['subinfo'=>['age'=>27, 'height'=>170]]]
];

要根据 age 从小到到,age相同的按 height 从大到小排序:
$arr = [

['name'=>'dee','info'=>['subinfo'=>['age'=>28, 'height'=>180]]],
['name'=>'emperor', 'info'=>['subinfo'=>['age'=>27, 'height'=>181]]],
['name'=>'Lee', 'info'=>['subinfo'=>['age'=>20, 'height'=>170]]],
['name'=>'Arshavin', 'info'=>['subinfo'=>['age'=>33, 'height'=>173]]],
['name'=>'Totti', 'info'=>['subinfo'=>['age'=>40, 'height'=>183]]],
['name'=>'Jonh', 'info'=>['subinfo'=>['age'=>27, 'height'=>170]]]

];

function get_key($arr, $key) {

$weight = [];
if(is_array($arr)) {foreach ($arr as $k => $v) {if ($key === $k) {$weight[] = $v;
        } else {$tmp = get_key($v, $key);
            if(! empty($tmp)) {$weight[] = $tmp[0];
            }
        }
    }
    return $weight;
}

}

$age = ‘age’;
$age = get\_key($arr, $age);

$height = ‘height’;
$height = get\_key($arr, $height);
array_multisort($age, SORT\_ASC, $height, SORT_DESC, $arr);

echo ‘<pre>’;
print_r($arr);

类和对象

题 1 接口与抽象类的区别是什么?

抽象类:

抽象类是不能被实例化的类,只能作为其他类的父类来使用,抽象类是通过关键字 abstract 来声明

抽象类与普通类类似,都包含成员变量和成员方法,两者的区别在于,抽象类中至少包含一个抽象方法

抽象方法没有方法体,该方法天生就是要被子类重写的

抽象方法的格式为:abstract function abstractMethod()

子类继承抽象类使用 extends

接口:

接口是通过 interface 关键字来声明,接口中的成员常量和方法都是 public 的,方法可以不写关键字 public

接口中的方法也是没有方法体的,接口中的方法也是天生要被子类实现的

接口能实现多继承

子类实现接口使用 implements

题 2 以下代码会输出什么
class Dog {
    public $age;
    public function __construct($age) {$this->age = $age;}
}

function test($dog) {$dog->age = 10;}

$dog = new Dog(100);
test($dog);
echo $dog->age;

题 3 以下代码会输出什么

class Dog {
    public $age;
    public function __construct($age) {$this->age = $age;}
}

function test($dog) {$dog = new Dog(10);
}

$dog = new Dog(100);
test($dog);
echo $dog->age;

题 4 以下代码会输出什么

class Dog {
    public $age;
    public function __construct($age) {$this->age = $age;}
}

function test($dog) {$dog = null;}

$dog = new Dog(100);
test($dog);
echo $dog->age;

题 5 以下代码会输出什么

class Dog {
    public $age;
    public function __construct($age) {$this->age = $age;}
}

function test(&$dog) {$dog = new Dog(10);
}

$dog = new Dog(100);
test($dog);
echo $dog->age;

错误和异常


缓存与静态化


时间日期

题 1 知道一天的日期,求任意一天叮得日期
题 2 用 php 打印出前一天的时间格式

文件

题 1 写一个函数,算出两个文件的相对路径
<?php
function relative_path($a, $b) {$a2arr = explode('/', $a);
    $b2arr = explode('/', $b);

    $diff = array_diff($a2arr, $b2arr);

    $count = count($diff);
    $relative = str_repeat('../', $count);
    array_pop($b2arr);

    return $relative.implode('/', array_diff($b2arr, $a2arr));
}
题 2 写一个函数, 能够遍历一个文件夹下的所有文件和子文件夹
<?php

function tree($dir) {
    static $files;
    if(is_dir($dir)) {if($handle = opendir($dir)) {while(($file = readdir($handle)) !== false) {if(is_dir("$dir/$file")) {if(! in_array($file, array('.', '..'))) {$files[] = "$dir/$file";
                        tree("$dir/$file");
                    }
                } else {$files[] = "$dir/$file";
               }
            }
       }
   }
   return $files;

}

$dir = ‘D:practise/test’;

题 3 删除某目录下的子目录及文件

分析:需要使用到的目录函数包括is_dir()opendir()readdir()rmdir()closedir();需要用到的文件函数包括is_file()unlink(),使用递归算法

function deldir($dir) {if(is_dir($dir)) {$handle = opendir($dir);
        while (false !== ($file = readdir($handle))) {if($file == '.' || $file == '..'){continue;}    
            if(is_dir($dir.'/'.$file)) {deldir($dir.'/'.$file);
            } else {unlink($dir.'/'.$file);
            }
        }
        closedir($handle);
        rmdir($dir);
    } elseif(is_file($dir)) {unlink($dir);
    } else {return 'error dir';}
    return 'del complete';
}
题 4 php 中 Web 上传文件的原理是什么,如何限制上传文件的大小?

分析:
通过 form 表单使用 POST 方法上传,可以使用户上传文本和二进制文件
客户端 html 部分如下

<form action=""method="post"enctype="multipart/form-data">
    <input type="file" name="userfile">
    <input type="submit" value="上传">
</form>

服务端通过 $\_FILES 接收通过 HTTP 上传到服务器的文件,上传的内容存储在 $_FILES’xx’ 中,然后再通过 move_uploaded_file,将上传的文件移动到新位置:

if(! empty($_FILES)) {if(is_uploaded_file($_FILES['userfile']['tmp_name'])) {if($_FILES['userfile']['error'] == UPLOAD_ERR_OK) {
            $upload_dir = './upload';
            $tmp_name = $_FILES['userfile']['tmp_name'];
            $name = $_FILES['userfile']['name'];
            if(move_uploaded_file($tmp_name, $upload_dir.'/'.$name)) {echo 'success.';} else {echo 'error';}
        }
    }
}

有多个配置可以限制上传文件的大小,在 php.ini 中:
post_max_size:(php 5.5.12)默认 3M
upload_max_filesize:(php 5.5.12)默认 64M
max_execution_time:必要的情况下还需要修改该配置,设置了脚本被解析器中止之前允许的最大执行时间,单位秒。(php 5.5.12)默认 120
memory_limit:(php 5.5.12)默认 128M


Cookie 和 Session

题 1 禁用 COOKIE 后 SEESION 还能用吗?

分析:在默认情况下 (PHP>=4.3.0),php.inisession.use_only_cookies的值为 1,也就是说在不更改任何配置的情况下,禁用了浏览器的 Cookie 功能,是没有办法使用 Session 的,因为默认情况下,Session_ID 保存在 Cookie 中。如果希望在禁用了 Cookie 之后仍然可以使用 Session,至少需要更改php.ini 中的以下几个配置:session.use_cookies改为 0session.use_only_cookies 改为0、·session.use_trans_id·改为1(表示 Session_ID 通过 url 的参数进行传递)。使用 url 传递 Session_ID 相比使用 Cookie 传递 Session_ID 来说非常不安全,一是完全暴露 Session_ID,二是容易遭到 Session 固定攻击。

题 2 如何设置 session 的过期时间

分析:如果没有设定 Session 的生存周期,保存 Session_ID 的 Cookie 是保存在内存中的,关闭浏览器后该 ID 自动注销。如果客户端没有禁用 Cookie,Cookie 在启动 Session 时扮演的是存储 Session_ID 和 Session 生命周期的角色,可以手动设置 Session 的生存期:

$lifetime = 24 * 3600;
set_cookie(session_name(), session_id(), time() + $lifetime, '/');

也可以使用 session_set_cookie_params()函数设置 Session 的生存期。
Session 过期后,PHP 会对其进行回收,因此 Session 并不是随着浏览器的关闭而消失。
如果浏览器禁用了 Cookie,那么 Session 的生命周期会随着浏览器进程的结束而结束,即只要关闭了浏览器,再次请求页面就要重新注册 Session。

题 3 请介绍 Session 的原理

分析:由于 HTTP 协议的无状态特性,协议本身并不支持服务器端保存客户端的状态信息,为了让服务器端和客户端保持联系,引入了 Session 的概念,用其来保持客户端的状态信息。Session 通过一个成为 PHPSESSID(可以更改名称)的 Cookie 和服务器端联系。Session 通过 Session_ID 判断用户。
当使用了 session_start()函数,用户第一次访问站点时,PHP 会为用户创建一个 session ID,这就是该用户的唯一标识,每一个访问的用户都会得到一个自己唯一的 session ID。session ID 会存放在响应头(Response)里的 cookie 中,之后发送给客户端,于是客户端就有了该用户在这个站点的 session ID。当用户第二次访问该站点时,浏览器会带着本地存放的 cookie(里面存有上次得到的 session ID)随着请求(Request)一起发送到服务器,服务端接到请求后会检测是否有 session ID,如果有就会找到响应的 session 文件,把其中的信息读取出来;如果没有就重新创建一个 Session_ID。

题 4 如何注销 Session

分析:Session 的注销分为 4 个步骤:
1. 开启 Session,所有有关 Session 的操作都需要先开启 Session(除非在 php.ini 中设置 session.auto_start 为 1,默认为 0)

session_start();

2. 删除所有的 session 变量,即把 session 数组清空

$_SESSION = array();

3. 如果是基于 Cookie 的 Session,则需要把保存 Session_ID 的 Cookie 删除

if(isset($_COOKIE[session_name()])) {setcookie(session_name(), '', time() - 1,'/');
}

4. 彻底销毁 Session

session_destroy();
题 5 简述 Session 的回收机制

分析:如果用户退出网站时没有主动注销帐号,那么 Session 的回收将是被动的。php.ini中和 Session 回收有关的配置有:
session.gc_maxlifetime: 表示 Session 文件的过期时间,默认为 1440 秒即 24 分钟
session.gc_probability: 默认值为 1
session.gc_divisor: 默认值为 1000
后面两个配置代表 session_start() 函数每调用 1000 次触发一次 Session 文件的全部扫描,把过期的 Session 文件删除。过期的 Session 文件的判断标准是:文件的修改时间和当前时间相差是否大于 session.gc_maxlifetime。当用户每进行一个操作哪怕是一个刷新页面的动作时,都会修改 Session 文件的修改时间。对于设置分级目录存储的 Session,php 不会自动回收,需要自己实现回收机制。

题 6 session 共享问题解决方案

分析:可以将 Session 入库(普通数据库、内存表),或者使用内存存储系统例如 Redis 来存储 Session,达到服务器间 Session 的共享。通过 session_save_handler() 函数实现。

题 7 大型网站中 Session 方面应注意什么

分析:大型网站有很多衡量标准,访问量是其中一个。对于大访问量的网站,如果 Session 按照默认的设置存储,会影响系统性能。默认情况下,Session 由文件的形式保存在指定的目录下,如果同一个目录下文件数超过 10000,文件的定位将会非常耗时,可以通过修改 php.inisession.save_path将 Session 文件存储在多级目录下;也可以保存在数据库中;最好的方式是保存在 key-value 形式的内存数据库中,例如 Redis。
另一个方面,大型网站一般有多台服务器,要做好 Session 的同步。如果是使用数据库或者 NoSQL 来存储 Session,Session 的同步会比较容易。如果是文件形式保存 Session 文件,可以使用 NFS 或者 FastDFS 等文件系统存储 Session 文件。

题 8 Session 和 Cookie 的联系和区别

分析:
如果不修改 php 的配置,则 Session 是基于 Cookie 的:Session 的唯一标识 Session_ID 保存在 Cookie 中,通过 Cookie 中的 Session_ID 来维持服务器端和客户端之间的状态。
可以通过设置保存 Session_ID 的 Cookie 的过期时间来设置 Session 的生存周期。
Cookie 通过文件或者数据库的形式保存客户端,Session 通过文件、数据库等形式保存在服务器端。

题 9 服务器端设置了 cookie 的过期时间,然后修改本地时间小于 cookie 过期时间,cookie 会不会被删除

分析:
会。因为 Cookie 的过期时间依赖于客户端时钟的标准。

题 10 如何设置 Cookie 的过期时间,关闭浏览器 Cookie 会消失吗

分析:
通过 setcookie() 函数的第三个参数 expire 设置 Cookie 的过期时间,单位为秒,例如

setcookie('username', 'dee', time() + 24 * 3600, '/');

这段代码表示 name 为 username 的 Cookie,会在 24 小时后过期。

如果要删除某个 Cookie,同样使用 setcookie() 函数,例如

setcookie('user', '', time() - 1,'/');

只需要把 Cookie 的过期时间改为小于当前的一个时间即可。


HTTP 协议

题 1 HTTP 状态码
题 2 get 请求和 post 请求的区别

算法

题 1 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

分析:可以用递归和递推两种方法实现。
① 递归
如果台阶只有 1 级,则只有 1种跳法,如果台阶有 2 级,则有 2 种跳法(一次 1 级跳 2 次或一次 2 级)。把 n 级台阶时的跳法设为 f(n),如果第一次只跳1 级,则后面的 n-1 级的跳法为 f(n-1),如果第一次跳2 级,则后面 n-2 级的跳法为 f(n-2)。当n > 2 时,n级台阶的跳法为 f(n) = f(n-1) + f(n-2),这就是一个 斐波那契数列 的问题

<?php

function fibonacci($n) {$result = [0, 1, 2];
    if($n <= 2) {return $result[$n]; // $n = 1,1 种跳法;$n = 2,2 种跳法
    }
    return fibonacci($n - 1) + fibonacci($n - 2);
}

② 递推

<?php

function climbstairs($n) {$dp = [1, 1];
    if($n < 2) {return 1;}
    for($i = 2; $i <= $n; $i++) {$dp[2] = $dp[0] + $dp[1];
        $dp[0] = $dp[1];
        $dp[1] = $dp[2];
    }
    return $dp[2];
}
题 2 用一段代码实现无限级分类

分析:此题考察递归算法。

<?php

function level_layer($cate, $name = 'child', $pid = 0) {$arr = [];
    foreach($cate as $val) {if($val['pid'] == $pid) {$val[$name] = level_layer($cate, $name, $val['id']);
            $arr[] = $val;}
    }
    return $arr;
}

$data = [['id' => 1, 'title' => '北京市', 'pid' => 0],
    ['id' => 2, 'title' => '朝阳区', 'pid' => 1],
    ['id' => 3, 'title' => '海淀区', 'pid' => 1],
    ['id' => 4, 'title' => '江西省', 'pid' => 0],
    ['id' => 5, 'title' => '九江市', 'pid' => 4],
    ['id' => 6, 'title' => '浔阳区', 'pid' => 5],
    ['id' => 7, 'title' => '庐山区', 'pid' => 5],
    ['id' => 8, 'title' => '南昌市', 'pid' => 4],
    ['id' => 9, 'title' => '河北省', 'pid' => 0],
    ['id' => 10, 'title' => '保定市', 'pid' => 9],
    ['id' => 11, 'title' => '石家庄市', 'pid' => 9],
    ['id' => 12, 'title' => '江苏省', 'pid' => 0],
    ['id' => 13, 'title' => '浙江省', 'pid' => 0],
];

echo '<pre>'; print_r(level_layer($data));
题 3 一群猴子排成一圈,按 1,2,…,n 依次编号。然后从第 1 只开始数,数到第 m 只, 把它踢出圈,从它后面再开始数,再数到第 m 只,在把它踢出去 …,如此不停的进行下去,直到最后只剩下一只猴子为止,那只猴子就叫做大王。要求编程模拟此过程,输入 m、n,输出最后那个大王的编号。

分析:这是一个“约瑟夫环”问题,可以使用循环队列、循环链表等多种方法来解决,这里使用循环链表。

<?php

// 猴子类,链表上的每一个元素
class Monkey {
    public $number;
    public $name = null;

    public function __construct($number, $name = null) {
        $this->number = $number;
        $this->name = $name;
    }
}

// 初始化链表,添加元素
function init(&$first, $n) {
    $current = null;
    for($i = 0; $i < $n; $i++) {$monkey = new Monkey($i + 1);
        if($i == 0) {
            $first = $monkey;
            $first->next = $monkey; // 只有一个元素时
            $current = $first;
        } else {
            $current->next = $monkey;
            $monkey->next = $first; // $monkey 是变化的
            $current = $current->next;
        }
    }
}

// 显示链表
function showList($first) {
    $current = $first;
    while($current->next != $first) {
        echo '猴子的编号是:',$current->number,'<br />';
        $current = $current->next;
    }
    echo '猴子的编号是:',$current->number,'<br />';
}

// 删除元素
function delElement($first, $m, $start = 1) {
    $tail = $first; // 指向尾部节点的引用,在 first 节点之前
    while($tail->next != $first) {$tail = $tail->next; // 把 tail 节点设置在 first 节点之前}

    // 从第几个元素开始数
    for($i = 0; $i < $start - 1; $i++) {
        $tail = $tail->next;
        $first = $first->next;
    }

    while($tail != $first) { // 当 $tail==$first 则说明只有最后一个元素
        for($i = 0; $i < $m - 1; $i++) {
            $tail = $tail->next;
            $first = $first->next;
        }
        echo '<br /> 删除编号'.$first->number,'的元素';
        // 跳过被删除的节点(first 指向的节点)
        $first = $first->next;
        $tail->next = $first;
    }
    echo '<br /> 最后剩下的编号是:'.$tail->number;
}

$n = 4; // 元素的个数(猴子的数量)$m = 3;   // 每次数几个元素
$start = 1; // 从第几个元素开始数

$first = null; // 链表头部节点的引用

init($first, $n);

showList($first);

delElement($first, $m, $start);
题 4 写一段 php 代码实现冒泡排序

解答:

function bubble($array) {$count = count($array);
    for($i = 0; $i < $count - 1; $i++) {for($j = 0; $j < $count - 1 - $i; $j++) {
            // 递减
            if($array[$j] < $array[$j + 1]) {$tmp = $array[$j];
                $array[$j] = $array[$j + 1];
                $array[$j + 1] = $tmp;
            }
        }
    }
    return $array;
}
题 5 写一段 php 代码实现快速排序
/* 快速排序 */
function quick_sort($array) {
// 递归到只有一个元素
if(count($array) <= 1) {return $array;}

$key = $array[0];
$left_arr = [];
$right_arr = [];

// 左边数组的元素都比右边数组的元素小
for($i = 1; $i < count($array); $i++) {if($array[$i] <= $key) {$left_arr[] = $array[$i];
    } else {$right_arr[] = $array[$i];
    }
}

// 递归对左右两个数组分别进行排序
$left_arr = quick_sort($left_arr);
$right_arr = quick_sort($right_arr);

// 合并结果
return array_merge($left_arr, array($key), $right_arr);
}

$arr = [10, 21, 1, 3, 89, 27, 75, 45, 91, 200, 451, 37, 2];
var_dump(quick_sort($arr));
题 6 写一段 php 代码实现二分查找
<?php

function binarySearch(array $array, $needle) {
    $start = 0;
    $end = count($array) - 1;

    while($start < $end) {$middle = floor(($start + $end) / 2);
        if($array[$middle] == $needle) {return $middle;}
        if($needle < $array[$middle]) {$end = $middle - 1;}
        if($needle > $array[$middle]) {$start = $middle + 1;}
    }
    return false;
}
题 7 换硬币问题:如果有 100 元,可以使用 1 元、2 元、5 元、10 元 4 种面额的零钱来兑换,有多少种兑换方法?
<?php

$a = 1; 
$b = 2;
$c = 5;
$d = 10;
$sum = 0;
for ($i = 0; $i <= 100; $i++) {for ($j = 0; $j <= 50; $j++) {for ($k = 0; $k <= 20; $k++) {for($h = 0; $h <= 10; $h++) {if ($a * $i + $b * $j + $c * $k + $d * $h == 100) {
              $sum += 1;
              printf("%d 个 1 元,%d 个 2 元,%d 个 5 元,%d 个 10 元 <br />", $i, $j, $k, $h);
            }
               }
       }
   }
}
printf("共 %d 种换法", $sum); 
题 8 鸡兔同笼问题

正则表达式

题 1 请写一个函数验证电子邮件的格式是否正确

设计模式

题 1 请用单态设计模式方法设计类满足如下需求,使用 php 代码编写类实现在每次对数据库连接的访问中都只能获取唯一的一个数据库连接,具体连接数据库的详细代码忽略,请写出主要逻辑代码。

MySQL

题 1 where 和 having 的区别
题 2 内连接、左连接、右连接的区别
题 3 mysql 优化常见的方案
题 4 explain 的用法
题 5 索引的种类和用法
题 6 mysql 的存储引擎 Innodb 和 MyISAM 的区别
题 7

一个足球网站有一张新闻表 news,字段包括新闻编号id、新闻分类cid、标题title、点击量click,现在需要对 news 表按照 cid(分类 id)进行过分组,按照每组新闻的数量进行排序,同时取出每组点击量(click)最多的两篇新闻。
分析:
创建表

CREATE TABLE `news` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '新闻编号',
  `cid` int(11) NOT NULL COMMENT '分类编号 例如 1 世界足球新闻 2 英超新闻 3 西甲新闻',
  `title` varchar(25) NOT NULL COMMENT '新闻标题',
  `click` int(11) NOT NULL DEFAULT '0' COMMENT '点击量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

表数据

INSERT INTO `news` VALUES ('1', '1', '法国 2 - 0 力克德国进决赛', '1888');
INSERT INTO `news` VALUES ('2', '2', '传曼联 1 亿英镑报价博格巴', '201');
INSERT INTO `news` VALUES ('3', '3', '队报:巴萨接近签法国国脚', '150');
INSERT INTO `news` VALUES ('4', '1', '克罗斯:德国队表现最好的一场', '100');
INSERT INTO `news` VALUES ('5', '1', '决赛对阵:法国 vs 葡萄牙', '205');
INSERT INTO `news` VALUES ('6', '1', '格列兹曼 6 场 6 球仅次普拉蒂尼', '211');
INSERT INTO `news` VALUES ('7', '1', '阿森纳法国双星赛后安慰厄齐尔', '188');
INSERT INTO `news` VALUES ('8', '2', '回声报:利物浦要求艾比交易中加入回购条款', '11');
INSERT INTO `news` VALUES ('9', '3', '普约尔:没有比巴萨更适合梅西的地方', '225');
INSERT INTO `news` VALUES ('10', '3', '皇家贝蒂斯有意狼堡前锋多斯特', '13');
INSERT INTO `news` VALUES ('11', '3', '哈维:我觉得梅西不会想离开巴萨的', '1000');
INSERT INTO `news` VALUES ('12', '2', '传温格已将西迪贝看作重点目标', '1200');

此题的需求是 1. 按新闻分类分组 2. 按照新闻分类下新闻数量给分好的组排序 3. 排好序的分组还需要取出该组分类下点击量最多的两篇新闻
需求 1 可以使用 group by 来进行分组
需求 2 可以使用 count()+group by,取出每组分类下新闻的数量
以上 2 个需求可以用一条 sql 完成

select *, count(*) as num from news group by cid order by num desc;

查询结果

可以看到已经按照数量 num 进行了排序

需求 3 可以 cid+click 进行排序,这个需求可以用一条 sql 完成,用子查询实现

select a.* from news a 
where (select count(*) from news b where a.cid = b.cid and b.click > a.click
) < 2
order by cid, click desc; 

查询结果

此时每个 cid 下点击量最多的两篇文章已经查询出来了,只不过查询的结果没有按照每个分类下新闻数量的多少来排序

只需要最后一步,把两个 sql 语句进行左(右)连接即可

select * from (
    select a.* from news a 
    where (select count(*) from news b where a.cid = b.cid and b.click > a.click
    ) < 2
    order by cid, click desc
) tablea RIGHT JOIN (select cid, count(*) as num from news group by cid order by num desc
) tableb on tablea.cid = tableb.cid 
order by tableb.num desc, click desc;

查询结果:

题 8 一张表有 id,sortid,title,如何按照 sortid 进行分组,按照 sortid 数量进行排序,输出结果。

分析:此题和题 6 类似。
表结构

CREATE TABLE `test` (`id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(25) NOT NULL,
  `sortid` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

创建数据

INSERT INTO `test` VALUES ('1', 'title1', '106');
INSERT INTO `test` VALUES ('2', 'title2', '78');
INSERT INTO `test` VALUES ('3', 'title3', '56');
INSERT INTO `test` VALUES ('4', 'title4', '78');
INSERT INTO `test` VALUES ('5', 'title5', '78');
INSERT INTO `test` VALUES ('6', 'title6', '12');
INSERT INTO `test` VALUES ('7', 'title7', '56');

分三步创建 sql

-- step 1
select a.* from test a 
where (select count(*) from test b where  b.sortid > a.sortid
) < (select count(*) from test)
order by sortid; 

-- step2
SELECT *, count(*) AS num, GROUP_CONCAT(id) AS ids FROM test GROUP BY sortid ORDER BY num; 

-- final
select * from (
    select a.* from test a 
    where (select count(*) from test b where  b.sortid > a.sortid
    ) < (select count(*) from test)
    order by sortid
) tablea LEFT JOIN (SELECT *, count(*) AS num, GROUP_CONCAT(id) AS ids FROM test GROUP BY sortid ORDER BY num
) tableb 
on tablea.sortid = tableb.sortid
order by tableb.num asc;

此题没有要求列出每个分组的前 N 条数据,那就把每个分组下所有的数据都列出来,查询结果如下:

题 9

有如下表及数据

name

subject

score

张三

数学

90

张三

语文

50

张三

地理

40

李四

语文

55

李四

政治

45

王五

政治

30

要求: 查询出 2 门及 2 门以上不及格者的平均成绩
解析:
先查出所有人的平均分(AVG(score)),再找出每个人 2 门及 2 门以上不及格的数量(SUM(score<60)),最后筛选出数量大于等于 2 的人的信息(Having num>=2)

SELECT name, AVG(score) AS avg, SUM(score < 60) AS num 
FROM result 
GROUP BY name 
HAVING num >= 2;

查询结果:

方法 2:

SELECT `name`, AVG(score) AS avg FROM result WHERE `name` in (SELECT name FROM result GROUP BY name HAVING SUM(score<60) >= 2 
)
GROUP BY `name`;

查询结果:

方法 3:复杂很多

SELECT tablea.name, tablea.avg, tableb.num FROM (SELECT *,AVG(score) AS avg FROM result GROUP BY name
) tablea RIGHT JOIN (SELECT *, COUNT(*) AS num FROM (SELECT * FROM result WHERE score < 60) a
    GROUP BY a.name HAVING num >= 2
) tableb on tablea.name = tableb.name;

查询结果:

题 10

有两张表:
表 A

id

num

a

5

b

10

c

15

d

10

表 B

id

num

b

5

c

15

d

20

e

99

要求查询出以下效果:

id

sum(num)

a

5

b

15

c

30

d

30

e

99

分析:
先使用 UNION ALL 将两张表联合起来,再使用SUM()+GROUP BY

SELECT id, SUM(num) FROM (
    SELECT * FROM A 
    UNION ALL
    SELECT * B
) AS tmp
GROUP BY tmp.id
;
题 11 优化 MySQL 数据库的方法

综合 / 解决方案

题 1 对于大流量的网站, 可以采用哪些方法来解决访问量问题

安全


NoSQL

题 1 为什么不能使用 Memcached 存储 Session
题 2 Redis 相比 Memcached 有哪些优势
题 3 Redis 有哪些优点
题 4 如何使用 Redis 存储 Session
题 5 Redis 如何与 MySQL 同步

Linux

题 1 查看系统负载有哪些命令

Node.js


JavaScript / jQuery


HTML / CSS


SVN / Git

正文完
 0