点击上方“ 大数据与人工智能 ”,“星标或置顶公众号”
第一时间获取好内容
今天,我们开始《大数据分析师入门课程》系列的第一堂课,主讲内容是—— 大数据分析师必备的java基础。
如果你正一脸懵,课?啥课?数据分析?啥意思?
那么,亲爱的小伙伴们,建议大家可以先去看下本公众号的 《 大数据分析工程师入门--0.开篇词》
是的!我们要出课程啦!!!
那么,问题来了。
第一篇为啥以Java开场?
本文想达到怎样的课程目标?
本文的讲解思路是怎样的?
在正式开课学习之前,作者觉得有必要先就这三个问题简单说明一下,好让我们亲爱的读者朋友们可以决定看还是不看,或者也可以选择有针对性的只看某一部份。
为啥以Java开场
作为稳居编程语言排行榜前三的java语言,具有非常多的优秀特性,同时拥有庞大的类库生态和大量的开发者。 Java语言在大数据生态体系中地位也是无可撼动,目前流行的大数据生态组件,很多都是用Java语言或基于JVM的语言(如Scala)开发的。
因此,要想玩转大数据, 或多或少需要对Java有所了解 。
本文想达到怎样的课程目标
由于我们的课程针对的是大数据分析工程师这个特定岗位,所以本文针对Java基础知识的讲解范围,是基于数据分析人员日常工作中最常使用到的那些Java相关知识来圈定的。
也就是每个知识点只会挑选重点部分进行基础性讲解,讲解内容偏向如何使用,不会对底层实现原理等进行深挖。
本文的讲解思路是怎么的?
第1部分 :基本概念及特性
这部分会花较大篇幅给大家介绍java中的几个很重要的基础概念,面向对象、类、对象、封装、继承、多态和泛型,一来给后续讲解中有示例代码的地方打个基础,二来让之前没怎么了解过Java的小伙伴对Java有个初步认识。
第2部分 :初始化过程
主要讲述类(子类和父类)中各成员变量的初始化顺序。
第3部分 :常见的集合及方法
主要简述Collection和Map两个接口以及各集合的常用方法。
第4部分 :常用的字符串处理方法
这部分会对每种方法进行详细说明并通过示例帮助小伙伴们加深理解。
第5部分 :日期处理方法
主要通过示例代码对最常用到的日期处理类java.util.Date、java.util.Calendar、java.text.SimpleDateFormat进行介绍。
第6部分 :json的解析与操作
重点介绍了两点,一是java变量和json格式之间的相互转化,二是json对象与字符串的相互转化。
第7部分 :正则表达式
讲述了正则表达式的概念、作用、基本规则,并给出了示例代码。
第8部分 :异常处理
此处根据实际经验给大家总结下异常处理常遇到的几种问题。
第9部分 :JDBC
主要是通过示例代码给出java连接数据库的操作步骤。
OK,让我们开始吧!
1
基本概念及特性
Java 是 面向对象 的高级编程语言,所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念 ,正所谓万物皆对象。
对象有以下特点:
以上图例中,第四个特点说到:对象都是某个类别的实例。借此,我们要引出Java中另外两个非常重要的概念,那就是 类 和 对象 。
类 :
对某类事物的普遍一致性特征、功能的抽象、描述和封装。
对象:
使用 new 关键字或反射技术创建的某个类的实例。同一个类的所有对象,都具有相同的基础属性(比如人的年龄、性别)和行为(比如人的吃饭、睡觉),但是每个对象的具体属性值和行为表现会具有自己独特的个性。
举例:
老师是java中的一个类,一位24岁的、性别女、名字叫马冬梅的老师就是老师这个类别里对应的一个具体对象。
我们来看一下创建一个具体的java类和对象的代码框架是什么样子的:
public class Teacher {
//属性:姓名、所教课程名、性别、年龄
Stringname;
StringteachClass;
Stringsex;
int age;
publicvoid setName(String name) {
this.name= name;
}
publicvoid setTeachClass(String teachClass) {
this.teachClass = teachClass;
}
publicvoid setSex(String sex) {
this.sex = sex;
}
publicvoid setAge(int age) {
this.age = age;
}
publicTeacher() {
}
//方法:获得老师所教授的课程名
publicvoid getClass(String name, String teachClass) {
System.out.println(name + "老师所教授的课程是"+ teachClass);
}
}
(可左右滑动)
//创建老师类的一个具体对象
public class Main {
public static void main(String[] args) {
//new一个具体对象teacherA
TeacherteacherA = new Teacher();
//具体对象的属性值
teacherA.setName("马冬梅");
teacherA.setSex("女");
teacherA.setAge(22);
//具体对象的行为:马冬梅老师教授的课程是生物
teacherA.getClass(teacherA.getName(), "生物");
}
}
(可左右滑动)
ok,通过以上示例,希望大家对于java的类和对象有了一个初步的概念和了解。
那么, 为什么这种所谓的面向对象的编程理念会得到大家的接纳和推崇呢?
因为面向对象程序设计有以下优点:
1. 可重用 :它是面向对象软件开发的核心思路,提高了开发效率。面向对象程序设计的抽象、继承、封装和多态四大特点都围绕这个核心。
2. 可扩展 :它使面向对象设计脱离了基于模块的设计,便于软件的修改。
接下来就针对Java的 继承、封装、多态和泛型 4 个特性进行讲解,来了解一下它们是如何实现代码重用和扩展的。
1.继承
如同现实生活中的子女继承父母的遗产一样,在java中继承指的是子类继承父类的属性和方法。见以下实例:
大家有没有发现语文老师类和数学老师类里的属性和方法大部分是相同的,以上说到java面向对象编程的优点是可重用性和可扩展性,如何通过继承来实现重用和扩展呢?
那就是通过再提炼一个上层父类--老师类来实现,语文老师类和数学老师类再作为子类对其继承使用。
如下:
采用这种向上抽象方式,是为了将多个类的通用属性和方法提取出来,放在它们的父类中,避免同样的代码写多份(即为了实现复用),在子类中只需要定义自己独有的属性和方法,以继承的方式在父类中获取通用属性和方法即可。
//继承代码结构
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方法。
该方法用于返回参数字符串s在指定字符串中首次出现的索引位置,如果没有检索到字符串s,该方法返回-1
String str ="We are students";
int size = str.indexOf("a"); // 变量size的值是3
该方法用于返回字符串最后一次出现的索引位置。如果没有检索到字符串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类型。
该方法用于判断当前字符串对象的前缀是否是参数指定的字符串。
该方法用于判断当前字符串是否以给定的子字符串结束
判断字符串是否相等
如果两个字符串具有相同的字符和长度,则使用equals()方法比较时,返回true。同时equals()方法比较时区分大小写。
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官方: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<Integer> 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"));
}
(可左右滑动)
打印结果如下:
string
2
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. 替换: 针对匹配成功的特定部分,替换为新的字符串
规则
图片摘自云游道士的博客
地址为https://www.cnblogs.com/yyds/p/6913550.html
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
讲到最后,关于java连接数据库的桥梁jdbc自然要提及一下,主要还是讲讲如何使用。
1.加载数据库驱动
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
2.建立连接
//URL用于标识数据库的位置,username和password是访问数据的用户名和密码
String url ="jdbc:mysql://localhost:3306/test";
String username ="test";
String password ="yqz56258";
Connection conn = null
//2.获取与数据库的链接
conn =DriverManager.getConnection(url, username, password);
(可左右滑动)
3.执行sql获得结果集
Statement st = null;
//3.获取用于向数据库发送sql语句的statement
st =conn.createStatement();
//4.向数据库发sql
String sql = "select id,name,sex,age from teachers";
ResultSet rs =st.executeQuery(sql);
(可左右滑 动)
4.结果集遍历
//next():类似指针的效果,会向下移动
while(rs.next()){
//getString(int index):根据索引获取指定位置的值
String id = rs.getString(1);
String name = rs.getString(2);
String sex = rs.getString(3);
//getString(String columnname):根据列名获取本列的值
String age =rs.getString("age");
}
(可左右滑 动)
10
小结
到此为止,所有在大数据分析工作中最常用到的java基础知识算是带着大家温习一遍啦,让我们一起来回顾下总共讲了哪些知识点:
其中json解析与操作、泛型和初始化部分的示例代码引用自书籍或网络,特此说明,希望阅读完本文的小伙伴们,有所收获哦!
-end-