字节跳动客户端研发实习生面试 目前大三


Android四大组件

activity、service、content provider、broadcast receive
其中activity的生命周期是怎么样的?
oncreate()->onstart()->onResume()->onRestart()->onPouse()->onStop()->onDestory()

**onCreate()**: 当点击activity的时候,系统会调用activity的oncreate()方法,在这个方法中会初始化当前布局setContentLayout()方法。
**onStart()**: onCreate()方法完成后,此时activity进入onStart()方法,当前activity是用户可见状态,但没有焦点,与用户不能交互,一般可在当前方法做一些动画的初始化操作。
**onResume()**: onStart()方法完成之后,此时activity进入onResume()方法中,当前activity状态属于运行状态 (Running),可与用户进行交互。
**onRestart()**: 此方法在按下home()之后,再次进入到当前activity的时候调用。调用顺序onPouse()->onStop()->onRestart()->onStart()->onResume().
**onPouse()**: 当另外一个activity覆盖当前的acitivty时,此时当前activity会进入到onPouse()方法中,当前activity是可见的,但不能与用户交互。
**onStop()**: onPouse()方法完成之后,此时activity进入onStop()方法,此时activity对用户是不可见的,在系统内存紧张的情况下,有可能会被系统进行回收。所以一般在当前方法可做资源回收。
**onDestory()**: onStop()方法完成之后,此时activity进入到onDestory()方法中,结束当前activity。

一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接。

1.预编译过程主要做4件事:

①展开头文件 ②宏替换 ③去掉注释 ④条件编译

2.编译
将代码转成汇编代码,并且在这个步骤中做了两件很重要的工作: ①编译器在每个文件中保存一个函数地址符表,该表中存储着当前文件内包含的各个函数的地址;
②因为这步要生成汇编代码,即一条一条的指令,而调用函数的代码会被编译成一条call指令,call指令后面跟的是jmp指令的汇编代码地址,而jmp指令后面跟的才是“被调用的函数编译成汇编代码后的第一条指令”的地址,但是给call指令后面补充上地址的工作是在链接的时候做的事情。

3.汇编
将汇编代码转成机器码
4.链接
编译器将生产的多个.o文件链接到一起生成一个可执行.exe文件; 但是在这个过程中,编译器做的一个重要的事情是将每个文件中call指令后面的地址补充上;方式是从当前文件的函数地址符表中开始找,如果没有,继续向别的文件的函数地址符表中找,找到后填补在call指令后面,如果找不到,则链接失败。
其中,编译是主要部分,其中又分为六个部分:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化。 链接中,分为静态链接和动态链接

const与宏的区别:

1、编译时刻:宏-预编译 const-command+b(编译阶段)编译。
2、宏不会检查代码错误,只是替换,但是const会编译报错。
3、宏的好处:定义代码或字符串、方法、参数 const不能。
坏处:
使用大量宏,容易造成编译时间久,每次都需要重新替换。

宏与内联函数的对比:

1、内联函数相比较于宏而言,内联函数要做参数类型检查,从而内联函数相比宏而言更加安全。
2、内联函数在运行时可调试,而宏定义不可以。

为什么说B+树比B树更适合数据库索引?

1、B+树的磁盘读写代价更低:
B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
2、B+树的查询效率更加稳定:
由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
3、由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

AVL 不能做索引吗?为什么?

平衡二叉树没能充分利用磁盘预读功能,而B树是为了充分利用磁盘预读功能来而创建的一种数据结构,也就是说B树就是为了作为索引才被发明出来的的。

B+树索引结构解析

B+树是通过二叉查找树,再由平衡二叉树,B树演化而来。
  二叉查找树中定义:左子树的键值总是小于根的键值,右子树的键值总是大于根的键值。因此可以通过中序遍历得到键值的排序输出
  若想最大性能的构造一颗二叉查找树,需要这颗二叉查找树是平衡,从而引出了新的定义-----平衡二叉树,或称为AVL树。
  平衡二叉树定义:首先复合二叉查找树的定义,其次必须满足任何节点的两个子树的高度最大差为1.
  平衡二叉树的查询速度很快,但是维护一颗平衡二叉树的代价很大,通常来说,需要1次或多次左旋和右旋来得到插入或更新后树的平衡性。
B+树由B树和索引顺序访问方法(ISAM,这就是MyISAM引擎最初参考的数据结构)演化而来,实际中已经没有使用B树的情况了。
  B+树是为磁盘或其他直接存储辅助设备设计的一种平衡查找时。
  B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点上,由各叶子节点指针进行连接。
B+树的插入必须保证插入后叶子节点中的记录依然排序,同时需要考虑插入到B+树的三种情况,每种情况都会导致不同的插入算法。
B树:有序数组+平衡多叉树; B+树:有序数组链表+平衡多叉树;

O(log n)
void CreatBiTree ( BiTree *T) {
	TElemType ch;
	scanf(%c”,&ch);
	if (ch==’#’)
		*T=NULL;
	else{
		*T=(BiTree)malloc(sizeof(BiTNode));
		if(!*T)
			exit (OVERFLOW);
		(*T)->data=ch;
		creatBiTree (&(*T)->lchild);
		creatBiTree (&(*T)->rchild);
	}
}

先序遍历
void PreOrderTraverse (BiTree T){
	if(T==NULL)
		return ;
	printf(%c”,T->data);
	preOrderTraverse (T->lchild);
	preOrderTraverse (T->rchild);
}

(递归实现)查找"红黑树x"中键值为key的节点 
Node* search(RBTree x, Type key) { 
	if (x==NULL || x->key==key) 
		return x; 
	if (key < x->key) 
    	return search(x->left, key); 
     else 
   		 return search(x->right, key);
}

销毁红黑树
void rbtree_destroy(RBTree tree) {
	if (tree==NULL)
		return ;
	if (tree->left != NULL)
		rbtree_destroy(tree->left);
	if (tree->right != NULL)
		rbtree_destroy(tree->right);
	free(tree); 
}
void destroy_rbtree(RBRoot *root) {
	if (root != NULL)
		rbtree_destroy(root->node);
	free(root);
}

说一下hashmap这个数据结构

他现在的实现方式是数组+链表+红黑树,通过计算hash,将对象存进hashmap中,当链表的长度大于8,链表进化为红黑树,链表小于6,红黑树退化为链表,这样能防止频繁的进行红黑树的转化,然后红黑树的话通过着色和旋转进行自平衡。

进行一次查找的话haspmap的时间复杂度是多少

O(1),这个是作为数组的查询复杂度。

给你一个算法你看一下,有一个无限长的整型数组,从小到大排序,非递增。那么怎么找到数组中一个key

可以模仿计算机网络的慢开始,第一次步长为2,第二次步长为4,第三次为8,持续为2的幂次,大于key时,再在当前位置和当前位置-步长范围内查找。

订单表有几个属性:订单id,用户user_id、下单日期date(精确到天)等,请问索引怎样建立

a. 查询某个用户的所有订单
b. 查询某一天的所有订单
c. 查询某一天某个用户的所有订单 综合考虑三个场景,建立尽量少的索引。
建立两个索引 第一个是日期索引, 因为b要求查询某一天 第二个是组合索引,用户user_id,订单id,下单日期date
因为c用到了三个,同时在建立索引时,将三个都建立上,另外由于索引结构中,int类型最佳,其次是date,所以将date放在后面。

快速排序

时间复杂度:
最好:O(n log2 n)
最坏:O(n^2)
平均:O(n log2 n)
空间复杂度:O(n log2 n)

#include <stdio.h>
#include <stdlib.h>

int getStandard(int array[], int i, int j) {
	//基准数据 
	int key = array[i];
	while (i < j) {
			while (i < j && array[j] >= key) {
				j--;
			}
			if (i < j) {
				array[i] = array[j];
			}
			while (i < j && array[i] <= key) {
				i++;
			}
			if (i < j) {
				array[j] = array[i];
			}
		}
	array[i] = key;
	return i;
}

void QuickSort(int array[], int low, int high) {
	if (low < high) {
		int standard = getStandard(array, low, high);
		QuickSort(array, low, standard - 1);
		QuickSort(array, standard + 1, high);
	}
}
void display(int array[], int size) {
	for (int i = 0; i < size; i++) {
		printf("%d ", array[i]);
	}
	printf("\n");
}

int main() {
	int array[] = { 49,38,65,97,76,13,27,49,10 };
	int size = sizeof(array) / sizeof(int);
	printf("%d \n", size);
	QuickSort(array, 0, size - 1);
	display(array, size);
	return 0;
}

归并排序O(nlogn)

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
void merge(int a[],int l,int r,int mid)
{
  int aux[r-l+1],i,j,k;
  for(k=l;k<=r;k++)
  aux[k-l]=a[k];
  i=l;
  j=mid+1;
  for(k=l;k<=r;k++)
  {
  	if(i>mid)
  	{
  		a[k]=aux[j-l];
  		j++;
	  }
	else if(j>r)
	{
		a[k]=aux[i-l];
		i++;
	  }
	else if(aux[i-l]>aux[j-l])
	{
		a[k]=aux[j-l];
		j++;
		}
	else
	{
		a[k]=aux[i-l];
		i++;
			}
	  }	
}
void merge_sort(int a[],int l,int r)
{
    if(l>=r)
	return ;	
	int mid=(l+r)/2;
	merge_sort(a,l,mid);
	merge_sort(a,mid+1,r);
	merge(a,l,r,mid);	
}
void mergesort(int a[],int l,int r)
{
	merge_sort(a,l,r-1);
}
int main()
{
	int a[105],n,i;
	scanf("%d",&n);	
	for(i=0;i<n;i++)
	scanf("%d",&a[i]);
	mergesort(a,0,n);
	for(i=0;i<n;i++)
	printf("%d ",a[i]);
	return 0;
 } 

ACID,是指在可靠数据库管理系统(DBMS)中,事务(transaction)所应该具有的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability).这是可靠数据库所应具备的几个特性.

原子性是指事务是一个不可再分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。
多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。
持久性,意味着在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
即使出现了任何事故比如断电等,事务一旦提交,则持久化保存在数据库中。

事务的(ACID)特性是由关系数据库管理系统(RDBMS,数据库系统)来实现的。数据库管理系统采用日志来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所做的更新,如果某个事务在执行过程中发生错误,就可以根据日志,撤销事务对数据库已做的更新,使数据库退回到执行事务前的初始状态。

数据库管理系统采用锁机制来实现事务的隔离性。当多个事务同时更新数据库中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。

数据库系统的实现中采用了哪些常用的数据结构?

  1. 缓存使用linked-list实现LRU:

https://github.com/BohuTANG/nessDB/blob/master/cache/cache.c#L30

  1. 索引使用b-tree结构(Fractal-Tree):

https://github.com/BohuTANG/nessDB/blob/master/tree/buftree.c

  1. 事务并发使用基于binary-tree的locktree:

https://github.com/XeLabs/ft-index/blob/master/locktree/locktree.cc

  1. MVCC使用stack结构实现嵌套事务版本控制:

https://github.com/BohuTANG/nessDB/blob/4c36de92ef238bf9449993e6b3b5c09994860af3/tree/lmb.h#L20

  1. 最重要的是要有一个快速的、并发友好的排序结构(vector):

https://github.com/BohuTANG/nessDB/blob/master/util/pma.c

  1. ConcurrentHashMap用于LRU

https://github.com/BohuTANG/nessDB/blob/master/util/xtable.c

数据库中的连接操作

select * from A left join B on A.id=B.id
select * from A right join B on A.id=B.id
select * from A inner join B on A.id=B.id
select * from A full join B on A.id=B.id

虚函数跟普通函数的区别就是当基类指针或引用指向派生类对象时,通过指针或引用调用成员函数,对于非虚函数,调用的是静态类型(指针/引用类型)的版本,对于虚函数,在运行时动态绑定,调用的是实际类型的版本。例子你可以看一下输出的前两行。

1.虚函数(impure virtual)

C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。
子类可以重写父类的虚函数实现子类的特殊化。

2.纯虚函数(pure virtual)

C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。
C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。
C++中的纯虚函数也是一种“运行时多态”。

3.普通函数(no-virtual)

普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数。
普通函数是父类为子类提供的“强制实现”。  
因此,在继承关系中,子类不应该重写父类的普通函数,因为函数的调用至于类对象的字面值有关。

(1)static关键字的作用:

函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值
在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问
在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内
在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝
在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量

(2)const关键字至少有下列n个作用

欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了
对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const
在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值
对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的 成员变量
对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”

堆与栈区别

堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
(1)管理方式不同。
栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。
每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
(3)生长方向不同。
堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。
栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
(6)存放内容不同。
栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

如何避免内存泄漏、溢出的几种常用方法

尽早释放无用对象的引用。 尽量少用静态变量。 尽量运用对象池技术以提高系统性能。

malloc和new有以下不同:

new、delete是操作符,可以重载,只能在c++中使用。 malloc、free是函数,可以覆盖,c、c++中都可以使用。
new可以调用对象的构造函数,对应的delete调用相应的析构函数。
malloc仅仅负责分配内存,free仅仅回收内存,并不执行构造和析构函数。
new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。
注意:malloc申请的内存空间要用free释放,而new申请的内存空间要用delete释放,不要混用。因为两者实现的机理不同。

有了malloc/free为什么还要new/delete?

malloc/free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存
对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构
函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
对于内部数据类型的“对象”没有构造与析构过程,对它们而言,malloc/free和new/delete是等价的。为什么C++不把malloc
/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

vector(向量容器)

特点
内存可2倍增长的动态数组
数据结构:线性连续空间
维护三个迭代器:start、finish、end_of_storage

deque(双端队列)

特点
数据结构:一种双向开口的存储空间分段连续的数据结构,每段数据空间内部是连续的,而每段数据空间之间则不一定连续

deque与vector的最大差异 :

1.允许常数时间内对起首端进行元素的插入和删除
2.deque没有所谓的容量概念,因为它是以分段连续空间组合而成,随时可以增加一段新的空间并连接起来

list

特点
有效利用空间
数据结构:环状双向链表 插入(insert)和接合(splice)操作都不会造成原来list的迭代器失效
删除(erase)操作仅仅使“指向被删除元素”的迭代器失效,其它迭代器不受影响 随机访问比较慢

关联容器

set(集合)

特点
数据结构:底层使用平衡的搜索树——红黑树实现

multiset

特点
数据结构:底层实现与set一样,也采用了红黑树
允许插入重复的键值,使用insert_equal机制 插入、删除操作的时间复杂度为O(log2n)

map(key,value

特点
map中key的值是唯一的
数据结构:红黑树变体的平衡二叉树数据结构 提供基于key的快速检索能力
元素插入是按照排序规则插入的,不能指定位置插入 对于迭代器来说,可以修改实值,而不能修改key。
根据key值快速查找,查找的复杂度基本是log2n

multimap

特点
与 map 不同,multimap 可以包含重复键

解决Hash碰撞冲突方法总结

Hash碰撞冲突

我们知道,对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突。如下将介绍如何处理冲突,当然其前提是一致性hash。
1.开放地址法
开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1) 其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。
如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。
如果di取值可能为伪随机数列。称伪随机探测再散列。
2.再哈希法
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。 比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止
3.链地址法(拉链法)
将所有关键字为同义词的记录存储在同一线性链表中。如下: 因此这种方法,可以近似的认为是筒子里面套筒子
4.建立一个公共溢出区
假设哈希函数的值域为[0,m-1],则设向量HashTable[0…m-1]为基本表,另外设立存储空间向量OverTable[0…v]用以存储发生冲突的记录。

拉链法的优缺点:

优点:
①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结
点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在
用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
缺点:
指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫萧幕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值