字节iOS实习一二三面试
2020年度总结(毕业+就业+字节求职经验+内推+ 解答)
github求职笔记
CSDN剑指offer
leetcode主页
全部岗位的内推码和链接(校招):RPAD99Q
目录:
在浏览器输入一个网址接下来会发生的事情
栈和堆的区别
分页、分段、段页式
设计模式及demo相关问题
复制黏贴怎么实现的
一个可执行文件的编译过程
如何将一个长URL转换为一个短URL
两个线程同时访问i++ 10000次操作,多线程并发问题
image框架 Kingfisher
一二三面算法题
字节一二三面问题整理
面的时候这些问题都答的不太好……之后查了下,稍微清楚一点点,也不是很能查得到说的清楚的资料(:з」∠)
一面二面是一个下午面完的,都是技术面,三面是一面二面面完后再约的,三面更偏HR面
在浏览器输入一个网址接下来会发生的事情
详情见 “牛客IOS面经整理”
大概是有DNS解析、HTTPS整套流程(TCP链接,SSL链接,对称加密、非对称加密、CA证书链)等
栈和堆的区别
详情见 “牛客IOS面经整理”
分页、分段、段页式
详情见 “牛客IOS面经整理”
设计模式及demo相关问题
详情见 “牛客IOS面经整理”
复制黏贴怎么实现的
详情见 “牛客IOS面经整理”
问:比如一个应用复制了另一个应用怎么拿到的,复制的内容太大了怎么办,如何分辨是image还是text?
操作系统中会有一块地方,称作剪贴板(clipboard),专门用来处理复制粘贴。
不同系统的细节可能会不同,但大致上是这样的:
复制文本时,会把所复制的文本克隆一份到剪贴板里面。粘贴文本时,再将剪贴板里的文本克隆到所粘贴应用程序之中;
- 复制文本时会保留其样式(比如在 Office 软件中复制,也会存储字体、字号等等信息,复制到剪贴板的实质上是一种「标记语言」)。但粘贴时若应用程序(比如记事本)不支持这些样式,则会去掉样式;
- 复制图片、混合富文本时,也是同样先克隆到剪贴板里。
复制文件时,系统只会把文件的路径复制到剪贴板,等到粘贴时再分情况处理:
- 同一分区下,粘贴(或剪切)文件,都不会真正在存储设备里直接克隆、挪动,而是更改此文件的路径(path)属性。当然这与不同文件系统的具体实现有关;
- (这也就是为什么,「复制 → 删除复制源文件 → 粘贴」这个操作会在大部分系统中失效了)
- 不同分区下,粘贴(或剪切)文件,会重新开辟空间,然后克隆文件;
- 涉及到与其他设备(即插即用设备等)之间的复制粘贴则更加复杂,实现各有不同。
还要考虑的情况,就是涉及虚拟机、远程主机的复制粘贴机制。虚拟机软件、远程主机软件都会有一个「介于两系统之间的」剪贴板,「连接起」这两个系统的各自剪贴板,并做一些编码格式转换的工作。
- 关于虚拟机复制粘贴,更具体的细节可以看这里:Is it possible to copy paste between Mac OS and its virtual machine? 各软件实现有异。
按照我自己想的和面试时候回答的,如果复制的是文件本身,那都是01串,在比特层面,不同后缀的文件是在某一块区域有区别的,CTF比赛里就是通过这种方式把隐藏的图片啊、文本啊提取出来的
然后复制的图片/文件太大了的话,复制的是路劲是地址,然后粘贴时候看情况处理,如上面所说的可能是重新开辟新空间然后克隆文件,网络图片那复制的只有地址
一个可执行文件的编译过程
这个问题本应该会的,本科汇编课还研究过,但是长久没有复习都忘记了,直接的java文件是javac这个命令编译,C++是gcc这个当时都没想起来,我只记得那个命令是什么cc
编译器:GCC
操作系统:linux系统
gcc 编译四个过程
1、预处理过程(头文件的包涵,去掉注释,宏展开)—#include 预处理过程不做语法检查
命令:gcc -E helloworld.c -o helloworld.i
2、 编译:编译过程做语法检查 生成汇编语言
命令:gcc -S helloworld.i -o helloworld.s
3、汇编:将汇编语言生成对应的二进制数据
命令:gcc -c helloworld.s -o helloworld.o
4、链接:添加对应操作系统可以执行的链接,否则无法在系统下运行
命令:gcc helloworld.o -o helloworld
源文件
源代码是相对目标代码和可执行代码而言的。
源代码就是用汇编语言和高级语言写出来的地代码。
源文件就是存放程序代码的文件,通常我们编辑代码的文件就是源文件,如.c、.cpp、.java、.asm等都是源文件
目标文件
目标代码是指源代码经过编译程序产生的能被cpu直接识别二进制代码。
目标文件由编译器生成,具体的生成方法在不同的开发环境上是不同的。
目标代码文件包含着机器语言代码,它并不需要是完整的程序代码,如.o(.OBJ)
例1:gcc -c test.c
编译test.c ,生成目标文件test.o,但不进行link
例2:masm lab02.asm
编译lab02.asm ,生成目标文件lab02.OBJ,但不进行link。
可执行文件
可执行文件包含了着组成可执行程序的全部机器语言代码。
可执行代码就是将目标代码连接(link)后形成的可执行文件,连接程序系统库文件就生成可执行文件。
常见的有.exe、.class、.com等
例1:gcc -o test test.c 编译test.c生成可执行文件test ./test 运行可执行文件
例2:link lab02.OBJ 生成可执行文件lab02.EXE lab02 运行可执行文件
一个程序从源代码到可执行程序的过程(详细版)
一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接。 其中,编译是主要部分,其中又分为六个部分:词法分析、语法分析、语义分析、中间代码生成、目标代码生成和优化。 链接中,分为静态链接和动态链接,本文主要是静态链接。
一、预编译:主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下 1.删除所有的#define,展开所有的宏定义。 2.处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。 3.处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。 4.删除所有的注释,“//”和“/**/”。 5.保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。 6.添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。
C语言的宏替换和文件包含的工作,不归入编译器的范围,而是交给独立的预处理器。 C语言中源代码文件的文件扩展名为.c,头文件的文件扩展名为.h,经预编译之后,生成xxx.i文件。 在C++,源代码文件的扩展名是.cpp或.cxx,头文件的文件扩展名为.hpp,经预编译之后,生成xxx.ii文件。
二、编译:把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
(结合程序来说明编译的几个步骤) 有C语言的源代码如下: arr[3] = (a+4)*(3+8);
1.词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。 以上的一行C语言程序,一共有16个空字符,经扫描机扫描之后,产生了16个记号。lex可以实现词法分析。见下表:
见上图: 词法分析产生的记号分类有:关键字、标识符、字面量(数字、字符串)、特殊符号(加号、等号等)
2.语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。上述的代码就是 各种表达式的组合:赋值表达式、加法表达式、乘法表达式、数组表达式和括号表达式组成的复杂表达式。yacc可以实现语法分析,根据用户给定的规则(不同的编程语言对应不同的语法规则)对记号表进行解析。
见上图: 整个语句被看作是一个“赋值表达式”,“=”左边是一个“数组表达式”,右边是一个“乘法表达式”。数组表达式又由两个符号表达式组成,符号表达式就是最小的表达式,之后同理。
在语法分析的同时,就把运算符的优先级确定了下来,如果出现表达式不合法,——各种括号不匹配、表达式中缺少操作,编译器就会报错。
3.语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。 其中,静态语义通常包括:声明和类型的匹配,类型的转换,那么语义分析就会对这些方面进行检查,例如将一个int型赋值给int*型时,语义分析程序会发现这个类型不匹配,编译器就会报错。
经过语义分析阶段之后,所有的符号都被标识了类型(如果有些类型需要做隐式转化,语义分析程序会在语法树中插入相应的转换节点),见下图:
这个语句中的类型都是int型,无须做转换。
4.优化:源代码级别的一个优化过程,例如该语句中的(3+8)的值可以在编译期确定,源代码优化器会将整个语法树转换成中间代码——语法树的顺序表示,十分接近目标代码。 中间代码有很多种类型,最常见的是“三地址码”和“P-代码”,其中三地址码的基本形式为:x = y op z,表示将变量y和z进行op操作后,赋值给x,op操作可以是加减乘除等。 经优化之后的语法树为:
该语句的三地址码: t1 = 3 + 8; t2 = a + 4; t3 = t2 * t1; arr[3] = t3;
t1由数字11代替,省去t3,经优化或的三地址码为: t2 = a +4; t2 = t2 + 11; arr[3] = t2;
另一个关于中间代码的要点:中间代码使得编译器可以被分成前端和后端,编译器前端负责产生与机器无关的中间代码,编译器后端将中间代码转换为机器代码。 源代码优化去产生中间代码标志着下面的过程都属于编译器后端,后端主要包括:代码生成器和目标代码优化器。
5.目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
6.目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。
上述的六个步骤完毕之后,编译过程也就告一段落了。最终产生了由汇编语言编写的目标代码。
gcc把预编译和编译两个步骤合并成一个步骤。对于C语言的代码,是用“cc1”这个程序来完成这两步,对于C++代码,对应的程序为“cc1plus”。gcc这个命令只是后台程序的包装,根据不同的参数去调用:预编译编译程序——cc1,汇编器——as,连接器——ld。
C语言的代码,经编译后产生的文件名为xxx.s。
三、汇编:将汇编代码转变成机器可以执行的指令(机器码文件)。 汇编器的汇编过程相对于编译器来说更简单,没有复杂的语法,也没有语义,更不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。
经汇编之后,产生目标文件(与可执行文件格式几乎一样)xxx.o(Windows下)、xxx.obj(Linux下)。
但是,经过预编译、编译、汇编之后,生成机器可以执行的目标文件之后,还有一个问题——变量a和数组arr的地址还没有确定。这就需要链接器来搞定啦~
四、链接: 1、历史过程:曾经,程序猿门在编程时,使用纸带作为最原始的存储设备,每当程序需要修改时,都要重新扎一条纸带,扎孔的表示1,不扎的是0,一串串1和0就组成了各种各样的指令——跳转等等…. 每一次的修改都非常痛苦,所以先知们就发明了汇编语言,这种编程语言方便之处在于符号的引用,表示跳转指令不再需要记住一串串0和1,终于可以使用符号——foo来表示这个动作了! 随着汇编语言的普及,程序的代码量也就开始快速膨胀了,汇编语言说它也撑不住了….不过还好,高级编程语言Fortran、C、C++等一个接一个地问世,语言越来越方便了,追求perfect的人们就想:代码咋写更好呢?可不可以把代码按照功能的不同,分成不同的部分,便于日后的修改和重复使用呢? 有了这个启发,程序猿们越来越得心应手,他们开始把代码按照功能和性质划分,分别形成不同的功能模块,不同的模块之间又按照各种结构来组织。 发展到如今,软件的规模越来越大,代码动辄数百万行代码,放在一个模块那是万万不行的,维护起来会非常麻烦,所有现在的大型软件往往拥有成千上万的模块, 模块之间相互独立又相互依赖。 新的问题来了,一个程序被分割成这么多模块,最后要怎么把这些模块组合形成一个单一的程序? 答案就是:模块之间,符号的引用! 这就像是一张画有大树的拼图,叶子、枝干、根系都零散的分布在那些拼图碎片上,想要看到完整的大树,我们就会耐心地把那些碎片拼合在一起。
这些模块之间同样如此,它们依靠那些凸起和凹陷联系在一起,最终组合成一个完整的程序,这样的过程称为——链接。
这样基于符号的模块化,使得链接过程在整个程序开发中显得十分重要和突出…..
2、下面就静态链接,进行分析。 1.链接:“组装”模块的过程。 2.链接的内容:把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。(就像拼图,凸起和凹槽的位置一定一一对应,否则…) 3.链接的过程:地址和空间的分配、符号决议(也叫“符号绑定”,倾向于动态链接)和重定位 以gcc编译器为例,看基本的链接过程:
.c文件经过编译器、汇编器之后得到目标文件.o,目标文件再与库进行链接得到可执行文件.out。 库其实就是一组目标文件的打包,这些目标文件中都是一些常用的代码。
我们在fun.c模块中定义了函数foo(),在main.c模块中引用了foo()函数,在编译过程当中,编译器并不知道main.c中foo()的地址,所以将调用foo()的指令的目标地址部分搁置, 等到了链接的阶段,链接器会去找到foo()定义的那个模块,在main.o中填入正确的函数地址,这个修改地址的过程被叫做“重定位”,每个被修正的地方叫“重定位入口”。
以上就是一个程序从源代码到可执行程序的大致过程,这是博主根据《程序员的自我修养——链接、装载与库》来整理的,有兴趣的同学可以自己去琢磨琢磨~
如何将一个长URL转换为一个短URL
URL 重定向,也称为 URL 转发,是一种当实际资源,如单个页面、表单或者整个 Web 应用被迁移到新的 URL 下的时候,保持(原有)链接可用的技术。HTTP 协议提供了一种特殊形式的响应—— HTTP 重定向(HTTP redirects)来执行此类操作。
HTTP重定向:服务器无法处理浏览器发送过来的请求(request),服务器告诉浏览器跳转到可以处理请求的url上。(浏览器会自动访问该URL地址,以至于用户无法分辨是否重定向了。) 重定向的返回码3XX说明。Location响应首部包含了内容的新地址或是优选地址的URL。
状态码 301:在请求的URL已被移除时使用。响应的Location首部中应该包含资源现在所处的URL。 302:与301状态码类似,但是,客户端应该使用Location首部给出的URL来零食定位资源,将来的请求仍然使用老的URL。
官方的比较简洁的说明:
301 redirect: 301 代表永久性转移(Permanently Moved) 302 redirect: 302 代表暂时性转移(Temporarily Moved ) 尽量使用301跳转!301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取(用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。
两个线程同时访问i++ 10000次操作,多线程并发问题
多线程访问最后的结果不确定,可能是各加一次,也可能只加一次
image框架 Kingfisher
- 异步下载和缓存图片
- 基于networking的URLSession, 提供基础的图片处理器和过滤器
- 内存和磁盘的多层缓存
- 可撤销组件,可根据需要分开地使用下载器和缓存系统
- 必要时可从缓存中读取并展示图片
- 扩展UIImageView、NSImage、UIButton来直接设置一个URL图片
- 设置图片时,内置过渡动画
- 支持扩展图片处理和图片格式
Kingfisher从url下载图片,将图片存储在内存缓存和磁盘缓存,然后将它展示在imageView上。当使用同样的代码后(url不变),就会直接从内存中获取之前缓存的图片并立即显示出来。
算法题
以下不一定是完整代码,因为有些调试没有进行到最后,做的也不是很完美,三次都各出现了点小状况,惭愧。算法题只有一个main函数,不是核心代码模式哦,三条感觉都是easy里偏难,medium里偏简单的题目,不过输出、调试比较坑,害,被leetcode惯坏了。
一面 445. 两数相加 II
给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。
import java.util.*; public class Main { public static void main(String[] args) { //Scanner in = new Scanner(System.in); //int a = in.nextInt(); //System.out.println(a); System.out.println("Hello World!"); reverse(l1); reverse(l2); reverse(add(l1, l2)); } public static ListNode add(ListNode l1, ListNode l2) { ListNode dummpy = new ListNode(0); ListNode cur = dummpy; int carry = 0 while(l1 != null || l2 != null) { int n1 = l1 == null ? 0 : l1.val; int n2 = l2 == null ? 0 : l2.val; int sum = n1 + n2 + carry; carry = sum / 10; cur.next = new ListNode(sum % 10); cur = cur.next; } if(carry != 0) cur.next = new ListNode(carry); return dummpy.next; } public static ListNode reverse(ListNode head) { ListNode pre = null; ListNode cur = head; while(cur != null) { ListNode temp = cur.next; cur.next = pre; pre = cur; cur = temp; } return pre; } }
二面
找出一个数组中的所有K数。
K数定义:前面的数都比它小,后面的数都比它大。
举例:1 3 2 4 7 5 9 其中K数有:1 4 9
思路一看就是快排,暂时不知道leetcode上哪题,印象里没见过
import java.util.*; public class Main { public static int index = 0; public static void main(String[] args) { //Scanner in = new Scanner(System.in); //int a = in.nextInt(); //System.out.println(a); System.out.println("Hello World!"); int[] nums = {1, 3, 2, 4, 7, 5, 9}; int[] ans = isK(nums); for(int i = 0; i < index; i++) System.out.print(ans[i] + " "); } } public static int[] isK(int[] nums) { //ArrayList<Integer> ans = new ArrayList<>(); int len = nums.length; if(len == 0) return new int[]{}; int[] ans = new int[len]; for(int i = 0; i < len; i++) { swap(nums, 0, i); if(position(nums, 0, len - 1) == i) { ans[index++] = nums[i]; } swap(nums, 0, i); } return ans; } public static void swap(int nums[], int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } //一次划分 public static int position(int[] nums, int left, int right) { int pos = nums[left]; // 基准 while(left < right) { while(nums[right] > pos) { right--; } if(left < right){ nums[left] = nums[right]; left++; } while(nums[left] <= pos) { left++; } if(left < right){ nums[right] = nums[left]; right--; } } return left; } }
三面 剑指 Offer 67. 把字符串转换成整数
判断一个字符串是否是10进制32位正整数
import java.util.*; public class Main { public static void main(String[] args) { //Scanner in = new Scanner(System.in); //int a = in.nextInt(); //System.out.println(a); System.out.println("Hello World!"); System.out.println(Integer.MAX_VALUE); String str = "+12649901"; int ans = judge(str); System.out.println(ans); } public static int judge(String str) { int len = str.length(); if(len == 0) return -1; char[] c = str.toCharArray(); int i = 0; // 找第一个不是空格且不是+的字符 while(i < len && c[i] == ' ') { i++; } if(i < len && c[i] == '+') { i++; } while(i < len && c[i] == '0') { i++; } if(i == len) return -1; int num = 0; int max = Integer.MAX_VALUE / 10; if(c[i] < '0' && c[i] > '9') { return -1; } else { while(i < len) { if(c[i] >= '0' && c[i] <= '9') { int temp = (int)(c[i] - '0'); // 处理越界 if(max < num || (max == num && temp > 7)) { return -1; } num = num * 10 + temp; } else { return -1; } i++; } } return num; } }#实习##面经##iOS工程师#