Object是所有类的顶级父类,常用方法如下:
clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
getClass:获取运行时类型、finalize方法:对象销毁之前执行该方法、
hashCode:该方法用于哈希查找,可以减少在查找中使用equals的次数,重写了equals方法一般都要重写hashCode方法
String toString():
默认返回类名@地址格式,来展示对象的地址值(并不是内存中的地址,只是一种转换);
如果想看属性值我们可以重写这个方法,重写后返回的就是把属性值拼接成一个字符串。
boolean equals(Object obj) :
当前对象和参数对象比较大小,默认是比较内存地址==,如果要比较对象的属性,可以重写该方法。
wait():wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
(1)其他线程调用了该对象的notify方法。
(2)其他线程调用了该对象的notifyAll方法。
(3)其他线程调用了interrupt中断该线程。
(4)时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。
notify():唤醒沉睡线程, notifyAll():唤醒所有沉睡线程;
必须在synchronized内调用,等待通知的对象,必须是加锁的对象(synchronized同步监视器对象),wait外面,应该是一个循环条件判断;
生产者类: public class Producer extends Thread {
private LinkedList<Character> list;
public void run() {
while(true) {
char c = (char)('a'+new Random().nextInt(26));
//避免共享的数据的混乱,保证多线程共享数据的安全;
synchronized (list) {
list.add(c);//加至集合尾部
System.out.println("<<< << < "+c);
list.notifyAll();//通知所有的线程,有货了可以醒了}}}
public Producer(LinkedList<Character> list) {
this.list = list;}
消费者类: public class ConSumer extends Thread {
private LinkedList<Character> list;
public ConSumer(LinkedList<Character> list) {
this.list = list;}
public void run() {
while(true) {
synchronized (list) {
while(list.size()==0) {//没有数据的情况下
try {list.wait();//线程休眠;
} catch (InterruptedException e) {
e.printStackTrace();}}
Character c = list.removeFirst();//从头部移除
System.out.println(">>> >> > " +c);}} }
概述:String底层维护着一个char[]数组,并且字符串对象不可变,每次操作都会创建新对象,所有任何改变字符串的操作都应该赋值接收; 为何不许修改呢?只读,这有什么好处呢?好处大大的:字符串在实际开发中比重非常大,几乎代码都会涉及到字符串。字符串是引用类型,它的分配和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序的性能。 JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为字符串开辟一个字符串常量池,类似于缓存区。创建字符串常量时,首先判断字符串常量池是否存在该字符串,存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中。 上面的意思就是,JVM实现字符串的共享,这就带来一个新的问题。多线程并发访问就会造成安全隐患。线程安全可是非常难解决的。加锁阻塞(就像商城去试衣间,就一间,你要进去,别人就得等着了。)可想而知,性能受到极大的影响。这个怎么好呢?java太厉害了,它就用了一招轻松化解。什么呢?字符串只读。啊,这样也可以,没错。 如果是第一次使用字符串,java会在字符串常量池创建一个对象。再次使用相同的内容时,会直接访问常量池中存在的对象。java中字符串字面量之间的拼接,编译器在编译期会进行优化,在.class文件中保存的是拼接后的结果;String str = new String(“a”+“b”);字符串变量之间的拼接,编译器不会优化,底层采用new的方式创建了一个新的String对象;
Stirngl类常用方法:
new String(byte数组,字符集);
new String(StringBuilder builder) ;
length(): int
indexOf(String str):int
indexOf(String str,int fromIndex):int
lastIndexOf(String str):int
substring(int fromIndex):String
substring(int start,int end):String
trim():String
charAt(int):char
startsWith(String str):boolean
endsWith(String str):boolean:
toUpperCase(): string
toLowerCase():String
s.contact(s1):s字符串拼接到s1末尾
s.contains(String str):是否包含给定字符串
s.toCharArray():转换成字符数组
Str = Str.replaceAll("\\s+", ""):
String 类实现了Comparable接口,并实现了compareTo()方法 方法在实现中,将两个字符串内容中的字符进行ASCII码值减的操作, 从前往后减,如果第一个字符相同,就操作第二个字符,再次减,直到 运行到不相同的字符,将结果返回,如果字符内容和大小写都相同,则返回0 ;
概述:是可变的字符序列,提供一组对内部字符修改方法,用来代替字符串,作高效率字符串连接运算
1、 封装了char[]数组,内部数组初始容量16,放满了容量翻倍+2;
2、 是可变的字符序列
3、 提供了一组可以对字符内容修改的方法
4、 常用append()来代替字符串做字符串连接
5、 内部字符数组默认初始容量是16:super(str.length() + 16);
6、 如果大于16会尝试将扩容,新数组大小原来的变成2倍+2,容量如果还不够,直接扩充到需要的容量大小。int newCapacity = value.length * 2 + 2;
集合扩容:
凡是利用数组实现的长度可变的存结构,都会使用数组复制实现扩容。
ArrayList
1.默认情况下创建 ArrayList时候,其数组容量是10;
2.可以在创建 ArrayList时候指定数组容量。
3.当元素数量超过数组容量时候,进行扩容(数组复制,更换数组),扩容到1.5倍。
4.添加元素多时候,频繁扩容会影响性能,所以建议在可以预知元素数量时候,采用指定容量创建ArraysList。
StringBuilder
1.内部封装的是一个“可变”字符数组,其默认容量是16元素。
2.向StringBuilder添加字符数量超过16时候,会自动扩容(复制更换数组),扩展规则 1倍+2
3.为了避免频繁扩容,可以在创建StringBuilder时候指定容量。
Sb常用方法:
StringBuilder sb = new StringBuilder(); 创建16位的空数组;
StringBuilder sb = newStringBuilder(String “str”):
创建一个包含‘s’‘t’‘r’字符的16位字符数组;
sb.append(String): 拼接字符串
sb.delete(int start,int end):
删除从start开始 到end结束的字符串
sb.insert(int index,String str):
将给定的字符串插入到index位置
sb.replace(int start,int end,String str)
使用给定的字符串替换(下标是包头不包尾)
sb.reverse(): 反转字符串,(判断回文)
StringBuffer和StringBuilder区别:
StringBuffer和StringBuilder功能相同,api的使用也是相同的,区别在于StringBuilder是非线程安全的,StringBuffer是线程安全的;
1.在执行效率上,StringBuilder 1.5> StringBuffer 1.0 > String
2.源码体现:本质上都是在调用父类抽象类AbstractStringBuilder来干活,只不过Buffer把代码加了同步关键字,使得程序可以保证线程安全问题;
概述:数字包装类的抽象父类,提供了各种获取值的方式: (例如) int intvalue();以int形式返回指定的数值;以下是代表子类:
1 java.lang.Integer :
int的包装类,存在MAX_VALUE/MIN_VALUE常量
1、Integer i = new Integer(5);
2、Integer i = Integer.valueOf(5);
在Integer类中,包含256个Integer缓存对象,范围是-128到127。使用valueOf()时,如果指定范围内的值,访问缓存对象,而不新建;如果指定范围外的值,直接新建对象。
static int parseInt(String s): 将字符串参数作为有符号的十进制整数进行解析。
static Integer valueOf(String s): 返回保存指定的String的值的Integer对象。
2 java.lang. Double:
double的包装类
Double d = new Double(3.14)
Double d = Double.valueOf(3.14) //和 new 没有区别
static double parseDouble(String s)
返回一个新的 double 值,该值被初始化为用指定String表示的值,这与 Double 类的 valueOf 方法一样。
static Double valueOf(double d) //返回表示指定的double值的Double实例。
3 BigDecimal/BigInteger:
BigDecimal:常用来解决精确的浮点数运算。
BigInteger:常用来解决超大的整数运算。
BigDecimal.valueOf(2);
add(BigDecimal bd): 做加法运算
substract(BigDecimal bd) : 做减法运算
multiply(BigDecimal bd) : 做乘法运算
divide(BigDecimal bd) : 做除法运算
divide(BigDecimal bd,保留位数,舍入方式):除不尽时使用
setScale(保留位数,舍入方式):同上
pow(int n):求数据的几次幂
日期类,其每一个实例都用于表示一个确切的时间点。内部维护一个Long值,该值为UTC时间,表示的时自1970年1月1日00:00:00到当前Date表示的时间之间所经过的毫秒数;
Date date = new Date():封装的是系统当前时间的毫秒值.
long time = date.getTime():1970年到现在的毫秒数
date.setTime(671094845218L):设置毫秒数,使得date表示该日期,负数表示1970之前,正数表示1970之后
getMonth():获取当前月份
getHours():获取当前小时
int getDay():获得星期几
int getMinutes() :获得当前分钟数
long getTime() :1970-01-01到现在的毫秒
int getYear() :1900年到现在多少年;
String toLocaleString():2019-10-14 16:50:41
compareTo(Date):当前对象与参数对象比较。当前对象大返回正数,小返回负数,相同0。
SimpleDateFormat类可以按照指定的日期格式在Date与String之间相互转换
1、yyyy-MM-dd HH:mm:ss 示例:2019-1-1 12:12:12
2、MM/dd/yyyy 示例:10/14/2019
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String d = format(Date) 把Date格式化成字符串
Date d = parse(String) 把String解析成Date
日历类Calendar是一个抽象类,定义了操作时间的相关方法,常用实现类:GregorianCalendar(阳历)
Calendar calendar = Calendar.getInstance();
根据当前系统所在地区获取一个适用的实现类,通常返回的就是GregorianCalendar;
创建2019年7月15日的Calendar对象
Calendar c1 = Calendar.getInstance();
c1.set(2019, 7 - 1, 15); //月份从0月开始算起的
Date date = calendar.getTime();将当前Calendar表示的时间以一个Date实例返回
calendar.setTime(date);调整当前Calendar的时间为给定的Date所表示的时间。
Int get(int Field) 该方法可以获取指定的时间分量所对应的值,
时间分量为一个int值,无需记住每个值的含义,Calendar提供了大量的常量与之对应
void set(int field,int value) 调整指定的时间分量为指定的值
calendar.set(Calendar.YEAR, 2008): 调整时间为2008年
void add(int field,int amount)
对指定的时间分量加上给定的值,若给定的值为负数则是减去。
calendar.add(Calendar.MONTH,- 3):例如查看三个月前的此时此刻
Ø 流只能单方向流动
Ø 输入流用来读取in
Ø 输出流用来写出Out
Ø 数据只能从头到尾顺序的读写一次。
节点流(低级流):是实际连接程序与数据源的“管道”,针对二进制数据,用于实际搬运数据的流,读写一定是建立在低级流的基础上进行的。
处理流(高级流):高级流不能独立存在,必须连接在其他流上,目的是当数据“流经”当前流时可以对其进行某种加工处理,简化我们读写数据时的操作。
串联一组高级流最终连接到低级流上,在读写的过程中以流水线式的加工处理完成读写操作称为“流的连接”,这也是JAVA IO的精髓所在(学习重点);
JAVA.IO.InputStream:字节输入流的抽象超类(read)。
JAVA.IO.OutputStream:字节输出流的抽象超类(write)。
void close()
关闭此输出流并释放与此流有关的所有系统资源。
void flush()
刷新此输出流并强制写出所有缓冲的输出字节。
void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b)
将指定的字节写入此输出流。
FileInputStream /FileOutputStream(文件流):文件流是一对低级流,用于读写文件的流。文件流的两种创建方式:
覆盖原文件模式:new FileInputStream(File/路径);
追加至末尾模式:new FileInputStream(File路径,true);
randomAccessFile与文件流区别:
文件流其功能与RandomAccessFile一致。但是底层实际的读写方式不同,RandomAccessFile是以指针形式对文件进行随机读写的,并且RAF既可以读数据也可以写数据,能做到对文件部分数据的修改操作 ;
文件流以标准IO形式读写数据的,而标准IO是以顺序读写形式进行操作的(只能向后读或写,不能回退)所以在读写的灵活性上文件流不如RAF,但是由于文件流是以标准IO形式读写,可以利用强大的流连接将一个复杂的数据读写操作简单化,这是RAF做不到的;
缓冲字节流:(高级流)
//1、创建from文件
File from = new File("D:\\teach\\a\\1.jpg");
//2、字节流,读取源文件。
InputStream in = new BufferedInputStream(
new FileInputStream(from) );
//3、创建to文件,同时准备写出流。
File to = new File("D:\\teach\\a\\2.jpg");
OutputStream out = new BufferedOutputStream(
new FileOutputStream(to));
/*4、边读,边向to文件中写出数据
int b = 0;
while( ( b = in.read() ) !=-1) {//单字节读取文件
out.write(b);//写出数据到指定文件 }*/
int b = 0;
//缓存要操作的数据,底层源码一般设置的数组默认大小就是8m
byte[] bs = new byte[8*1024];
while( ( b = in.read(bs) ) !=-1) {//按照数组批量读取
out.write(bs);//按照数组批量写出 }
ObjectInputStream/ObjectOutputStream(对象流):
对象流是一对高级流,使用它们可以方便的在对象与字节之间进行转换,方便我们读写任何java对象。
对象输入流,用于读取一个JAVA对象, 需要注意,读取的这组字节必须是通过对象输出流将一个对象转换的一组字节,否则读取过程会抛出异常。
对象输入流将一组字节按照其描述结构还原该对象的过程称为:对象的序列化。 希望被对象流序列化的类必须实现接口Serializable
/*
* 编码转换流():
* InputStreamReader
* OutPutStreamWriter
* 处理换行的流:
* BufferedReader readLine();
* PrintWriter println()
* BR---ISR---网络输入流(inputStream):
* PW---OPR---网络输出流(outputStream):
*/
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(),"utf-8"));
String line;
while((line = br.readLine())!= null) {
pw.println(line);
pw.flush();
序列化一般使用实现Serializable接口,它是一个空接口,只起到标识作用,当编译器在编译一个类的源代码时若发现其实现了可序列化接口,那么会在编译class文件时包含一个方法,这个方法的功能就是将这个类的实例转换为一组字节。对象流就是用这个方法将该对象转换为字节的。但是由于源代码中不需要体现,因此我们实现可序列化接口时不需要重写任何方法。当一个对象属性被关键字transient修饰后,那么这个对象在进行序列化时这个属性的值就会被忽略。
import java.io.*;
public class SerializeDemo {
public static void main(String [] args) {
JavaBean b = new JavaBean();
b.name = "java_bean";
try {
FileOutputStream fileOut = new FileOutputStream("/tmp/javaBean");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(e);
out.close();
fileOut.close();
}catch(IOException e) {
e.printStackTrace(); }}}
如上图,序列化不会序列静态属性和transient修饰属性。
由于读写文本数据在实际开发中非常常见,因此java推出了一些列用于读写文本数据的流,并给他们单独分类为字符流;
int read()
读取单个字符。
int read(char[] cbuf)
将字符读入数组。
abstract int read(char[] cbuf, int off, int len)
将字符读入数组的某一部分。
int read(CharBuffer target)
试图将字符读入指定的字符缓冲区。
abstract void close()
关闭该流并释放与之关联的所有资源。
创建对象
OutputStreamWriter(OutputStream out, String charsetName)
创建使用指定字符集的 OutputStreamWriter。
OutputStreamWriter(OutputStream out)
创建使用默认字符编码的 OutputStreamWriter。
java.io.PrintWriter:具有自动行刷新的缓冲字符输出流,内部总是连接BufferedWriter作为加速操作。
在流连接中创建PrintWriter时,可以再传入一个参数,该值为boolean值,若该值为true则当前PW具有了自动行刷新功能。
此时每当我们调用Println方法后,写出指定内容就会自动flush. BufferedReader:缓冲字符输入流 ,特点:读写效率高,可以按行读取字符串,提供的方法:
String readLine()
* 读取一行字符串,返回值不含有该行末尾的换行符。
* 如果返回值为null,则表示读取到了流的末尾。
* 若是读取的文件,则表示文件读取到了末尾。
[NIO:]也叫new io,对比传统的同步阻塞IO操作,实际上NIO是同步非阻塞IO。NIO的优势主要体现在网络数据传输上。利用”信道”,进行多路复用。Channel:通道,类似于流
Buffer:缓冲,用缓冲的内存空间可以临时存放要读写的数据,用空间交换时间
BIO - 阻塞IO:
BIO读取过程:共三次复制拷贝过程
NIO 与BIO的区别有如下两点:
相对于bio的一个一个byte传,nio是以channel形式读取buffer缓冲区,然后以块数据传输;
nio减少了复制过程,直接把磁盘映射到JVM进程的虚拟地址空间,放置对应到页表上。
BIO、NIO、AIO的区别:
阻塞IO: BIO 就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用时可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
非阻塞IO: NIO 是 Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
异步IO: AIO 是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。但目前还不够成熟,应用不多。
private static String from = "C:/迅雷下载/白象.rmvb";
private static String bioto = "c:/bioto.rmvb";
private static String nioto = "c:/nioto.rmvb";
public static void main(String[] args) throws Exception {
System.out.println("正在拷贝文件,请稍等...");
bioCopy();
nioCopy();
}
//阻塞IO方式
private static void bioCopy() throws Exception {
long startTime = System.currentTimeMillis();
* 开发步骤:
* 1、读取源文件放在输入流中
* 2、创建输出流,写文件
* 3、声明buf缓存区
* 4、读取缓冲区
* 5、写输出流
* 6、倒着关闭释放资源
InputStream is = new FileInputStream(from);
OutputStream os = new FileOutputStream(bioto);
byte[] buffer = new byte[8192];//习惯使用8192
int len;
while((len = is.read(buffer))>0) {
os.write(buffer, 0, len); }
is.close();
os.close();
long endTime = System.currentTimeMillis();
System.out.println("bioCopy 耗时:" + (endTime - startTime));
//非阻塞IO方式
private static void nioCopy() throws IOException {
long startTime = System.currentTimeMillis();
* 开发步骤:
* 1、使用nio的FileChannel通道技术,性能翻倍
* 2、复制文件要设置属性:可读+可写+创建新文件,如果已经存在保存
* 3、复制
* 4、关闭释放资源
FileChannel fcFrom = FileChannel.open(Paths.get(from), StandardOpenOption.READ);
FileChannel fcTo = FileChannel.open(Paths.get(nioto), StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE_NEW);
fcTo.transferFrom(fcFrom, 0, fcFrom.size()); //复制
fcTo.close();
fcFrom.close();
long endTime = System.currentTimeMillis();
System.out.println("nioCopy 耗时:" + (endTime - startTime));
}}
执行结果:性能nio快接近bio的10倍啊!
复制0.8g的电影耗时:
正在拷贝文件,请稍等…
bioCopy 耗费时间:17053
nioCopy 耗费时间:1303
是一个代表文件或目录(区分文件的路径)的类,file
封装一个磁盘路径字符串,对这个路径可以执行一次操作,可以用来封装文件路径、文件夹路径、不存在的路径。
File(String pathname)
File(File parent,String child)
File(String parent,String child) 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
length():文件的字节量
exists():是否存在,存在返回true
isFile():是否为文件,是文件返回true
isDirectory():是否为文件夹,是文件夹返回true
getName():获取文件/文件夹名
getParent():获取父文件夹
getAbsolutePath():获取文件的完整路径
createNewFile():新建文件,文件夹不存在会异常,文件已经存在返回false
mkdirs():是否需要新建多层文件夹
mkdir():是否需要新建单层文件夹
delete():删除文件,删除空文件夹
list():返回String[],包含文件名
listFiles():返回File[],包含文件对象
listFiles(FileFilter filter):File[]获取指定目录下所有满足条件的File对象
//递归求文件总大小
private static long method(File dir) {
File[] fs = dir.listFiles();//1、把dir目录的所有资源列出来
long size=0;//记录文件的总大小
for (int i = 0; i < fs.length; i++) {//2、遍历每个资源fs[i]
if( fs[i].isFile() ) {//判断是不是文件
size += fs[i].length();//每个文件求和
}else if( fs[i].isDirectory() ) {//判断是不是文件夹
//重复的干:继续列表,是文件就求和,是文件夹就继续遍历文件夹
size += method(fs[i]);//功能和method()一模一样就不再定义新方法了,直接使用自己本身的方法就可以了 ,这就形成了递归调用模式;
} } return size;}}
该类的实例支持读取和写入随机访问文件。 随机访问文件的行为类似于存储在文件系统中的大量字节。 有一种游标,或索引到隐含的数组,称为文件指针 ; 输入操作读取从文件指针开始的字节,并使文件指针超过读取的字节。 如果在读/写模式下创建随机访问文件,则输出操作也可用;
RandomAccessFile(File/String dest,String mode)
RandomAccessFile raf = new RandomAccessFile("文件名/路径","r/rw");
write(int n): 向文件中写入一个字节 ,写入的是int值的低八位(int值32位,最后八位,int4个字节,即最后一个字节)
read():int 读取一个字节数据,以int形式返回,若读取到文件末尾,返回-1;
long pos = raf.getFilePointer();获取RAF当前指针的位置,返回一个long值,不论读写操作指针都会移动到操作位置下标处
seek():移动当前读取文件的位置指针;
WriteInt(int d):连续写四个字节;按照单字节方式复制效率低,为了提高效率,我们可以采取每次复制一个字节数组的字节量,从而减少复制的次数,提高效率
read(byte[] bys):int将从文件中读取到的字节数据存入此数组中,返回的int值表示实际读取到的字节长度(读取到数据末尾返回-1)
write(byte[]):向文件中写入一个字节数组
write(byte[] bys,int index,int len)向目标文件中写入bys中从index开始的连续len个字节
使用方法没有参数、没有返回值、方可用@Test注解;
@Before: 在@Test注释的方法前执行
@After: 在@Test注释的方法后执行
处理节点:必须写在第一行,可省略。
标记/标签(tag): ( <开始标记> 结束标记>)
标记规则:中文英文都可以作为标记名,建议使用英文,区分大小写,开始标记和结束标记要成对使用不能交叉嵌套,一个XML中只有唯一的根标记,标记名可以扩展,标记的嵌套关系可以扩展
注释(Comment):<!-- 写啥都行,注释不能嵌套使用 -->
内容(Content):开始标记和结束标记之间的信息称为内容。
文本内容,标记,多个标记和文本的混合,内容是可扩展的!
元素(Element):标记 + 内容 = 元素
XML 只有一个根元素(rootElement),元素中的元素称为子元素,外层元素称为父元素;
属性(attribute):在开始标记上定义元素的属性,可以定义多个属性,多个属性不分先后次序,属性之间需要有空格
属性名=“属性值” 属性和值之间等号前后不用写空格,属性值必须用 引号
实体 Entity:类似于 Java 的字符串中的转义字符"",一些用于替换表示XML语法的字符。 用来解决XML文字解析错误
常用实体:(< : <) (>: >) (&: &)
<![CDATA[ 文本内容 ]]> 使用CDATA包裹的文本,可以写任何字符,无需进行实体替换;
夜猫不能使用tab制表符,都要使用空格,同一层次,必须对齐,至少两个空格2个,冒号后面需要加空格(jackson 提供了 yaml 数据处理工具,来读取解析yml配置文件);
夜猫格式:(本例三层map结构)
spring:
datasource:
driver: com.mysql.jdbc.Driver
username: root
password: "123456"
url: jdbc:mysql://127.0.0.1:3306/jt?character=utf-8
获得application.yml配置文件的路径
d://xx//xx/xx//application.yml绝对路径(移动,换平台就行不通了)
"/" ---- 表示当前类对象(.class文件)所在的文件夹(bin文件夹)
String path = 当前类.class.getResource("/application.yml").getPath();
// path 是经过URL编码的字符串,编译成d:/中/xxx----->d:/%e4%b8%ad/xxx --解码--> d:/中/xxx
// URLDecoder类的decode(String,编码集)方法可解码
path = URLDecoder.decode(path,"UTF-8");
ObjectMapper m = new ObjectMapper(new YAMLFactory());
Map cfg = m.readValue(new File(path), Map.class);
是一种轻量级的数据交换格式,后面代替了xml:其中包含了若干个key-value结构,其中key只能是字符串(引号可以省略),value可以是任意类型:数值、字符串、布尔值、数组、null、object(json)、方法;
json语法1:{"name": "John Doe", "age": 18}; JS对象
Json语法2:[{"id":1,"name":"张","school":"培优班"},{"id":2,"name":"齐","school":"高手班"}]; 数组
Jackson:Jackson中有个ObjectMapper类很是实用,用于Java对象与JSON的互换。常用于序列化和反序列化;
序列化是指将程序中的java对象序列化成二进制文件保存到磁盘中永久存储。
反序列化时指将磁盘存储的二进制文件解析成java对象在程序中使用。
1、 把json串转化成Java对象/反序列化
ObjectMapper.readValue(json串,对象类.class);
2、 把Java对象转换成Json串/序列化
ObjectMapper.writeValueAsString(对象);
第三方java包,利用SAXReader对象把xml数据,读取在内存中,组织成一个树状结构,再用不同对象来表示其他节点;
jar:JAVA专属压缩包,JVM不需要解压就可以使用jar中的内容
模拟Mybatis框架解析XML:
<mapper namespace="cn.yhmis.mapper.OrderMapper">
<select id="list" resultType="Order">
SELECT * FROM tb_order
select>
<delete id="delete" parameterType="int">
DELETE FROM tb_order WHERE id=#{id}
delete>
mapper>
解析要求:根据传入的namespace和id找到对应的标签,然后解析出其SQL语句
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class Test5MybatisXml {
private static Map<String, String> map = new HashMap<String, String>();
public void configuration() throws Exception {
String path = getClass().getResource("/OrderMapper.xml").getPath();
//读取解析xml文件,再获得xml的根元素,即元素
Element root = new SAXReader().read(path).getRootElement();
//获得namespace属性的值
String ns = root.attributeValue("namespace");
//获取根元素下一层的所有元素列表
List<Element> elements = root.elements();
//遍历元素列表
for (Element e : elements) {
String name = e.getName();//元素名称
String id = e.attributeValue("id");//id属性的值
String sql = e.getText().trim();//内部包含的文本,即sql语句
map.put(name+":"+ns+"."+id, sql);//数据放入map
} }
public static void main(String[] args) throws Exception {
Test5MybatisXml t = new Test5MybatisXml();
t.configuration();
for(Entry<String, String> e:map.entrySet()) {
String k = e.getKey();
String v = e.getValue();
System.out.println(k+"="+v);
}}}
执行结果:
select:cn.yhmis.mapper.OrderMapper.list=SELECT * FROM tb_order
delete:cn.yhmis.mapper.OrderMapper.delete=DELETE FROM tb_order WHERE id=#{id}
【了解】这部分课程了解即可,在实际项目使用中都被封装好了,我们直接使用即可,上面的过程只是模拟实现mybatis底层的工作原理,让大家将来学习mybatis框架时知道,底层到底干了什么,怎么干的。从而更加容易学习更难懂的知识,以及加深基础功底。了解会用即可,无需完全独立敲出来。
java.util.Properties:继承自Hashtable,本质是个Hash表;(可以和流相关联的集合对象,一般用于生成配置文件和读取配置)
.setProperty(String key,String value):给属列表添加键值对
.getProperty(String key)用指定的键在此属性列表中搜索属性
.load(Reader reader)将文本文件中的数据加载到属性集合中
例如:创建读取配置文件的对象
Properties prop = new Properties();
1:InputStream ips = 当前类名.class.getResourceAsStream("/jdbc.properties");
//通过类加载器,获取文件输入流,这种获取输入流方法输入流在寻找文件的时会自动在src/main/resources包下寻找文件
2:InputStream ips = 当前类名.class.getClassLoader().getResourceAsStream("jdbc.properties");
prop.load(ips); //加载输入流
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String username = prop.getProperty("username");
String password = prop.getProperty("password");
进程(Process)与线程关系:就是正在运行的程序。也就是代表了程序所占用的内存区域。操作系统包含多个进程,当一个进程运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程;
进程有自己的私有内存,这块内存会在主存中有一段映射,而所有的线程共享JVM中的内存。在现代的操作系统中,线程的调度通常都是集成在操作系统中的,操作系统能通过分析更多的信息来决定如何更高效地进行线程的调度,这也是为什么Java中会一直强调,线程的执行顺序是不会得到保证的,因为JVM自己管不了这个,所以只能认为它是完全无序的。
另外,类java.lang.Thread中的很多属性也会直接映射为操作系统中线程的一些属性。Java的Thread中提供的一些方法如sleep和yield其实依赖于操作系统中线程的调度算法。
时间片
即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片。即该进程允许运行的时间,使各个程序从表面上看是同时进行的。
如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程,将当前进程挂起。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换,而不会造成CPU资源浪费。当又切换到之前执行的进程,把现场恢复,继续执行。
在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。
在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力。
线程:
线程是操作系统能够进行运算调度的最小单位。
它被包含在进程之中,是进程中的实际运作单位。
一个进程可以开启多个线程。
多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。
简而言之,一个操作系统中可以有多个进程,一个进程中可以有多个线程。每个进程有自己独立的内存,每个线程共享一个进程中的内存,每个线程又有自己独立的内存
多线程:多线程主要解决的问题是改变了代码的执行方式,将原有的多段代码之间“串行”操作(一句一句的先后执行)改为“并行”操作(多个代码片段可以一起执行)【扩展:JVM也是多线程,最少要启动main线程和GC线程。】;
同步一定是线程安全的,但线程安全不一定是同步的。线程不安全一定是异步的。
每个线程都拥有其他线程所需要的锁,同时又等待其他线程锁拥有的锁,并且每个线程在获得所需锁之前不会释放自己的锁,当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
Thread线程:main方法也是靠一个线程运行的,通常我们称运行main方法的线程为主线程;
并发编程三要素:
如果start()一个线程后想其立即执行,技巧:Thread.sleep(1)
一个多处理器的机器上,将会有多个线程并行(Parallel)执行,当线程数大于处理器数时,才会出现多个线程并发(Concurrent),在一个CPU上轮换执行
并行和并发的差异,一个是同时执行,一个是交替执行
线程在运行中,一般会被中断,目的是给其他线程执行机会,雨露均沾
线程调用sleep()方法主动放弃所占用的CPU资源
调用yield()方法可以让运行状态的线程转入就绪状态
join()方法是一个线程等待另外一个线程(在b中调a.join,让b等a运行完)
wait:释放资源,释放锁,是Object方法,可指定等待时间也可不指定,需要被唤醒,释放执行权,释放锁。是个普通的成员方法。
sleep:释放资源,不释放锁,是Thread方法,需要指定睡眠时间,到点自动醒,释放执行权,不释放锁。静态方法,通过类名直接调用。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程。(被唤醒的线程是进入了可运行状态,cpu分配时间片)
notifyAll:唤醒持有同一监视器中调用wait的所有线程。
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。Start()方法是一个native方法,它将通知底层操作系统,最终由操作系统启动一个新线程,操作系统将执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
Thread t1 =new Thread(){public void run(){要执行的方法 };
t1.start;
第二种创建线程的方式:实现Runnable接口单独定义线程任务;
class MyRunnable1 implements Runnable{
public void run(){
for(int i=0;i<1000;i++) {
System.out.println("hello姐~");}
public static void main(String[] args) {
Runnable mr1 = new MyRunnable1();
Thread th1 = new Thread(mr1,"可加可不加:线程名");
th1.start();
第三种创建线程的方式(争议):实现call方法,可抛出异常,有返回值和future对象
Future future = 线城池.submit(Callable任务/Runnable任务);
返回值 = future.get(); Runnable任务没有返回值
class MyCallable implements Callable<Double>{
public Double call() throws Exception {//Callable重写call()
System.out.println("执行耗时计算....");
Thread.sleep(3000);
double d = Math.random();
return d;}}}
public class Test_Callable {
public static void main(String[] args) throws Exception {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
MyCallable c = new MyCallable();
//把任务丢进线程池,并得到Future对象
Future<Double> future = threadPool.submit(c);
Double d = future.get();
System.out.println("主线程已经获取任务的结果:"+d);
线程主动放弃cpu时间片,让给其他线程: Thread.yield();
获取线程优先级 int priority = main.getPriority();
//线程的优先级分为10个等级,用整数1-10标识 * 其中1为最低优先级,10为最高优先级,5为默认值
设置线程优先级main.setPriority(Thread.MIN_PRIORITY(线程中的常量最小值1 Thread.MAX_PRIORITY最大10);
判断线程是否处于活动状态 boolean isAlive = main.isAlive();
判断线程是否是守护线程 boolean isDaemon = main.isDaemon()
判断线程是否被中断了 boolean isInterrupted = main.isInterrupted();
Thread()
分配新的 Thread 对象。
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
Thread(String name)
分配新的 Thread 对象。
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
long getId()
返回该线程的标识符。
String getName()
返回该线程的名称。
int getPriority()
返回线程的优先级。
StackTraceElement[] getStackTrace()
返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
Thread.State getState()
返回该线程的状态。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
static void sleep(long millis, int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
注意:进程在切换时有挂起和现场恢复,这些操作都是额外的,单进程就没有这个问题。进程如此线程亦如此,所以有时我们会发现开启多个线程,感觉会很快,可实际发现多线程有时不一定快,反而还慢,就是这个原因造成的。
在宏观上:我们可以同时打开多个应用程序,每个程序并行,同时运行。但在微观上:由于只有一个CPU,一次只能处理程序要求的一部分,如何处理公平,一种方法就是引入时间片,每个程序轮流执行。多核提高了并发能力,但本质不变。
多线程并发安全问题:
当多个线程并发操作同一临界资源时,由于线程切换时机 不确定,导致操作代码执行顺序未按照设计顺序执行,从而出现操作混乱,严重时可能导致系统瘫痪;(解决:将并发操作同一临界资源改成同步操作:有先后秩序的排队干;用synchroinzed实现)
同步:一个方法使用关键字synchroinzed修饰后,这个方法遍为“同步方法”,同步方法不允许多个线程同时到方法内部执行代码。 将多个线程并发操作同一资源改为先后顺序的同步执行就可以解决多线程并发的安全问题了。但是使用synchronized效率低,为了保证线程安全,必须牺牲性能,降低效率,尽量锁定越少的代码越好,须知抢哪个对象的锁? 哪段代码是在修改/访问数据?
在方法上直接使用synchronized,那么同步监视器对象就是当前方法所属对象,即方法中看到的this;
public synchronized void method() 抢当前实例(this)的锁
public static synchronized void method() 抢“类对象”的锁
同步块: synchronized(自定义同步监视器对象){
需要同步运行的代码片段、 }
例如:* String 类调用它的静态属性 String.class;
Class cls = String.class || Class cls = String.forName("String");
悲观锁和乐观锁:
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();//读上锁
lock.writeLock().unlock();//读解锁
lock.readLock().lock();//写上锁
lock.readLock().unlock();//写解锁
package javapro.thread;
悲观锁
public class TicketThread extends Thread{
//总票数,多个线程共享这个变量,能修改 ticket–
private int ticket = 10;
//执行业务,重写父类run方法
@Override
public void run() {
//业务处理,卖票:票–
while(true) { //线程非常多,我想尽量给我资源
synchronized (this) { //对象锁
//判断一个条件,出去条件
if(ticket<=0) { //多线程可能ticket=-1
break; //退出死循环
}
//不出现,线程run方法执行太快,不会发生线程冲突
try { //不能抛出异常,抛出就不是重写run方法
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“窗口:” + Thread.currentThread().getName()
+”, 剩余票数:” + ticket-- );
}
}
}
//3个窗口都买这一个票
public static void main(String[] args) {
//目标
Thread target = new TicketThread();
for(int i=0; i<3; i++) {
new Thread(target).start(); //3个线程共同作用一个target
}
}
}
package javapro.thread;
import java.util.concurrent.locks.ReentrantReadWriteLock;
乐观锁
public class TicketLock implements Runnable {
private int ticket = 10;
//jdk1.6前,性能差异很大,1.6后synchronized底层实现类似Lock,性能伯仲之间
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
@Override
public void run() {
while (true) {
try {
lock.writeLock().lock();
if (ticket <= 0) {
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“线程:” + Thread.currentThread().getName() + “,还剩票数:” + ticket--);
} catch (Exception e) {
// TODO: handle exception
} finally {
lock.writeLock().unlock(); //防止死锁,会自动释放,而synchronized不会释放
}
}
}
public static void main(String[] args) {
Runnable target = new TicketLock ();
int windows = 3; // 窗口数量
for (int I = 1; I < windows + 1; i++) {
new Thread(target, “窗口” + i).start();
}
}
}
线程的辅助工具类:Executors/threadLocal/Callable/Future
set():绑定值;
get(): 获取值
remove(): 删除绑定的值
线程B怎么知道线程A修改了变量
volatile修饰变量,提供多线程共享变量可见性和禁止指令重排序优化
synchronized修饰,它是悲观锁,属于抢占式,会引起其他线程阻塞
wait 释放对象锁,notify 通知
while轮询
Synchronized关键字
Lock锁实现
分布式锁:Redis、ZooKeeper
线程池(ThreadPool):底层是利用object的wait()和notifyAll()实现管理线程;(享元模式)
线程池是用来管理线程的,它主要有两个作用: 1>控制线程数量 2>重用线程
当线程执行完毕任务而没有任务分配时,线程会等待分配任务,而不会被销毁。除非调用线程池的shutdown方法;
//线程池的创建方法
ExecutorService threadPool = Executors.newFixedThreadPool(2);
创建固定参数条数的线程池,超过条数多余的线程任务会放在一个队列,等空余处理;
ExecutorService threadPool1 = Executors.newCachedThreadPool();
创建弹性的线城池;
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
创建单条线程的线程池;
threadPool.shutdown();
线程池的关闭:当调用此方法关闭线程池,线程池里的线程不再接受新任务,执行任务完毕关闭;
threadPool.shutdownNow():当调用此方法关闭线程池,将立刻强制调用方法关闭所有线程;
threadPool.execute(Runnable任务对象);
线城池添加任务:自动创建线程并执行线程的run方法;
threadPool.submit(Callable任务对象);
Callable:可以代替Runnable,它的call()可以有返回值(用submit丢进线程池时可返回一个future对象,future.get(取得返回值)),可指定泛型,可以在call()上抛出异常;
Future future = 线城池.submit(Callable任务/Runnable任务);
返回值 = future.get(); Runnable任务没有返回值
class MyCallable implements Callable<Double>{
public Double call() throws Exception {//Callable重写call()
System.out.println("执行耗时计算....");
Thread.sleep(3000);
double d = Math.random();
return d;}}}
public class Test_Callable {
public static void main(String[] args) throws Exception {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
MyCallable c = new MyCallable();
//把任务丢进线程池,并得到Future对象
Future<Double> future = threadPool.submit(c);
Double d = future.get();
System.out.println("主线程已经获取任务的结果:"+d);
线城池运行,可返回一个Future对象,Future有一个get方法可以拿到run()的返回值,并且在执行到future.get()时,会等待run()运行完毕,在没有返回值的时候等同于(Runnable任务对象.join())
线程锁和ThreadLocal的区别:
package com.cy.pj.java.thread.pool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
//任务执行时间=CPU计算时间+IO操作时间
//CPU密集型操作时,线程池的核心线程数一般会设计为CPU核数+1
//IO密集型操作时,线程池中的核心线程数一般会设计为CPU核数*(1+IO操作时间/CPU计算时间)
public class TestThreadPool01 {
//阻塞式工作队列
private static BlockingQueue<Runnable> workQueue=
new ArrayBlockingQueue<>(1);
//线程工厂对象
private static ThreadFactory factory=new ThreadFactory() {
private AtomicLong at=new AtomicLong(1);//CAS算法
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "DB-thread-pool-"+at.getAndIncrement());
}
};
public static void main(String[] args)throws Exception {
int processors=
Runtime.getRuntime().availableProcessors();//获取cpu
//构建池对象
ThreadPoolExecutor tPool=
new ThreadPoolExecutor(
1, //corePoolSize 核心线程数
2,//maximumPoolSize 最大线程数
30, //keepAliveTime线程允许的空闲时间
TimeUnit.SECONDS,//unit 时间单位
workQueue,//工作队列(阻塞式队列),用于存储线程要执行的任务对象
factory,//线程工厂
new CallerRunsPolicy());//拒绝策略
//提交任务
tPool.execute(new Runnable() {
@Override
public void run() {
String tName=Thread.currentThread().getName();
System.out.println(tName+"==>task-01");
try{Thread.sleep(5000);}
catch(Exception e) {}
}
});
tPool.execute(new Runnable() {
@Override
public void run() {
String tName=Thread.currentThread().getName();
System.out.println(tName+"==>task-02");
try{Thread.sleep(5000);}
catch(Exception e) {}
}
});
tPool.execute(new Runnable() {
@Override
public void run() {
String tName=Thread.currentThread().getName();
System.out.println(tName+"==>task-03");
try{Thread.sleep(5000);}
catch(Exception e) {}
}
});
tPool.execute(new Runnable() {
@Override
public void run() {
String tName=Thread.currentThread().getName();
System.out.println(tName+"==>task-04");
try{Thread.sleep(5000);}
catch(Exception e) {}
}
});
}
}
套接字是基于网络进行传输的API
插头:一个ip端口插入另一个ip端口进行通信,封装了TCP协议的通讯细节,使得我们可以通过它来与远程计算机建立TCP连接,并利用两个流的读写完成数据交互。
互联网层协议
IP(internet protocol)协议:提供关于数据应如何传输以及传输到何处的信息
ARP(Address Resolution Protocol)协议:处理信息的路由以及主机地址解析
传输层协议
TCP(Transmission Control Protocol)协议:传输控制协议,该协议主要用于在主机间建立一个虚拟连接,以实现高可靠性的数据包交换,如文件下载
TCP的三次握手与四次挥手
所谓三次握手即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发。
所谓四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。
UDP(User Datapram Protocol)协议:用户数据报协议,一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,如早期的QQ聊天
应用层协议
HTTP(Hyper Text Transport Protocol):超文本传输协议,如网页
FTP(File Transfer Protocol):文件传输协议
TELNET:远程登录协议,用于远程联接服务的标准协议或者实现此协议的软件,利用远程计算机完成当前计算机不能完成的功能
网络编程是指,多个计算机包含的程序,通过网络传输连接起来。
//创建一个UDP协议的套接字对象
DatagramSocket ds = new DatagramSocket(8070);
//准备一个数据包
DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
//接收数据 阻塞
ds.receive(dp);
ds.close();//关流
byte[]bs = dp.getData();
dp.getAddress();//获取发送端的地址
Socket实例化是需要传入两个参数的:
回环ip:127.0.0.1 连接本机ip:Localhost
抽象端口(0-65535个):端口是独占的,0到1024是一些常见服务的默认端口(http:80 https:443 ftp:21....)
5万以后是系统保留端口,用来自动分配,我们选1024到5万之间的;
DNS服务器:解析域名,将域名与对应IP地址关联(首先在本地Hosts文件中查找)
//创建一个TCP连接的客户端对象
socket = new Socket("192.168.7.90",8088);
OutputStream out = socket.getOutputStream();
该方法获取的输出流是一个字节输出流,通过这个流写出的字节会通过网络发送到远端计算机上
对于客户端这边而言,远端计算机指的就是服务端了。
ServerSocket server = new ServerSocket(8088);
Socket socket = server.accept();获得服务端的插头
accept() 就是一个阻塞方法,调用后会一直等待,直到一个客户端建立连接为止,
此时该方法会返回一个Socket实例,通过这个Socket就可以与刚连接上的
客户端进行数据交互了。多次调用accept可以等待不同客户端的连接。
InputStream in = socket.getInputStream();
通过Socket获取输入流,可以读取到远端计算机发送过来的字节。
String host =socket.getInetAddress().getHostAddress();
获取连接上的客户端地址
Map体现的结构是一个多行两列的表格。左列称为key,右列称为value。Map可以通过key获取对应的value,所以我们将要查询的数据作为value,查询的条件作为key将他们成对的保存到Map中。常用实现类:hashMap
HashMap是使用散列算法实现的Map,当今查询速度最快的数据结构! key和value允许存放null;HashMap即是采用了数组+链表的数据结构
HashMap内部使用数组(Entry数组)存放数据(每组键值对都是一个Entry实例)默认初始容量16;当存放数据时调用key.hashCode()获取键的哈希值(int类型的整数),用哈希值和数组长度计算下标(假设为i),新建一个Entry实例来封装键值对数据,把Entry实例放入下标i位置,再放入同一个下标的时候会依次用equals()比较两个key是否一样(一样就覆盖原值),两个key不一样则会串在链表尾部(下标i处还是一个Entry实例);当负载率/加载因子(数据量/容量)到0.75时,会新建翻倍容量的新数组;所有的数据重新执行哈希运算放入新数组;在jdk1.8时,链表长度到8时会转成红黑树(当红黑树上的数据减少的6时会转回成链表)
Put(key,value) 放入键值对,放入重复的键,会覆盖原值;
Size() map中存放的键值对数
get(key) 用键,提取对应的值,键不存在,获得null值;
remove(key) 删除key对应键值对,返回key对应value
boolean = map.containsKey(key) 判断是否包含给定的key
boolean = map.containsValue(value); 判断是否包含给定的value
Set<String> keySet = map.keySet(); 将当前Map中所有的key以一个Set集合形式返回。
Collection<Integer> values = map.values(); 将当前Map中所有的value以一个集合形式返回。
Set<Entry<String,Integer>> entrySet = map.entrySet(); 将当前Map中每组键值对以若干Entry实例并存入Set集合进行返回;
for(java.util.Map.Entry<String,Integer> e:entrySet) {
String key = e.getKey();
Integer value = e.getValue();}
HashMap 非线程安全,全部方法不加锁,性能最好
Hashtable 线程安全,全部方法加锁,用同一把锁。慢,淘汰的API。
ConcurrentHashMap,线程安全,每个存储位置一把锁,性能好。
LinkeHashMap:可保证key的顺序的map
集合转换为数组:String[]arr = 原集合.toArray(new String[c.size()]);
数组转换为集合:Collection c = Arrays.asList(原数组);数组只可以转换成List集合,不可以转Set集合,对集合的操作就是原来数组的操作,因为数组的长度固定,所以不可以通过集合的方法增删元素,但是可以使用List集合的set方法更换元素内容;
集合和集合方法有:addAll©添加所有集合元素 removeAll(c)删除本集合共有的元素 contains©判断是否包含集合所有元素
所有的集合在new的时候都支持传入另外一个集合eg: List list2 = new ArrayList<>(list);
集合的排序(默认升序):Collections.sort(集合);Collections是Collection的工具类(类似于Array的Arrays类),存在着大量静态方法操作集合。
java中所有的集合都是该java.util.Colletction接口的实现类,它定义了所有集合都应当具备的功能(例如存取元素等等);
java.util.Collection接口下的子接口:Deque , List , NavigableSet , Queue , Set , SortedSet ;
java.util.List:可重复集合,并且有序 Colletction c = new ArrayList()
List除了可重复之外,还有一个特点是有序,因此List 集合提供了一套可通过下标操作元素的方法:
E get(int index): 返回下标元素
E set(int index,E e):元素设置到下标位置,返回原元素
void add(int index, E e):重载集合add,给定元素插入指定位置
E remove(int index):删除并返回指定下标处对应元素
List<Integer> subList = list.subList(3, 8):返回新集合:截取到的list集合下标位置包头不包尾的元素:注意此方法获取到的子集的增删改插元素依然会改变母集的元素(list.subList(2, 9).clear()删除list集合3-9下标位置元素)
List有两个常用的实现类:
java.util.set:不可重复集合(LinkedHashSet有序,其余无序):
这里的重复指的是元素是否重复,而重复的判断依据是依靠元素自身equals比较的结果。set集合不允许存放equals比较为true的元素两次。重写equals就显得重要了Colletction c1 = new HashSet();
E poll()出队操作,获取队首元素的同时将该元素删除。
E peek()引用队首元素,不做出队操作。
Deque<String> deque = new LinkedList<>();
deque.offer||deque.offerFirst(添加首/尾元素)
e = deque.pollFirst()||e = deque.pollLast(取出首/尾元素)
stack.push("one");//压入元素
String str = stack.pop();//弹出元素
Collections.addAll(集合,值1,值2,值3....)批量添加值
Collections.binarySearch(list,目标值):二分法查找
Collections.swap(List,i,j)
Collections.sort(list)集合排序:默认首字符比大小,再比后续字符
该类提供了一个静态方法sort,可以对List集合进行自然排序。这种排序只能排实现了Comparable接口的类元素,否则编译不通过。当需要排序自定义的元素时,首先元素类实现Comparable接口,参照以下:
Collections.sort(list,new Comparator<Point>(){//比较器对象
* 该方法用来指定o1与o2的大小关系
* 返回值不关心具体取值,只关心取值范围
* 当返回值是正数:o1>o2
* 当返回值是负数:o1<o2
* 当返回值=0:o1与o2相等
public int compare(Point o1, Point o2) {
int len1 = o1.getX()*o1.getX()+o1.getY()*o1.getY();
int len2 = o2.getX()*o2.getX()+o2.getY()*o2.getY();
return len1-len2; }});
用于遍历集合以及删除元素之类,该接口规定历所有迭代器实现类遍历集合的通用操作方法,而遍历结合遵循的步骤为:问,取,删,其中删除元素不是必要操作,不同的集合都提供了一个Iterator的实现类,我们无需,记住这些实现类的名字,以Iterator接口接受并调用方法遍历即可。
Iterator it = 集合.iterator;
while(it.hasNext){判断是否含有下一个元素,返回boolean值
Object o = it.next():获得的是Object类型,带泛型可直接获得
if("xxx".equals(o)){ 比对不想要的元素
it.remove;} }删除不想要的元素
在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”和“右子树”。二叉树常被用于实现二叉查找树和二叉堆。
二叉树分类:
完全二叉树:若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
满二叉树:除了叶子结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树
平衡二叉树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
二叉树的遍历:
前序遍历:
先访问根节点,然后访问左子树,最后访问右子树。对于左子树和右子树,也采用前序遍历。
中序遍历(重点):
先访问左子树,再访问根节点,最后访问右子树。对于左子树和右子树,也采用中序遍历。后序遍历:先访问左子树,再访问右子树,最后访问根节点。对于左子树和右子树,也采用后序遍历。
二叉树的失衡(不平衡二叉树)
对于某个节点,其左子树与右子树的高度差大于1.如果一树二叉树发生了失衡现象,会影响查找的效率(二叉树会退化成一个链表)。要将二叉树重新变成一个平衡的二叉树,需要对二叉树进行旋转,重新调整节点的位置。
红黑树就是一种将不平衡的二叉树转换成弱平衡的二叉树的一种算法。
红黑树的规则:
a.节点可以是红色的或者是黑色的。
b.根节点必须是黑色的。
c.叶子节点必须是黑色的。
d.红色节点的子节点必须是黑色的。
e.从根节点到任何叶子节点的路径上,
包含的黑色节点的个数是一样的。
第三方开源API,执行http请求,并处理响应,方便从HTML中提取所需内容;
2 实战:爬虫京东
2.1 http协议
向服务器发送的 http 协议数据
GET / HTTP/1.1
Host: www.tedu.cn
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
服务器返回的数据
HTTP/1.1 200 OK
Date: Tue, 24 Sep 2019 15:30:45 GMT
Content-Type: text/html
Content-Length: 275688
Connection: keep-alive
Server: tarena
Last-Modified: Tue, 24 Sep 2019 01:14:40 GMT
ETag: "5d896e00-434e8"
Accept-Ranges: bytes
Age: 7092
X-Via: 1.1 PShbsjzsxqo180:5 (Cdn Cache Server V2.0), 1.1 PSjlbswt4dm34:3 (Cdn Cache Server V2.0), 1.1 bdwt64:8 (Cdn Cache Server V2.0)
package day18;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class Test2 {
public static void main(String[] args) throws Exception {
String host = "www.tedu.cn";
Socket s = new Socket(host, 80);
System.out.println("已连接");
OutputStream out = s.getOutputStream();
String http = "GET / HTTP/1.1\n"+
"Host: "+host+"\n"+
"Connection: keep-alive\n"+
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36\n"+
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\n"+
"Accept-Language: zh-CN,zh;q=0.9\n\n";
out.write(http.getBytes());
out.flush();
System.out.println("已发送");
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
String line;
while((line = in.readLine()) != null) {
System.out.println(line);
}
}
}
2.3 爬虫
Jsoup 第三方开源API,方便的执行http请求,并处理响应,方便的从html中提取需要的内容
package day18;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.junit.Test;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Test3 {
@Test //抓整个页面
public void html() throws IOException{
String url = "http://tech.qq.com/a/20170330/003855.htm";
//doc代表一个页面
String html = Jsoup.connect(url).execute().body();
System.out.println(html);
}
@Test //抓整站,找到所有a链接,然后进行广度优先/深度优先进行遍历
public void getAllATag() throws IOException{
String url = "http://tech.qq.com/a/20170330/003855.htm";
//获取到页面
Document doc = Jsoup.connect(url).get();
//获取到页面中的所有a标签
Elements eles = doc.getElementsByTag("a");
for(Element ele : eles){
String title = ele.text(); //获取a标签的内容
String aurl = ele.attr("href"); //获取a标签的属性
System.out.println(title+" – "+aurl);
}
}
@Test //京东商城,商品标题
public void getItemTile() throws IOException{
String url = "https://item.jd.com/3882469.html";
String title = getTitle(url);
System.out.println(title);
}
private String getTitle(String url) throws IOException {
Document doc = Jsoup.connect(url).get();
Element ele = doc.select("div.sku-name").get(0);
String title = ele.text();
return title;
}
@Test //当当商城,商品标题
public void getDDItemTile() throws IOException{
String url = "http://product.dangdang.com/23579654.html";
Document doc = Jsoup.connect(url).get();
Element ele = doc.select("div.name_info h1").get(0);
String title = ele.text();
System.out.println(title);
}
@Test
public void price() throws IOException {
double price = getPrice("J_100003717483");
System.out.println(price);
}
public double getPrice(String id) throws IOException {
String url = "https://p.3.cn/prices/mgets?skuIds="+id;
String userAgent = "Mozilla/5.0 (Windows NT 5.1; zh-CN) AppleWebKit/535.12 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/535.12";
String json = Jsoup
.connect(url)
.userAgent(userAgent)
.ignoreContentType(true)
.execute()
.body();
//System.out.println(json);
ObjectMapper mapper =new ObjectMapper();
/*
* JsonNode可以是
* 对象: {"a":"1", "b":"2"}
* 数组: [{...}, {...}, {...}]
*
* isArray() 判断是否是一个数组
* get(i) 取数组中指定下标的节点
* get(name) 取对象中该属性的值
*/
JsonNode node = mapper.readTree(json);
double price = node.get(0).get("p").asDouble();
return price;
}
@Test //商品描述
public void getItemDesc() throws IOException{
ObjectMapper mapper =new ObjectMapper();
String url = "http://d.3.cn/desc/3882469";
String body = Jsoup.connect(url).ignoreContentType(true).execute().body();
System.out.println(body);
String json = body.substring(9, body.length()-1); //把函数名去掉
JsonNode jsonNode = mapper.readTree(json);
String desc = jsonNode.get("content").asText();
System.out.println(desc);
}
@Test
public void testCat3() throws IOException {
List<String> catList = getCat3();
for(String url : catList) {
System.out.println(url);
}
System.out.println("处理标准三级分类:"+catList.size());
}
//返回所有三级分类。京东三级分类:1286,标准有:1190
public static List<String> getCat3() throws IOException{
//所有分类都在里面
String url = "http://www.jd.com/allSort.aspx";
//通过选择器把三级分类过滤出来,集合
Elements els = Jsoup.connect(url).get()
.select("dl.clearfix dd a");
//存放爬取所有三级分类链接
List<String> catList = new ArrayList<String>();
for(Element e : els) {
//获取到三级分类a标签的链接值
String catUrl = e.attr("href");
//排除异常链接
if(catUrl.startsWith("//list.jd.com/list.html?cat=")) {
catList.add("http:"+catUrl);
}
}
return catList;
}
@Test
public void pageNum() throws IOException {
String catUrl = "http://list.jd.com/list.html?cat=1713,3274&jth=i";
int num = getPageNum(catUrl);
System.out.println(num);
}
//获取某个分类下总页数,参数就是分类链接
public static int getPageNum(String catUrl) throws IOException {
String p = Jsoup.connect(catUrl).get().select(".fp-text I").get(0).text();
int num = Integer.parseInt(p);
return num;
}
@Test //测试某个分类下的所有的分页链接
public void pageList() throws IOException {
String catUrl = "https://list.jd.com/list.html?cat=1713,3274";
Integer maxNum = getPageNum(catUrl);
List<String> itemList = getPageUrlList(catUrl, maxNum);
for(String item : itemList) {
System.out.println(item);
}
}
//https://list.jd.com/list.html?cat=1713,3274&page=1
//https://list.jd.com/list.html?cat=1713,3274&page=2
//拼接某个一个分类下的所有的分页链接
public static List<String> getPageUrlList(String catUrl, Integer maxNum){
List<String> pageUrlList = new ArrayList<String>();
//遍历所有页数
for(int i=1; i<=maxNum; i++) {
String pageUrl = catUrl + "&page=" + i;
pageUrlList.add(pageUrl);
}
return pageUrlList;
}
@Test //某个列表页面上所有商品链接地址
public void itemList() throws IOException {
String pageUrl = "http://list.jd.com/list.html?cat=1713,3274&page=2";
List<String> itemUrlList = getItemUrlList(pageUrl);
for(String itemUrl : itemUrlList) {
System.out.println(itemUrl);
}
}
//抓取某个列表页面中所有商品链接的地址,
public static List<String> getItemUrlList(String pageUrl) throws IOException{
Elements els = Jsoup.connect(pageUrl).get().select(".p-img a");
List<String> itemUrlList = new ArrayList<String>();
//所有a标签,过滤过的
for(Element e : els) {
//e是a标签,href属性值
String itemUrl = e.attr("href");
itemUrlList.add("http:"+itemUrl);
}
return itemUrlList;
}
@Test
public void site() throws IOException, InterruptedException {
//获取所有的三级分类
List<String> catUrlList = getCat3();
//遍历3级分类
for(String catUrl : catUrlList) {
//延时
Thread.sleep(10);
//返回当前分类的总页数
int maxNum = getPageNum(catUrl);
//某个分类下所有分页链接
List<String> pageUrlList = getPageUrlList(catUrl, maxNum);
//遍历当前分类下的所有分页链接
for(String pageUrl : pageUrlList) {
//当前分页商品链接
List<String> itemUrlList = getItemUrlList(pageUrl);
for(String itemUrl : itemUrlList) {
//获取其标题、价格。。。
String id = itemUrl.substring(itemUrl.lastIndexOf("/")+1, itemUrl.lastIndexOf("."));
double price = getPrice(id);
String title = getTitle(itemUrl);
System.out.println(itemUrl + " - "+ price + " - "+title);
System.out.println();
}
}
}
}
}
2.4 抓取的五种方式
2.4.1 抓取页面
@Test //抓整个页面
public void html() throws IOException{
String url = "http://tech.qq.com/a/20170330/003855.htm";
//doc代表一个页面
String html = Jsoup.connect(url).execute().body();
System.out.println(html);
}
2.4.2 抓取整个网站
@Test //抓整站,找到所有a链接,然后进行广度优先/深度优先进行遍历
public void getAllATag() throws IOException{
String url = "http://tech.qq.com/a/20170330/003855.htm";
//获取到页面
Document doc = Jsoup.connect(url).get();
//获取到页面中的所有a标签
Elements eles = doc.getElementsByTag("a");
for(Element ele : eles){
String title = ele.text(); //获取a标签的内容
String aurl = ele.attr("href"); //获取a标签的属性
log.debug(title+" – "+aurl);
}
}
2.4.3 抓取标题 – 页面上的内容
可以多级父子样式嵌套
@Test //京东商城,商品标题
public void getItemTile() throws IOException{
String url = "https://item.jd.com/3882469.html";
Document doc = Jsoup.connect(url).get();
Element ele = doc.select(".itemInfo-wrap .sku-name").get(0);
String title = ele.text();
log.debug(title);
}
@Test //当当商城,商品标题
public void getDDItemTile() throws IOException{
String url = "http://product.dangdang.com/1052875306.html";
Document doc = Jsoup.connect(url).get();
Element ele = doc.select("article").get(0);
String title = ele.text();
log.debug(title);
}
2.4.4 抓取价格 – json
2017年4月,京东开始对价格进行反爬虫控制,访问过多的IP地址会被禁止。
2019年8月,京东开始限定爬虫爬取价格,必须伪装请求头,让它觉得是浏览器访问
@Test
public void json() throws IOException {
String url = "https://p.3.cn/prices/mgets?skuIds=J_100003717483";
Connection cn = Jsoup.connect(url).userAgent("Mozilla/5.0 (Windows NT 5.1; zh-CN) AppleWebKit/535.12 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/535.12");
String json = cn.ignoreContentType(true).execute().body();
System.out.println(json);
ObjectMapper MAPPER =new ObjectMapper();
JsonNode node = MAPPER.readTree(json);
Double price = node.get(0).get("p").asDouble();
System.out.println(price);
}
2.4.5 抓取描述 – jsonp
@Test //商品描述
public void getItemDesc() throws IOException{
String url = "http://d.3.cn/desc/3882469";
String jsonp = Jsoup.connect(url).ignoreContentType(true).execute().body();
String json = jsonp.substring(9, jsonp.length()-1); //把函数名去掉
JsonNode jsonNode = MAPPER.readTree(json);
String desc = jsonNode.get("content").asText();
log.debug(desc);
}
2.5 爬取京东
抓取商品先要找到商品ID,有两个方案:
方案一:商品ID是一串数字,猜测它是自增的,于是我们可以是做一个自增的循环。但如果商品的ID不是连续,会造成很多访问无法继续访问,报链接超时。
方案二:找到网站的所有商品的列表页面,解析html找到商品的ID,这个方式解析麻烦些,但商品ID直接可以获得。所有一般来说都是采用第二种方案。
分类、商品列表、商品详情
那抓取京东网站就变成抓取所有分类,按分类找到商品列表页面,从商品列表页面抓取出商品ID,最终循环商品ID,抓取所有商品详情页面,解析商品详情页面,找到所有商品的详细信息。
断点抓取、离线分析
京东有近22个大类143个二级分类,1286三级分类,8615683种商品,近九百万种商品。如果持续在线抓取,会很快比屏蔽。也不方便测试。所以我们采取断点抓取,离线分析。先将分类抓取,将榨取后的信息保存到磁盘中,后期对磁盘中的文件进行分析入库。
2.5.1 商品三级分类
@Test
public void testCat3() throws IOException {
List<String> catList = JD.getCat3();
for(String url : catList) {
System.out.println(url);
}
System.out.println("处理标准三级分类:"+catList.size());
}
//返回所有三级分类。京东三级分类:1286,标准有:1190
public static List<String> getCat3() throws IOException{
//所有分类都在里面
String url = "http://www.jd.com/allSort.aspx";
//通过选择器把三级分类过滤出来,集合
Elements els = Jsoup.connect(url).get()
.select("div dl dd a");
//存放爬取所有三级分类链接
List<String> catList = new ArrayList<String>();
for(Element e : els) {
//获取到三级分类a标签的链接值
String catUrl = e.attr("href");
//排除异常链接
if(catUrl.startsWith("//list.jd.com/list.html?cat=")) {
catList.add("http:"+catUrl);
}
}
return catList;
}
2.5.2 某个分类下的列表总数
@Test
public void pageNum() throws IOException {
String catUrl = "http://list.jd.com/list.html?cat=1713,3274&jth=i";
int num = JD.getPageNum(catUrl);
System.out.println(num);
}
//获取某个分类下总页数,参数就是分类链接
public static int getPageNum(String catUrl) throws IOException {
Integer num = Integer.parseInt(
Jsoup.connect(catUrl).get()
.select(".fp-text I").get(0).text()
);
return num;
}
2.5.3 某个分类下所有分页链接
@Test //测试某个分类下的所有的分页链接
public void pageList() throws IOException {
String catUrl = "https://list.jd.com/list.html?cat=1713,3274";
Integer maxNum = JD.getPageNum(catUrl);
List<String> itemList = JD.getPageUrlList(catUrl, maxNum);
for(String item : itemList) {
System.out.println(item);
}
}
//https://list.jd.com/list.html?cat=1713,3274&page=1
//https://list.jd.com/list.html?cat=1713,3274&page=2
//拼接某个一个分类下的所有的分页链接
public static List<String> getPageUrlList(String catUrl, Integer maxNum){
List<String> pageUrlList = new ArrayList<String>();
//遍历所有页数
for(int i=1; i<=maxNum; i++) {
String pageUrl = catUrl + "&page=" + I;
pageUrlList.add(pageUrl);
}
return pageUrlList;
}
2.5.4 某个分页下所有商品链接
@Test //某个列表页面上所有商品链接地址
public void itemList() throws IOException {
String pageUrl = "http://list.jd.com/list.html?cat=1713,3274&page=210";
List<String> itemUrlList = JD.getItemUrlList(pageUrl);
for(String itemUrl : itemUrlList) {
System.out.println(itemUrl);
}
}
//抓取某个列表页面中所有商品链接的地址,
public static List<String> getItemUrlList(String pageUrl) throws IOException{
Elements els = Jsoup.connect(pageUrl).get()
.select(".p-img a");
List<String> itemUrlList = new ArrayList<String>();
//所有a标签,过滤过的
for(Element e : els) {
//e是a标签,href属性值
String itemUrl = e.attr("href");
itemUrlList.add("http:"+itemUrl);
}
return itemUrlList;
}
2.5.5 获取京东所有商品链接
@Test
public void site() throws IOException, InterruptedException {
//获取所有的三级分类
List<String> catUrlList = JD.getCat3();
//遍历3级分类
for(String catUrl : catUrlList) {
//延时
Thread.sleep(10);
//返回当前分类的总页数
int maxNum = JD.getPageNum(catUrl);
//某个分类下所有分页链接
List<String> pageUrlList = JD.getPageUrlList(catUrl, maxNum);
//遍历当前分类下的所有分页链接
for(String pageUrl : pageUrlList) {
//当前分页商品链接
List<String> itemUrlList = JD.getItemUrlList(pageUrl);
for(String itemUrl : itemUrlList) {
System.out.println(itemUrl);
//获取到当前商品链接,调用方法获取其标题、价格。。。
}
}
}
}
https://item.jd.com/100002795959.html
https://p.3.cn/prices/mgets?skuIds=100002795959
l IoC - Inverse of Control
控制反转,自己不控制对象的创建,而是权利反转,交给工具来创建对象
需要对象,从工具来取用@component
l DI - Dependency Injection
依赖注入@Autowired
对象需要的数据,使用的其他对象,进行自动装配
import java.io.File;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import ognl.Ognl;
public class Configure {
private static Map<String,Map> cfg = new HashMap<String, Map>();
static {
try {
load();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void load() throws Exception{
/*获得application.yml配置文件的路径
d://xx//xxx/xx//application.yml绝对路径(移动,换平台就行不通了)
"/" ---- 表示当前类对象(.class文件)所在的文件夹(bin文件夹)*/
String path = Configure.class.getResource("/application.yml").getPath();
// path 是经过URL编码(ISO8859-1)的字符串,
编译成d:/中/xxx----->d:/%e4%b8%ad/xxx --解码--> d:/中/xxx
path = URLDecoder.decode(path,"UTF-8");
ObjectMapper m = new ObjectMapper(new YAMLFactory());
//获得三层map
cfg = m.readValue(new File(path), Map.class);
}
public static String get(String ognl) {
// ognl表达式格式:${spring.datasource.driver}
// ---> 先拆除标记,获得内容:spring.datasource.driver
ognl = ognl.trim();
ognl = ognl.substring(2,ognl.length()-1);
try {//使用ognl表达式,从cfg集合提取数据
return (String)Ognl.getValue(ognl,cfg);
} catch (Exception e) {
e.printStackTrace();
return null; } } }
import java.io.File;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
//spring环境/上下文/容器对象
public class SpringContext {
private Map<String,Object> map = new HashMap<String, Object>();
//自动扫描,在文件夹中,自动找到所有的类,自动创建所有实例
public void autoScan() throws Exception{
String path = SpringContext.class.getResource("/").getPath();
path = URLDecoder.decode(path,"utf-8");
File dir = new File(path);
StringBuilder sb = new StringBuilder();
scan(dir,sb);
autowire();//自动装配
}
private void autowire()throws Exception{
Collection<Object> values = map.values();//从map取出所有的实例
for (Object object : values) {//遍历所有对象
Class<? extends Object> cls = object.getClass();//获得对象的类对象
Field[] a = cls.getDeclaredFields();//获得对象的所有成员变量
for (Field f : a) {//遍历成员变量
//判断变量上是否存在Autowired或者Value注解
if(f.isAnnotationPresent(Autowired.class)) {
injectObject(cls,object,f);//注入对象
}else if(f.isAnnotationPresent(Value.class)) {
injectValue(cls,object,f);//注入配置数据
}
}
}
}
private void injectValue(Class<? extends Object> cls, Object object, Field f) throws Exception{
/*
* @Value("${spring.datasource.username}")
* String username
*
* - 获得@Value注解
* - 获得注解的属性 name(value)
* - 从Configure类获取数据
* - 把配置数据保存到这个变量
*/
Value value = f.getAnnotation(Value.class);
String name = value.name();
if(name.equals("")) {
name = value.value();
}
String v = Configure.get(name);
f.setAccessible(true);
f.set(object, v);
}
private void injectObject(Class<? extends Object> cls, Object object, Field f) throws Exception {
/*
* @Autowired
* UserDao userDao
*
* -获得变量的类型(类对象)
* -用类对象来获得实例
* -把实例保存到这个变量
*/
Class<?> type = f.getType();
Object v = get(type);
f.setAccessible(true);
f.set(object, v);
}
private void scan(File dir, StringBuilder sb)throws Exception {
File[] list = dir.listFiles();
if(list == null) {
return;
}
for (File file : list) {
if(file.isFile()) {
String name = file.getName();
if(name.endsWith(".class")) {
name = name.substring(0,name.length()-6);
name = sb +"." + name;
handle(name);
}
} else {
if(sb.length()!=0) {//第一层包不加点
sb.append(".");
}
sb.append(file.getName());
scan(file,sb);
int index = sb.lastIndexOf(".");
if(index == -1) {
index = 0;
}
sb.delete(index, sb.length());
}
}
}
private void handle(String name)throws Exception{
Class<?> c = Class.forName(name);
if(c.isAnnotationPresent(Component.class) ||c.isAnnotationPresent(Service.class)
||c.isAnnotationPresent(Controller.class) ) {
Object obj = c.newInstance();
map.put(name, obj);
}
}
public <T> T get(Class<T> c) {
String name = c.getName();
return (T)map.get(name);
}
public static void main(String[] args)throws Exception {
SpringContext ctx = new SpringContext();
ctx.autoScan();
UserController controller = ctx.get(UserController.class);
controller.test();
// System.out.println(ctx.map);
//
// UserDao userDao = ctx.get(UserDao.class);
// System.out.println(userDao);
}
}
step1. 在WEB-INF下添加hello.jsp。
<%@ page contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>Insert title heretitle>
head>
<body style="font-size:30px;">
Hello,SmartMVC!
body>
html>
step2. 在demo包下添加HelloController类。
参考代码:
public class HelloController {
@RequestMapping("/hello.do")
public String hello() {
System.out.println(
"HelloController's hello()");
/*
* 返回视图名。
* DispatcherServlet依据视图名定位到
* 某个视图对象,比如某个jsp。
*/
return "hello";
}
}
step3. 在base.annotation下添加@RequestMapping注解。
参考代码:
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
public String value();
}
step4. 在resources下添加smartmvc.xml。
<beans>
<bean class="demo.HelloController"/>
beans>
step5. 在base.web下添加DispatcherServlet。
在init方法里,先读取smartmvc.xml配置文件,然后将 处理器实例化,并有将处理器实例放到list集合里面,然后 将这些处理器实例交给HandlerMapping来处理。
参考代码:
public class DispatcherServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private HandlerMapping handlerMapping;
public DispatcherServlet() {
System.out.println(
"DispatcherServlet's constructor");
}
@Override
/**
* 读取smartmvc的配置文件(配置有处理器类名等信息),
* 然后将处理器实例化,最后将处理器实例交给
* HandlerMapping来处理。
*/
public void init() throws ServletException {
SAXReader saxReader = new SAXReader();
InputStream in =
getClass().getClassLoader()
.getResourceAsStream("smartmvc.xml");
try {
Document doc = saxReader.read(in);
Element root = doc.getRootElement();
List<Element> elements = root.elements();
//beans用于存放处理器实例
List beans = new ArrayList();
for(Element ele : elements) {
String className = ele.attributeValue("class");
//将处理器实例化
Object obj = Class.forName(className).newInstance();
beans.add(obj);
}
System.out.println("beans:" + beans);
//创建HandlerMapping对象
handlerMapping = new HandlerMapping();
//将处理器实例交给HandlerMapping来处理
handlerMapping.process(beans);
} catch (Exception e) {
e.printStackTrace();
}
}
}
step6. 在base.common下添加HandlerMapping类。
在process方法里,遍历处理器实例组成的集合,对于 每一个处理器实例,读取加在方法前的@RequestMapping注解中的 请求路径,然后以请求路径作为key,以处理器实例及method对象的 封装(Handler)作为value,创建maps。
参考代码:
public class HandlerMapping {
//maps存放了请求路径与处理器实例及方法的对应关系
private Map<String,Handler> maps =
new HashMap<String,Handler>();
/**
* 依据请求路径返回Handler对象
*/
public Handler getHandler(String path) {
return maps.get(path);
}
/**
* 创建一个maps集合,里面存放有请求路径与
* Handler的对应关系。其中,Handler封装了
* 处理器实例及method对象。
*/
public void process(List beans) {
//遍历处理器实例组成的集合
for(Object bean : beans) {
//获得处理器实例对应的class对象
Class clazz = bean.getClass();
//获得处理器的所有方法
Method[] methods =
clazz.getDeclaredMethods();
//对处理器的方法进行遍历
for(Method mh : methods) {
//获得加在方法前的@RequestMapping注解
RequestMapping rm =
mh.getAnnotation(
RequestMapping.class);
//获得@RequestMapping注解中配置的请求路径
String path = rm.value();
Handler handler = new Handler(bean,mh);
//将请求路径与handler的对应关系保存到map
maps.put(path, handler);
}
}
System.out.println("maps:" + maps);
}
}
step7. 在base.common下添加Handler类。
参考代码:
public class Handler {
private Object obj;
private Method mh;
public Handler(Object obj, Method mh) {
this.obj = obj;
this.mh = mh;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Method getMh() {
return mh;
}
public void setMh(Method mh) {
this.mh = mh;
}
}
Step8.使用smartmvc
step1.导包
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
step2.将smartmvc核心类拷贝过来
step3.配置DispatcherServlet
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>base.web.DispatcherServlet</servlet-class>
<!--
配置启动加载:
当servlet容器启动之后,会立即将
配置有load-on-startup参数的servlet实例化。
参数值是一个大于等于零的整数,越小,越优先
被创建。
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
step4.添加jsp文件。
step5.添加处理器类。
step6.添加smartmvc配置文件。
练习2:
完成smartmvc,并增加一个简单的功能(可以在 web.xml文件中配置文件名):
提示
step1. 在web.xml中,使用初始化参数来配置文件名
configLocation
smartmvc.xml
step2. 在DispatcherServlet的init方法中,读取初始化 参数。 String fileName = getInitParameter(“configLocation”);
1.SmartMVC
(1)SmartMVC是什么?
是一个web mvc框架(类似于SpringMVC),主要是实现了一个通用的控制器。
注:开发人员借助于该框架,只需要写处理器类与视图(目前只支持jsp
作为视图层技术)就可以于开发基于MVC架构的web应用。
(2)SmartMVC架构
(3)SmartMVC各个组件之间的关系
(4)主要版本
第一版: 实现了一个通用的控制器(DispatcherServlet),只支持
转发。
第二版: 可以在web.xml配置文件名,并支持重定向(在返回的视图名
前添加"redirect:"。
第三版: 支持在处理器方法中添加参数(只支持添加request和response)。
第四版: @RequestMapping注解可以添加到类前。
(5)如何使用SmartMVC?
step1.导包
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
step2.将smartmvc核心类拷贝过来
step3.配置DispatcherServlet
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>base.web.DispatcherServlet</servlet-class>
<!--
配置启动加载:
当servlet容器启动之后,会立即将
配置有load-on-startup参数的servlet实例化。
参数值是一个大于等于零的整数,越小,越优先
被创建。
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
step4.添加jsp文件。
step5.添加处理器类。
step6.添加smartmvc配置文件。
1.什么是链表:
链表由一系列节点构成,其中,每个节点包含两部分内容(一部分是数据,另外一部分是指针)。
注: 指针指向了下一个节点(也就是说通过该指针可以找到下一个节点)。
如果只有一个指向下一个节点的指针,称之为单向链表。
如果有两个指针(一个指向上一个节点,一个指)向下一个节点),称之为双向链表。
如果最后一个节点与第一个节点也通过指针连接起来,称之为双向循环链表。
jdk8.0之前的LinkedList是采用双向循环链表实现的,
jdk8.0之后,采用双向链表实现。
2.链表跟数组相比,优缺点是什么:
数组的优点:
依据下标可以非常快速的找到某个元素。
数组的缺点:
删除和插入操作比较耗费时间,需要重新移动大量元素(包括扩容)。
数组需要有连续的地址空间。
链表的优点:
删除和插入操作速度非常快。
链表不需要连续的地址空间。
链表的缺点:
依据下标查找某个元素比较慢。
链表节点需要保存节点的地址,同数组相比,
需要占用更多的内存(需存储前后指针)
public class LinkedList<E> {
//存放表头(即第一个节点)的地址(引用)
private Node head;
//存放节点的个数(链表的长度)
private int size;
/**
* 在指定位置插入某个元素
* @param index 位置
* @param e 要插入的元素
*/
public void add(int index,E e) {
if(index < 0 || index > size) {
throw new IndexOutOfBoundsException("下标越界");
}
//如果位置等于链表的长度,表示向
//链表末尾添加。
if(index == size) {
add(e);
return;
}
//先找到指定位置的节点
Node next = getNode(index);
Node prev = next.prev;
//将next和prev指定为新添加的节点的前驱
//和后继节点
Node node = new Node(e);
next.prev = node;
node.prev = prev;
prev.next = node;
node.next = next;
//如果添加的位置是0,则新添加的节点
//成为头节点。
if(index == 0) {
head = node;
}
size ++;
}
/**
* 返回指定位置的元素。
* @param index 位置(位置是从0开始的)
* @return 指定位置的元素
*/
public E get(int index) {
if(index < 0 || index >= size) {
throw new
IndexOutOfBoundsException("下标越界");
}
Node node = getNode(index);
return node.data;
}
/**
* 删除指定位置的元素
* @param index 位置
* @return 元素
*/
public E remove(int index) {
if(index < 0 || index >= size) {
throw new
IndexOutOfBoundsException("下标越界");
}
//只有一个节点
if(size == 1) {
E data = head.data;
head = null;
size --;
return data;
}
//找到要删除的节点
Node node = getNode(index);
//找到该节点的上一个(前驱)和
//下一个节点(后继)
Node prev = node.prev;
Node next = node.next;
//让前驱和后继建立引用关系
prev.next = next;
next.prev = prev;
//如果删除的是头节点,需要将下一个节点
//指定为头节点。
if(index == 0) {
head = next;
}
size --;
//返回删除的元素
return node.data;
}
private Node getNode(int index) {
Node node = head;
if(index < size /2) {
System.out.println("向后遍历");
for(int i = 0; i< index; i ++) {
node = node.next;
}
}else {
System.out.println("向前遍历");
for(int i = size; i > index; i--) {
node = node.prev;
}
}
return node;
}
/**
* 获得链表的长度。
* @return 链表的长度(即链表当中有几个节点)
*/
public int size() {
return size;
}
@Override
public String toString() {
//如果链表为空,返回[]
if(head == null) {
return "[]";
}
StringBuilder buf =
new StringBuilder("["
+ head.data);
Node node = head.next;
while(node != head) {
buf.append("," + node.data);
node = node.next;
}
return buf.append("]").toString();
}
/**
* 在链表的末尾添加某个元素。
* @param e 要添加的元素。
* @return 添加成功,返回true。
*/
public boolean add(E e) {
/*
* 如果当前链表为空,则该节点为头节点。
*/
if(head == null) {
head = new Node(e);
head.next = head;
head.prev = head;
size ++;
return true;
}
/*
* 如果当前链表不为空,则将该元素添加到
* 链表末尾。
*/
Node node = new Node(e);
//先找到链尾(最后一个节点)
Node end = head.prev;
//将新节点添加到链尾(即重新建
//立新的引用关系)
end.next = node;
node.next = head;
head.prev = node;
node.prev = end;
size ++;
return true;
}
/**
* 节点类:
* 用于封装要添加的元素
*
*/
private class Node{
//data是要添加的元素
E data;
//存放下一个节点的地址(引用)
Node next;
//存放上一个节点的地址
Node prev;
public Node(E e) {
data = e;
}
}
}
/**
public class ProxyList<E> implements List<E> {
private List<E> target;//被代理的目标list对象
public ProxyList() {
target = new ArrayList<E>();
}
public ProxyList(List<E> list) {
target = list;
}
public void add(int index, E element) {
synchronized (this) {
target.add(index, element);
}
}
public synchronized int size() {
return target.size();
}
public synchronized boolean isEmpty() {
return target.isEmpty();
}
public synchronized boolean contains(Object o) {
return target.contains(o);
}
public synchronized Iterator<E> iterator() {
return target.iterator();
}
public synchronized Object[] toArray() {
return target.toArray();
}
public synchronized <T> T[] toArray(T[] a) {
return target.toArray(a);
}
public synchronized boolean add(E e) {
return target.add(e);
}
@Override
public synchronized String toString() {
return target.toString();
}
public synchronized boolean remove(Object o) {
return target.remove(o);
}
public synchronized boolean containsAll(Collection<?> c) {
return target.containsAll(c);
}
public synchronized boolean addAll(Collection<? extends E> c) {
return target.addAll(c);
}
public synchronized boolean addAll(int index, Collection<? extends E> c) {
return target.addAll(index, c);
}
public synchronized boolean removeAll(Collection<?> c) {
return target.removeAll(c);
}
public synchronized boolean retainAll(Collection<?> c) {
return target.retainAll(c);
}
public synchronized void clear() {
target.clear();
}
public synchronized E get(int index) {
return target.get(index);
}
public synchronized E set(int index, E element) {
return target.set(index, element);
}
public synchronized E remove(int index) {
return target.remove(index);
}
public synchronized int indexOf(Object o) {
return target.indexOf(o);
}
public synchronized int lastIndexOf(Object o) {
return target.lastIndexOf(o);
}
public synchronized ListIterator<E> listIterator() {
return target.listIterator();
}
public synchronized ListIterator<E> listIterator(int index) {
return target.listIterator(index);
}
public synchronized List<E> subList(int fromIndex, int toIndex) {
return target.subList(fromIndex, toIndex);
}
}
动态代理API
1.Java在反射包中提供的一个API
2.这个API用于实现代理模式编程
3.动态代理API可以动态的创建接口的实现类实例
动态代理:
/**
public class ProxyHandler<E>
implements InvocationHandler {
private List<E> target;//被代理的目标对象
public ProxyHandler() {
target = new ArrayList<E>();
}
public ProxyHandler(List<E> target) {
this.target = target;
}
//返回值是目标方法执行以后的返回值
public Object invoke(
Object proxy, //动态生成的代理对象
Method method,//正在被调用的方法
Object[] args)//调用方法时候传递的实际参数
throws Throwable {
System.out.println("正在调用:"+method);
synchronized (proxy) {
//在target对象上执行method对应的方法
return method.invoke(target, args);
//假设 add() list e
//则执行了 : list.add(e)
}
}
}
public class Demo05 {
public static void main(String[] args) {
/**
* 使用动态代理API创建接口的实例
*/
List<String> list=(List<String>)
Proxy.newProxyInstance(
Demo05.class.getClassLoader(), //类加载器
new Class[] {List.class}, //实现接口类型列表
new ProxyHandler<String>());//调用处理器对象
list.add("Andy");
list.add("Jerry");
list.add("Tom");
list.add("Lee");
System.out.println(list);
//Java 线程安全解List决方案之一
List<String> lst=new ArrayList<String>();
lst=Collections.synchronizedList(lst);
}
}
ArrayList线程安全问题?
1.采用代理模式实现(繁琐)
2.采用动态代理API(简单)
3.synchronizedList() (淘汰了!)
4.Java并发包中提供的线程安全的CopyOnWriteArrayList可以解决线程并发安全问题,并且可以在遍历期间操作集合元素,不会发生并发修改异常!
5.Vector 线程安全的类,淘汰了!
编程建议:
1.单线程使用 ArrayList
多线程使用 CopyOnWriteArrayList