how2j学习笔记(JAVA中级)
文章目录
- 常见问题
- throw和throws的区别
- 总结Lock和synchronized的区别
- 异常处理
- 常用手段
- try - catch
- throws
- 异常的分类
- 自定义异常
- IO操作
- 文件路径与File对象的创建
- 常用操作
- 什么是流
- 字节流
- 关闭流
- 字符流
- 缓存流(好用)
- 数据流(socket通信会大量使用)
- 对象流
- 控制台输入输出
- 集合框架
- ArratList
- 常用方法
- 遍历
- 其他集合——LinkedList
- 其他集合——二叉树
- 其他集合——HashMap
- 其他集合——HashSet
- 集合框架Collection
- 工具类——Collections
- 关系与区别
- ArrayList与HashSet
- ArrayList和LinkedList
- HashMap和Hashtable
- 几种set
- hashcode原理
- 比较器Comparator(条件排序,设置排序规则)
- 比较器Comparable(在类里写好,然后直接调用sort就可以了)
- 聚合操作
- Lambda表达式
- 初探
- **使用匿名类:**
- **使用lambda表达式:**
- **演变过程**
- Lambda表达式弊端
- 方法引用
- 聚合操作
- 泛型
- 定义
- 使用泛型的好处
- 泛型的定义与使用
- 定义和使用含有泛型的类
- 定义和使用含有泛型的方法
- 定义和使用含有泛型的接口
- 泛型通配符
- 多线程
- 进程(Processor)和线程(Thread)的区别
- 创建多线程(三种方式)
- 继承线程类,重写run方法
- 实现Runnable接口
- 匿名类(直接在run方法中写业务代码)
- 常用方法
- 同步问题(核心)
- 把synchronized放在类的方法里
- 线程安全类
- HashMap和Hashtable的区别
- StringBuffer和StringBuilder的区别
- ArrayList和Vector的区别
- 把非线程安全的集合转换为线程安全,使用Collections
- 死锁
- 交互
- 使用wait和notify进行线程交互
- 关于wait、notify和notifyAll
- 线程池(了解)
- 多线程同步(Lock对象)
- 线程交互
- 原子访问
- 简介
- AtomicInteger
- 同步测试
- 网络编程
- TCP聊天代码
- TCP实现文件上传
- Tomcat
- UDP聊天代码
- 多线程聊天代码
- URL下载网络资源(爬虫)
- 图形界面swing
- 模板
- 监听事件
- 按钮绑定事件(按钮监听)
- 键盘监听
- 鼠标监听
- 适配器(常用)
- 窗口类别
- 布局器
- 绝对定位(本人常用)
- FlowLayout顺序布局器
- BorderLayout
- GridLayout(网格布局器)
- setPreferredSize
- CardLayout
- 常用组件
- 面板
- 基本面板(JPanel)
- ContentPane
- 分隔条(SplitPanel)
- 滚动条(JScrollPanel)
- JScrollPanel
- 多个小面板(TabbedPanel)
- 布局器(CardLayerout)
- 使用菜单(JMenu)
- 工具栏(JToolBar)
- 表格控件(JTable、TableModel)
- 日期控件
- Swing的线程
- Swing的皮肤
针对how2j的JAVA基础做的个人笔记,若有帮助不胜荣幸
学习网址请点击:https://how2j.cn?p=162023
常见问题
throw和throws的区别
- throws 出现在方法声明上,而throw通常都出现在方法体内。
- throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某个异常对象。
总结Lock和synchronized的区别
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
2、Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
3、synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
异常处理
异常定义:
导致程序的正常流程被中断的事件,叫做异常
常用手段
异常处理常见手段: try、catch、finally、throws
FileNotFoundException是Exception的子类,使用Exception也可以catch住任何异常
finally是最后执行的语句,不论怎样都会执行
try - catch
public static void main(String[] args) {File f= new File("d:/LOL.exe");try{System.out.println("试图打开 d:/LOL.exe");new FileInputStream(f);//尝试打开文件System.out.println("成功打开");}catch(FileNotFoundException e){System.out.println("d:/LOL.exe不存在");e.printStackTrace();}}
多异常捕获,写多个catch即可
catch (FileNotFoundException | ParseException e)是把多个catch放在一起
throws
一个方法1调用另体外一个方法2,谁调用谁处理,即方法2throws
public class TestException {public static void main(String[] args) {method1();}private static void method1() {try {method2();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}}private static void method2() throws FileNotFoundException {File f = new File("d:/LOL.exe");System.out.println("试图打开 d:/LOL.exe");new FileInputStream(f);System.out.println("成功打开");}
}
异常的分类
- 可查异常(CheckedException)如果不处理,编译器,就不让你通过
- 运行时异常(RuntimeException)除数不能为0、空指针、数组越界等等,即便不进行try catch,也不会有编译错误
- 错误(Error)系统级别的异常,通常是内存用光了,一般java程序启动的时候,最大可以使用16m的内存
自定义异常
通过继承Exception可以自定义异常或者跑出自定异常
IO操作
文件和文件夹都是用File代表
文件路径与File对象的创建
public static void main(String[] args) {// 绝对路径File f1 = new File("d:/LOLFolder");System.out.println("f1的绝对路径:" + f1.getAbsolutePath());// 相对路径,相对于工作目录,如果在eclipse或IDEA中,就是项目目录File f2 = new File("LOL.exe");System.out.println("f2的绝对路径:" + f2.getAbsolutePath());// 把f1作为父目录创建文件对象File f3 = new File(f1, "LOL.exe");System.out.println("f3的绝对路径:" + f3.getAbsolutePath());
}
常用操作
import java.io.File;
import java.util.Date;public class TestFile {public static void main(String[] args) {File f = new File("d:/LOLFolder/LOL.exe");System.out.println("当前文件是:" +f);//文件是否存在System.out.println("判断是否存在:"+f.exists());//是否是文件夹System.out.println("判断是否是文件夹:"+f.isDirectory());//是否是文件(非文件夹)System.out.println("判断是否是文件:"+f.isFile());//文件长度System.out.println("获取文件的长度:"+f.length());//文件最后修改时间long time = f.lastModified();Date d = new Date(time);System.out.println("获取文件的最后修改时间:"+d);//设置文件修改时间为1970.1.1 08:00:00f.setLastModified(0);//文件重命名File f2 =new File("d:/LOLFolder/DOTA.exe");f.renameTo(f2);System.out.println("把LOL.exe改名成了DOTA.exe");System.out.println("注意: 需要在D:\\LOLFolder确实存在一个LOL.exe,\r\n才可以看到对应的文件长度、修改时间等信息");}
}
import java.io.File;
import java.io.IOException;public class TestFile {public static void main(String[] args) throws IOException {File f = new File("d:/LOLFolder/skin/garen.ski");// 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)f.list();// 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)File[]fs= f.listFiles();// 以字符串形式返回获取所在文件夹f.getParent();// 以文件形式返回获取所在文件夹f.getParentFile();// 创建文件夹,如果父文件夹skin不存在,创建就无效f.mkdir();// 创建文件夹,如果父文件夹skin不存在,就会创建父文件夹f.mkdirs();// 创建一个空文件,如果父文件夹skin不存在,就会抛出异常f.createNewFile();// 所以创建一个空文件之前,通常都会创建父目录f.getParentFile().mkdirs();// 列出所有的盘符c: d: e: 等等f.listRoots();// 刪除文件f.delete();// JVM结束的时候,刪除文件,常用于临时文件的删除f.deleteOnExit();}
}
什么是流
当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
数据源可以是文件,还可以是数据库,网络甚至是其他的程序
比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
输入流: InputStream
输出流:OutputStream
字节流
输入:
public class TestStream {public static void main(String[] args) {try {//准备文件lol.txt其中的内容是AB,对应的ASCII分别是65 66File f =new File("d:/lol.txt");//创建基于文件的输入流FileInputStream fis =new FileInputStream(f);//创建字节数组,其长度就是文件的长度byte[] all =new byte[(int) f.length()];//以字节流的形式读取文件所有内容fis.read(all);for (byte b : all) {//打印出来是65 66System.out.println(b);}//每次使用完流,都应该进行关闭fis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
输出:
public class TestStream {public static void main(String[] args) {try {// 准备文件lol2.txt其中的内容是空的File f = new File("d:/lol2.txt");// 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Ybyte data[] = { 88, 89 };// 创建基于文件的输出流FileOutputStream fos = new FileOutputStream(f);// 把数据写入到输出流fos.write(data);//写入的是XY// 关闭输出流fos.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
关闭流
所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。
如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。
- 在try中关闭,不太好,因为前面可能会有其他报错导致无法关闭
- 在finally中关闭,引用声明在try外面
- 使用try()的方式==(常用)==
使用try()的方式JDK1.7开始推出:
public class TestStream {public static void main(String[] args) {File f = new File("d:/lol.txt");//把流定义在try()里,try,catch或者finally结束的时候,会自动关闭try (FileInputStream fis = new FileInputStream(f)) {byte[] all = new byte[(int) f.length()];fis.read(all);for (byte b : all) {System.out.println(b);}} catch (IOException e) {e.printStackTrace();}}
}
字符流
Reader字符输入流
Writer字符输出流
专门用于字符的形式读取和写入数据
字符流读取:
public class TestStream {public static void main(String[] args) {// 准备文件lol.txt其中的内容是ABFile f = new File("d:/lol.txt");// 创建基于文件的Readertry (FileReader fr = new FileReader(f)) {// 创建字符数组,其长度就是文件的长度char[] all = new char[(int) f.length()];// 以字符流的形式读取文件所有内容fr.read(all);for (char b : all) {// 打印出来是A BSystem.out.println(b);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
字符流输出:
public class TestStream {public static void main(String[] args) {// 准备文件lol2.txtFile f = new File("d:/lol2.txt");// 创建基于文件的Writertry (FileWriter fr = new FileWriter(f)) {// 以字符流的形式把数据写入到文件中String data="abcdefg1234567890";char[] cs = data.toCharArray();fr.write(cs);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
缓存流(好用)
以介质是硬盘为例,字节流和字符流的弊端:
在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。
为了解决以上弊端,采用缓存流。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
写的时候也是同理
缓存流必须建立在一个存在的流的基础上
读取文件:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;public class TestStream {public static void main(String[] args) {// 准备文件lol.txt其中的内容是// garen kill teemo// teemo revive after 1 minutes// teemo try to garen, but killed againFile f = new File("d:/lol.txt");// 创建文件字符流// 缓存流必须建立在一个存在的流的基础上try (FileReader fr = new FileReader(f);BufferedReader br = new BufferedReader(fr);){while (true) {// 一次读一行String line = br.readLine();if (null == line)break;System.out.println(line);}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
写入文件:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;public class TestStream {public static void main(String[] args) {// 向文件lol2.txt中写入三行语句File f = new File("d:/lol2.txt");try (// 创建文件字符流FileWriter fw = new FileWriter(f);// 缓存流必须建立在一个存在的流的基础上 PrintWriter pw = new PrintWriter(fw); ) {pw.println("garen kill teemo");pw.println("teemo revive after 1 minutes");pw.println("teemo try to garen, but killed again");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class TestStream {public static void main(String[] args) {//向文件lol2.txt中写入三行语句File f =new File("d:/lol2.txt");//创建文件字符流//缓存流必须建立在一个存在的流的基础上try(FileWriter fr = new FileWriter(f);PrintWriter pw = new PrintWriter(fr);) {pw.println("garen kill teemo");//强制把缓存中的数据写入硬盘,无论缓存是否已满pw.flush(); pw.println("teemo revive after 1 minutes");pw.flush();pw.println("teemo try to garen, but killed again");pw.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
数据流(socket通信会大量使用)
优点:会自动在数据之间添加分割符号,使得数据写和读好识别,例如11.388,可能是11.3 然后后面是整数88
DataInputStream 数据输入流
DataOutputStream 数据输出流
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class TestStream {public static void main(String[] args) {write();read();}private static void read() {File f =new File("d:/lol.txt");try (FileInputStream fis = new FileInputStream(f);DataInputStream dis =new DataInputStream(fis);){boolean b= dis.readBoolean();int i = dis.readInt();String str = dis.readUTF();System.out.println("读取到布尔值:"+b);System.out.println("读取到整数:"+i);System.out.println("读取到字符串:"+str);} catch (IOException e) {e.printStackTrace();}}private static void write() {File f =new File("d:/lol.txt");try (FileOutputStream fos = new FileOutputStream(f);DataOutputStream dos =new DataOutputStream(fos);){dos.writeBoolean(true);dos.writeInt(300);dos.writeUTF("123 this is gareen");} catch (IOException e) {e.printStackTrace();}}
}
对象流
对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘
一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口
Serializable用来标志序列化
控制台输入输出
输入:
Scanner in = new Scanner(System.in);
int a=in.nextInt();//读取浮点数、整数、字符串等等
集合框架
ArratList
1、如果要存放多个对象,可以使用数组,但是数组有局限性
比如 声明长度是10的数组,不用的数组就浪费了,超过10的个数,又放不下
2、为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是 ArrayList
3、ArrayList实现了接口List,常见的写法会把引用声明为接口List类型
//接口引用指向子类对象(多态)
List heros = new ArrayList();
4、默认取出的类型时Object
5、如果这样写List heros = new ArrayList();,那么heros既能放hero,也能放item,要指定类型,即引入了泛型
//接口引用指向子类对象(多态)
List<hero> heros = new ArrayList<hero>();
//其实写成这种的就OK了,系统会自动识别
List<hero> heros = new ArrayList<>();
常用方法
关键字 | 简介 |
---|---|
size | 现在大小 |
add | 增加 |
contains | 判断是否存在 |
get | 获取指定位置的对象 |
indexOf | 获取对象所处的位置 |
remove | 删除 |
set | 替换 |
size | 获取大小 |
toArray | 转换为数组 |
addAll | 把另一个容器所有对象都加进来 |
clear | 清空 |
1、add可以在指定位置加元素,list.add(3,“我草”);
2、使用get获取指定位置元素,list.get(5);就是获取第六个元素
3、删除有两种方式,heros.remove(2); heros.remove(specialHero);
4、使用set替换指定下标的元素heros.set(5, new Hero(“hero 5”));
5、把一个ArrayList添加到另外一个,heros.addAll(anotherHeros);
遍历
方法 | 描述 |
---|---|
for | 用for循环遍历 |
iterator | 迭代器遍历 |
for: | 用增强型for循环 |
1、for循环使用i<list.size()
2、iterator例子Iterator<Hero> it= heros.iterator();,首先问hasNext(),然后调用Hero h = it.next();
迭代器还可以往前找
3、增强for循环,for(Hero h : heros)
其他集合——LinkedList
1、简介
与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。
除了实现了List接口外,LinkedList还实现了双向链表结构即队列Deque,可以很方便的在头尾插入删除数据,先入先出的
2、常用方法
addFirst、addLast、getFirst、getLast、removeFirst、removeLast
3、实现了队列接口,先进先出
4、队列常用方法
poll,取出第一个元素,peek查看元素,但不取出来
其他集合——二叉树
二叉树由各种节点组成
二叉树特点:
每个节点都可以有左子节点,右子节点,每一个节点都有一个值
排序很快
package collection;public class Node {// 左子节点public Node leftNode;// 右子节点public Node rightNode;// 值public Object value;
}
二叉树排序——插入数据
使用了递归嗷,相对当前节点而言,小的值去左边,大的值去右边
过程:
67 放在根节点
7 比 67小,放在67的左节点
30 比67 小,找到67的左节点7,30比7大,就放在7的右节点
73 比67大, 放在67的右节点
10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。
…
10比67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,找到30的左节点10,10和10一样大,放在左边
package collection;public class Node {// 左子节点public Node leftNode;// 右子节点public Node rightNode;// 值public Object value;// 插入 数据public void add(Object v) {// 如果当前节点没有值,就把数据放在当前节点上if (null == value)value = v;// 如果当前节点有值,就进行判断,新增的值与当前值的大小关系else {// 新增的值,比当前值小或者相同if ((Integer) v -((Integer)value) <= 0) {if (null == leftNode)leftNode = new Node();leftNode.add(v);}// 新增的值,比当前值大else {if (null == rightNode)rightNode = new Node();rightNode.add(v);}}}public static void main(String[] args) {int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };Node roots = new Node();for (int number : randoms) {roots.add(number);}}
}
二叉树排序-遍历
二叉树的遍历分左序,中序,右序
左序即: 中间的数遍历后放在左边
中序即: 中间的数遍历后放在中间
右序即: 中间的数遍历后放在右边
如图所见,我们希望遍历后的结果是从小到大的,所以应该采用中序遍历
import java.util.ArrayList;
import java.util.List;public class Node {// 左子节点public Node leftNode;// 右子节点public Node rightNode;// 值public Object value;// 插入数据public void add(Object v) {// 如果当前节点没有值,就把数据放在当前节点上if (null == value)value = v;// 如果当前节点有值,就进行判断,新增的值与当前值的大小关系else {// 新增的值,比当前值小或者相同if ((Integer) v -((Integer)value) <= 0) {if (null == leftNode)leftNode = new Node();leftNode.add(v);}// 新增的值,比当前值大else {if (null == rightNode)rightNode = new Node();rightNode.add(v);}}}// 中序遍历所有的节点public List<Object> values() {List<Object> values = new ArrayList<>();// 左节点的遍历结果if (null != leftNode)values.addAll(leftNode.values());// 当前节点values.add(value);// 右节点的遍历结果if (null != rightNode)values.addAll(rightNode.values());return values;}public static void main(String[] args) {int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };Node roots = new Node();for (int number : randoms) {roots.add(number);}System.out.println(roots.values());}
}
其他集合——HashMap
键值对,类似字典
键不可以重复,值可以
import java.util.HashMap;public class TestCollection {public static void main(String[] args) {HashMap<String,String> dictionary = new HashMap<>();dictionary.put("adc", "物理英雄");dictionary.put("apc", "魔法英雄");dictionary.put("t", "坦克");System.out.println(dictionary.get("t"));//清空数据dictionary.clear();}
}
其他集合——HashSet
Set中的元素,不能重复。Set中的元素,没有顺序。
严格的说,是没有按照元素的插入顺序排列
不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样
遍历:只能用迭代器或者增强for(增强for简单)
import java.util.HashSet;
import java.util.Iterator;public class TestCollection {public static void main(String[] args) {HashSet<Integer> numbers = new HashSet<Integer>();for (int i = 0; i < 20; i++) {numbers.add(i);}//Set不提供get方法来获取指定位置的元素//numbers.get(0)//遍历Set可以采用迭代器iteratorfor (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext();) {Integer i = (Integer) iterator.next();System.out.println(i);}//或者采用增强型for循环for (Integer i : numbers) {System.out.println(i);}}
}
通过观察HashSet的源代码
可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map.
HashSet是作为Map的key而存在的
而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。
集合框架Collection
Collection是 Set List Queue和 Deque的接口
Queue: 先进先出队列
Deque: 双向链表
**注:**Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
**注:**Deque 继承 Queue,间接的继承了 Collection
工具类——Collections
1、Collections是一个类,容器的工具类,就如同Arrays是数组的工具类
2、synchronizedList 把非线程安全的List转换为线程安全的List。
3、常用方法
关键字 | 简介 |
---|---|
reverse | 反转 |
shuffle | 混淆 |
sort | 排序 |
swap | 交换 |
rotate | 滚动 |
synchronizedList | 线程安全化 |
4、交换元素
Collections.swap(numbers,0,5);
5、示例
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class TestCollection {public static void main(String[] args) {//初始化集合numbersList<Integer> numbers = new ArrayList<>();for (int i = 0; i < 10; i++) {numbers.add(i);}System.out.println("集合中的数据:");System.out.println(numbers);Collections.shuffle(numbers);//别看走眼啊,是Collections不是CollectionSystem.out.println("混淆后集合中的数据:");System.out.println(numbers);Collections.sort(numbers);System.out.println("排序后集合中的数据:");System.out.println(numbers);}
}
关系与区别
ArrayList与HashSet
ArrayList: 有顺序,数据可以重复
HashSet: 无顺序,数据不能够重复
ArrayList和LinkedList
ArrayList 插入,删除数据慢
LinkedList, 插入,删除数据快
ArrayList是顺序结构,所以定位很快,指哪找哪。 就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList 是链表结构,就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去,所以定位慢
在最前面插入十万条数据,ArrayList 耗时4969ms,LinkedList耗时16ms
ArrayList 定位很快,几乎是0ms,但是LinkedList用36s
HashMap和Hashtable
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
HashMap可以存放 null
Hashtable不能存放null
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类
几种set
HashSet: 无序
LinkedHashSet: 按照插入顺序
TreeSet: 从小到大排序
hashcode原理
1、HashMap找数据比list快很多,HashMap几乎是0ms
就类似英汉字典,知道页数了,一翻就翻到了
每一个变量都有一个hashcode,理解可以参看下图(空间换时间)
2、HashSet的数据是不能重复的,相同数据不能保存在一起,到底如何判断是否是重复的呢?
根据HashSet和HashMap的关系,我们了解到因为HashSet没有自身的实现,而是里面封装了一个HashMap,
所以本质上就是判断HashMap的key是否重复,因此速度很快
比较器Comparator(条件排序,设置排序规则)
1、假设Hero有三个属性 name,hp,damage
一个集合中放存放10个Hero,通过Collections.sort对这10个进行排序
那么**到底是hp小的放前面?还是damage小的放前面?**Collections.sort也无法确定
所以要指定到底按照哪种属性进行排序
这里就需要提供一个Comparator给定如何进行两个对象之间的大小比较
2、其实是重写了compare方法
3、示例代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;import charactor.Hero;public class TestCollection {public static void main(String[] args) {Random r =new Random();List<Hero> heros = new ArrayList<Hero>();for (int i = 0; i < 10; i++) {//通过随机值实例化hero的hp和damageheros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));}System.out.println("初始化后的集合:");System.out.println(heros);//直接调用sort会出现编译错误,因为Hero有各种属性//到底按照哪种属性进行比较,Collections也不知道,不确定,所以没法排//Collections.sort(heros);//引入Comparator,指定比较的算法Comparator<Hero> c = new Comparator<Hero>() {@Overridepublic int compare(Hero h1, Hero h2) {//按照hp进行排序if(h1.hp>=h2.hp)return 1; //正数表示h1比h2要大elsereturn -1;}};Collections.sort(heros,c);System.out.println("按照血量排序后的集合:");System.out.println(heros);}
}
比较器Comparable(在类里写好,然后直接调用sort就可以了)
1、在Hero类中直接去实现这个接口,implementsComparable<Hero>,然后写一个compareTo方法
2、Hero类代码
public class Hero implements Comparable<Hero>{public String name;public float hp;public int damage;public Hero(){}public Hero(String name) {this.name =name;}//初始化name,hp,damage的构造方法public Hero(String name,float hp, int damage) {this.name =name;this.hp = hp;this.damage = damage;}public int compareTo(Hero anotherHero) {if(damage<anotherHero.damage)return 1; elsereturn -1;}public String toString() {return "Hero [name=" + name + ", hp=" + hp + ", damage=" + damage + "]\r\n";}
}
3、Test代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;import charactor.Hero;public class TestCollection {public static void main(String[] args) {Random r =new Random();List<Hero> heros = new ArrayList<Hero>();for (int i = 0; i < 10; i++) {//通过随机值实例化hero的hp和damageheros.add(new Hero("hero "+ i, r.nextInt(100), r.nextInt(100)));}System.out.println("初始化后的集合");System.out.println(heros);//Hero类实现了接口Comparable,即自带比较信息。//Collections直接进行排序,无需额外的ComparatorCollections.sort(heros);System.out.println("按照伤害高低排序后的集合");System.out.println(heros);}
}
聚合操作
JDK8之后,引入了对集合的聚合操作,可以非常容易的遍历,筛选,比较集合中的元素。
使用Lambda表达式进行聚合操作
Lambda表达式
初探
平时需要写一个匿名类或者一个函数,调用的时候使用lambda可以简化操作
其实就是把代码逻辑写清楚了,其他冗杂的东西直接省略
源代码:
import java.util.ArrayList;
import java.util.List;
import java.util.Random;import charactor.Hero;public class TestLambda {public static void main(String[] args) {Random r = new Random();List<Hero> heros = new ArrayList<Hero>();for (int i = 0; i < 10; i++) {heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));}System.out.println("初始化后的集合:");System.out.println(heros);System.out.println("筛选出 hp>100 && damange<50的英雄");filter(heros);}private static void filter(List<Hero> heros) {for (Hero hero : heros) {if(hero.hp>100 && hero.damage<50)System.out.print(hero);}}}
使用匿名类:
HeroChecker是一个接口
import charactor.Hero;public interface HeroChecker {public boolean test(Hero h);
}
import java.util.ArrayList;
import java.util.List;
import java.util.Random;import charactor.Hero;public class TestLambda {public static void main(String[] args) {Random r = new Random();List<Hero> heros = new ArrayList<Hero>();for (int i = 0; i < 5; i++) {heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));}System.out.println("初始化后的集合:");System.out.println(heros);System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");HeroChecker checker = new HeroChecker() {@Overridepublic boolean test(Hero h) {return (h.hp>100 && h.damage<50);}};filter(heros,checker);}private static void filter(List<Hero> heros,HeroChecker checker) {for (Hero hero : heros) {if(checker.test(hero))System.out.print(hero);}}}
使用lambda表达式:
HeroChecker是一个上面使用匿名类里的同一个接口
匿名类本身只是载体,他是里面方法逻辑的载体,lambda也可以实现
import java.util.ArrayList;
import java.util.List;
import java.util.Random;import charactor.Hero;public class TestLamdba {public static void main(String[] args) {Random r = new Random();List<Hero> heros = new ArrayList<Hero>();for (int i = 0; i < 5; i++) {heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));}System.out.println("初始化后的集合:");System.out.println(heros);System.out.println("使用Lamdba的方式,筛选出 hp>100 && damange<50的英雄");filter(heros,h->h.hp>100 && h.damage<50);}private static void filter(List<Hero> heros,HeroChecker checker) {for (Hero hero : heros) {if(checker.test(hero))System.out.print(hero);}}
}
演变过程
package lambda;import java.util.ArrayList;
import java.util.List;
import java.util.Random;import charactor.Hero;public class TestLamdba {public static void main(String[] args) {Random r = new Random();List<Hero> heros = new ArrayList<Hero>();for (int i = 0; i < 5; i++) {heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));}System.out.println("初始化后的集合:");System.out.println(heros);System.out.println("使用匿名类的方式,筛选出 hp>100 && damange<50的英雄");// 匿名类的正常写法HeroChecker c1 = new HeroChecker() {@Overridepublic boolean test(Hero h) {return (h.hp > 100 && h.damage < 50);}};// 把new HeroChcekcer,方法名,方法返回类型信息去掉// 只保留方法参数和方法体// 参数和方法体之间加上符号 ->HeroChecker c2 = (Hero h) -> {return h.hp > 100 && h.damage < 50;};// 把return和{}去掉HeroChecker c3 = (Hero h) -> h.hp > 100 && h.damage < 50;// 把 参数类型和圆括号去掉HeroChecker c4 = h -> h.hp > 100 && h.damage < 50;// 把c4作为参数传递进去filter(heros, c4);// 直接把表达式传递进去filter(heros, h -> h.hp > 100 && h.damage < 50);}private static void filter(List<Hero> heros, HeroChecker checker) {for (Hero hero : heros) {if (checker.test(hero))System.out.print(hero);}}
}
Lambda 其实就是匿名方法,这是一种把方法作为参数进行传递的编程思想。
虽然代码是这么写
filter(heros, h -> h.hp > 100 && h.damage < 50);
但是,Java会在背后,悄悄的,把这些都还原成匿名类方式。
引入Lambda表达式,会使得代码更加紧凑,而不是各种接口和匿名类到处飞。
Lambda表达式弊端
Lambda表达式虽然带来了代码的简洁,但是也有其局限性。
1、可读性差,与啰嗦的但是清晰的匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解
2、不便于调试,很难在Lambda表达式中增加调试信息,比如日志
3、版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。
Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。
方法引用
聚合操作
这两个先过吧,有点不在状态
泛型
定义
泛型是泛指一切类型,当你不知道是用的数据是什么类型的时候,系统会自动判断。
E e:Element
反省可以使用任意字母代替,常用E
使用泛型的好处
1,避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型
2.把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)
弊端:只能存储一种数据类型
import java.util.ArrayList;
public class Main {public static void main(String[] args) {show2();}/*创建集合对象,不使用泛型好处:默认类型是Object,可以存储任意类型数据弊端:不安全,容易产生异常*/public static void show1(){ArrayList list=new ArrayList();list.add(1);list.add("abc");Iterator it=list.iterator();while (it.hasNext()) {Object obj = it.next();System.out.println(obj);String s = (String) obj;//转为string类数据System.out.println(s + "-->" + s.length());//会报错}}/*创建集合对象,使用泛型好处:1,避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型2.把运行期异常(代码运行之后会抛出的异常),提升到了编译期(写代码的时候会报错)弊端:泛型是什么类型,只能存储什么类型的数据*/public static void show2(){ArrayList<String> list=new ArrayList();//list.add(1); 报错list.add("abc");Iterator it=list.iterator();while (it.hasNext()) {String obj = it.next();System.out.println(obj + "-->" + obj.length());//会报错}}
}
泛型的定义与使用
我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
定义和使用含有泛型的类
定义: E表示泛型,任何一个字母都行
public class GenericClass<E>{private E name;public E getName(){return name;}public E setName(E name){this.name=name;}
}
使用: 前提是在同一个包下哈
public class DemoGeneriClass{public static void main(String[] args){//不写泛型默认为Object类型GenericClass gc=new GenericClass();gc.setName("只能字符串");//创建对象,泛型使用IntegerGenericClass gc2=new GenericClass();gc2.setName(1);Integer name=gc2.getName();System.out.println(name);}}
定义和使用含有泛型的方法
定义:
public class GenericMethod{//定义一个含有泛型的方法public <M> void method(M m){System.out.println(m);}//定义一个静态方法public static <S> void method2(S s){System.out.println(s);}
}
使用: 传入什么数据就是什么类型
public class DemoGenericMethod{public static void main(String[] args){//不写泛型默认为Object类型GenericMethod gc=new GenericMethod();gc.method("我草");gc.method(666);gc.method2("静态方法,不建议创建对象使用");//静态方法,通过类名.方法名(参数)可以直接调用GenericMethod.method2(666);}
}
定义和使用含有泛型的接口
传送门:https://www.bilibili.com/video/BV1uJ411k7wy?p=250
从250开始学
定义一个接口:
public interface GenericInterface<I>{public abstract void method(I i)
}
定义接口的实现类:
测试类:
泛型通配符
通配符: ? 代表任意的数据类型
使用方式:
不能创建对象时使用,即创建的时候不能用,见list03。只能作为方法的参数使用
public class Main {public static void main(String[] args) {ArrayList<Integer> list01=new ArrayList<>();list01.add(1);list01.add(2);ArrayList<String> list02=new ArrayList<>();list02.add("a");list02.add("b");printArray(list01);printArray(list02);/*定义的时候不能写?ArrayList<String> list02=new ArrayList<>();会报错*/}//定义一个方法,能遍历所有类型的ArrayList集合//可以使用通配符 ? 来接收数据类型,注意:反省没有继承概念public static void printArray(ArrayList<?> list){Iterator<?> it=list.iterator();while (it.hasNext()){//取出来的类型是Object,可以接收任意的数据类型Object o=it.next();System.out.println(o);}}
}
通配符的高级用法——受限泛型
用的不多,了解一下就好
泛型的上限限定:<? extends E> 代表使用的泛型只能是E类型的子类/本身
泛型的下限限定:<? super E> 代表使用的泛型只能是E类型的父类/本身
多线程
进程(Processor)和线程(Thread)的区别
首先要理解进程(Processor)和线程(Thread)的区别
**进程:**启动一个LOL.exe就叫一个进程。 接着又启动一个DOTA.exe,这叫两个进程。
**线程:**线程是在进程内部同时做的事情,比如在LOL里,有很多事情要同时做,比如"盖伦” 击杀“提莫”,同时“赏金猎人”又在击杀“盲僧”,这就是由多线程来实现的。
创建多线程(三种方式)
启动线程是start()方法,run()并不能启动一个新的线程
继承线程类,重写run方法
KillThread
import charactor.Hero;public class KillThread extends Thread{private Hero h1;private Hero h2;public KillThread(Hero h1, Hero h2){this.h1 = h1;this.h2 = h2;}public void run(){while(!h2.isDead()){h1.attackHero(h2);}}
}
TestKillThread
两个线程同时运行
import charactor.Hero;public class TestThread {public static void main(String[] args) {Hero gareen = new Hero();gareen.name = "盖伦";gareen.hp = 616;gareen.damage = 50;Hero teemo = new Hero();teemo.name = "提莫";teemo.hp = 300;teemo.damage = 30;Hero bh = new Hero();bh.name = "赏金猎人";bh.hp = 500;bh.damage = 65;Hero leesin = new Hero();leesin.name = "盲僧";leesin.hp = 455;leesin.damage = 80;KillThread killThread1 = new KillThread(gareen,teemo);killThread1.start();KillThread killThread2 = new KillThread(bh,leesin);killThread2.start();}
}
实现Runnable接口
创建类Battle,实现Runnable接口
启动的时候,首先创建一个Battle对象,然后再根据该battle对象创建一个线程对象,并启动
Battle battle1 = new Battle(gareen,teemo);
new Thread(battle1).start();
匿名类(直接在run方法中写业务代码)
使用匿名类,继承Thread,重写run方法,直接在run方法中写业务代码
匿名类的一个好处是可以很方便的访问外部的局部变量。
//匿名类Thread t1= new Thread(){public void run(){//匿名类中用到外部的局部变量teemo,必须把teemo声明为final//但是在JDK7以后,就不是必须加final的了while(!teemo.isDead()){gareen.attackHero(teemo);} }};t1.start();Thread t2= new Thread(){public void run(){while(!leesin.isDead()){bh.attackHero(leesin);} }};t2.start();
常用方法
关键字 | 简介 |
---|---|
sleep | 当前线程暂停 |
join | 加入到当前线程中 |
setPriority | 线程优先级 |
yield | 临时暂停 |
setDaemon | 守护线程 |
1、sleep
Thread.sleep(1000); 表示当前线程暂停1000毫秒 ,其他线程不受影响
Thread.sleep(1000); 会抛出InterruptedException 中断异常,因为当前线程sleep的时候,有可能被停止,这时就会抛出 InterruptedException
2、join
//代码执行到这里,一直是main线程在运行try {//t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走t1.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}
3、setPriority
参数是一个整数
t2优先级小,但是也会运行,只不过几率小而已,主要供t1运行
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
4、守护线程
守护线程的概念是: 当一个进程里,所有的线程都是守护线程的时候,结束当前进程。
就好像一个公司有销售部,生产部这些和业务挂钩的部门。
除此之外,还有后勤,行政等这些支持部门。
如果一家公司销售部,生产部都解散了,那么只剩下后勤和行政,那么这家公司也可以解散了。
守护线程就相当于那些支持部门,如果一个进程只剩下守护线程,那么进程就会自动结束。
守护线程通常会被用来做日志,性能统计等工作。
使用方法:
在线程start之前使用==t1.setDaemon( true );==即可
同步问题(核心)
多线程的同步问题指的是多个线程同时修改一个数据的时候,可能导致的问题
多线程的问题,又叫Concurrency 问题,会产生脏数据,示例如图
使用synchronized关键字来解决这个问题,同一时间,某一个变量只能被一个线程访问
示例代码:
import java.text.SimpleDateFormat;
import java.util.Date;public class TestThread {public static String now(){return new SimpleDateFormat("HH:mm:ss").format(new Date());}public static void main(String[] args) {final Object someObject = new Object();Thread t1 = new Thread(){public void run(){try {System.out.println( now()+" t1 线程已经运行");System.out.println( now()+this.getName()+ " 试图占有对象:someObject");synchronized (someObject) {System.out.println( now()+this.getName()+ " 占有对象:someObject");Thread.sleep(5000);System.out.println( now()+this.getName()+ " 释放对象:someObject");}System.out.println(now()+" t1 线程结束");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};t1.setName(" t1");t1.start();Thread t2 = new Thread(){public void run(){try {System.out.println( now()+" t2 线程已经运行");System.out.println( now()+this.getName()+ " 试图占有对象:someObject");synchronized (someObject) {System.out.println( now()+this.getName()+ " 占有对象:someObject");Thread.sleep(5000);System.out.println( now()+this.getName()+ " 释放对象:someObject");}System.out.println(now()+" t2 线程结束");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};t2.setName(" t2");t2.start();}
}
把synchronized放在类的方法里
参数添加this就好啦
//掉血public void hurt(){//使用this作为同步对象synchronized (this) {hp=hp-1; }}//回血public synchronized void recover(){hp=hp-1;}
线程安全类
如果一个类,其方法都是有synchronized修饰的,那么该类就叫做线程安全的类
同一时间,只有一个线程能够进入 这种类的一个实例 的去修改数据,进而保证了这个实例中的数据的安全(不会同时被多线程修改而变成脏数据)
比如StringBuffer和StringBuilder的区别
StringBuffer的方法都是有synchronized修饰的,StringBuffer就叫做线程安全的类
而StringBuilder就不是线程安全的类
HashMap和Hashtable的区别
HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
HashMap可以存放 null
Hashtable不能存放null
区别2:
HashMap不是
Hashtable是线程安全的类
StringBuffer和StringBuilder的区别
StringBuffer 是线程安全的
StringBuilder 是非线程安全的
所以当进行大量字符串拼接操作的时候,如果是单线程就用StringBuilder会更快些,如果是多线程,就需要用StringBuffer 保证数据的安全性
非线程安全的为什么会比线程安全的 快? 因为不需要同步嘛,省略了些时间
ArrayList和Vector的区别
两者一模一样的,区别也在于,Vector是线程安全的类,而ArrayList是非线程安全的。
把非线程安全的集合转换为线程安全,使用Collections
ArrayList是非线程安全的,换句话说,多个线程可以同时进入一个ArrayList对象的add方法
借助Collections.synchronizedList,可以把ArrayList转换为线程安全的List。
与此类似的,还有HashSet,LinkedList,HashMap等等非线程安全的类,都通过工具类Collections转换为线程安全的
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class TestThread {public static void main(String[] args) {List<Integer> list1 = new ArrayList<>();List<Integer> list2 = Collections.synchronizedList(list1);}
}
死锁
使用synchronized可能会出现死锁,使用Lock就好了
当业务比较复杂,多线程应用里有可能会发生死锁
线程1 首先占有对象1,接着试图占有对象2
线程2 首先占有对象2,接着试图占有对象1
线程1 等待线程2释放对象2
与此同时,线程2等待线程1释放对象1
就会。。。一直等待下去,直到天荒地老,海枯石烂,山无棱 ,天地合。。。
交互
使用wait和notify进行线程交互
写在了Hero里面
public class Hero {public String name;public float hp;public int damage;public synchronized void recover() {hp = hp + 1;System.out.printf("%s 回血1点,增加血后,%s的血量是%.0f%n", name, name, hp);// 通知那些等待在this对象上的线程,可以醒过来了,如第20行,等待着的减血线程,苏醒过来this.notify();}public synchronized void hurt() {if (hp == 1) {try {// 让占有this的减血线程,暂时释放对this的占有,并等待this.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}hp = hp - 1;System.out.printf("%s 减血1点,减少血后,%s的血量是%.0f%n", name, name, hp);}public void attackHero(Hero h) {h.hp -= damage;System.out.format("%s 正在攻击 %s, %s的血变成了 %.0f%n", name, h.name, h.name, h.hp);if (h.isDead())System.out.println(h.name + "死了!");}public boolean isDead() {return 0 >= hp ? true : false;}
}
关于wait、notify和notifyAll
这里需要强调的是,wait方法和notify方法,并不是Thread线程上的方法,它们是Object上的方法。
因为所有的Object都可以被用来作为同步对象,所以准确的讲,wait和notify是同步对象上的方法。
wait()的意思是: 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。
线程池(了解)
使用多线程很舒服,但是每一个线程的启动和结束都是比较消耗时间和占用资源的。
如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。
为了解决这个问题,引入线程池这种设计思想。
线程池的模式很像生产者消费者模式,消费的对象是一个一个的能够运行的任务
多线程同步(Lock对象)
1、区别于synchronized,能达到同样效果
Lock是一个接口,有很多类,需要使用使用finally释放,使用lock.unlock()释放
Lock lock = new ReentrantLock();
然后在线程使用lock.lock()方法就好啦
2、synchronized 是不占用到手不罢休的,会一直试图占用下去。
与 synchronized 的钻牛角尖不一样,Lock接口还提供了一个trylock方法。
trylock会在指定时间范围内试图占用,占成功了,就啪啪啪。 如果时间到了,还占用不成功,扭头就走~
注意: 因为使用trylock有可能成功,有可能失败,所以后面unlock释放锁的时候,需要判断是否占用成功了,如果没占用成功也unlock,就会抛出异常
3、lock.trylock(1,TimeUnit.SECONDS),返回布尔值
线程交互
使用synchronized方式进行线程交互,用到的是同步对象的wait,notify和notifyAll方法
Lock也提供了类似的解决办法,首先通过lock对象得到一个Condition对象,然后分别调用这个Condition对象的:await, signal,signalAll 方法
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class TestThread {public static String now() {return new SimpleDateFormat("HH:mm:ss").format(new Date());}public static void log(String msg) {System.out.printf("%s %s %s %n", now() , Thread.currentThread().getName() , msg);}public static void main(String[] args) {Lock lock = new ReentrantLock();Condition condition = lock.newCondition();Thread t1 = new Thread() {public void run() {try {log("线程启动");log("试图占有对象:lock");lock.lock();log("占有对象:lock");log("进行5秒的业务操作");Thread.sleep(5000);log("临时释放对象 lock, 并等待");condition.await();log("重新占有对象 lock,并进行5秒的业务操作");Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();} finally {log("释放对象:lock");lock.unlock();}log("线程结束");}};t1.setName("t1");t1.start();try {//先让t1飞2秒Thread.sleep(2000);} catch (InterruptedException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}Thread t2 = new Thread() {public void run() {try {log("线程启动");log("试图占有对象:lock");lock.lock();log("占有对象:lock");log("进行5秒的业务操作");Thread.sleep(5000);log("唤醒等待中的线程");condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {log("释放对象:lock");lock.unlock();}log("线程结束");}};t2.setName("t2");t2.start();}
}
原子访问
简介
所谓的原子性操作即不可中断的操作,比如赋值操作,int i=5
原子性操作本身是线程安全的
但是 i++ 这个行为,事实上是有3个原子性操作组成的。
步骤 1. 取 i 的值
步骤 2. i + 1
步骤 3. 把新的值赋予i
这三个步骤,每一步都是一个原子操作,但是合在一起,就不是原子操作。就不是线程安全的。
AtomicInteger
JDK6 以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。
而AtomicInteger提供了各种自增,自减等方法,这些方法都是原子性的。 换句话说,自增方法 incrementAndGet 是线程安全的,同一个时间,只有一个线程可以调用这个方法。
import java.util.concurrent.atomic.AtomicInteger;public class TestThread {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicI =new AtomicInteger();int i = atomicI.decrementAndGet();int j = atomicI.incrementAndGet();int k = atomicI.addAndGet(3);}
}
同步测试
分别使用基本变量的非原子性的**++运算符和 原子性的AtomicInteger对象的 incrementAndGet** 来进行多线程测试。
发现直接使用++会出问题,但是使用incrementAndGet方法就不会
网络编程
百度:如何开启Windows端口,就可以一起玩了嘿嘿
TCP:打电话 C/S
连接,稳定
三次握手,四次挥手
客户端、服务端
传输完成,释放连接,效率低
服务端:自定义 S
客户端:自定义 C
UDP:发短信 B/S
- 不连接,不稳定
- 客户端、服务端,没有明确的界限
- 不管有没有准备好,都发给你
- 导弹
- DDOS:洪水攻击(饱和攻击)
- 服务端:Tomcat S
- 客户端:浏览器 B
TCP聊天代码
记得关闭资源,否则一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出异常(Connection reset)。
服务端做一个while(true)就能一直重复操作了
客户端
1、连接服务器Socket
2、发送消息
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;public class TcpClientDemo1 {public static void main(String[] args) {//要知道服务器ip和端口InetAddress serverIP = null;Socket socket=null;OutputStream os = null;try {serverIP = InetAddress.getByName("127.0.0.1");int port=9999;//创建一个socket连接socket = new Socket(serverIP,port);//使用IO流发送消息os = socket.getOutputStream();os.write("你好啊,程序已经联通了".getBytes());} catch (Exception e) {e.printStackTrace();}finally {try {os.close();} catch (IOException e) {e.printStackTrace();}try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
}
服务端
1、建立服务的端口
2、等待用户的连接accept()
3、接收用户的消息
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class TcpServerDemo1 {public static void main(String[] args){ServerSocket serversocket=null;Socket socket=null;InputStream is=null;ByteArrayOutputStream baos=null;try {//我得有一个地址serversocket = new ServerSocket(9999);//等待客户端连接,得到socket对象socket = serversocket.accept();//读取客户端的消息is = socket.getInputStream();//管道流清洗数据,可以避免乱码,并且不会由于中文导致超出1024baos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {baos.write(buffer);}System.out.println(baos.toString());}catch (Exception e) {e.printStackTrace();}finally {//从后往前关try {baos.close();} catch (IOException e) {e.printStackTrace();}try {is.close();} catch (IOException e) {e.printStackTrace();}try {socket.close();} catch (IOException e) {e.printStackTrace();}try {serversocket.close();} catch (IOException e) {e.printStackTrace();}}}
}
TCP实现文件上传
就是把文件变成一个流,然后输出出去
客户端
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;import java.io.*;
import java.net.InetAddress;
import java.net.Socket;public class TcpClientDemo2 {public static void main(String[] args) throws IOException {//1、创建一个socket连接Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9999);//2、创建一个输出流OutputStream os = socket.getOutputStream();//3、文件流读取文件FileInputStream fis = new FileInputStream(new File("img.jpg"));//4、将文件写出byte[] buffer = new byte[1024];int len;while ((len=fis.read(buffer))!=-1){os.write(buffer,0,len);}//通知服务端,我传输完了socket.shutdownOutput();//确认服务器接收完了信息InputStream is = socket.getInputStream();ByteOutputStream baos = new ByteOutputStream();byte[] buffer2 = new byte[1024];int len2;while ((len2=is.read())!=-1){baos.write(buffer2,0,len);}System.out.println(baos.toString());//5、关闭资源baos.close();is.close();fis.close();os.close();socket.close();}
}
服务端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class TcpServerDemo2 {public static void main(String[] args) throws IOException {//1、创建服务ServerSocket serverSocket = new ServerSocket(9999);//2、监听客户端,阻塞,会一直等待客户端Socket socket = serverSocket.accept();//3、获取输入流InputStream is = socket.getInputStream();//4、把文件输出FileOutputStream fos = new FileOutputStream("wocao.jpg");byte[] buffer = new byte[1024];int len;while ((len=is.read(buffer))!=-1){fos.write(buffer,0,len);}//通知客户端,我接收完了OutputStream os = socket.getOutputStream();os.write("我接受完啦,牛皮!".getBytes());//5、关闭资源fos.close();is.close();socket.close();}
Tomcat
Windows环境下,双击bin目录的startup.bat文件,Linux双击startup.sh文件,启动,默认端口号是8080
出现乱码就修改conf的logging.properties,最后一个UTF-8改为GBK编码
UDP聊天代码
不需要知道对方地址
建立连接:DatagramSocket
发包:DatagramPacket
客户端
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;public class UdpSenderDemo1 {public static void main(String[] args) throws Exception {DatagramSocket socket = new DatagramSocket();while (true){//准备数据,控制台读取BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));String data=reader.readLine();byte[] datas=data.getBytes();DatagramPacket packet = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 6666));socket.send(packet);}//socket.close();}
}
服务端
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.security.cert.TrustAnchor;public class UdpReceiveDemo1 {public static void main(String[] args) throws Exception {//开放端口DatagramSocket socket = new DatagramSocket(6666);while (true){//准备接收数据byte[] container = new byte[1024];DatagramPacket packet = new DatagramPacket(container, 0, container.length);socket.receive(packet);//阻塞监听byte[] data = packet.getData();String ReceiveData=new String(data);System.out.println(ReceiveData);//断开链接,去掉换行符if(ReceiveData.trim().equals("bye")){break;}socket.close();}}
}
how2j学习笔记(JAVA中级)相关推荐
- JDBC学习笔记——Java语言与数据库的鹊桥
JDBC学习笔记--Java语言与数据库的鹊桥 JDBC(Java DataBase Connectivity):SUN公司提供的 一套操作数据库的标准规范,说白了就是用Java语言来操作数据 ...
- 学习笔记-Java并发(一)
学习笔记-Java并发(一) 目录 学习笔记-Java并发一 目录 Executer Callable和Future 后台线程 线程加入 小计 今天看了这一篇 Java编程思想-java中的并发(一) ...
- 深入理解Java虚拟机(第3版)学习笔记——JAVA内存区域(超详细)
深入理解Java虚拟机(第3版)学习笔记--JAVA内存区域(超详细) 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 对象的创建 对象的内存布局 ...
- 狂神说学习笔记 Java流程控制
目录 Java流程控制 1.用户交互Scanner Scanner对象 next() nextLine(): 2.顺序结构 3.选择结构 4.循环结构 5.Break & Continue 6 ...
- 狂神说Java学习笔记 Java基础
目录 机器语言 第二代语言(汇编语言) 第三代语言 高级语言 Java特性和优势 JDK(Java Development Kit) JRE(Java Runtime Enviroment) JVM( ...
- Linux学习笔记------java学习
前言 学习笔记仅供参考 该笔记是作者根据b站狂神说视频以及自己翻阅的一些资料而写 视频连接:狂神Linux视频链接 如果有兴趣的小伙伴可以前去观看 如果购买过服务器的话,可以直接使用宝塔进行傻瓜式安装 ...
- java jdk 8学习笔记,Java JDK 8学习笔记 PDF_源雷技术空间
资源名称:Java JDK 8学习笔记 PDF 内容简介: ●本书是作者多年来教学实践经验的总结,汇集了学员在学习课程或认证考试中遇到的概念.操作.应用等问题及解决方案 ●针对Java SE 8新功能 ...
- 学习笔记-java基础-网络编程
刚在培训机构学习完JAVA基础和WEB,感觉边学边忘.就把这作为记忆的笔记,我也不知道要不要记笔记,写在本子上太耗费时间了. 一.要想实现网络通信,首先要解决两个问题: ①:如何从网络中定位到一台或多 ...
- 学习笔记-java代码审计-反序列化
Java代码审计-反序列化 0x00 漏洞挖掘 业务代码 简单来说,找readObject/readUnshared就好了 protected void doPost(HttpServletReque ...
- java学习笔记—java的学习路线
Java体系涉及到三个方面:J2SE,J2EE,J2ME(KJAVA). J2SE,Java 2 Platform Standard Edition,我们经常说到的JDK,就主要指的这个,它是三者的基 ...
最新文章
- android java style_Android 在Java代码中设置style属性--使用代码创建ProgressBar对象
- 实战项目---模拟商品采购中心信息平台
- 指标搭建篇:如何搭建指标体系?——以公众号实战为例
- 如何打开屏幕坏的手机_每天打开手机屏幕20次?打开10次以上的朋友进~
- flume1.8实现hdfsSink整点滚动文件
- Android中ListView数据处理优化
- php_connect_nonb,net2ftp无法显示文件列表输出为空
- 用ISA 2004发布内部FTP服务器
- 【资源】同济线性代数教材(第五版)
- 用Python暴力破解WiFi
- 基于Python爬取天眼查网站的企业信息
- EXCEL常规格式数字转换为日期时间格式的方法
- [机器学习] 实验笔记 - 表情识别(emotion recognition)
- Web前端:什么是前端框架?
- 1.1 Linux内核代码下载、编译
- 【Oracle】B-tree和函数索引
- java调用webService接口的几种方法
- MySQL 清除表碎片空间
- 软件构造:防御式拷贝(Defensive Copying)
- 【Latex】在标题下插入头图 teaser
热门文章
- sqlmap中的columns哪里看_ROC,AUC 还是看我的吧,别人都千篇一律
- python Word 文档
- vue3[Vue warn]: Failed to resolve component: XXX If this is a native custom element, make sure to ex
- 利用尾注插入参考文献
- 大学本科毕业论文查重有什么要求?
- 解决npm ERR gyp ERR
- 即构科技廖念波:构建产品矩阵,加快音视频技术全面开花
- Monitor Hot Plug Detection(MSDN翻译)
- UVA10110-灯光
- 概率论与数理统计_陈希儒版_第一章:事件的概率