如下:
采用这种向上抽象方式,是为了将多个类的通用属性和方法提取出来,放在它们的父类中,避免同样的代码写多份(即为了实现复用),在子类中只需要定义自己独有的属性和方法,以继承的方式在父类中获取通用属性和方法即可。
//继承代码结构public class Chinese extends Teacher{` `//定义自己独有的属性:工作内容` `Stringcontent;` ` //定义自己独有的方法` `publicvoid writeModels(String name, string content) {` `System.out.println(name + "老师主要工作内容是"+ content);` `}
}
特别说明:继承只能是单继承,即一个子类只能继承一个父类。
2.封装
封装的目的在于保护信息。
Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的所有信息,私有的方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望发生的事情。
封装主要优点如下:
那么,怎么理解封装实现了复用和扩展呢?
读者可以理解为所谓封装其实只是将属性和功能封装成类,并对类里的成员定义了不同的访问权限,最终还是通过与继承机制的结合实现的代码复用和扩展。
3.多态
所谓多态,就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用的到底是哪个类中实现的方法,必须在由程序运行期间才能决定。即不修改程序代码就可以让程序有多个运行状态可以选择,这就是多态性。
上文是根据为啥这个特性叫做多态性的角度给大家解释了一下,如果从实际运用中最终看到的实际效果这个方面来总结,可以理解为,多态是同一个行为具有多个不同表现形式或形态的能力。
举个形象的例子:
现实中,比如我们按下 F1 键这个动作:
如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;
在 Windows 下弹出的就是 Windows 帮助和支持。
同一个事件发生在不同的对象上会产生不同的结果,可见,多态实现了很好的扩展性。
4.泛型
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
public class GenericTest{` `// 泛型方法 printArray
publicstatic < E > void printArray( E[] inputArray )
{
// 输出数组元素 for( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
publicstatic void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[]doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { ‘H’, ‘E’, ‘L’, ‘L’, ‘O’ };
System.out.println( “整型数组元素为:” );
printArray( intArray ); // 传递一个整型数组
System.out.println( “\n双精度型数组元素为:” );
printArray(doubleArray ); // 传递一个双精度型数组
System.out.println( “\n字符型数组元素为:” );
printArray( charArray ); // 传递一个字符型数组
}
}
结果如下:
整型数组元素为:``1 2 3 4 5 ``双精度型数组元素为:``1.1 2.2 3.3 4.4 ``字符型数组元素为:``H E L L O
同一个方法,允许传入不同的参数类型,得到不同的运行结果,以此实现了代码的复用和扩展。
2、初始化过程
虽然本文讲解的绝大部分内容偏向应用,但对于初始化过程这种看上去有点涉及到底层原理的内容,考虑再三,还是要稍微提及一下,那是因为在实际工作中,经常发生因为对整个初始化过程的机制不了解而写出不合理代码,从而产生bug的现象。
本段会针对类的各成员的初始化顺序重点强调一下,这也是犯错最多且唯一我们能控制的地方,希望大家务必对此要有所了解。
话不多说,上实例:
class Father{` `static{` `System.out.println("父类静态代码块初始化" );` `}` `{` `System.out.println("父类代码块初始化" );` `}` `private static void s=print();` `public static void print()` `{` `System.out.println("父类静态方法" );` `}` `public Father()` `{` `System.out.println("父类无参构造函数初始化完成" );` `show();` `}` `public void show()` `{` `System.out.println("父类show()方法" );` `}
}class Son extends Father
{
static{
System.out.println(“子类静态代码块初始化” );
}
{
System.out.println(“子类代码块初始化” );
}
private static int i=1;
private String s=“子类私有成员变量” ;
public void show()
{
System.out.println(“子类show()方法:i=” +i);
}
public Son()
{
System.out.println(“子类构造函数初始化完成” );
System.out.println(“子类成员变量初始化完成:s=”+s);
show();
}}
public class TestClassLoadSeq {
public static void main(String[]args)
{
new Son();
}``}
执行顺序:
父类静态代码块初始化
父类静态方法
子类静态代码块初始化
父类代码块初始化
父类无参构造函数初始化完成
子类show()方法:i=1 //因为创建的是son实例,所以父类里的show方法被初始化时,实际调用的是子类show方法
子类代码块初始化
子类构造函数初始化完成
子类成员变量初始化完成:s=子类私有成员变量
子类show()方法:i=1
在遇到初始化失败的相关bug时,通常错误提示不会直接指向有问题的那行。
因此,需要你对类初始化过程有所了解,这样才能快速定位到哪个环节最有可能出问题。
3、常见的集合及方法
在日常的数据分析工作中,常常需要使用到集合来存储和处理数据,因此需要大家对集合的分类和功能有所了解。Java的集合框架分为两部分,分别对应两大接口:Collection接口和Map接口。以下就通过这两大接口开始讲解。
1.Collection接口
Collection接口涉及三种类型的集合:1.Set(规则集) 2.List(线性表) 3.Queue(队列),其层级关系如图:
ps:图片源自网络
这三种类型集合的常用方法及特性总结如下:
集合类型 | 特性 | 常用方法 |
Set | 集合中没有相同的元素 | hashCode() //返回对象存储的物理地址 equals() //判断值是否相等 |
List | 线性表是一个有序允许重复的集合 | add(int index) //指定下标添加元素 addAll(int index, Collection<? extends E> c) //指定下标处添加c中所有元素 get(int index) //返回指定下标元素 lastIndexOf(Object o) //返回相同元素的下标 listIterator() //返回遍历列表的迭代器 listIterator(int startIndex) //返回从startIndex开始的所有元素的迭代器 remove(int index) //删除指定下标的元素 set(int index, E element) //设置指定下标的元素 subList(int fromIndex, int toIndex) //返回从fromIndex到toIndex元素子列表 |
Queue | 是一种先进先出的数据结构 | offer(E e) //添加元素 poll() // 返回并删除队头元素,否则返回null remove() //返回并删除队头元素,否则抛出异常 peek() // 返回队头元素,否则返回null element() //返回队头元素,否则抛出异常 |
2.Map接口
Map接口涉及三种类型的集合:1.HashMap 2.LinkedHashMap 3.TreeMap。其层级关系如下:
Map----
|
|----SortMap----TreeMap
|
|----HashMap----LinkedHashMap
Map的特性为键值不能重复。每个键值对应着一个值,键与值一起存储在集合中。
Map接口中有如下方法:
clear() //删除所有条目
containsKey(Object key) //如果包含指定键值返回true
containsValue(Object value) //如果包含指定值返回true
get(Object key) //获得指定键值对应的值
entrySet() //返回包含条目的规则集
isEmpty() //判断是否空
keySet() //返回包含键值的一个规则集
put(Object key, Object value) //添加键值对
putAll( ) //将指定实例中的键值对添加到当前实例中
remove(Object key) //删除指定键值对应的值
size() //键值对个数
values() //返回包含的集合
4、常用的字符串处理方法
数据分析工作中,最基本的一项工作就是通过hive写类sql语言处理数据,而类sql语法中处理字符串的方法都是通过对java的字符串处理方法进行一层封装得到的,接下来,我们就一起来看下常用Java字符串处理方法有哪些。
字符串查找
String提供了两种查找字符串的方法,即indexOf与lastIndexOf方法。
1、indexOf(String s)
该方法用于返回参数字符串s在指定字符串中首次出现的索引位置,如果没有检索到字符串s,该方法返回-1
String str =“We are students”;``int size = str.indexOf(“a”); // 变量size的值是3
2、lastIndexOf(String str)
该方法用于返回字符串最后一次出现的索引位置。如果没有检索到字符串str,该方法返回-1。如果lastIndexOf方法中的参数是空字符串"" ,则返回的结果与length方法的返回结果相同。
获取指定索引位置的字符
使用charAt()方法可将指定索引处的字符返回。
String str = “hello word”;``char mychar = str.charAt(5); // mychar的结果是w
获取子字符串
通过String类的substring()方法可对字符串进行截取。
1、substring(int beginIndex)
该方法返回的是从指定的索引位置开始截取直到该字符串结尾的子串。
String str = “Hello,word”;``String substr = str.substring(3); //获取字符串,此时substr值为lo,word
2、substring(int beginIndex, int endIndex)
String str = “Hello word”;``String substr = str.substring(0,3); //substr的值为hel
去除空格
trim()方法返回字符串的副本,忽略前导空格和尾部空格。
字符串替换
replace()方法可实现将指定的字符或字符串替换成新的字符或字符串
String str= “address”;``String newstr = str.replace(“a”,“A”);// newstr的值为Address
判断字符串的开始与结尾
startsWith()方法与endsWith()方法分别用于判断字符串是否以指定的内容开始或结束。这两个方法的返回值都为boolean类型。
1、startsWith(Stringprefix)
该方法用于判断当前字符串对象的前缀是否是参数指定的字符串。
2、endsWith(Stringsuffix)
该方法用于判断当前字符串是否以给定的子字符串结束
判断字符串是否相等
1、equals(Stringotherstr)
如果两个字符串具有相同的字符和长度,则使用equals()方法比较时,返回true。同时equals()方法比较时区分大小写。
2、equalsIgnoreCase(Stringotherstr)
equalsIgnoreCase()方法与equals()类似,不过在比较时忽略了大小写。
字母大小写转换
字符串的toLowerCase()方法可将字符串中的所有字符从大写字母改写为小写字母,而toUpperCase()方法可将字符串中的小写字母改写为大写字母。
str.toLowerCase();
str.toUpperCase();
字符串分割
使用split()方法可以使字符串按指定的分隔字符或字符串对内容进行分割,并将分割后的结果存放在字符串数组中。
str.split(‘&’);
str.split(String sign, in limit);
该方法可根据给定的分割符对字符串进行拆分,并限定拆分的次数。
5、常用的日期处理方法
另一个在数据分析工作中,跟字符串处理一样使用较为频繁的就是关于日期的相关处理。
其中最常用到的日期处理类有:java.util.Date、java.util.Calendar、java.text.SimpleDateFormat。
1.java.util.Date的使用
构造函数
-
Date() :分配 Date 对象并用当前时间初始化此对象,以表示分配它的时间(精确到毫秒)。
-
Date(long date) :分配 Date 对象并初始化此对象,以表示自从标准基准时间(即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。
常用方法
2.java.text.SimpleDateFormat的使用
java.text.SimpleDateFormat主要用于格式化日期,需要说明的一点是该类的实例是线程不安全的。
构造函数
public SimpleDateFormat(String pattern):pattern是描述日期和时间格式的模式 如:yyyyMMDD
使用方式
public class TestDateFormat {
public voiddateFormat(){
Date date= new Date(); //创建不同的日期格式` `SimpleDateFormat dt1 = new SimpleDateFormat('YYYY-MM-DD HH:MM:SS');
dt1.format(date);//返回的日期格式如:2019-05-13 04:03:45
SimpleDateFormat dt2 = new SimpleDateFormat(‘YYYY-MM-DD’);
dt2.format(date);//返回的日期格式如:2019-05-13
SimpleDateFormat df3 = new SimpleDateFormat(“yyyy年MM月dd日 hh时mm分ss秒 EE”,Locale.CHINA) ;
dt3.format(date);//返回的日期格式如:2019年05月13日 04时03分45秒 星期一
}``}
3.java.util.Calendar的使用
java.util.Calendar是个抽象类,它可以通过特定的方式设置和读取日期的特定部分,比如年、月、日、时等,并为操作日历字段(例如获得下星期的日期)提供了一些方法。
创建实例
Calendar dt = Calendar.getInstance();
常见操作
//设置时间dt.setTime(new Date());` `//获取年月日时分秒
dt.get(Calendar.YEAR);dt.get(Calendar.MONTH);
dt.get(Calendar.DAY_OF_MONTH);dt.get(Calendar.HOUR);
dt.get(Calendar.MINUTE);dt.get(Calendar.SECOND);` `//获取上午下午
dt.get(Calendar.AM_PM);
//获取一周中的星期几dt.get(Calendar.DAY_OF_WEEK);` `//当前日期基础上加减指定天数
dt.add(Calendar.YEAR, -1);``dt.sub(Calendar.YEAR, -1);
6、json的解析与操作
json的表达能力非常强,一方面拥有常用的数据类型,另一方面可以表达复杂的数据结构。因此,在大数据领域,经常使用json作为信息的载体,将数据封装起来。所以,理解json的结构,对json进行解析与操作,在数据分析工作中非常重要。
下面是几个常用的 JSON 解析类库:
-
Json官方:Douglas Crockford在2001年开始推广使用的数据格式,解析最具有通用性,但是有点小麻烦
-
Gson:谷歌开发的 JSON 库,功能十分全面
-
FastJson:阿里巴巴开发的 JSON 库,性能十分优秀
-
Jackson:社区十分活跃且更新速度很快
下面我们主要通过代码示例来了解下java变量和json格式之间的相互转化以及json对象与字符串的相互转化。
1.编码
从 Java 变量到 JSON 格式的编码过程如下:
public void testJson() {
JSONObject object = new JSONObject();
//string
object.put(“string”,“string”);
//int
object.put(“int”,2);
//boolean
object.put(“boolean”,true);
//array
List integers = Arrays.asList(1,2,3);
object.put(“list”,integers);
//null
object.put(“null”,null);
System.out.println(object);``}
输出结果如下:
{“boolean”:true,“string”:“string”,“list”:[1,2,3],“int”:2}
2.解码
从 JSON 对象到 Java 变量的解码过程如下:
public void testJson2() {
JSONObjectobject = JSONObject
.parseObject("{\"boolean\":true,\"string\":\"string\",\"list\":[1,2,3],\"int\":2}");
//string
String s = object.getString("string");
System.out.println(s);
//int
int i = object.getIntValue("int");
System.out.println(i);
//boolean
boolean b = object.getBooleanValue("boolean");
System.out.println(b);
//list
List<Integer> integers = JSON.parseArray(object.getJSONArray("list").toJSONString(),Integer.class);
integers.forEach(System.out::println);
//null
System.out.println(object.getString("null"));``}
打印结果如下:string2``true``1``2``3``null
3.JSON 对象与字符串的相互转化
//前提说明:JSON是一个抽象类,以下实例中的parseObject(String text)、parseArray(String text)、toJSONString(JSONObject obj)、toJSONString(JSONArray arr)是该抽象类的静态方法 //从字符串解析JSON对象JSONObject obj =JSON.parseObject("{\"runoob\":\"测试实例\"}");
//从字符串解析JSON数组JSONArray arr =JSON.parseArray("[\"测试实例\",\"RUNOOB\"]\n");
//将JSON对象转化为字符串String objStr =JSON.toJSONString(obj);
//将JSON数组转化为字符串``String arrStr =JSON.toJSONString(arr);
7、正则表达式
数据分析过程中,经常需要对字符串进行匹配、替换、提取等操作,而有时简单的字符串处理方法已经无法满足复杂的处理逻辑时,就需要使用到正则表达式来完成。
这部分的内容,在ETL中的数据提取和处理环节应用非常频繁,所以大家要重点掌握。
概念
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
–摘自百度百科
作用
1.匹配:给定的字符串是否符合正则表达式的过滤逻辑
2.**提取:**可以将匹配成功的特定部分提取出来
3.**替换:**针对匹配成功的特定部分,替换为新的字符串
规则
图片摘自云游道士的博客
PS:由于篇幅有限,上述规则只是列出了常用的一些规则,如果在实际工作中发现上述规则不满足业务需求,可以百度一下,寻找其他规则。以下为一些常用的匹配示例。
示例
匹配目标 | 常用正则表达式 |
邮箱 | ^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ |
手机号码 | ^1[3-9]\d{9}$ |
网页URL | ^(https?://)?[\S]+$ |
IP地址 | ^(((?:[1-9]\d?)|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))[.]){3}((?:[1-9]\d?)|(?:1\d{2})|(?:2[0-4]\d)|(?:25[0-5]))$ |
密码(8-16位,只允许字母、数字、指定符号(_+-.?)) | ^[\w\-\.+?]{8,16}$ |
注意事项
1. 使用"|"时,应尽量使用括号来标识边界,否则容易引起歧义
2. 当使用正则匹配是否包含某一字符串时,不需要在前后加模糊匹配,否则会引起性能下降。例如匹配文本(“This is a pretty dog”)中是否包含“pre”开头的单词时,正则表达式应写作“pre[a-z]+”,而不要写作“.*pre[a-z]+.*”
3. 所有的特殊字符在[ ]内都将失去其原有的特殊含义,使用时要特别注意。上面的示例中密码的匹配就是一个字符(+?)含义变化例子
4. 正则表达式中的符号都是英文半角符号
5. 写好正则后,尤其是复杂的表达式,最好可以测试一下。
可以使用在线工具完成测试,如:
https://c.runoob.com/front-end/854
http://tool.oschina.net/regex/
在java中的使用示例
import java.util.regex.*;// 下面只是使用示例代码,不能直接运行
// 目标字符串String url ="https://aa.bb.com/cn/index.html";
// URL匹配规则String regex = "^(https?://)?[\S]+$";
// 编译正则表达式Pattern pattern =Pattern.compile(regex);
// 匹配正则表达式与目标字符串,返回匹配结果对象Matcher matcher =pattern.matcher(url);
// 判断字符串是否与正则表达式匹配成功boolean result =matcher.matches();
System.out.println(result);
PS:在java中还有一种忽略正则表达式内英文字母大小写的写法,意思是在匹配时,忽略目标字符串中英文字母的大小写,都能匹配成功,写法如下所示:
// 忽略大小写的写法``Pattern pattern =Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
8、异常处理
相信大家对异常都不陌生,当程序运行中发生错误时,通常都会抛出异常(Exception)来警醒开发人员,如果不处理就会导致程序直接退出。
数据分析工作中,经常会遇到各种问题,这时异常便提供了找到相应问题诱发原因的重要线索,因此我们要保持良好的编码习惯,避免错过重要提示线索。
讲解这部分知识的主要目的,是用于数据分析日常工作中排查程序问题。怎么捕获异常,怎么根据异常寻找错误发生的位置,相信大家都比较熟悉了,这里我们不再赘述,而是说一下异常处理常遇到的几种问题。
1.你可能没捕获到你的“异常”
我们常见的异常都是Exception的子类,但是异常的顶级父类是Throwable,它下面有两个子类一个是Exception,另一个是Error。由于Error非常少见,所以大家通常会忽略它。如果你发现你的try catch语句没能捕获住“异常”,那就要考虑下,抛出的可能是Error。
还有一种可能性,就是分布式程序的异常。例如,编写Spark的处理代码时,要能分清你的代码是运行在Driver还是Executor上,如果异常发生在Executor,而你在Driver上捕获,那么也会出现无法捕获的问题。
2.捕获异常后,不要让异常被吃掉
工作中经常被问到一个问题,我的代码没有任何报错,但是运行后却没有输出结果,或者结果数据量很少。
为什么会这样呢?
这是因为,有些同学在捕获异常时,可能不太关心异常的发生,如由于脏数据引起的异常(非法json解析失败),捕获到这种解析异常,只需要过滤掉对应脏数据即可,便在catch语句块中什么都没写。
这虽不影响程序运行,但是却吞掉了非常重要的信息。尤其是使用异常的父类Exception来进行捕获时,可能有超出你预想的异常,就这样被吃掉了。
根据实际工作的经验,建议大家至少要把错误打印出来,方便后续排查。不然很容易出现,程序内部一直在出错,但是表面上却风平浪静。
打印异常有两种方式,一是只打印异常摘要信息,即打印异常的message,通过getMessage()方法获得后打印;
另一种是打印详细的堆栈信息,通过调用printStackTrace()方法完成详细信息打印。一般建议,在Executor中执行的代码(如逐条数据处理的代码)打印摘要信息,在Driver上执行的代码,打印堆栈信息。
3. 不要在finally里使用return
finally代码块通常是用来做一些必须要做的收尾工作,如释放资源、修改特定标记位等。
尽量不要在finally代码块中使用return,这会使代码的执行结果变得不好预期。如果需要确保一定有返回值,请catch住所有异常,然后在finally代码块的下面写return。
4. 巧妙利用异常返回信息
当调用一个方法时,通常我们会使用return给调用者返回数据。但还有一种不太常用的做法,就是使用异常返回信息,让调用者通过捕获异常的方式,获取到异常内携带的信息。关于这种方式的应用,在后续讲解spark时会提到。
5. 合理使用自定义异常
通过自定义异常,我们可以建立自己的异常处理体系,针对不同的业务错误,进行相应等级的处理。举个例子,在数据处理过程中,需要读取多个数据源,但是可能存在要读取的路径不存在,或者读入的数据量为0等情况,这时通常需要阻断流程,最好的方式就是抛异常,但有时我们又希望阻断部分流程,这时只有抛一个自定义的异常,然后外层流程控制中捕获这个异常,有针对性地处理才能达成目的。
9、JDBC
最后,关于java连接数据库的桥梁jdbc自然要提及一下,主要还是讲讲如何使用。
1.加载数据库驱动
//1.加载驱动``Class.forName(“com.mysql.jdbc.Driver”);
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
等级的处理。举个例子,在数据处理过程中,需要读取多个数据源,但是可能存在要读取的路径不存在,或者读入的数据量为0等情况,这时通常需要阻断流程,最好的方式就是抛异常,但有时我们又希望阻断部分流程,这时只有抛一个自定义的异常,然后外层流程控制中捕获这个异常,有针对性地处理才能达成目的。
9、JDBC
最后,关于java连接数据库的桥梁jdbc自然要提及一下,主要还是讲讲如何使用。
1.加载数据库驱动
//1.加载驱动``Class.forName(“com.mysql.jdbc.Driver”);
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-CfnoNRFI-1715605153286)]
[外链图片转存中…(img-tvbpyROB-1715605153287)]
[外链图片转存中…(img-YqmfB2Hd-1715605153287)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!