Java基础(中)
1. 使用Junit来分段测试
package com.itheima.demo01_junit;
import org.junit.Test;
/*
案例: 演示Junit的用法.
Junit单元测试简介:
概述:
它属于白盒测试, 早期是给测试人员使用的, 但是非常好用, 目前很多开发人员也都在使用.
作用:
让代码不用放到main方法中, 也能执行.
步骤:
1. 在项目(或者模块)下新建一个文件夹, lib
2. 把要用的jar包拷贝进来.
3. 配置运行变量.
选中jar, 右键 -> add as library
注意事项(细节):
*/
public class UsersUtils {
@Test
public void add() {
//...
System.out.println("往 users 表中 添加数据.");
}
@Test
public void delete() {
//...
System.out.println("从 users 表中 删除数据.");
}
}
2. 内部类
package com.itheima.demo02_inner;
/*
案例: 演示匿名内部类的用法.
内部类简介:
概述:
类里边还有一个类, 里边那个叫内部类, 外边那个叫外部类.
格式:
详见类A
分类:
成员内部类:
定义在成员位置的内部类, 一般是用来对类的功能做延伸的, 多用于底层源码.
局部内部类:
定义在局部位置的内部类, 我们用的最多的匿名内部类就是属于 局部内部类.
匿名内部类:
概述:
指的是没有名字的局部内部类.
格式:
new 类名或者接口名() {
//重写类中所有的抽象方法
};
本质:
专业版: 匿名内部类就是一个继承了类, 或者实现了接口的子类的匿名对象.
大白话: 匿名内部类的本质是一个 子类对象.
使用场景:
1. 当对成员方法仅调用一次的时候.
2. 可以作为方法的实参进行传递.
*/
public class Demo01 {
public static void main(String[] args) {
//需求: 调用Animal#eat()方法.
//方式1: 普通做法, 搞一个类继承Animal, 然后: 抽象类多态
//an解释: 就是一个继承了Animal类的子类Cat类的对象, 子类名叫:Cat, 对象名叫: an
Animal an = new Cat();
an.eat();
System.out.println("--------------------");
//方式2: 匿名对象, 即: 没有名字的对象.
//new Cat()解释: 就是一个继承了Animal类的子类Cat类的对象, 子类名叫:Cat, 对象名叫: 不知道
new Cat().eat();
System.out.println("--------------------");
//方式3: 匿名内部类, 本质就是一个 该类(该接口)的子类对象
//匿名内部类解释: 就是一个继承了Animal类的子类对象, 子类名叫:不知道, 对象名叫: 不知道
new Animal() {
//重写类中所有的抽象方法
@Override
public void eat() {
System.out.println("匿名内部类方式创建Animal的子类, 动物会吃!");
}
}.eat();
}
}
package com.itheima.demo02_inner;
/*
案例: 演示匿名内部类的使用场景之: 可以作为方法的实参进行传递.
*/
public class Demo02 {
public static void main(String[] args) {
//调用printAnimal()方法
//格式: printAnimal(Animal类的子类对象);
//方式1: 普通做法, 搞一个类继承Animal, 然后: 抽象类多态
Animal an = new Cat();
printAnimal(an);
System.out.println("-----------------------------");
//方式2: 匿名对象, 即: 没有名字的对象.
printAnimal(new Cat());
System.out.println("-----------------------------");
//方式3: 匿名内部类, 本质就是一个 该类(该接口)的子类对象
printAnimal(new Animal() {
@Override
public void eat() {
System.out.println("匿名内部类实现, 动物会吃!");
}
});
}
//打印动物.
public static void printAnimal(Animal an) {
an.eat();
}
}
3. collection
package com.itheima.demo03_collection;
import java.util.ArrayList;
import java.util.Collection;
/*
案例: 演示Collection集合入门.
集合简介:
概述:
就使用来同时存储 多个 同类型元素的容器, 其长度是 可变的.
体系:
Collection: 单列集合顶层接口
List:
特点:
元素有索引, 有序, 可重复.
常用子类:
ArrayList:
LinkedList:
Set:
特点:
元素无索引, 无序, 唯一.
常用子类:
HashSet:
TreeSet:
Map: 双列集合顶层接口
特点:
键具有唯一性, 值可以重复.
常用子类:
HashMap:
TreeMap:
泛型:
概述:
泛指某种具体的数据类型.
格式:
<数据类型>
细节:
1. 泛型是JDK1.5出来的, 必须是引用类型.
2. 前后泛型必须一致, 或者后边的泛型可以省略不写(JDK1.7的特性: 菱形泛型 )
3. 实际开发中, 泛型一般只结合集合一起使用.
4. 泛型进阶, 泛型分为: 泛型类, 泛型方法, 泛型接口, 这些内容目前了解即可.
5. 泛型一般用字母: T: type(类型), E: Element(元素), K: key(键), V: value(值) 表示.
细节:
1. 集合的顶层都是接口(例如: Collection, List, Set, Map), IO流的顶层都是抽象类(InputStream, OutputStream, Reader, Writer)
2. 集合如何限定里边存储元素的数据类型呢?
通过 泛型 实现.
3. 当我们接触一个新的继承体系, 建议采用"学顶层, 用底层的"的方式学习.
顶层: 是整个继承体系的共性内容, 大家都有.
底层: 才是具体的体现, 实现.
*/
public class Demo01 {
public static void main(String[] args) {
//1. 创建集合对象, 不指定泛型.
Collection list1 = new ArrayList();
list1.add(10);
list1.add(false);
list1.add("abc");
//2. 创建集合对象, 指定泛型.
//Collection<String> list2 = new ArrayList<String>();
Collection<Integer> list2 = new ArrayList<>();
list2.add(10);
//list2.add(false);
//list2.add("abc");
}
}
package com.itheima.demo03_collection;
import com.itheima.pojo.Student;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/*
案例: List集合存储学生对象并遍历.
*/
public class Demo04 {
public static void main(String[] args) {
//1. 创建集合对象.
List<Student> list = new ArrayList<>();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
list.add(new Student("刘亦菲", 33));
list.add(new Student("赵丽颖", 31));
list.add(new Student("高圆圆", 35));
//4. 遍历集合.
//方式1: 普通迭代器.
Iterator<Student> it = list.iterator();
while (it.hasNext()) {
Student s = it.next();
System.out.println(s);
}
System.out.println("-------------------");
//方式2: 普通for循环.
for (int i = 0; i < list.size(); i++) {
Student s = list.get(i);
System.out.println(s);
}
}
}
package com.itheima.demo03_collection;
import com.itheima.pojo.Student;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/*
案例:演示列表迭代器的用法.
列表迭代器简介:
概述:
它指的是ListIterator, 是Iterator接口的子接口, 它独属于List体系, 即: Collection, Set用不了.
成员方法:
public booelan hasPrevious(); 判断有没有上一个元素.
public E previous(); 如果有, 就获取上一个元素.
细节:
1. 进行逆向遍历之前, 必须进行一次正向遍历.
2. 正向遍历和逆向遍历必须使用同一个(列表)迭代器.
*/
public class Demo05 {
public static void main(String[] args) {
//1. 创建集合对象.
List<Student> list = new ArrayList<>();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
list.add(new Student("刘亦菲", 33));
list.add(new Student("赵丽颖", 31));
list.add(new Student("高圆圆", 35));
//4. 遍历集合.
//4.1 根据集合对象, 获取其对应的迭代器对象.
ListIterator<Student> lit = list.listIterator();
//正向遍历, 从前往后.
//4.2 判断迭代器中是否有下一个元素.
while (lit.hasNext()) {
Student s = lit.next();
//4.3 如果有, 就获取.
System.out.println(s);
}
System.out.println("-----------------");
//逆向遍历.
while (lit.hasPrevious()) {
System.out.println(lit.previous());
}
}
}
package com.itheima.demo03_collection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
案例: 回顾上午内容, List集合存储字符串, 并遍历.
List集合的遍历方式:
1. 增强for
2. 列表迭代器.
3. 普通for循环.
4. 普通迭代器.
*/
public class Demo08 {
public static void main(String[] args) {
//1. 创建集合对象.
List<String> list = new ArrayList<>();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
list.add("hello");
list.add("world");
list.add("java");
//4. 遍历集合.
//方式1: 普通迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
System.out.println("---------------------");
//方式2: 列表迭代器
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
String s = lit.next();
System.out.println(s);
}
System.out.println("---------------------");
//方式3: 普通for循环
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------------");
//方式4: 增强for
for (String s : list) {
System.out.println(s);
}
}
}
4. map
package com.itheima.demo06_map;
import java.util.HashMap;
import java.util.Map;
/*
案例: 演示Map集合入门.
Map简介:
概述:
它是双列集合的顶层接口, 键具有唯一性, 值可以重复, 数据结构只针对于键有效.
成员方法:
V put(K key,V value) 添加元素, 如果键是第一次添加, 就返回null, 如果键是重复添加, 就用新值覆盖旧值,并返回覆盖前的旧值.
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
*/
public class Demo01 {
public static void main(String[] args) {
//1. 创建集合对象.
Map<String, String> hm = new HashMap();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
System.out.println(hm.put("杨过", "大雕")); //null
System.out.println(hm.put("杨过", "小龙女")); //大雕
//4. 遍历集合.
System.out.println("hm: " + hm);
}
}
package com.itheima.demo06_map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
案例: 演示Map集合的获取功能.
涉及到的Map集合的成员方法:
public V get(K key); 根据键获取其对应的值.
public Collection<V> values(); 获取所有值的集合.
public Set<K> keySet(); 获取所有键的集合.
*/
public class Demo02 {
public static void main(String[] args) {
//1. 创建集合对象.
HashMap<String, String> hm = new HashMap();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
hm.put("乔峰", "阿朱");
hm.put("虚竹", "梦姑");
hm.put("段誉", "王语嫣");
//public Collection<V> values(); 获取所有值的集合.
Collection<String> values = hm.values();
System.out.println("values: " + values);
System.out.println("-----------------------");
//public Set<K> keySet(); 获取所有键的集合.
Set<String> keys = hm.keySet();
System.out.println("keys: " + keys);
System.out.println("-----------------------");
//public V get(K key); 根据键获取其对应的值.
System.out.println(hm.get("乔峰"));
System.out.println(hm.get("虚竹"));
System.out.println(hm.get("段誉"));
}
}
package com.itheima.demo06_map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Set;
/*
案例: 演示Map集合的遍历方式.
Map集合的遍历方式: 根据键找值, 即: 根据丈夫找妻子.
1. 获取所有键的集合. 即: 获取所有的丈夫. Map#keySet()
2. 遍历, 获取到每一个键. 即: 获取到每一个丈夫. 增强for, 普通迭代器.
3. 根据键获取其对应的值. 即: 根据丈夫找妻子. Map#get()
*/
public class Demo03 {
public static void main(String[] args) {
//1. 创建集合对象.
HashMap<String, String> hm = new HashMap();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
hm.put("乔峰", "阿朱");
hm.put("虚竹", "梦姑");
hm.put("段誉", "王语嫣");
//4. 遍历集合.
//4.1 获取所有键的集合. 即: 获取所有的丈夫. Map#keySet()
Set<String> keys = hm.keySet();
//4.2 遍历, 获取到每一个键. 即: 获取到每一个丈夫. 增强for, 普通迭代器.
for (String key : keys) {
//4.3 根据键获取其对应的值. 即: 根据丈夫找妻子. Map#get()
String value = hm.get(key);
System.out.println(key + " = " + value);
}
System.out.println("----------------------");
//实际开发写法
for (String key : hm.keySet())
System.out.println(key + " = " + hm.get(key));
}
}
5. 可变参数
package com.itheima.demo08_params;
/*
案例: 演示可变参数.
需求:
1. 定义getSum()方法, 用来获取n个整数的和(n可能是任意的一个数字).
2. 在main方法中, 调用getSum()方法.
可变参数解释:
概述:
JDK1.5的特性, 表示参数个数可变, 多用于方法的 形参列表.
格式:
数据类型... 变量名
使用场景:
当某一个方法有多个同类型参数的时候, 且具体个数不明确的时候, 就可以考虑使用可变参.
本质:
可变参数的本质就是一个: 数组.
细节:
1. 可变参数表示参数的个数可变, 最少0个, 最多无数个.
2. 一个方法的形参列表有且只能有1个可变参数, 并且可变参必须放到形参列表的最后.
*/
public class Demo01 {
public static void main(String[] args) {
//测试getSum()方法
System.out.println(getSum()); //可变参数表示参数的个数可变, 最少0个, 最多无数个.
System.out.println(getSum(1, 2)); //可变参数表示参数的个数可变, 最少0个, 最多无数个.
System.out.println(getSum(1, 2, 3, 4, 5)); //可变参数表示参数的个数可变, 最少0个, 最多无数个.
System.out.println(getSum(200, 33)); //可变参数表示参数的个数可变, 最少0个, 最多无数个.
int[] arr = {11, 22, 33};
System.out.println(getSum(arr));
}
//定义getSum()方法, 求和.
//public static int getSum(double b, int... arr) {
public static int getSum(int... arr) {
int sum = 0;
for (int i : arr) {
sum += i;
}
return sum;
}
}
6. reflection 反射
package com.itheima.demo09_reflect;
import com.itheima.demo02_inner.Cat;
import com.itheima.pojo.Student;
/*
案例: 演示反射入门之 如何获取某个类的字节码文件对象.
反射入门:
概述:
指的就是在程序的运行期间, 通过类的字节码文件对象 来操作 类中成员(成员变量, 构造方法, 成员方法)的过程.
如何获取某个类的字节码文件对象?
方式1: 通过类的 class属性, 一般用于充当 锁对象.
方式2: 通过类的 getClass()方法实现, 一般用于判断两个对象是否是同一个类的对象.
方式3: 通过反射的方式实现, 一般用于强制加载某个类的字节码文件进内存.
反射案例:
1. 反射操作类中的构造方法.
2. 反射操作类中的成员变量.
3. 反射操作类中的成员方法.
4. 动态代理, 即: 运行指定配置文件中指定类的指定方法, 目的是告诉大家, 为什么很多框架我们只要配置了配置文件信息, 就会有不同的效果.
反射步骤:
1. 获取该类的字节码文件对象.
2. 获取要操作的成员对象(构造器, 成员变量, 成员方法)
3. 创建该类的实例.
4. 操作指定的成员.
*/
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException {
//演示如何获取某个类的字节码文件对象
//方式1: 通过类的 class属性
Class<Student> clazz1 = Student.class;
//方式2: 通过类的 getClass()方法实现
Student s = new Student();
Class<? extends Student> clazz2 = s.getClass();
//方式3: 通过反射的方式实现
Class<?> clazz3 = Class.forName("com.itheima.pojo.Student");
System.out.println(clazz1 == clazz2); //true
System.out.println(clazz1 == clazz3); //true
System.out.println(clazz2 == clazz3); //true
}
}
package com.itheima.demo09_reflect;
import com.itheima.pojo.Student;
import java.lang.reflect.Field;
/*
反射案例: 反射操作类中的成员变量.
反射步骤:
1. 获取该类的字节码文件对象.
2. 获取要操作的成员对象(构造器, 成员变量, 成员方法)
3. 创建该类的实例.
4. 操作指定的成员.
*/
public class Demo03 {
public static void main(String[] args) throws Exception {
//1. 获取该类的字节码文件对象.
Class<?> clazz = Class.forName("com.itheima.pojo.Student");
//2. 获取要操作的成员对象(构造器, 成员变量, 成员方法)
Field name = clazz.getDeclaredField("name");
//3. 创建该类的实例.
Student s = (Student) clazz.newInstance();
//暴力反射
name.setAccessible(true);
//4. 操作指定的成员.
name.set(s, "刘亦菲");
//5. 打印对象
System.out.println(s);
}
}
package com.itheima.demo09_reflect;
import com.itheima.pojo.Student;
import java.lang.reflect.Method;
/*
反射案例: 反射操作类中的成员方法.
反射步骤:
1. 获取该类的字节码文件对象.
2. 获取要操作的成员对象(构造器, 成员变量, 成员方法)
3. 创建该类的实例.
4. 操作指定的成员.
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
//1. 获取该类的字节码文件对象.
Class<?> clazz = Class.forName("com.itheima.pojo.Student");
//2. 获取要操作的成员对象(构造器, 成员变量, 成员方法)
Method study = clazz.getMethod("study");
//3. 创建该类的实例.
Student s = (Student) clazz.newInstance();
//4. 操作指定的成员.
study.invoke(s);
//5. 打印对象
System.out.println(s);
}
}
6. Lambda表达式
package com.itheima;
public interface Addable {
int getSum(int x, int y);
}
package com.itheima;
public class Demo03 {
public static void main(String[] args){
//方式1: 匿名内部类, 体现的是面向对象思想, 自己创建对象, 自己重写方法, 自己写代码逻辑.
useAddable(new Addable() {
@Override
public int getSum(int x, int y) {
System.out.println("匿名内部类");
return x + y;
}
});
System.out.println("----------------------");
//方式2: Lambda表达式, 体现的是面向函数式思想, 即: 只关心做什么, 而不是怎么做.
useAddable((int x, int y)->{
System.out.println("Lambda表达式");
return x+y;
});
System.out.println("----------------------");
//方式3: Lambda表达式, 体现的是面向函数式思想, 即: 只关心做什么, 而不是怎么做.
//格式: useAddable(Addable接口的子类对象, int x, int y )
useAddable((int x, int y)->{
System.out.println("Lambda表达式");
return x+y;
}, 20, 20);
System.out.println("----------------------");
//省略模式 1. 参数类型可以省略, 但是如果有多个参数的情况下, 不能只省略一个.
useAddable((x, y)->{
System.out.println("Lambda表达式");
return x+y;
}, 20, 20);
System.out.println("----------------------");
//省略模式 3. 如果方法体只有一句话, 则: return, {}, ; 都可以省略
useAddable((x, y)->x+y, 20, 30);
}
public static void useAddable(Addable a){
int sum = a.getSum(10, 20);
System.out.println(sum);
}
public static void useAddable(Addable a, int x, int y){
int sum = a.getSum(x, y);
System.out.println(sum);
}
}
7. IO流
package com.itheima.demo02_io;
import java.io.FileOutputStream;
import java.io.IOException;
/*
IO流简介:
概述:
指的是Input(输入) / Output(输出), 也叫输入/输出流, 就是用来实现文件传输的.
应用场景:
1. 文件上传.
2. 文件下载.
分类:
按照流向分:
输入流: 就是用来 读取 数据的.
输出流: 就是用来 写 数据的.
按照操作分:
字节流:
特点:
以字节为单位拷贝数据, 能操作任意类型的文件, 也叫万能流.
分类:
字节输入流: 以字节为单位读取数据, 顶层抽象类: InputStream
FileInputStream: 基本的字节输入流.
BufferedInputStream: 高效的字节输入流.
字节输出流: 以字节为单位写数据, 顶层抽象类: OutputStream
FileOutputStream: 基本的字节输出流.
BufferedOutputStream: 高效的字节输出流.
字符流:
特点:
以字符为单位拷贝数据, 只能操作纯文本文件.
纯文本文件解释: 一个文件能用微软自带的记事本打开, 且里边的内容你也能看懂, 就是纯文本文件.
分类:
字符输入流: 以字符为单位读取数据, 顶层抽象类: Reader
FileReader: 基本的字符输入流.
BufferedReader: 高效的字符输入流.
字符输出流: 以字符为单位写数据, 顶层抽象类: Writer
FileWriter: 基本的字符输出流.
BufferedWriter: 高效的字符输出流.
记忆:
1. 实际开发中, 优先使用字符流, 字符流搞不定再考虑使用字节流.
2. 如果目的地文件不存在, 程序会自动创建(前提: 该文件的父目录必须存在)
3. 只要出现乱码问题, 原因只有一个: 编解码不一致.
4. 如果一个类的对象想实现序列化或者反序列化操作, 则该类必须实现 Serializable接口.
*/
public class Demo01 {
}
package com.itheima.demo02_io;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
案例: 基本的字节输出流入门.
需求:
1. 创建FileOutputStream对象, 关联指定的目的地文件.
2. 往文件中写入字符'a', 'b', 'c'.
FileOutputStream简介:
概述:
它表示基本的字节输出流, 即: 以字节为单位往文件中写数据.
构造方法:
public FileOutputStream(String path); 往文件中添加数据, 默认是: 覆盖.
public FileOutputStream(String path, boolean append); 往文件中添加数据, 第二个参数: true(追加), false(覆盖)
成员方法:
public void write(int n); 一次写一个字节
public void write(byte[] bys); 一次写一个字节数组
public void write(byte[] bys, int start, int len); 一次写一个字节数组的一部分
public void close(); 关闭流对象, 释放资源.
细节:
如果目的地文件不存在, 程序会自动创建(前提: 该文件的父目录必须存在)
*/
public class Demo02 {
public static void main(String[] args) throws IOException {
//1. 创建输出流对象, 关联: 目的地文件.
//FileOutputStream fos = new FileOutputStream("day05_lambda&IO&Socket/data/1.txt"); //覆盖
FileOutputStream fos = new FileOutputStream("day05_lambda&IO&Socket/data/1.txt", true); //追加
//2. 往文件中写数据.
//方式1: 一次写一个字节.
/*fos.write(97);
fos.write(98);
fos.write(99);*/
//方式2: 一次写一个字节数组.
/* //写入换行
fos.write("\r\n".getBytes()); //写入换行, 默认用的是: utf-8码表
byte[] bys = {65, 66, 67, 68, 69}; //ABCDE
fos.write(bys);*/
//方式3: 一次写一个字节数组的一部分.
byte[] bys = {65, 66, 67, 68, 69}; //BCD
fos.write(bys, 1, 3);
//3. 关流, 释放资源.
fos.close();
}
}
package com.itheima.demo02_io;
import java.io.FileOutputStream;
import java.io.IOException;
/*
案例: 字节输出流, 创建一个指定大小的空文件.
需求:
在指定的路径下(例如: d:/abc)创建一个大小为1G的空文件.
*/
public class Demo03 {
public static void main(String[] args) throws IOException {
//1. 创建输出流, 关联目的地文件.
FileOutputStream fos = new FileOutputStream("d:/abc/房老师&鲁老师.avi");
//2. 往文件中写数据.
byte[] bys = new byte[1024]; //1024个字节 = 1KB
//循环实现
for (int i = 0; i < 1024 * 1024 * 1; i++) {
fos.write(bys);
}
//3. 释放资源.
fos.close();
}
}
package com.itheima.demo02_io;
import java.io.FileInputStream;
import java.io.IOException;
/*
案例: 演示字节输入流入门.
需求:
1. 创建FileInputStream对象, 关联指定的数据源文件.
2. 通过一次读取一个字节的形式, 读取该文件中的数据.
FileInputStream简介:
概述:
它表示基本的字节输入流, 即: 以字节为单位读取数据.
构造方法:
public FileInputStream(String path); 关联数据源文件, 如果文件不存在, 会报错.
成员方法:
public int read();
一次读取一个字节, 并返回读取到的内容(例如: 'a' -> 97), 读不到返回-1
public int read(byte[] bys);
一次读取一个字节数组, 并将读取到的内容存储到字节数组中, 然后返回读取到的有效字节数(例如: 'a' -> 1), 读不到返回-1
public void close(); 释放资源
*/
public class Demo04 {
public static void main(String[] args) throws IOException {
//需求1: 一次读取一个字节.
/* //1. 创建输入流, 关联: 数据源文件.
FileInputStream fis = new FileInputStream("day05_lambda&IO&Socket/data/1.txt");
//2. 定义变量, 记录读取到的内容(字节)
int len = 0;
//3. 循环读取.
*//*
(len = fis.read()) != -1 这样代码做了 3 件事儿:
1. 执行 fis.read(), 一次读取一个字节, 并返回读取到的内容.
2. 执行 len = fis.read(), 把上一步读取到的数据赋值给变量.
3. 执行 len != -1, 判断读取到的内容是否是-1, 如果是: 说明读取完毕, 如果不是说明还有内容.
*//*
while((len = fis.read()) != -1) {
System.out.println(len);
}
//4. 释放资源.
fis.close();*/
//需求2: 一次读取有一个字节数组.
//1. 创建输入流, 关联: 数据源文件.
FileInputStream fis = new FileInputStream("day05_lambda&IO&Socket/data/1.txt");
//2. 定义变量, 记录读取到的内容(字节)
int len = 0;
byte[] bys = new byte[3]; //实际开发, 数组长度一般是 1024 的整数倍.
//3. 循环读取.
//合并版
while ((len = fis.read(bys)) != -1) {
System.out.println(new String(bys, 0, len)); //abc d
}
//分解版
/*int len1 = fis.read(bys);
System.out.println("bys: " + Arrays.toString(bys)); //a, b, c
System.out.println("len1: " + len1); //3
int len2 = fis.read(bys);
System.out.println("bys: " + Arrays.toString(bys)); //d, b, c
System.out.println("len2: " + len2); //1
int len3 = fis.read(bys);
System.out.println("bys: " + Arrays.toString(bys)); //d, b, c
System.out.println("len3: " + len3); //-1*/
//4. 释放资源.
fis.close();
}
}
package com.itheima.demo02_io;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/*
案例: 演示字节高效流的用法.
字节高效流简介:
概述:
字节高效流也叫高效字节流, 字节缓冲流, 缓冲字节流, 指的是: BufferedInputStream, BufferedOutputStream.
构造方法:
BufferedInputStream: 字节高效输入流.
public BufferedInputStream(InputStream is);
BufferedOutputStream: 字节高效输出流.
public BufferedOutputStream(OutputStream os);
问题: 为什么高效流封装的不是数据源或者目的地文件的路径, 而是一个基本的字节流呢?
答案: 因为高效流仅仅提供缓冲区的功能, 具体的读写还是有基本的流来实现的.
*/
public class Demo05 {
public static void main(String[] args) throws IOException {
//需求: 把1.txt -> 2.txt文件中.
//方式1: 字节高效流 一次读写一个字节.
/* //1. 创建输入流, 关联: 数据源文件.
//分解版
//创建基本的字节输入流, 关联: 数据源文件, 用于读取文件内容.
FileInputStream fis = new FileInputStream("day05_lambda&IO&Socket/data/1.txt");
//创建高效的字节输入流, 关联: 基本的字节流, 用来提供 缓冲区的功能.
BufferedInputStream bis = new BufferedInputStream(fis);
//2. 创建输出流, 关联: 目的地文件
//合并版.
//字节高效输出流 = 普通的字节输出流 + 文件路径
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day05_lambda&IO&Socket/data/2.txt"));
//3. 定义变量, 记录读取到的内容(字节, 有效字节数, 字符, 有效字符数, 字符串)
int len;
//4. 循环读取, 并将读取到的内容赋值给变量, 只要条件满足, 就一直读.
while ((len = bis.read()) != -1) {
//5. 将读取到的数据写入到目的地文件中.
bos.write(len);
}
//6. 释放资源.
bis.close();
bos.close();*/
//方式2: 字节高效流 一次读写一个字节数组.
//1. 创建输入流, 关联: 数据源文件.
//分解版
//创建基本的字节输入流, 关联: 数据源文件, 用于读取文件内容.
FileInputStream fis = new FileInputStream("day05_lambda&IO&Socket/data/1.txt");
//创建高效的字节输入流, 关联: 基本的字节流, 用来提供 缓冲区的功能.
BufferedInputStream bis = new BufferedInputStream(fis);
//2. 创建输出流, 关联: 目的地文件
//合并版.
//字节高效输出流 = 普通的字节输出流 + 文件路径
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("day05_lambda&IO&Socket/data/3.txt"));
//3. 定义变量, 记录读取到的内容(字节, 有效字节数, 字符, 有效字符数, 字符串)
int len;
byte[] bys = new byte[1024];
//4. 循环读取, 并将读取到的内容赋值给变量, 只要条件满足, 就一直读.
while ((len = bis.read(bys)) != -1) {
//5. 将读取到的数据写入到目的地文件中.
bos.write(bys, 0, len);
}
//6. 释放资源.
bis.close();
bos.close();
}
}
package com.itheima.demo02_io;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/*
案例: 演示编解码的问题.
字符流简介:
背景: 即, 为什么有字符流?
原因: 因为中文字符在不同的码表中占用字节数不一样, 如果采用字节流拷贝文本文件, 当数据源和目的地文件码表不一致的时候, 可能会产生乱码问题.
即: 字符流 = 字节流 + 编码表.
常见的码表:
ASCII: 美国信息通用交换码表, 定义了最基础的规则, 大多数的码表几乎都支持这个码表.
'a' -> 97, 'A' -> 65, '0' -> 48
ISO-8859-1: 欧洲通用码表.
GBK: 国内用的码表.
utf-8: 国际通用码表, 也叫: 万国码, 统一码.
细节:
1. 英文字母, 数字, 特殊符号不管在什么码表中都只占1个字节, 且都是正数.
2. 中文字符在GBK码表中占2个字节, UTF-8码表中占3个字节.
3. 中文字符的第一个字节都是负数.
4. 字符串及其对应的字节数组之间如何相互转换:
字符串 -> 字节数组:
String类中的成员方法:
public byte[] getBytes(); 把字符串转成对应的码表, 采用: 默认码表
public byte[] getBytes(String characterName); 把字符串转成对应的码表, 采用: 指定码表
字节数组 -> 字符串:
String类中的构造方法:
public String(byte[] bys) 把字节数组转成其对应的字符串, 采用: 默认码表.
public String(byte[] bys, String characterName) 把字节数组转成其对应的字符串, 采用: 指定码表.
概述:
字符流指的是以字符为单位拷贝数据, 它的顶层抽象类是: Reader 和 Writer
*/
public class Demo06_ASCII {
public static void main(String[] args) throws Exception {
//字符串 -> 字节数组: 这个动作叫 编码.
//utf-8码表: 97, 98, 99, 49, 50, 51, -28, -67, -96, -27, -91, -67
//gbk码表: 97, 98, 99, 49, 50, 51, -60, -29, -70, -61
String s = "abc123你好";
byte[] bys1 = s.getBytes(); //utf-8
byte[] bys2 = s.getBytes("utf-8"); //utf-8
byte[] bys3 = s.getBytes("gbk"); //utf-8
System.out.println("bys1: " + Arrays.toString(bys1));
System.out.println("bys2: " + Arrays.toString(bys2));
System.out.println("bys3: " + Arrays.toString(bys3));
System.out.println("----------------------");
//字节数组 -> 字符串 : 这个动作叫 解码.
//byte[] bys = {97, 98, 99, 49, 50, 51, -28, -67, -96, -27, -91, -67};
byte[] bys = {97, 98, 99, 49, 50, 51, -60, -29, -70, -61};
String utf8Str = new String(bys);
String gbkStr = new String(bys, "gbk");
System.out.println("utf8Str: " + utf8Str);
System.out.println("gbkStr: " + gbkStr);
}
}
package com.itheima.demo02_io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
/*
案例: 演示字符流拷贝文件.
字符流拷贝文件的4种方式:
1. 基本的字符流一次读写一个字符.
2. 基本的字符流一次读写一个字符数组.
3. 高效的字符流一次读写一个字符.
4. 高效的字符流一次读写一个字符数组.
5. 高效的字符流独有的拷贝方式: 一次读写一行.
细节:
字符流只能拷贝纯文本文件.
*/
public class Demo07 {
public static void main(String[] args) throws IOException {
//拷贝方式1. 基本的字符流一次读写一个字符.
//method01();
//拷贝方式2. 基本的字符流一次读写一个字符数组.
//method02();
//拷贝方式3. 高效的字符流一次读写一个字符.
//method03();
//拷贝方式4. 高效的字符流一次读写一个字符数组.
method04();
}
private static void method04() throws IOException {
//1. 创建输入流, 关联: 数据源文件.
BufferedReader br = new BufferedReader(new FileReader("day05_lambda&IO&Socket/data/1.txt"));
//2. 创建输出流, 关联: 目的地文件
BufferedWriter bw = new BufferedWriter(new FileWriter("day05_lambda&IO&Socket/data/5.txt"));
//3. 定义变量, 记录读取到的内容(字节, 有效字节数, 字符, 有效字符数, 字符串)
int len;
char[] chs = new char[1024];
//4. 循环读取, 并将读取到的内容赋值给变量, 只要条件满足, 就一直读.
while ((len = br.read(chs)) != -1) {
//5. 将读取到的数据写入到目的地文件中.
bw.write(chs, 0, len);
}
//6. 释放资源.
br.close();
bw.close();
}
private static void method03() throws IOException {
//1. 创建输入流, 关联: 数据源文件.
BufferedReader br = new BufferedReader(new FileReader("day05_lambda&IO&Socket/data/1.txt"));
//2. 创建输出流, 关联: 目的地文件
BufferedWriter bw = new BufferedWriter(new FileWriter("day05_lambda&IO&Socket/data/4.txt"));
//3. 定义变量, 记录读取到的内容(字节, 有效字节数, 字符, 有效字符数, 字符串)
int len;
//4. 循环读取, 并将读取到的内容赋值给变量, 只要条件满足, 就一直读.
while ((len = br.read()) != -1) {
//5. 将读取到的数据写入到目的地文件中.
bw.write(len);
}
//6. 释放资源.
br.close();
bw.close();
}
private static void method02() throws IOException {
//1. 创建输入流, 关联: 数据源文件.
FileReader fr = new FileReader("day05_lambda&IO&Socket/data/1.txt");
//2. 创建输出流, 关联: 目的地文件
FileWriter fw = new FileWriter("day05_lambda&IO&Socket/data/3.txt");
//3. 定义变量, 记录读取到的内容(字节, 有效字节数, 字符, 有效字符数, 字符串)
int len;
char[] chs = new char[1024];
//4. 循环读取, 并将读取到的内容赋值给变量, 只要条件满足, 就一直读.
while ((len = fr.read(chs)) != -1) {
//5. 将读取到的数据写入到目的地文件中.
fw.write(chs, 0, len);
}
//6. 释放资源.
fr.close();
fw.close();
}
private static void method01() throws IOException {
//1. 创建输入流, 关联: 数据源文件.
FileReader fr = new FileReader("day05_lambda&IO&Socket/data/1.txt");
//2. 创建输出流, 关联: 目的地文件
FileWriter fw = new FileWriter("day05_lambda&IO&Socket/data/2.txt");
//3. 定义变量, 记录读取到的内容(字节, 有效字节数, 字符, 有效字符数, 字符串)
int len;
//4. 循环读取, 并将读取到的内容赋值给变量, 只要条件满足, 就一直读.
while ((len = fr.read()) != -1) {
//5. 将读取到的数据写入到目的地文件中.
fw.write(len);
}
//6. 释放资源.
fr.close();
fw.close();
}
}
package com.itheima.demo02_io;
import java.io.FileWriter;
import java.io.IOException;
/*
案例: 演示IO流的两个常见面试题.
问题1: 字节高效流, 字符高效流, Writer各自的缓冲区大小分别是多少?
答案: 8kb 16KB 2KB
问题2: flush() 和 close()方法的区别是什么?
答案:
flush(): 用来刷新缓冲区的, 把缓冲区的数据刷出来, 刷新之后, 流对象还可以继续使用.
close(): 用来关闭流释放资源的, 关闭之前会自动刷新一次缓冲区, 关闭之后, 流对象不能继续使用.
*/
public class Demo08 {
public static void main(String[] args) throws IOException {
/*FileOutputStream fos = new FileOutputStream("day05_lambda&IO&Socket/data/2.txt");
fos.write("abc".getBytes());*/
FileWriter fw = new FileWriter("day05_lambda&IO&Socket/data/2.txt");
fw.write("马老师好!");
//fw.close();
fw.flush();
fw.write("赵老师好!");
//fw.flush();
fw.close();
}
}
package com.itheima.demo02_io;
import com.itheima.pojo.Student;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashSet;
/*
案例: 演示序列化操作.
序列化操作:
概述:
指的是把对象写入到文件中, 用的是 ObjectOutputStream.
成员方法:
public void writeObject(Object obj); 把对象序列化到文件中.
public void close(); 释放资源.
需求: 序列化学生对象到文件中.
细节:
如果一个类的对象想实现序列化或者反序列化操作, 则该类必须实现 Serializable接口.
Serializable接口解释:
它是一个标记接口, 里边没有任何的成员, 只是做一个标记的.
细节:
1. 如果先序列化, 然后在修改JavaBean类内容, 再反序列化行不行?
不行.
2. 如何解决?
方式1: 重新序列化一次, 再反序列化.
方式2: 给一个 serialVersionUID;
3. 如果某个属性不想被序列化怎么办?
通过 transient(瞬态关键字) 修饰即可.
*/
public class Demo09 {
public static void main(String[] args) throws IOException {
//1. 创建对象序列化流, 关联: 目的地文件.
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day05_lambda&IO&Socket\\data\\2.txt"));
//2. 创建学生对象, 并将其序列化到文件中.
Student s = new Student("刘亦菲", 33);
oos.writeObject(s);
//3. 释放资源.
oos.close();
}
}
package com.itheima.demo02_io;
import com.itheima.pojo.Student;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
/*
案例: 演示反序列化操作.
反序列化操作:
概述:
指的是从文件中读取对象, 用的是 ObjectInputStream.
成员方法:
public Object readObject(); 读取对象.
public void close(); 释放资源.
需求: 序列化学生对象到文件中.
细节:
如果一个类的对象想实现序列化或者反序列化操作, 则该类必须实现 Serializable接口.
Serializable接口解释:
它是一个标记接口, 里边没有任何的成员, 只是做一个标记的.
*/
public class Demo10 {
public static void main(String[] args) throws Exception {
//1. 创建反序列化流, 关联数据源文件.
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day05_lambda&IO&Socket/data/2.txt"));
//2. 反序列化动作.
Object obj = ois.readObject();
Student s = (Student)obj;
//3. 打印对象.
System.out.println(s);
//4. 释放资源.
ois.close();
}
}
8. JDBC数据链接
数据库连接地址的标准写法,必须要按照这种写法否则,插入数据时候中文就会产生乱码了。
jdbc:mysql://192.168.88.100:3306/day06?user=root&password=123456&useUnicode=true&characterEncoding=utf-8"
package com.itheima.demo01_jdbc;
import com.mysql.jdbc.Driver;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/*
案例: 演示JDBC入门案例.
JDBC简介:
概述:
全称叫Java DataBase Connectivity, Java数据库连接, 就是通过Java代码操作不同的数据库.
原理:
由Sun公司提供统一的规则,规范(JDBC规范), 具体的实现和体现交给不同的数据库厂商来做.
即: 我们要连接哪个数据库, 只要导入它的驱动(数据库厂商提供)即可.
核心作用:
1. 连接不同的数据库.
2. 向数据库发送指令(SQL语句).
3. 接收返回的结果集.
JDBC入门案例:
JDBC查询表数据.
JDBC的核心步骤:
0. 导入指定数据库的驱动(就是 jar包), 即: 连接哪个库, 就导入谁的驱动.
1. 注册驱动.
2. 获取连接对象.
3. 根据连接对象, 获取可以执行SQL语句的对象.
4. 执行SQL语句, 获取结果集.
5. 操作结果集.
6. 释放资源.
JDBC的相关API介绍:
DriverManager类解释:
概述:
它是一个类, 表示驱动管理者, 主要用于: 注册驱动 和 获取连接对象的.
成员方法:
public static void registerDriver(Driver driver);
注册驱动, 但是这种方式会导致驱动注册两次, 实际开发没人用.
public static Connection getConnection(String url, String username, String password);
获取连接对象的, 三个参数的解释如下:
参数1: 数据库连接字符串
格式:
协议:子协议://要连接的数据库的ip地址:端口号/具体的要连接的数据库.
示例:
jdbc:mysql://192.168.30.68:3306/day06
jdbc:mysql://localhost:3306/day06
jdbc:mysql://127.0.0.1:3306/day06
jdbc:mysql:///day06 如果操作本机, 可以省略: 主机名和端口号
Connection接口:
概述:
它表示连接对象, 即: Java 和 不同数据库之间的连接对象, 主要负责: 事务管理, 获取可以执行SQL语句的对象.
成员方法:
事务管理方法:
setAutoCommit(), commit(), rollback()
获取可以执行SQL语句的对象:
public Statement createStatement();
获取可以执行SQL语句的对象, 没有预编译功能, 会发生SQL注入攻击问题.
public PreparedStatement prepareStatement(String sql);
获取可以执行SQL语句的对象, 有预编译功能, 不会发生SQL注入攻击问题.
Statement接口:
概述:
它表示可以执行SQL语句的对象, 没有预编译功能, 主要负责: 批处理, 执行SQL语句, 获取结果集.
作用:
关于批处理的方法:
addBatch(), executeBatch(), clearBatch();
执行SQL语句, 获取结果集:
public ResultSet executeQuery(String sql) 执行查询语句, 获取结果集.
public int executeUpdate(String sql) 执行更新(增, 删, 改)语句, 获取结果集.
ResultSet接口:
概述:
它表示执行完查询语句后返回的结果集(本质是一张结果表), 主要用于获取 数据.
成员方法:
public boolean next(); 判断结果集中是否有下一条数据, 类似于 Iterator#hasNext()
public Xxx getXxx(int columnIndex); 根据列的编号获取列的数据, 编号从 1 开始.
public Xxx getXxx(String columnName); 根据列的名字获取列的数据, 推荐使用.
*/
public class Demo01 {
//JDBC入门案例, 最粗糙版.
@Test
public void show1() throws Exception {
//1. 注册驱动.
DriverManager.registerDriver(new Driver());
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) { //类似于Iterator#hasNext()
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + ", " + username + ", " + password);
}
//6. 释放资源.
rs.close();
stat.close();
conn.close();
}
//JDBC的相关API解释之: DriverManager类
@Test
public void show2() throws Exception {
//1. 注册驱动.
//DriverManager.registerDriver(new Driver());
//实际开发注册驱动方式: 反射.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
//Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day06", "root", "123456");
Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) { //类似于Iterator#hasNext()
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + ", " + username + ", " + password);
}
//6. 释放资源.
rs.close();
stat.close();
conn.close();
}
//JDBC的相关API解释之: Connection接口, 代码同上
//JDBC的相关API解释之: Statement接口, 代码同上
//JDBC的相关API解释之: ResultSet接口
@Test
public void show3() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select username, uid, password from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
//方式1: 根据列的编号获取.
/*int uid = rs.getInt(1);
String username = rs.getString(2);
String password = rs.getString(3);*/
//方式2: 根据列的名字获取.
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + ", " + username + ", " + password);
}
//6. 释放资源.
rs.close();
stat.close();
conn.close();
}
//JDBC入门案例: 最终版.
}
package com.itheima.demo01_jdbc;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
/*
案例: 演示JDBC的CURD操作.
*/
public class Demo02 {
//JDBC的 查
@Test
public void method1() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select * from users";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
System.out.println(rs.getInt("uid") + ", " + rs.getString("username") + ", " + rs.getString("password"));
}
//6. 释放资源.
rs.close();
stat.close();
conn.close();
}
//JDBC的 增
@Test
public void method2() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "INSERT INTO users VALUES(NULL, '唐老师', 'tanglaoshi');";
int num = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(num > 0 ? "添加成功" : "添加失败");
//6. 释放资源.
stat.close();
conn.close();
}
//JDBC的 改
@Test
public void method3() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "update users set username='杨飞' where uid=4;";
int num = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(num > 0 ? "修改成功" : "修改失败");
//6. 释放资源.
stat.close();
conn.close();
}
//JDBC的 删
@Test
public void method4() throws Exception {
//1. 注册驱动.
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "delete from users where uid=4;";
int num = stat.executeUpdate(sql);
//5. 操作结果集.
System.out.println(num > 0 ? "删除成功" : "删除失败");
//6. 释放资源.
stat.close();
conn.close();
}
}
package com.itheima.demo01_jdbc;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
/*
案例: 演示SQL注入攻击问题.
SQL注入攻击问题:
概述:
指的是如果我们的SQL语句中部分代码是要求用户录入的, 当用户录入一些非法字符或者非法值的时候,
被我们的SQL语句识别了, 从而改变了SQL语句的结构, 就会引发一系列的安全问题, 这些安全问题就叫:
SQL注入攻击问题.
案例: 模拟登陆
select * from users where username = '用户录入的账号' and password = '用户录入的密码';
听话的用户:
账号: 刘亦菲
密码: liuyifei
小屌丝:
账号: sdkjfsd
密码: sldf ' or '1=1
老屌丝:
账号: slkdfs
密码: klsdf ' or '1=1'; drop database day08;'
*/
public class Demo03 {
public static void main(String[] args) throws Exception {
//1. 提示用户录入账号和密码, 并接收.
Scanner sc = new Scanner(System.in);
System.out.println("请录入您的账号: ");
String uname = sc.nextLine();
System.out.println("请录入您的密码: ");
String pwd = sc.nextLine();
//2. 判断用户是否登录成功.
//2.1. 注册驱动.
//2.2. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//2.3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//2.4. 执行SQL语句, 获取结果集.
String sql = "select * from users where username = '" + uname + "' and password = '" + pwd + "';";
ResultSet rs = stat.executeQuery(sql);
//2.5. 操作结果集.
System.out.println(rs.next() ? "登录成功" : "登录失败");
//2.6. 释放资源.
JDBCUtils.release(null, stat, conn);
}
}
package com.itheima.demo01_jdbc;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
/*
案例: 解决SQL注入攻击问题, 采用PreparedStatement的预编译功能.
Connection接口的成员方法:
public PreparedStatement prepareStatement(String sql);
获取可以执行SQL语句的对象, 有预编译功能, 不会发生SQL注入攻击问题.
PreparedStatement接口是Statement接口的子接口.
预编译解释:
即: 预先对SQL语句进行一次编译, 此时已经决定了SQL语句的格式, 把需要用户传入的内容用占位符替代即可, 预编译之后,
不管用户传入什么内容, 都只会当做普通字符处理.
细节:
如果用PreparedStatement接口的预编译功能, 因为已经预先传入SQL语句了, 所以执行SQL语句的时候, 不需要额外传入SQL语句.
*/
public class Demo04 {
public static void main(String[] args) throws Exception {
//1. 提示用户录入账号和密码, 并接收.
Scanner sc = new Scanner(System.in);
System.out.println("请录入您的账号: ");
String uname = sc.nextLine();
System.out.println("请录入您的密码: ");
String pwd = sc.nextLine();
//2. 判断用户是否登录成功.
//2.1. 注册驱动.
//2.2. 获取连接对象.
Connection conn = JDBCUtils.getConnection();
//2.3. 根据连接对象, 获取可以执行SQL语句的对象.
String sql = "select * from users where username = ? and password=? ;";
PreparedStatement ps = conn.prepareStatement(sql);
//给占位符填充值.
ps.setString(1, uname);
ps.setString(2, pwd);
//2.4. 执行SQL语句, 获取结果集.
//ResultSet rs = ps.executeQuery(sql); //预编译之后, 不要再传入了, 否则报错.
ResultSet rs = ps.executeQuery();
//2.5. 操作结果集.
System.out.println(rs.next() ? "登录成功" : "登录失败");
//2.6. 释放资源.
JDBCUtils.release(null, ps, conn);
}
}
9. DBCP连接池
9.1 c3p0-config.xml文件
需要放在src目录下面
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day06</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池参数 -->
<!-- 数据连接池中默认的 连接对象的个数 -->
<property name="initialPoolSize">5</property>
<!-- 数据连接池中最大的 连接对象的个数 -->
<property name="maxPoolSize">10</property>
<!-- 连接对象的最大超时时间, 单位是: 毫秒. -->
<property name="checkoutTimeout">3000</property>
</default-config>
<named-config name="otherc3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">8</property>
<property name="checkoutTimeout">1000</property>
</named-config>
</c3p0-config>
package com.itheima.demo03_dbcp;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/*
案例: 演示数据库连接池入门.
数据库连接池简介:
概述:
大白话版:
实际开发中, 当我们需要使用大量生命周期短的连接对象时, 每次频繁的创建和销毁这些连接对象是非常消耗资源的,
针对于这种情况, 我们可以搞一个池子出来, 里边放一些连接对象, 用的时候从里边拿, 用完之后再放回去, 这个池子
就叫: 数据库连接池(Data Base Connection Pool, 简称: DBCP), 这样做的好处是: 节约资源, 提高效率.
专业版:
数据库连接池就使用来解决频繁的创建和销毁连接对象的, 将其从销毁改为"自动归还到数据库连接池中".
分类:
DBCP: 属于Apache软件基金会, 不能自动回收空闲连接.
C3P0: 属于Apache软件基金会, 能自动回收空闲连接.
Java的框架中, 底层但凡涉及到数据库连接池了, 默认用的都是: C3P0
Druid: 属于阿里巴巴, 中文名叫: 德鲁伊, 采用分布式事务的方式实现的, 能更好的兼容分布式集群.
作用:
数据库连接池就使用来解决频繁的创建和销毁连接对象的, 将其从销毁改为"自动归还到数据库连接池中".
步骤:
0. 准备动作
A. 拷贝C3P0的jar包到lib文件夹中, 并配置运行变量.
B. 将C3P0数据库连接池的配置文件, 放到 src目录下.
1. 创建数据库连接池对象, 它会自动去src目录下读取 c3p0-config.xml 这个配置文件.
2. 从数据库连接池中获取 连接对象.
3. 其他步骤和之前一样, 例如: 执行SQL语句, 获取结果集, 操作结果集.
4. 释放资源即可, 同样都是些 conn.close(), 但是细节如下:
如果连接对象是我们自己创建的, conn.close() 就是销毁.
如果连接对象是从连接池获取的, conn.close() 就是 自动归还连接对象到连接池中, 因为连接池底层已经 重构了 Connection#close()方法.
目前的问题:
虽然加入数据库连接池后, 看起来效率好像是高了, 但其实效率反而低了, 因为目前我们的数据库连接池默认有 5 个连接对象,
在这里我们就用了1个, 其它4个是闲置的状态, 之后每创建一个数据库连接池, 就默认会有5个连接对象, 效率反而低.
之所以出现这样的问题, 原因是因为: 我们没有实现数据库连接池对象的共享, 应该让大家用1个数据库连接池.
所以, 我们要自定义 C3P0Utils工具类, 实现连接池的操作.
*/
public class Demo01 {
public static void main(String[] args) throws Exception {
//1. 注册驱动.
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接对象.
//方式1: 我们自己创建连接对象.
//Connection conn = DriverManager.getConnection("jdbc:mysql:///day06", "root", "123456");
//方式2: 从数据库连接池中获取连接对象.
//1. 创建数据库连接池对象, 它会自动去src目录下读取 c3p0-config.xml 这个配置文件.
//ComboPooledDataSource cpds = new ComboPooledDataSource(); //默认读取 <default-config> 标签中的信息.
ComboPooledDataSource cpds = new ComboPooledDataSource("otherc3p0"); //读取 name为 otherc3p0 标签中的信息.
//2. 从数据库连接池中获取 连接对象.
Connection conn = cpds.getConnection();
//3. 根据连接对象, 获取可以执行SQL语句的对象.
Statement stat = conn.createStatement();
//4. 执行SQL语句, 获取结果集.
String sql = "select username, uid, password from users;";
ResultSet rs = stat.executeQuery(sql);
//5. 操作结果集.
while (rs.next()) {
//方式1: 根据列的编号获取.
/*int uid = rs.getInt(1);
String username = rs.getString(2);
String password = rs.getString(3);*/
//方式2: 根据列的名字获取.
int uid = rs.getInt("uid");
String username = rs.getString("username");
String password = rs.getString("password");
System.out.println(uid + ", " + username + ", " + password);
}
//6. 释放资源.
rs.close();
stat.close();
conn.close();
}
}
10. 事务
package com.itheima.demo04_transaction;
import utils.JDBCUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo01 {
public static void main(String[] args){
Connection conn = null;
Statement stat = null;
try{
conn = JDBCUtils.getConnection();
stat = conn.createStatement();
String sql1 = "update account set money = money - 1000 where aid = 1;";
String sql2 = "update account set money = money + 1000 where aid = 2;";
conn.setAutoCommit(false);
int i1 = stat.executeUpdate(sql1);
int i2 = stat.executeUpdate(sql2);
System.out.println(i1);
if (i1 == i2 && i1 == 1) {
System.out.println("转账成功");
conn.commit(); //提交事务
}
} catch (Exception e){
System.out.println("转账失败");
try{
conn.rollback();
}catch (SQLException throwables){
throwables.printStackTrace();
}
}finally {
JDBCUtils.release(null, stat, conn);
}
}
}
11. 线程
定义一个线程,使用Thread类
package com.itheima.thread;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("run........." + i);
}
}
}
调用这个线程
package com.itheima.thread;
public class Demo02 {
public static void main(String[] args){
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 100; i++) {
System.out.println("main..." + i);
}
}
}
定义一个线程使用Runable接口
package com.itheima.thread;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("run........." + i);
}
}
}
package com.itheima.thread;
public class Demo03 {
public static void main(String[] args){
MyRunnable mr = new MyRunnable();
Thread th = new Thread(mr);
th.start();
for (int i = 0; i < 100; i++) {
System.out.println("main..." + i);
}
}
}
综合
package com.itheima.thread;
public class Demo04 {
public static void main(String[] args){
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("继承Thread类...." + i);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("实现Runnable接口......." + i);
}
}
}).start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("Lambda表达式......." + i);
}
}).start();
}
}
11. 线程与同步锁
package com.itheima.demo07_tickets_synchronized;
public class MyThread extends Thread {
private static int tickets = 100;
public MyThread(){
}
public MyThread(String name){super(name);}
@Override
public void run() {
while (true){
synchronized (MyThread.class){
if (tickets < 1){
break;
}
}
try {
Thread.sleep(50);
} catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(getName() + " 正在售卖第 " + tickets-- + " 号票");
}
}
}
package com.itheima.demo07_tickets_synchronized;
public class Demo01 {
public static void main(String[] args) {
//1. 创建4个线程对象, 表示4个窗口.
MyThread mt1 = new MyThread("窗口1");
//mt1.setName("窗口1");
MyThread mt2 = new MyThread("窗口2");
MyThread mt3 = new MyThread("窗口3");
MyThread mt4 = new MyThread("窗口4");
//2. 开启线程.
mt1.start();
mt2.start();
mt3.start();
mt4.start();
}
//静态同步方法: 该类的字节码文件对象, 此处是: Demo01.class
public static synchronized void method01() {
System.out.println("start");
System.out.println("hello world 1");
System.out.println("hello world 2");
System.out.println("hello world 3");
System.out.println("end");
}
//非静态同步方法: this
public synchronized void method02() {
System.out.println("start");
System.out.println("hello world 1");
System.out.println("hello world 2");
System.out.println("hello world 3");
System.out.println("end");
}
}
12. 死锁
package com.itheima.demo08_deadlock;
/*
案例: 演示死锁(只在面试用).
死锁指的是同步代码块的嵌套.
死锁的思路:
1. 死锁需要两个线程, 两把锁.
2. 一个线程先抢锁A, 后抢锁B.
3. 另一个线程先抢锁B, 后抢锁A, 此时就有可能引发死锁问题.
4. 为了让效果更明显, 用 while(true)改进.
*/
public class Demo01 {
//1. 定义两把锁.
private static final String lockA = "锁A";
private static final String lockB = "锁B";`
public static void main(String[] args) {
//2. 一个线程先抢锁A, 后抢锁B.
new Thread("刘亦菲") {
@Override
public void run() {
// 4. 为了让效果更明显, 用 while(true)改进.
while (true) {
//先抢锁A,
synchronized (lockA) {
System.out.println(getName() + " 获取到" + lockA + ", 等待 " + lockB);
//后抢锁B.
synchronized (lockB) {
System.out.println(getName() + " 获取到" + lockB + ", 成功进入小黑屋!");
}
}
}
}
}.start();
//3. 另一个线程先抢锁B, 后抢锁A, 此时就有可能引发死锁问题.
new Thread("林志颖") {
@Override
public void run() {
// 4. 为了让效果更明显, 用 while(true)改进.
while (true) {
//先抢锁B
synchronized (lockB) {
System.out.println(getName() + " 获取到" + lockB + ", 等待 " + lockA);
//后抢锁A.
synchronized (lockA) {
System.out.println(getName() + " 获取到" + lockA + ", 成功进入小黑屋!");
}
}
}
}
}.start();
}
}
13. 线程
package com.itheima.demo09_join_daemon;
//自定义的线程类
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "....." + i);
}
}
}
优先级
package com.itheima.demo09_join_daemon;
/*
案例: 演示线程的优先级问题.
细节:
1. 线程的默认优先级是5, 范围是: 1 ~ 10, 1最低, 10最高.
2. 线程的优先级为10, 说明该线程一定第一个执行吗?
不一定, 线程的优先级越高, 只是说明该线程获取资源的几率更大, 并不一定第一个执行.
*/
public class Demo01 {
public static void main(String[] args) {
//查看线程默认优先级.
/*System.out.println(Thread.MAX_PRIORITY); //10
System.out.println(Thread.MIN_PRIORITY); //1
System.out.println(Thread.NORM_PRIORITY); //5*/
//1. 创建三个线程对象.
MyThread mt1 = new MyThread("飞机");
MyThread mt2 = new MyThread("高铁");
MyThread mt3 = new MyThread("游艇");
//2. 打印优先级.
/*System.out.println(mt1.getPriority());
System.out.println(mt2.getPriority());
System.out.println(mt3.getPriority());*/
//3. 设置优先级.
mt1.setPriority(1);
mt3.setPriority(10);
//4. 开启线程.
mt1.start();
mt2.start();
mt3.start();
}
}
加入线程
package com.itheima.demo09_join_daemon;
/*
案例: 演示加入线程.
加入线程解释:
即: 等待这个线程执行结束, 类似于: 插队, 当他没有执行结束的时候, 别的线程无法执行.
细节:
加入线程必须第一个开启, 才有效, 目前能看懂即可.
*/
public class Demo02 {
public static void main(String[] args) {
//1. 创建三个线程对象.
MyThread mt1 = new MyThread("康熙");
MyThread mt2 = new MyThread("四阿哥");
MyThread mt3 = new MyThread("八阿哥");
//2. 开启线程.
mt1.start();
//设置mt1为加入线程.
try {
mt1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mt2.start();
mt3.start();
}
}
守护线程
package com.itheima.demo09_join_daemon;
/*
案例: 演示 守护线程.
细节:
1. 默认每个线程都是非守护线程.
2. 当非守护线程死亡的时候, 和它关联的守护线程, 都是强制死亡.
3. 多线程的执行具有 随机性 和 延迟性, 原因是因为CPU在做着高效的切换.
*/
public class Demo03 {
public static void main(String[] args) {
//1. 创建两个线程.
MyThread mt1 = new MyThread("关羽");
MyThread mt2 = new MyThread("张飞");
//2. 设置它们为: 守护线程, 守护当前线程(即: 主线程).
mt1.setDaemon(true);
mt2.setDaemon(true);
//3. 修改主线程的名字.
//Thread.currentThread() 获取当前正在执行的线程对象.
Thread.currentThread().setName("刘备");
//4. 开启线程.
mt1.start();
mt2.start();
//5. 给主线程分配任务, 任务量小一点, 让它提前结束.
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "............" + i);
}
}
}