文章目录

  • 常见问题
    • 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;
}

二叉树排序——插入数据

使用了递归嗷,相对当前节点而言,小的值去左边,大的值去右边

过程:

  1. 67 放在根节点

  2. 7 比 67小,放在67的左节点

  3. 30 比67 小,找到67的左节点7,30比7大,就放在7的右节点

  4. 73 比67大, 放在67的右节点

  5. 10 比 67小,找到67的左节点7,10比7大,找到7的右节点30,10比30小,放在30的左节点。

  6. 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中级)相关推荐

  1. JDBC学习笔记——Java语言与数据库的鹊桥

    JDBC学习笔记--Java语言与数据库的鹊桥     JDBC(Java DataBase Connectivity):SUN公司提供的 一套操作数据库的标准规范,说白了就是用Java语言来操作数据 ...

  2. 学习笔记-Java并发(一)

    学习笔记-Java并发(一) 目录 学习笔记-Java并发一 目录 Executer Callable和Future 后台线程 线程加入 小计 今天看了这一篇 Java编程思想-java中的并发(一) ...

  3. 深入理解Java虚拟机(第3版)学习笔记——JAVA内存区域(超详细)

    深入理解Java虚拟机(第3版)学习笔记--JAVA内存区域(超详细) 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 对象的创建 对象的内存布局 ...

  4. 狂神说学习笔记 Java流程控制

    目录 Java流程控制 1.用户交互Scanner Scanner对象 next() nextLine(): 2.顺序结构 3.选择结构 4.循环结构 5.Break & Continue 6 ...

  5. 狂神说Java学习笔记 Java基础

    目录 机器语言 第二代语言(汇编语言) 第三代语言 高级语言 Java特性和优势 JDK(Java Development Kit) JRE(Java Runtime Enviroment) JVM( ...

  6. Linux学习笔记------java学习

    前言 学习笔记仅供参考 该笔记是作者根据b站狂神说视频以及自己翻阅的一些资料而写 视频连接:狂神Linux视频链接 如果有兴趣的小伙伴可以前去观看 如果购买过服务器的话,可以直接使用宝塔进行傻瓜式安装 ...

  7. java jdk 8学习笔记,Java JDK 8学习笔记 PDF_源雷技术空间

    资源名称:Java JDK 8学习笔记 PDF 内容简介: ●本书是作者多年来教学实践经验的总结,汇集了学员在学习课程或认证考试中遇到的概念.操作.应用等问题及解决方案 ●针对Java SE 8新功能 ...

  8. 学习笔记-java基础-网络编程

    刚在培训机构学习完JAVA基础和WEB,感觉边学边忘.就把这作为记忆的笔记,我也不知道要不要记笔记,写在本子上太耗费时间了. 一.要想实现网络通信,首先要解决两个问题: ①:如何从网络中定位到一台或多 ...

  9. 学习笔记-java代码审计-反序列化

    Java代码审计-反序列化 0x00 漏洞挖掘 业务代码 简单来说,找readObject/readUnshared就好了 protected void doPost(HttpServletReque ...

  10. java学习笔记—java的学习路线

    Java体系涉及到三个方面:J2SE,J2EE,J2ME(KJAVA). J2SE,Java 2 Platform Standard Edition,我们经常说到的JDK,就主要指的这个,它是三者的基 ...

最新文章

  1. android java style_Android 在Java代码中设置style属性--使用代码创建ProgressBar对象
  2. 实战项目---模拟商品采购中心信息平台
  3. 指标搭建篇:如何搭建指标体系?——以公众号实战为例
  4. 如何打开屏幕坏的手机_每天打开手机屏幕20次?打开10次以上的朋友进~
  5. flume1.8实现hdfsSink整点滚动文件
  6. Android中ListView数据处理优化
  7. php_connect_nonb,net2ftp无法显示文件列表输出为空
  8. 用ISA 2004发布内部FTP服务器
  9. 【资源】同济线性代数教材(第五版)
  10. 用Python暴力破解WiFi
  11. 基于Python爬取天眼查网站的企业信息
  12. EXCEL常规格式数字转换为日期时间格式的方法
  13. [机器学习] 实验笔记 - 表情识别(emotion recognition)
  14. Web前端:什么是前端框架?
  15. 1.1 Linux内核代码下载、编译
  16. 【Oracle】B-tree和函数索引
  17. java调用webService接口的几种方法
  18. MySQL 清除表碎片空间
  19. 软件构造:防御式拷贝(Defensive Copying)
  20. 【Latex】在标题下插入头图 teaser

热门文章

  1. sqlmap中的columns哪里看_ROC,AUC 还是看我的吧,别人都千篇一律
  2. python Word 文档
  3. vue3[Vue warn]: Failed to resolve component: XXX If this is a native custom element, make sure to ex
  4. 利用尾注插入参考文献
  5. 大学本科毕业论文查重有什么要求?
  6. 解决npm ERR gyp ERR
  7. 即构科技廖念波:构建产品矩阵,加快音视频技术全面开花
  8. Monitor Hot Plug Detection(MSDN翻译)
  9. UVA10110-灯光
  10. 概率论与数理统计_陈希儒版_第一章:事件的概率