1.python参数传递是值传递还是引用传递
都是引用,对于不可改变的数据类型来说,不能改变,如果修改了,事实上是新建一个对象来对待。
2.lambda更简单,省去命名函数名的麻烦
f = lambda x,y:x+y
print(f(1, 2))
3.format可以接受参数不限个数,并且位置可以不按顺序
print(“{1}-{0}”.format(“good”, 123)) #123-good
print(“{name}-{age}”.format(age=23, name=”lius”))
4.编码/解码
http://www.cnblogs.com/OldJack/p/6658779.html
编码是指信息从一种形式或格式转换为另一种形式或格式的过程。
在计算机中,编码,简而言之,就是将人能够读懂的信息(通常称为明文)转换为计算机能够读懂的信息。众所周知,计算机能够读懂的是高低电平,也就是二进制位(0,1组合)。而解码,就是指将计算机的能够读懂的信息转换为人能够读懂的信息。
在Python2.x中,有两种字符串类型:str和unicode类型。str存bytes数据,unicode类型存unicode数据
在Python3.x中,也只有两种字符串类型:str和bytes类型。str类型存unicode数据,bytse类型存bytes数据,与python2.x比只是换了一下名字而已。
当我们在编辑文本的时候,字符在内存对应的是unicode编码的,这是因为unicode覆盖范围最广,几乎所有字符都可以显示。但是,当我们将文本等保存在磁盘时,数据是怎么变化的?
答案是通过某种编码方式编码的bytes字节串。比如utf-8,一种可变长编码,很好的节省了空间;当然还有历史产物的gbk编码等等。于是,在我们的文本编辑器软件都有默认的保存文件的编码方式,比如utf-8,比如gbk。当我们点击保存的时候,这些编辑软件已经”默默地”帮我们做了编码工作。
那当我们再打开这个文件时,软件又默默地给我们做了解码的工作,将数据再解码成unicode,然后就可以呈现明文给用户了!所以,unicode是离用户更近的数据,bytes是离计算机更近的数据。
5.反转
def reverse(text=”abc”):
return text[::-1]
print(reverse(“12345”))
6.
7.
ll = [1,2,3,4,5,6,7]
print(ll[10:]) #输出[]
8.
keys = [“Name”, “Sex”, “Age”]
values = [“Jack”, “Male”, 19]
print(dict(zip(keys, values)))
9.
def extend(val, ll=[]):
ll.append(val)
return ll
l1 = extend(1) #[1, 3]
l2 = extend(123, []) #[123]
l3 = extend(3) #[1, 3]
l4 = extend(456, []) #[456]
print(l1, l2, l3, l4)
10.什么是GIL
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
- 设置GIL
- 切换到一个线程去运行
- 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0)) - 把线程设置为睡眠状态
- 解锁GIL
- 再次重复以上所有步骤
http://m.blog.csdn.net/universe_ant/article/details/51243137
线程全局锁(Global Interpreter Lock),即Python为了保证线程安全而采取的独立线程运行的限制,说白了就是一个核只能在同一时间运行一个线程.对于io密集型任务,python的多线程起到作用,但对于cpu密集型任务,python的多线程几乎占不到任何优势,还有可能因为争夺资源而变慢。
可以用多进程避免GIL的问题。
11.python3取消iteritems
在Python2.x中,items( )用于 返回一个字典的拷贝列表【Returns a copy of the list of all items (key/value pairs) in D】,占额外的内存。
iteritems() 用于返回本身字典列表操作后的迭代【Returns an iterator on all items(key/value pairs) in D】,不占用额外的内存。
Python 3.x 里面,iteritems() 和 viewitems() 这两个方法都已经废除了,而 items() 得到的结果是和 2.x 里面 viewitems() 一致的。在3.x 里 用 items()替换iteritems() ,可以用于 for 来循环遍历。
12.
try:
with open(“text.txt”) as f:
while True:
line = f.readline()
if not line:
break
print(line)
except FileNotFoundError:
print(“No text.txt”)
#with语句使用上下文管理器对代码块进行包装,允许上下文管理器实现一些设置和清理操作
#with用在支持上下文管理协议的对象中,如file,thread.LockType
#当with语句执行时,便执行上下文表达式(context_expr)来获得一个上下文管理器,上下文管理器的职责是提供一个上下文对象,用于在with语句块中处理细节:
#一旦获得了上下文对象,就会调用它的enter()方法,将完成with语句块执行前的所有准备工作,如果with语句后面跟了as语句,则用enter()方法的返回值来赋值;
#当with语句块结束时,无论是正常结束,还是由于异常,都会调用上下文对象的exit()方法,exit()方法有3个参数,如果with语句正常结束,三个参数全部都是 None;如果发生异常,三个参数的值分别等于调用sys.exc_info()函数返回的三个值:类型(异常类)、值(异常实例)和跟踪记录(traceback),相应的跟踪记录对象。
#因为上下文管理器主要作用于共享资源,enter()和exit()方法干的基本是需要分配和释放资源的低层次工作,比如:数据库连接、锁分配、信号量加/减、状态管理、文件打开/关闭、异常处理等。
13.生成斐波那契数列
def fibs(x):
result = [0, 1]
for i in range(x-2):
result.append(result[-2] + result[-1])
return result
print(fibs(0)) #[0,1]
14.反序的迭代一个序列
x = [1,2,3,4,5]
for i in range(len(x)-1, -1, -1):
print(x[i])
15.
L1 = [1,2,3,1,2,3,4,5]
L2 = []
[L2.append(i) for i in L1 if i not in L2]
print(L2)
16.
标准库线程安全的队列是哪一个,不安全是哪一个?logging是线程安全的吗?
线程安全即解决线程同步问题,Queue是线程安全队列,logging是线程安全的。
17.
import os
os.remove(filename)
18.得到list的交集,差集
l1 = [1,2,3,4,6]
l2 = [3,4,5,1,2]
l3 = [i for i in l1 if i not in l2]
l4 = [i for i in l1 if i in l2]
print(l3, l4)
19.
w = “Python is a very funny language”
w.find(“Python”)
w.replace(“Python”, “Ruby”)
20.
words = [“This”, “is”, “a”, “dog”]
words.sort(key=lambda x:x.lower())
print(words)
21.
lists = [“xyz”, “abc”, “opq”]
lists.sort(key=lambda x:x[1], reverse=True)
print(lists)
22.解析argv
import sys
for arg in sys.argv[1:]:
print(arg)
23.
python高并发解决方案
twisted->tornado->gevent
http://blog.csdn.net/screaming/article/details/51377870
24.
@staticmethod,@classmethod区别
http://www.cnblogs.com/elie/p/5876210.html
25.单例
class Singleton(object):
instance = None
def __new(cls, age, name):
#如果类数字能够instance没有或者没有赋值
#那么就创建一个对象,并且赋值为这个对象的引用,保证下次调用这个方法时
#能够知道之前已经创建过对象了,这样就保证了只有1个对象
if not cls.instance:
cls.instance = object.new__(cls)
return cls.__instance
26.
python用递归判断字符串是否是回文
def isPlidromNonRecursive(inputStr):
assert isinstance(inputStr, basestring)
strLen = len(inputStr)
currentStart = 0
currentEnd = strLen - 1
while currentStart <= currentEnd:
if inputStr[currentStart] != inputStr[currentEnd]:
return False
else:
currentStart += 1
currentEnd -= 1
return True
def isPlidromRecursive(inputStr):
assert isinstance(inputStr, basestring)
if 0 <= len(inputStr) <= 1:
return True
if inputStr[0] != inputStr[-1]:
return False
else:
return isPlidromRecursive(inputStr[1:-1])
def test_isPlidromNonRecursive():
assert isPlidromNonRecursive(“level”)
assert isPlidromNonRecursive(“noon”)
assert isPlidromNonRecursive(“abcd”) == False
def test_isPlidromRecursive():
assert isPlidromRecursive(“level”)
assert isPlidromRecursive(“noon”)
assert isPlidromRecursive(“abcd”) == False
if name == ‘main‘:
test_isPlidromNonRecursive()
test_isPlidromRecursive()
27.
def fib(times):
n = 0
a,b = 0,1
while n < times:
yield b
a,b = b,a+b
n+=1
return ‘done’
迭代器,yield,装饰器
yield:
https://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do
迭代器:
http://www.cnblogs.com/duwenxing/p/7397759.html
迭代,顾名思义就是重复做一些事很多次(就现在循环中做的那样)。迭代器是实现了next()方法的对象(这个方法在调用时不需要任何参数),它是访问可迭代序列的一种方式,通常其从序列的第一个元素开始访问,直到所有的元素都被访问才结束。 [注意]:迭代器只能前进不能后退
[迭代器的优点]:
使用迭代器不要求事先准备好整个迭代过程中的所有元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后元素可以不存在或者被销毁。因此迭代器适合遍历一些数量巨大甚至无限的序列。
生成器:
分类:生成器函数,生成器表达式
http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
http://www.cnblogs.com/kaituorensheng/p/3826911.html
http://python.jobbole.com/87805/
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143178254193589df9c612d2449618ea460e7a672a366000
https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/
http://blog.csdn.net/u013205877/article/details/70502508
http://python.jobbole.com/81692/
http://www.cnblogs.com/gide/p/6187080.html
http://www.cnblogs.com/patrick0715/p/5957387.html
http://blog.csdn.net/gvfdbdf/article/details/52116661
http://python.jobbole.com/86632/
可以直接作用于for循环的对象统称为可迭代对象:Iterable。
生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
import asyncio
@asyncio.coroutine
def countdown(number, n):
while n > 0:
print(‘T-minus’, n, ‘({})’.format(number))
yield from asyncio.sleep(1)
n -= 1
loop = asyncio.get_event_loop()
tasks = [
asyncio.ensure_future(countdown(“A”, 2)),
asyncio.ensure_future(countdown(“B”, 3))]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
#############
import asyncio
async def hello():
print(“Hello World”)
r = await asyncio.sleep(3)
print(“Again”)
loop = asyncio.get_event_loop()
tasks = [hello(), hello()]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
类装饰器
class Foo(object):
def init(self, func):
self.func = func
def _call(self):
print (‘class decorator runing’)
self._func()
print (‘class decorator ending’)
@Foo
def bar():
print (‘bar’)
装饰器其实也就是一个函数,一个用来包装函数的函数,返回一个修改之后的函数对象。经常被用于有切面需求的场景,较为经典的有插入日志、 性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
import functools
def log(tag=None):
def decorator(func):
@functools.wraps(func)
def wrapper(args, *kwarg):
print(tag)
return func(args, *kwarg)
return wrapper
return decorator
@log("first tag")
def run(*args, **kwarg):
print("run")
run()
print(run.__name__)
28.猴子补丁
#在函数活对象定义之后再去改变他们的行为
在运行时替换方法、属性等
在不修改第三方代码的情况下增加原来不支持的功能
在运行时为内存中的对象增加patch而不是在磁盘的源代码中增加
在运行时动态修改模块、类或函数,通常是添加功能或修正缺陷。猴子补丁在代码运行时(内存中)发挥作用,不会修改源码,因此只对当前运行的程序实例有效。因为猴子补丁破坏了封装,而且容易导致程序与补丁代码的实现细节紧密耦合,所以被视为临时的变通方案,不是集成代码的推荐方式。
29.
[1]*4 –>[1,1,1,1,1]
li = [[]] * 5
li[0].append(19)
li[1].append(29)
li.append(39)
print(li)
[[19, 29], [19, 29], [19, 29], [19, 29], [19, 29], 39]
30.
协程的概念
协程是在一个线程执行过程中可以在一个子程序的预定或者随机位置中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。他本身是一种特殊的子程序或者称作函数。
协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行。
简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.
31.
#如何设计deepcopy
http://blog.csdn.net/kuaileboy1989/article/details/44151163
#正则匹配邮箱
33.
class myClass(object):
def init(self):
self._some_property = “some property”
self._some_another_property = “some another property”
def normal_method(*args, **kwargs):
print("calling normal method{0},{1}".format(args, kwargs))
@classmethod
def class_method(*args, **kwargs):
print("calling class method{0},{1}".format(args, kwargs))
@staticmethod
def static_method(*args, **kwargs):
print("calling static method{0},{1}".format(args, kwargs))
@property
def some_property(self, *args, **kwargs):
print("calling some property getter{0},{1},{2}".format(self, args, kwargs))
return self._some_property
@some_property.setter
def some_property(self, *args, **kwargs):
print("calling some_property setter{0},{1},{2}".format(self, args, kwargs))
self._some_property = args[0]
@property
def some_another_property(self, *args, **kwargs):
print("calling some another property getter{0},{1},{2}".format(self, args, kwargs))
return self._some_another_property
o = myClass()
print(o.normal_method)#<bound method myClass.normal_method of <main.myClass object at 0x000000000267F160>>
o.normal_method()
print(o.class_method) #<bound method myClass.class_method of <class ‘main.myClass’>>
o.class_method()
print(o.static_method)#<function myClass.static_method at 0x00000000026737B8>
o.static_method()
o.some_property
o.some_another_property
o.some_property = [1,2,3]
print(o.some_property)
34
def print_content_name(path):
import os
for sChild in os.listdir(path):
sChildPath = os.path.join(path, sChild)
if os.path.isdir(sChildPath):
print_content_name(sChildPath)
else:
print(sChildPath)
35.
from functools import reduce
print(reduce(lambda x,y:x+y, [1,2,3])) #6
36.
tornado原理
https://www.rapospectre.com/blog/understanding-tornado-ioloop
http://www.nowamagic.net/academy/detail/13321002
39.
class P(object):
n = []
t = “test”
p1 = P()
p2 = P()
print(p1.t)
print(p2.t)
p1.t = “p1”
print(p1.t) #p1
print(p2.t) #test
p1.n.append(1)
p2.n.append(20)
print(p1.n) #[1, 20]
print(p2.n) #[1, 20]
40.字典推导式
dd = {“one”:1, “two”:2}
d = {k:v+1 for k,v in dd.items()}
print(d)
41.args, *kwargs
def print_everthing(args):
for count, thing in enumerate(args):
print(“{0}-{1}”.format(count, thing))
def table_things(*kwargs):
for k, v in kwargs.items():
print(“{0}-{1}”.format(k, v))
print_everthing(“one”, “two”, “three”)
table_things(first=1, second=2)
42.新式类和旧式类区别
为了统一type和class
在2.2之前,比如2.1版本中,类和类型是不同的,如a是ClassA的一个实例,那么a.class返回 ‘ class main.ClassA‘ ,type(a)返回总是<type ‘instance’>。而引入新类后,比如ClassB是个新类,b是ClassB的实例,b.class和type(b)都是返回‘class ‘main.ClassB’ ,这样就统一了。
在多继承中,新式类采用广度优先搜索,而旧式类是采用深度优先搜索。
43.Python2和Python3区别
#统一了字符编码支持
#增加了新的语法,print,格式化字符串变量,nonlocal,yield
#修改了一些语法,map,filter,dict的items/keys/values由返回列表到返回迭代对象
#去掉了一些语法xrange,不再有经典类
44.new和init的区别
http://www.jb51.net/article/52023.htm
通过运行这段代码,我们可以看到,new方法的调用是发生在init之前的。其实当 你实例化一个类的时候,具体的执行逻辑是这样的:
1.p = Person(name, age)
2.首先执行使用name和age参数来执行Person类的new方法,这个new方法会 返回Person类的一个实例(通常情况下是使用 super(Persion, cls).new(cls, … …) 这样的方式)
3.然后利用这个实例来调用类的init方法,上一步里面new产生的实例也就是 init里面的的 self。
所以,init 和 new 最主要的区别在于:
1.init 通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作,发生在类实例被创建完以后。它是实例级别的方法。
2.new 通常用于控制生成一个新实例的过程。它是类级别的方法。
new是一个静态方法,而init是一个实例方法.
new方法会返回一个创建的实例,而init什么都不返回.
只有在new返回一个cls的实例时后面的init才能被调用.
当创建一个新实例时调用new,初始化一个实例时用init.
45.
import copy
a = [1, 2, 3, 4, [‘a’, ‘b’]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(5)
a[4].append(‘c’)
print(“a=”, a) #[1,2,3,4,[‘a’, ‘b’, ‘c’], 5]
print(“b=”, b) #[1,2,3,4,[‘a’, ‘b’, ‘c’], 5]
print(“c=”, c) #[1,2,3,4,[‘a’, ‘b’, ‘c’]]
print(“d=”, d) #[1,2,3,4,[‘a’, ‘b’]]
46.is和==
is比较id,==比较值
a = 1
b = 1
print(“a=1, b=1”, a is b) #True
x = 123456789
y = 123456789
print(“x=123456789, y=123456789”, x is y) #True
a = “abc”
b = “abc”
print(“a=abc, b=abc”, a is b) #True
a = [1, 2]
b = [1, 2]
print(“a:[1,2], b:[1,2]”, a is b) #Flase
47.调度算法
先来先服务,最高优先权调度,时间片轮转,短作业优先
48.死锁
http://blog.csdn.net/abigale1011/article/details/6450845/
指多个进程循环等待它方占有的资源而无限期地僵持下去的局面。
产生死锁的必要条件:
互斥,不可抢占,占有且申请,循环等待
处理死锁基本方法:
预防死锁(摒弃除1以外的条件)
避免死锁(银行家算法)
检测死锁(资源分配图)
解除死锁
剥夺资源
撤销进程
49.
li = [lambda :x for x in range(10)]
print(type(li)) #list
print(type(li[0])) #function
res = li0
print(res) #9
50.
name = “peter”
def f1():
print(name)
def f2():
name = “bob”
f1()
f2() #peter
51.
def scope_test():
def do_local():
spam = “local spam”
def do_nonlocal():
nonlocal spam
spam = “nonlocal spam” #要想修改父函数的变量,需使用nonlocal
def do_global():
global spam
spam = “global spam”
spam = “test spam”
do_local()
print(“After local assignment:”, spam) #test spam
do_nonlocal()
print(“After nonlocal assignment:”, spam) #nonlocal spam
do_global()
print(“After global assignment:”, spam) #nonlocal spam
scope_test()
print(“In global scope:”, spam) #global spam
52.闭包 装饰器和闭包???
必须有一个内嵌函数
内嵌函数必须引用外部函数中的变量
外部函数的返回值必须是内嵌函数
Python中的闭包的概念, 就相当于在某个函数中又定义了一个或多个函数, 内层函数定义了具体的实现方式, 而外层返回的就是这个实现方式, 但并没有执行, 除非外层函数调用的内层的实现方法被执行了。
闭包函数必须含有内嵌函数,内嵌函数需要引用该嵌套函数上一级namespace中的变量,闭包函数必须返回内嵌函数。
def greeting_conf(prefix):
def greeting(name):
print(prefix, name)
return greeting
mG = greeting_conf(“Good morning”)
print(“mG name is:”, mG.name) #greeting
print(“id of mG is:”, id(mG))
mG(“Lius”)
aG = greeting_conf(“Good afternoon”)
print(“aG name is:”, aG.name)
print(“id of aG is:”, id(aG))
aG(“Lius”)
print(dir(aG))
print(aG.closure)
print(type(aG.closure[0]))
print(aG.closure[0].cell_contents)
mG name is: greeting
id of mG is: 51950456
Good morning Lius
aG name is: greeting
id of aG is: 51950592
Good afternoon Lius
53.
class A(object):
def init(self):
print(“Enter A”)
print(“Leave A”)
class B(A):
def init(self):
print(“Enter B”)
super(B, self).init()
print(“Leave B”)
class C(A):
def init(self):
print(“Enter C”)
super(C, self).init()
print(“Leave C”)
class D(A):
def init(self):
print(“Enter D”)
super(D, self).init()
print(“Leave D”)
class E(B, C, D):
def init(self):
print(“Enter E”)
super(E, self).init()
print(“Leave E”)
E()
Enter E
Enter B
Enter C
Enter D
Enter A
Leave A
Leave D
Leave C
Leave B
Leave E
54.
data = [‘1’, ‘2’, ‘3’]
print(sum(int(i) for i in data))
print(reduce(lambda x,y:int(x)+int(y), data))
- and or
对python而言
其一, 在不加括号时候, and优先级大于or
其二, x or y 的值只可能是x或y. x为真就是x, x为假就是y
第三, x and y 的值只可能是x或y. x为真就是y, x为假就是x
56.
import re
s= “123 and 222”
re.sub(“\d\d\d”, “hello”, s) #”hello and hello”
57.
import random
random.random()#0~1
random.randint(1, 20)
58.python拷贝文件
shutil模块copyfile函数
shutil.copyfile( src, dst) 从源src复制到dst中去。
58.垃圾回收:
引用计数
当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1,当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。
标记清除
- 寻找跟对象(root object)的集合作为垃圾检测动作的起点,跟对象也就是一些全局引用和函数栈中的引用,这些引用所指向的对象是不可被删除的;2. 从root object集合出发,沿着root object集合中的每一个引用,如果能够到达某个对象,则说明这个对象是可达的,那么就不会被删除,这个过程就是垃圾检测阶段;3. 当检测阶段结束以后,所有的对象就分成可达和不可达两部分,所有的可达对象都进行保留,其它的不可达对象所占用的内存将会被回收,这就是垃圾回收阶段。(底层采用的是链表将这些集合的对象连接在一起)
基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。
分代回收
将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”,Python默认定义了三代对象集合,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量,如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。
http://www.cnblogs.com/George1994/p/7349871.html
59.map,reduce,filter
60.元类
http://www.cnblogs.com/tkqasn/p/6524879.html
http://developer.51cto.com/art/201108/281521.htm
61.线程和协程区别
http://www.cnblogs.com/guokaixin/p/6041237.html
1、进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
2、线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
3、协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
1、进程多与线程比较
线程是指进程内的一个执行单元,也是进程内的可调度实体。线程与进程的区别:
1) 地址空间:线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间
2) 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
3) 线程是处理器调度的基本单位,但进程不是
4) 二者均可并发执行
5) 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
2、协程多与线程进行比较
1) 一个线程可以多个协程,一个进程也可以单独拥有多个协程,这样python中则能使用多核CPU。
2) 线程进程都是同步机制,而协程则是异步
3) 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态
数据库
1.mysql字符集,排序规则
字符集,即用于定义字符在数据库中的编码的集合。 常见的字符集:utf-8 gbk 等。
数据库中的排序规则用来定义字符在进行排序和比较的时候的一种规则。 常见的如下:
(1) utf8_general_cs 和 utf8_general_ci (后缀”_cs”或者”_ci”意思是区分大小写和不区分大小写(Case Sensitive & Case Insensitve))
(2) utf8_bin 规定每个字符串用二进制编码存储,区分大小写,可以直接存储二进制的内容
说明:所为排序规则,就是指字符比较时是否区分大小写,以及是按照字符编码进行比较还是直接用二进制数据比较。
2.varchar/char区别,大小限制,utf8字符集下varchar最多能存多少字符
1)
char的长度是不可变的,而varchar的长度是可变的,也就是说,定义一个char[10]和varchar[10],如果存进去的是‘csdn’,那么char所占的长度依然为10, 除了字符‘csdn’外,后面跟六个空格,而varchar就立马把长度变为4了,取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。
尽管如此,char的存取数度还是要比varchar要快得多,因为其长度固定,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固定,所以难免会有多余的空格占位符占据空间,可谓是以空间换取时间效率,而varchar是以空间效率为首位的。
2)存储的容量不同
对 char 来说,最多能存放的字符个数 255,和编码无关。
而 varchar 呢,最多能存放 65532 个字符。VARCHAR 的最大有效长度由最大行大小和使用的字符集确定。整体最大长度是 65,532字节。
http://blog.csdn.net/a347911/article/details/47280453
http://www.cnblogs.com/webph/p/6679815.html
4.0版本以下,varchar(20),指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节)
5.0版本以上,varchar(20),指的是20字符,无论存放的是数字、字母还是UTF8汉字(每个汉字3字节),都可以存放20个,最大大小是65532字节
3.外键有什么用,外键一定需要索引吗
外键:为某一表的一列,包含另一表的主键值
外键作用:保持数据完整性一致性。
需要
否则影响并发性,做很多无效操作
http://blog.csdn.net/lv996074344/article/details/45866291
http://blog.csdn.net/u012557538/article/details/44002789
1。主键用于唯一标识表中的行数据,不能为空,一个主键值对应一行数据。另外,会自动在主键上创建索引,用于加快查询。
2。 外键用于两个表的联系。两个表必须具有相同类型的属性,在该属性上有相同的值。该属性应为其中一个表的主键,在另外一个表设置为外键。约束内表的数据的更新,从定义外键时可以发现 外键是和主键表联系,数据类型要统一,长度(存储大小)要统一。这样在更新数据的时候会保持一致性。
4.primary key和unique的区别?
定义了 UNIQUE 约束的字段中不能包含重复值,可以为一个或多个字段定义 UNIQUE 约束。因此,UNIQUE 即可以在字段级也可以在表级定义, 在 UNIQUED 约束的字段上可以包含空值。
UNIQUED 可空,可以在一个表里的一个或多个字段定义;PRIMARY KEY 不可空不可重复,在一个表里可以定义联合主键。primary key = unique + not null
http://blog.csdn.net/zm2714/article/details/8482625
5.myisam和innodb区别,innodb的两阶段锁定协议是什么情况
两阶段锁协议,整个事务分为两个阶段,前一个阶段为加锁,后一个阶段为解锁。在加锁阶段,事务只能加锁,也可以操作数据,但不能解锁,直到事务释放第一个锁,就进入解锁阶段,此过程中事务只能解锁,也可以操作数据,不能再加锁。两阶段锁协议使得事务具有较高的并发度,因为解锁不必发生在事务结尾。它的不足是没有解决死锁的问题,因为它在加锁阶段没有顺序要求。如两个事务分别申请了A, B锁,接着又申请对方的锁,此时进入死锁状态。
http://blog.csdn.net/endlu/article/details/51531391
MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。
InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
mysql 数据库引擎: http://www.cnblogs.com/0201zcr/p/5296843.html MySQL存储引擎--MyISAM与InnoDB区别: https://segmentfault.com/a/1190000008227211
6.索引有什么用,大致原理是?
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
为表设置索引要付出代价的:一是增加了数据库的存储空间,二是在插入和修改数据时要花费较多的时间(因为索引也要随之变动)。
http://blog.csdn.net/suifeng3051/article/details/52669644
http://chriszeng87.iteye.com/blog/1180762
http://blog.csdn.net/kennyrose/article/details/7532032
7.什么场景用redis?为什么mysql不适合
缓存,消息队列(list),排行榜(set),计数器
http://www.scienjus.com/redis-use-case/
从效率来说:
Redis的数据存放在内存,所以速度快但是会受到内存空间限制。MySQL存放在硬盘,在速度上肯定没有Redis快,但是存放的数据量要多的多。
从功能来说:
Redis是一个K-V数据库,同时还支持List/Hash/Set/Sorted Set等几个简单数据结构。
持久化方面肯定不如MySQL可靠,内存价格。
8.redis事务?
http://blog.csdn.net/hechurui/article/details/49508749
http://blog.csdn.net/why_2012_gogo/article/details/51274388
redis的事务中,一次执行多条命令,本质是一组命令的集合,一个事务中所有的命令将被序列化,即按顺序执行而不会被其他命令插入。
在redis中,事务的作用就是在一个队列中一次性、顺序性、排他性的执行一系列的命令。
常用的关于事务的命令有:
MULTI:使用该命令,标记一个事务块的开始,通常在执行之后会回复OK,(但不一定真的OK),这个时候用户可以输入多个操作来代替逐条操作,redis会将这些操作放入队列中。
EXEC:执行这个事务内的所有命令
DISCARD:放弃事务,即该事务内的所有命令都将取消
WATCH:监控一个或者多个key,如果这些key在提交事务(EXEC)之前被其他用户修改过,那么事务将执行失败,需要重新获取最新数据重头操作(类似于乐观锁)。
UNWATCH:取消WATCH命令对多有key的监控,所有监控锁将会被取消。
Redis服务端是个单线程的架构,不同的Client虽然看似可以同时保持连接,但发出去的命令是序列化执行的,这在通常的数据库理论下是最高级别的隔离
用MULTI/EXEC 来把多个命令组装成一次发送,达到原子性
用WATCH提供的乐观锁功能,在你EXEC的那一刻,如果被WATCH的键发生过改动,则MULTI到EXEC之间的指令全部不执行,不需要rollback
其他回答中提到的DISCARD指令只是用来撤销EXEC之前被暂存的指令,并不是回滚
9.redis内存满了怎么办
1.加内存
2.清理数据,设置 lru 过期机制 Least Recently Used
3.搭集群 或者 再弄个实例用twemproxy均衡数据
http://blog.jobbole.com/107084/
10.mysql锁种类,死锁如何产生的
MySQL有三种锁的级别:页级、表级、行级。
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
所谓死锁
因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程.
表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的InnoDB.
死锁的关键在于:两个(或以上)的Session加锁的顺序不一致。
那么对应的解决死锁问题的关键就是:让不同的session加锁有次序
http://blog.csdn.net/csdn265/article/details/51780852
http://www.cnblogs.com/zejin2008/p/5262751.html
11.join种类
http://www.cnblogs.com/dynas/p/6908707.html
12.索引类型,BTree和hash索引区别
http://www.cnblogs.com/hanybblog/p/6485419.html
Hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位,不像B-Tree索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree索引。
(1)Hash索引仅仅能满足”=”,”IN”和”<=>”查询,不能使用范围查询。
(2)Hash 索引无法被用来避免数据的排序操作。
(3)Hash索引不能利用部分索引键查询。
13.Redis面试题及分布式集群
http://blog.csdn.net/jinfeiteng2008/article/details/53711752
http://blog.csdn.net/yajlv/article/details/73467865
14.事务
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
http://www.hollischuang.com/archives/898
15.悲观锁乐观锁
悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
乐观锁与悲观锁的具体区别: http://www.cnblogs.com/Bob-FD/p/3352216.html
redis和memcache比较
网络
1.
poll,epoll,select区别,边缘触发,水平触发区别
http://www.cnblogs.com/Anker/p/3265058.html
select,poll,epoll都是IO多路复用的机制。
I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。
水平触发(level-triggered,也被称为条件触发)LT: 只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你)。select属于条件触发。
边缘触发(edge-triggered)ET: 每当状态变化时,触发一个事件。
2.tcp粘包,如何处理
http://www.cnblogs.com/kex1n/p/6502002.html
http://blog.csdn.net/zhangxinrun/article/details/6721495
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
粘包与拆包是由于TCP协议是字节流协议,没有记录边界所导致的。 所以如何确定一个完整的业务包就由应用层来处理了。 (这就是分包机制,本质上就是要在应用层维护消息与消息的边界。) 分包机制一般有两个通用的解决方法: 1,特殊字符控制,例如FTP协议。 2,在包头首都添加数据包的长度,例如HTTP协议。
3.time_wait是什么情况,出现过多的close_wait原因
http://blog.csdn.net/yusiguyuan/article/details/21445883
http://www.cnblogs.com/Jessy/p/3535612.html
CLOSE_WAIT
发起TCP连接关闭的一方称为client,被动关闭的一方称为server。被动关闭的server收到FIN后,
但未发出ACK的TCP状态是CLOSE_WAIT。出现这种状况一般都是由于server端代码的问题,
如果你的服务器上出现大量CLOSE_WAIT,应该要考虑检查代码。
TIME_WAIT
根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态。
TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒。
TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,
如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket,
甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务。
4.https过程
http 运行在TCP 之上,数据明文传输。HTTPS 运行在SSL/TLS之上,SSL/TLS运行在TCP之上,是加密协议,因此HTTPS传输的已经是加密的数据,加密采用对称加密。但对称加密的密钥用服务器方的证书进行了非对称加密。SSL/TLS中使用了非对称加密,对称加密以及HASH算法。
http://www.cnblogs.com/binyue/p/4500578.html
HTTPS一般使用的加密与HASH算法如下:
非对称加密算法:RSA,DSA/DSS
对称加密算法:AES,RC4,3DES
HASH算法:MD5,SHA1,SHA256
5.TCP UDP的区别 滑动窗口
滑动窗口:滑动窗口是TCP传输时的一个缓冲区机制,用来解决传输控制和流量控制的问题,TCP在发送端和接收端都有一个滑动窗口,当接收端成功接收了某段数据并移动了接收窗口的时候,发送端的窗口也会随之移动到缓冲区后面的数据段中。
6.socket长连接
长连接与短连接的概念:前者是整个通讯过程,客户端和服务端只用一个Socket对象,长期保持Socket的连接;后者是每次请求,都新建一个Socket,处理完一个请求就直接关闭掉Socket。所以,其实区分长短连接就是:整个客户和服务端的通讯过程是利用一个Socket还是多个Socket进行的。
连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接;
这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,短连接在没有数据传输时直接关闭就行了。
http://blog.csdn.net/zdwzzu2006/article/details/7723738
对称加密与非对称加密区别?
对称加密是指加密和解密使用的密钥是同一个密钥,或者可以相互推算。
对称加密的优点是算法简单,加解密效率高,系统开销小,适合对大数据量加密。
缺点是解密加密使用同一个密钥,需要考虑远程通信的情况下如何安全的交换密钥,如果密钥丢失,所谓的加密解密就失效了。
非对称加密和解密使用的密钥不是同一密钥,其中一个对外界公开,被称为公钥,另一个只有所有者知道称作私钥。
用公钥加密的信息必须用私钥才能解开,反之,用私钥加密的信息只有用公钥才能解开。
7.get post区别
http://www.cnblogs.com/nankezhishi/archive/2012/06/09/getandpost.html
- GET使用URL或Cookie传参。而POST将数据放在BODY中。
- GET的URL会有长度上的限制,则POST的数据则可以非常大。
- POST比GET安全,因为数据在地址栏上不可见。
http://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html
8.session,cookie区别,为什么说session是安全的
http://blog.csdn.net/rongwenbin/article/details/51784620
9.uwsgi,nginx作用
WSGI 是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供什么功能,以便二者能够配合使用。
http://www.cnblogs.com/Xjng/p/aa4dd23918359c6414d54e4b972e9081.html
http://blog.csdn.net/u014761344/article/details/40146597
http://www.cnblogs.com/gdkl/p/6807667.html
http://www.cnblogs.com/luchuangao/p/Gunicorn.html
gunicorn
gunicorn 会启动一组 worker进程,所有worker进程公用一组listener,在每个worker中为每个listener建立一个wsgi server。每当有HTTP链接到来时,wsgi server创建一个协程来处理该链接,协程处理该链接的时候,先初始化WSGI环境,然后调用用户提供的app对象去处理HTTP请求。
10.浏览器一个请求从发送到返回经历了什么
http://www.cnblogs.com/xiexj/p/6439775.html
11.短连接如何设计
https://segmentfault.com/a/1190000011171643
12.微信红包设计
http://www.open-open.com/lib/view/open1430729729960.html
设计一个类似QQ的工具
13.下图是最基本的web服务器的结构图。
https://github.com/liukelin/canal_mysql_nosql_sync
14.
15.tcp面试
http://blog.csdn.net/u012658346/article/details/51192944
http://www.cnblogs.com/freebrid/p/4640748.html
http://blog.csdn.net/hyqwmxsh/article/details/52437499
16.
CSRF:
http://www.cnblogs.com/shytong/p/5308667.html
其原理是攻击者构造网站后台某个功能接口的请求地址,诱导用户去点击或者用特殊方法让该请求地址自动加载。用户在登录状态下这个请求被服务端接收后会被误以为是用户合法的操作。对于 GET 形式的接口地址可轻易被攻击,对于 POST 形式的接口地址也不是百分百安全,攻击者可诱导用户进入带 Form 表单可用POST方式提交参数的页面。
CSRF工具的防御手段
- 尽量使用POST,限制GET
GET接口太容易被拿来做CSRF攻击,看第一个示例就知道,只要构造一个img标签,而img标签又是不能过滤的数据。接口最好限制为POST使用,GET则无效,降低攻击风险。
当然POST并不是万无一失,攻击者只要构造一个form就可以,但需要在第三方页面做,这样就增加暴露的可能性。 - 浏览器Cookie策略
IE6、7、8、Safari会默认拦截第三方本地Cookie(Third-party Cookie)的发送。但是Firefox2、3、Opera、Chrome、Android等不会拦截,所以通过浏览器Cookie策略来防御CSRF攻击不靠谱,只能说是降低了风险。
PS:Cookie分为两种,Session Cookie(在浏览器关闭后,就会失效,保存到内存里),Third-party Cookie(即只有到了Exprie时间后才会失效的Cookie,这种Cookie会保存到本地)。
PS:另外如果网站返回HTTP头包含P3P Header,那么将允许浏览器发送第三方Cookie。 - 加验证码
验证码,强制用户必须与应用进行交互,才能完成最终请求。在通常情况下,验证码能很好遏制CSRF攻击。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。 - Referer Check
Referer Check在Web最常见的应用就是“防止图片盗链”。同理,Referer Check也可以被用于检查请求是否来自合法的“源”(Referer值是否是指定页面,或者网站的域),如果都不是,那么就极可能是CSRF攻击。
但是因为服务器并不是什么时候都能取到Referer,所以也无法作为CSRF防御的主要手段。但是用Referer Check来监控CSRF攻击的发生,倒是一种可行的方法。 - Anti CSRF Token
现在业界对CSRF的防御,一致的做法是使用一个Token(Anti CSRF Token)。
例子: - 用户访问某个表单页面。
- 服务端生成一个Token,放在用户的Session中,或者浏览器的Cookie中。
- 在页面表单附带上Token参数。
- 用户提交请求后, 服务端验证表单中 Token是否与用户Session(或Cookies)中的Token一致,一致为合法请求,不是则非法请求。
这个Token的值必须是随机的,不可预测的。由于Token的存在,攻击者无法再构造一个带有合法Token的请求实施CSRF攻击。另外使用Token时应注意Token的保密性,尽量把敏感操作由GET改为POST,以form或AJAX形式提交,避免Token泄露。
注意:
CSRF的Token仅仅用于对抗CSRF攻击。当网站同时存在XSS漏洞时候,那这个方案也是空谈。所以XSS带来的问题,应该使用XSS的防御方案予以解决。
AJAX,Asynchronous JavaScript and XML(异步的 JavaScript 和 XML), 是与在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术。
中间人攻击(Man-in-the-middle attack,通常缩写为MITM)是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。
Socket=Ip address+ TCP/UDP + port
HTTP 1.1与HTTP 1.0的比较
http://blog.csdn.net/elifefly/article/details/3964766
理解RESTful架构
http://www.ruanyifeng.com/blog/2011/09/restful.html
浏览器缓存机制
http://www.cnblogs.com/skynet/archive/2012/11/28/2792503.html
算法
leetcode:https://github.com/bluedazzle/leetcode_python
其他
工作流
session共享,redis怎么存储session,session失效机制
session过期时间,过长怎么样
redis复制过程,redis插槽分配,redis主节点宕机了怎么办
python自省
tcp,udp区别,应用场景
io密集,cpu密集
多进程同时拥有同一个描述符的情况:惊群
面试官又问多线程情况下如何保证每个线程都能平均io,我问是不是负载均衡,他说是,我就说了用线程池加round robin。他就追问怎么实现线程池,我说条件变量加blocking
秒杀系统设计
tcp如何保证可靠传输
HTTP请求方式
GET/POST 幂等、安全性,长度限制
HTTP状态码
设计一个Timer类,要求里面的类方法sleep,10s内只能被调用一次
两个列表s1,s2,长度不定,用什么方法可以快速判断s1,s2有没有重复的元素?比较s1,s2差异?
数据库是如何设计的?
nginx原理
项目缓存设计
觉得自己沟通能力怎么样?
为什么从上一家离职?
遇到最难解决的是什么问题?如何解决的?
哪件事情最有成就感?
缺点?
get,getattr,getattribute区别
为什么使用tornado?
对事件驱动编程模型的看法?
讲下多进程,多线程,协程
https://www.cnblogs.com/huangguifeng/p/7632799.html
介绍一下协程的任务调度
实现tail命令
快排
将句子反序i am abc –>abc am i
服务器调用accept()之前,如果服务端sleep(),客户端connect()能否成功
服务器调用accept()之后,如果服务器sleep(),客户端connect()能否成功
客户端连接成功后,如果服务器sleep(),客户端发送send(),是否可以成功
如果服务器主动关闭连接,那么客户端关闭连接过程
二叉查找树
改善python的91个建议
http协议
职业规划
生成器概念,用途,能实现什么
使用生成器模拟线程并发
gevent咋实现的
flask架构
什么是闭包
字节和字符区别
nginx负载均衡的实现
抢票系统
高并发的服务器设计
集群中的session同步
集群间做缓存,一个节点挂了怎么办
集群间消息队列
索引实现机制