java从入门到放弃(二)

       //求园面积 结果保留5位小数double ymj = Math.PI*Math.pow(``2.14``,` `2``);DecimalFormat df1 =new DecimalFormat();df1.applyPattern(".00000");System.out.println(df1.format(ymj));

System :

工具类

systrm.err("标准"错误输出流)

system.out(标准输出流)

system.exit(退出)终止当前正在运行的java虚拟机

system.gc(垃圾回收)

nanoTime()返回纳秒级系统时间。

Date

日期类:data:二进制,数据类

java.util 对应的是java的日期类型,包括年月日 时分秒

java.sql 对应的是数据库的日期类型,只包括 年月日

数据库的date转成java叫做自动类型装换

java的date转成数据库叫做强制类型转换

Date date = new Date();

System.out.println(date);西方格式

public class Demo5 {public static void main(String[] args) {//获取当前的时间Date date = new Date();System.out.println(date);//long型的时间,这个时间从1970年开始算long time = date.getTime();System.out.println(time);Date date2 =  new Date(time);System.out.println(date2);}
}

//1970年()

格式转换器

第一种: DateFormat(short,long,full.default)

第二种:SimpleDateFormat ("yyyy/MM/dd HH:mm:ss);//HH代表24小时,hh代表 12小时

/** 日期转换---日期格式转换器* Format:格式转换器* DateFormat:日期格式转换器*/
public class Demo6 {public static void main(String[] args) {//1.使用系统提供的默认的格式-DateFormat--提供了四种可选模式:short,long,full,default//第一个参数用于指定日期格式   第二个参数用于指定时间格式DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);String stringDate =  dateFormat.format(new Date());System.out.println(stringDate);//1993-02-03 23:23:12     1993/02/03 23:23:12//使用自定义的日期格式转换器实现自由转换----SimpleDateFormatSimpleDateFormat simpleDateFormat = new  SimpleDateFormat("yyyy/MM/dd  HH:mm:ss");//HH代表24小时制String stringDate1 = simpleDateFormat.format(new Date());System.out.println(stringDate1);}

日历类Calendar

public class Demo8 {public static void main(String[] args) {//日历类Calendar calendar = Calendar.getInstance();System.out.println(calendar);//获取当前的Date类型的时间Date date = calendar.getTime();System.out.println(date);//获取指定的值int value = calendar.get(Calendar.DATE);System.out.println(value);calendar.set(2019, 7, 2);//获取属性的最大值,最小值int value1 = calendar.getMaximum(Calendar.YEAR);int value2 = calendar.getMinimum(Calendar.YEAR);int value3 = calendar.DAY_OF_WEEK_IN_MONTH;int value4 =calendar.getWeekYear();System.out.println(value4+"   "+value3);System.out.println(value1+"    "+value2);}
}

Arrays:

数据工具类,内部封装了大量的操作数组的方法–静态方法

public class Demo9 {public static void main(String[] args) {//简单数据类型数组转字符串---方便我们对数组的内容进行查看int[] arr1 = {3,4,6,8,9};System.out.println(arr1);System.out.println(Arrays.toString(arr1));//数组转集合//简单数据类型数组转集合List list = Arrays.asList(arr1);System.out.println(list.size());//1  将整个简单数据类型的数组作为了集合的一个元素//引用数据类型数组转集合String[] strings = {"haha","hehe","hello"};List list2 = Arrays.asList(strings);System.out.println(list2.size());//3    //引用类型数组中的一个元素对应集合中的一个元素//注意点://转过来的集合长度是固定,所以不能执行增加,删除.但是可以执行修改,更改,遍历//list2.add("java");list2.set(0, "BigData");System.out.println(list2);//二分查找int[] arr3 = {3,6,8,67,678};System.out.println(Arrays.binarySearch(arr3, 67));}
}

修饰符

 修饰词:根据作用范围来进行划分private 默认的(default/friendly)    protected   public* 同一个类中 可见      可见                    可见      可见* * 同一个包中 不可见 可见                     可见       可见不同的包中有继承关系 不可见    不可见            可见       可见不同的包中没有继承关系不可见    不可见         不可见        可见
四种访问权限:
1.public:只能修饰类、方法(含构造方法)、成员。其修饰的部分所有人都能访问;2.默认修饰符(friendly):只能修饰类、方法(含构造方法)、成员变量。其修饰的部分只有同一包内的类能访问,对于这个包之外的所有类,这个成员是private的;3.private:只能修饰方法(含构造方法)、成员变量。其修饰的部分只能在自身类中访问。例如将某类的构造方法设置为private,那么在其它类中就不能调用“new”来创建该类的实例,通常在该类中会对外提供一种方法,该方法能返回该类的实例;再例如我们经常将成员变量设置为private的,对外提供set/get方法对其进行访问,在set/get方法中可做一些比如权限检测等操作,而不是将域直接暴露给所有用户;4.protected:只能修饰方法(含构造方法)、成员变量。在同一包中具有包访问权限,相当于默认访问权限;注意事项:在子类中,可继承不同包内父类的protected方法和属性,也只能在该子类中访问(除非重写方法或属性)

String

字符串的基础:

什么叫字符串:String类,java将与字符串相关的功能面向对象了,形成了对象的类–字符串类,表示方法:""

字符串的内容:

1.字符串的基础

2.不可变字符串的常用方法

3.可变字符串的常用方法

4.网址

String类,java将与字符串相关的功能面向对象了,形成了对象的类,表示方法:""。(字符串不是对象,可以看做常量,当成对象在用。)

不可变字符串:

String:继承object

可变字符串:继承object-----StringBuffer/StringBuilder,

分类:
不可变字符串:----String : 字符串本身不能发生改变,与指向字符串的引用无关,

可变字符串:字符串本身可以发生变化,与指向可变字符串的引用无关

//不可变字符串

String s1=“1000phone”;

//可变字符串

StringBuffer sBuffer = new stringBuffer(“1000phone”)

s1="1000";s2="1000";//字符串常量,所以值相同时候指向的同一个内存空间。
s3=new String({"1000");在堆里开新空间
s1==s2;(true,常量)
s1.equals=s3;(堆里和堆中常量区)//使用equlas方法比较多

字节和字符不同(汉字不同,英文字符数组和字节数组一样)

字典排序(默认排序)

对于不可变字符串本身是一个常量,保存在常量区.是存储在了堆区中一块儿特殊的区域里.
. 有人认为字符串不是在堆区,有的说字符串在堆区,我认为第二种,因为他是被当做对象在用。在堆区的开辟一块常量区。

 /** 当执行s1的时候,会到常量区找叫1000phone的字符串,如果有直接让s1保存他的地址,如果没有,会在常量区开辟一块儿* 空间存1000phone.* 执行s2是同理s1* 执行s3时,由于进行了new,一定会现在堆中开辟一块儿空间,而1000phone是作为参数传给了对象.保存在了对象的一个String* 类型的成员变量内,所以直接判断s1与s3不相同.* 执行s4同理s3*/String s1 = "1000phone";String s2 = "1000phone";String s3 = new String("1000phone");String s4 = new String("1000phone");System.out.println(s1 == s2);//trueSystem.out.println(s1 == s3);//堆中的常量区地址和堆地址相比System.out.println(s3 == s4);//false//使用equals//String默认重写了Object的equals方法,重新制定了比较规则,变成了让s1与s3属性的地址比较System.out.println(s1.equals(s3));//总结:以后尽量使用equals进行String的比较
public class Demo2 {public static void main(String[] args) {String s = "1000phone";//1.判断://判断是否包含一个子字符串//boolean contains(CharSequence s) System.out.println(s.contains("1000"));//true//判断两个字符串的内容是否相同//boolean equals(Object anObject) System.out.println(s.equals("1000"));//false//忽略大小写判断两个字符串的内容是否相同//boolean equalsIgnoreCase(String anotherString) System.out.println(s.equalsIgnoreCase("1000PHone"));//true//判断是否以某字符串开头//boolean startsWith(String prefix)System.out.println(s.startsWith("1000"));//true//判断是否以某字符串结尾//boolean endsWith(String suffix) System.out.println(s.endsWith("phone"));//true//2.转换:将字符数组转换成字符串//1:使用构造方法//String(char[] value) //String(char[] value, int offset, int count) //开始下标  数量 char[] arr = {'p','h','o','n','e'};String string1 = new String(arr);String string2 = new String(arr, 2, 3);System.out.println(string1+"     "+string2);//2:使用静态方法//static String copyValueOf(char[] data) //static String copyValueOf(char[] data, int offset, int count) //将字符串转成字符数组//char[] toCharArray() char[] arr1 = s.toCharArray();System.out.println(arr1+" "+arr);//将字节数组转成字符串//String(byte[] bytes) //String(byte[] bytes, int offset, int length)//String(byte[] bytes, String charsetName)//使用指定的编码将字节数组转换成字符串byte[] bytes = {97,98,99,100};String string3 = new String(bytes);System.out.println(string3);//将字符串转成字节数组//byte[] getBytes() byte[] bytes1 = string3.getBytes();System.out.println(bytes1[0]);//将基本数据类型转换成字符串//String.valueOf()String string4 = String.valueOf(true);System.out.println(string4);//3.替换://String replace(char oldChar, char newChar) String string5 = s.replace("1000", "haha");System.out.println(string5+"    s:"+s);//子串://String substring(int beginIndex)  //String substring(int beginIndex, int endIndex) //包含起始位置,不包含结束位置,到结束位置的前一位String string6 = "http://www.baidu.com:80/a/b/a/a?name=bing&age=18";String subString1 = string6.substring(7);String subString2 = string6.substring(7,20);System.out.println(subString2);//转换,去除空格,比较://大小写转换//String toLowerCase()   转成小写//String toUpperCase()   转成大写//将字符串两端的空格去掉//只能取出两边的,中间的不能去//String trim() String string7 = "    1000  phone     ";System.out.println(string7.trim()+"haha");//按字典顺序比较两个字符串/** 字典顺序:按照ASCII表比较当前的两个字符,ASCII码大的认为是大的字符* 规则:从左边第一个字符开始比较* 如果当前的字符不相同,直接认为ASCII大的字符串是大字符串,后面的字符停止比较* 当前字符比较的具体规则:使用前面的字符-后面的字符,返回差值.如果是负数,说明前面的字符串小于后面的.反之前面的大.* 如果当前的字符相同,再去比较第二个字符,依次往后推,如果比到最后都相同,则认为两个字符串相等,差值返回0.*///int compareTo(String anotherString)int value = s.compareTo("1000PHone");System.out.println(value);//4.切割: String[] split(String)//被作为刀的子字符串不会再被作为内容.String string8 = "a,b,c,d,e,f";String[] strings = string8.split(",");for (int i = 0; i < strings.length; i++) {System.out.println(strings[i]);}String string9 = "1.0.0.0.p.h.o.n.e";String[] strings1 = string9.split("\\.");//默认.是任意字符的意思,\\.将它转义成普通的.for (int i = 0; i < strings1.length; i++) {System.out.println("strings1:"+strings1[i]);}}
}

可变字符串

可变字符串:字符串缓冲区.StringBuffer/StringBuilder

区别:—了解

StringBuffer:出现在jdk1.0,是线程安全的,考虑了线程安全问题

StringBuilder:出现在jdk1.5,是线程不安全的,没有考虑线程安全问题

注意:在不考虑线程安全问题时,尽量使用StringBuilder,因为速度快,效率高(没有加线程安全的代码)

public static void main(String[] args) {StringBuffer sBuffer = new StringBuffer();//1.存储://StringBuffer append(boolean b)   从最后插入sBuffer.append(true);sBuffer.append("1000");sBuffer.append("phone");System.out.println(sBuffer);//StringBuffer insert(int offset, boolean b)   从指定位置插入sBuffer.insert(4, true);System.out.println(sBuffer);//2.删除://StringBuffer delete(int start, int end)   删除一部分字符串//StringBuffer deleteCharAt(int index)  删除一个字符System.out.println(sBuffer.delete(4, 8));//3.修改://StringBuffer replace(int start, int end, String str)  替换指定的子字符串//void setCharAt(int index, char ch) 修改一个字符//4.获取://char charAt(int index) System.out.println(sBuffer.charAt(0));//返回指定子字符串的下标  从左到右//int indexOf(String str)System.out.println(sBuffer.indexOf("phone"));//int indexOf(String str, int fromIndex)//返回指定子字符串的下标 从指定的索引开始System.out.println(sBuffer.indexOf("phone",7));//int lastIndexOf(String str) //返回指定子字符串(从右边开始计数)在此字符串第一次出现时的索引//int lastIndexOf(String str, int fromIndex) //返回指定子字符串(从右边开始计数,范围:当前指定的位置-0)在此字符串第一次出现时的索引 //int length() //返回长度(字符数)。 System.out.println(sBuffer.length());//5.反转://StringBuffer reverse()System.out.println(sBuffer.reverse());}

1.插入,可以插入任意类型的值(例子:布尔(true))

正则表达式

.matches 正则缺点:不能确定具体到哪一个错误()

[1-9] ,[5,6] [] 限制一位,- 几到几 “,”包括哪些

public class Demo10 {public static void main(String[] args) {String qq = "8776545644";//验证QQ//要求:1.全部是数字  2.首字母不能为0   3.位数是5-13//普通的方法boolean value = isQQ(qq);System.out.println(value);//使用正则表达式String regex = "[1-9]\\d{4,12}";boolean istrue = qq.matches(regex);System.out.println(istrue);}public static boolean isQQ(String qq) {if (qq.length() >=5 && qq.length() <= 13) {if (!qq.startsWith("0")) {try {Long.parseLong(qq);return true;}catch (NumberFormatException e) {System.out.println("不是全是数字");}}else {System.out.println("qq不能以0开头");}}else {System.out.println("输入的长度不匹配");}return false;}
}
package com.qf.test;import java.util.regex.Matcher;
import java.util.regex.Pattern;public class Demo11 {public static void main(String[] args) {//       * 使用
//       * 1.匹配
//       * 使用的是String类中的boolean matches(String regex)方法//piPei();
//       * 2.切割
//       * 使用的是String类中的 String[] split(String regex)方法
//       * 依据正则表达式的字符串来切割//qieGe();
//       * 3.替换
//       * 使用的是String类中的 String replaceAll(String regex, String replacement)
//       * 把字符串中符合正则表达式的内容替换成第二个参数的内容//tiHuan();
//       * 4.获取huoQu();}//匹配public static void piPei(){//实例:可以在h和l之间有0个或多个o
//      String s = "schooooooool";
//      String regex = "scho*l";//o*:代表0个或多个o     o+:代表1个或多个o//实例:匹配手机号码:18910909090String s = "18910909090";String regex = "1[345789]\\d{9}";boolean b = s.matches(regex);System.out.println(b);}//切割public static void qieGe(){//实例:使用,进行切割
//      String s = "axogj,owjgoiwrj,sgoiwrjoigjw,orisjgwr,jog34wjr";
//      String regex = ",";
//      String[] strings = s.split(regex);//要求按照空格切割
//      String s = "sdjof sjfsj      rjgojj          jgoj      jfoesjew  sd";
//      String regex = " +";
//      String[] strings = s.split(regex);//要求使用.进行切割String s = "sdjof.srj.gojjg.ofoe.sjesd";String regex = "\\.";//.默认代表任意字符,要想使用.需要进行转义\\.String[] strings = s.split(regex);for (String string : strings) {System.out.println("string:"+string);}}//替换public static void tiHuan(){//要求:将连续超过三个数字的部分进行替换---****String s = "sjfwfj4444sajose645sdgsjgrj3329889jsfjgowjsg888888888jsfjs";String regex = "\\d{4,}";String place = "****";String newString = s.replaceAll(regex, place);System.out.println(newString);}//获取public static void huoQu(){//要求:获取连续超过四个字母的子字符串String s = "abc   hello   world       look    book    d    jwejfe";String regex = "[a-zA-Z]{5,}";//相当于将正则表达式进行了简单的转化,但是Pattern本身不具有获取数据的能力Pattern pattern =  Pattern.compile(regex);//具有获取数据的能力Matcher matcher = pattern.matcher(s);//      matcher.find();//判断是否有符合当前表达式的内容
//      matcher.group();//是获取内容while (matcher.find()) {String string = matcher.group();System.out.println(string);}}
}

find 判断是否有符合当前表达式的内容

group取值 ,然后让指针加一

\\d代表数字,{4,12}至少4位,至多12位

网址

1:网址的作用:实现客户端与服务器端的通信。

服务器:数据中转,数据处理,数据存储。

(集群)不存储在服务器中,存储到数据库中,orcale。(端口号用于区分同一台主机有多个服务器app。)

客户端去往服务器的线,就是网址

服务器去往客户端的线,就是html(json)->内部解析器,->我们看到的东西

客户端:浏览器,app

网址:https://www.baidu.com:80/sdf/wef/sdfw?name=bingbing

​ ?/ : / / / ? = & &

每段的分割符

域名:标记网络上的唯一一台主机

端口号:0-60035 标记同一台主机上的某一台服务器 [0,65535]-----可以省,默认80

资源路径:可以省

查询条件:可以省

http协议: 超文本传输协议,实现的是网络间的通信的一个通信协议

https协议:安全的http协议

post 看不到查询条件的方式/get 可以在地址栏看到查询条件的方式

软件更新:

修复bug,增加功能,更新功能,增加系统的安全性。

native是java 用于引用C语言库的类

lambda表达式(这个东西很骚,也很好用)

lambda表达式:是Java8中的新特性

Java8中引入Lambda表达式,使得java可以函数式编程,在并发性能上迈出了实质性的一步。

函数式编程:(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,
它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础
是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)

比较lambda表达式和匿名内部类:
总结:lambda表达式就是简单的匿名内部类
1.匿名内部类可以为任意接口创建实例,不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法
即可; 但Lambda表达式只能为函数式接口创建实例(即只能有一个抽象方法)
2.匿名内部类可以为抽象类甚至是普通类创建实例;但Lambda表达式只能为函数式接口创建实例
3.匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认(default)方法;但Lambda表达式的代码块
不允许调用接口中的默认(default)方法

interface InterfaceA{public void show();
}
public class Demo1 {public static void main(String[] args) {//匿名内部类InterfaceA a = new InterfaceA() {public void show() {System.out.println("匿名内部类的show");}};a.show();//通过lambda表达式InterfaceA b = ()->{System.out.println("lambda表达式的show");};b.show();//简化InterfaceA bb = ()->System.out.println("简化的lambda表达式的show");bb.show();}
}

lambda表达式:java8中的新特性->对应一个抽象方法的接口

函数式编程,提高并发性能。

lambda表达式

//基本语法:
(参数)->表达式 或 (参数)->{方法体;}
1.形参列表:
形参列表允许省略形参类型,若形参列表中只有一个参数,形参列表的圆括号也可以省略代码
2.箭头(->)
必须通过英文的划线号和大于符号组成
3.代码块:
如果代码块只包含一条语句,lambda表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束
4.返回值:
lambda代码块只有一条return语句,甚至可以省略return关键字
lambda表达式需要返回值,而它的代码块中仅有一条省略了return的语句,lambda表达式会自动返回这条语句的结果
5.lambda表达式可以直接作为函数的参数
当要实现只有一个接口的抽象函数时,使用lambda表达式能够更灵活。
6.lambda表达式类型
Lambda表达式的类型,也被称为"目标类型(target type)",Lambda表达式的目标类型必须是"函数式接口(functional interface)"
Java8新引入的概念,函数接口(functional interface)。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来 (也可以不标记),函数式接口可以
包含多个default或static方法,但是只能声明一个抽象方法
@FuctionalInterface主要作用就是检查当前接口是不是函数接口
若想使用lambdaname目标必须是一个函数接口
7.lambda表达式中变量的使用
//如果是全局的变量直接用.如果是局部变量会被默认在前面添加final(被作为常量,类似于局部变量在局部内部类中使用的情况)

import org.junit.Test;public class Demo2 {public static void main(String[] args) {fun();}//@Testpublic static void fun() {//1.没有参数的InterA a = ()->{System.out.println("没有参数");};a.showA();//简化InterA aa = ()->System.out.println("没有参数");aa.showA();//2.一个参数的InterB b = (ib)->{System.out.println("一个参数:"+ib);};b.showB(4);//简化InterB bb = (ib)->System.out.println("一个参数:"+ib);bb.showB(5);//3.两个参数的InterC c = (ia,ib)->{int sum = ia+ib;System.out.println("两个参数:"+" ia:"+ia+"   ib:"+ib+"    sum:"+sum);};c.showC(4, 6);//简化InterC cc = (ia,ib)->System.out.println("两个参数:"+" ia:"+ia+"   ib:"+ib);cc.showC(4, 5);//4.返回值InterD d = (i,j)->{int sum = i+j;//System.out.println("sum:"+sum);return sum;};int value = d.showD(4, 6);System.out.println(value);//简化  简化版的不需要写return InterD dd = (i,j)->i+j;int value1 = dd.showD(4, 7);System.out.println(value1);//5.lambda表达式作为参数fun2(new InterC() {public void showC(int a, int b) {}});fun2((i,j)->System.out.println("lambda表达式作为参数"+" i:"+i));//7.lambda表达式中变量的使用String value11 = "hello world";InterA aaa = ()->{System.out.println(value11);//value11是final类型的是一个常量,值是不能改变的//value11 = "haha";};aaa.showA();}public static void fun2(InterC c) {c.showC(4, 6);}
}interface InterA{public void showA();
}
interface InterB{public void showB(int a);
}
interface InterC{public void showC(int a,int b);
}
interface InterD{public int showD(int a,int b);
}
@FunctionalInterface
interface InterE{public int showD(int a,int b);//public int showY(int a,int b);
}

方法引用与构造器引用

1.引用类方法

2.引用特定对象的实例方法

3.引用某类对象的实例方法

4.引用构造方法

public class Demo3 {public static void main(String[] args) {//       * 1.引用类方法Test1.fun1();
//       * 2.引用特定对象的实例方法Test2.fun2();
//       * 3.引用某类对象的实例方法Test3.fun3();
//       * 4.引用构造方法Test4.fun4();}
}
//* 1.引用类方法
interface Converter{//将字符串转换成整数Integer convert(String value);
}
class Test1{public static void fun1() {//原来的方法Converter converter = value->Integer.valueOf(value);Integer v1 = converter.convert("222");System.out.println(v1);//简化//引用类方法//通过::实现,这里会自动将lambda表达式方法的参数全部传递给当前的方法Converter converter2 = Integer::valueOf;Integer v2 = converter2.convert("333");System.out.println(v2);}
}
//* 2.引用特定对象的实例方法
interface IA{public void show(String message);
}
class A{public void play(String i) {System.out.println("这里是A的方法play"+"  i:"+i);}
}
class Test2{public static void fun2() {//原来IA ia = message->new A().play(message);ia.show("hello");//简化IA ia2 = new A()::play;ia2.show("world");}
}
//* 3.引用某类对象的实例方法
interface IB{String subString(String string,int stat,int end);
}
class Test3{public static void fun3() {//原来IB ib = (string,stat,end)->string.substring(stat, end);String sub1 = ib.subString("hello world", 2, 4);System.out.println(sub1);//简化//函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传递给该方法IB ib2 = String::substring;String sub2 = ib2.subString("class is over", 2, 5);System.out.println(sub2);}
}
//* 4.引用构造方法
interface IC{Object show(String name,int age);
}
class Person{String name;int age;public Person(String name, int age) {super();this.name = name;this.age = age;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + "]";}}
class Test4{public static void fun4() {IC ic = (name,age)->new Person(name, age);Object per = ic.show("bing", 19);System.out.println(per);//简化 //构造方法引用代替lambda表达式,函数式接口中被实现方法的全部参数传该构造方法作为参数IC ic2 = Person::new;Object per1 = ic2.show("chen", 10);System.out.println(per1);}
}

集合1

区分集合和数组

数组:可以存储不同类型的多个数据,数据类型可以是简单数据类型也可以引用数据类型

缺点:创建的是一个定值,只能存储固定长度的数据,一旦存满了,就不能再继续存储.

集合:可以存储不同类型的多个数据,但是只能存储引用数据类型.

缺点:只能存储引用数据类型

优点:存储空间会随着存储数据的增大而增大,所以可以更加合理的利用内存空间,方法很多,方便我们实现功能.

分类:

Collection:—接口

List—接口

ArrayList--类

​ Vector —类

​ LinkedList–类

Set—接口

​ HashSet–类

​ TreeSet–类

Map:–接口

​ HashMap–类

​ TreeMap–类

数组:可以存储不同类型的多个数据,数据类型可以是简单数据类型也可以引用数据类型。

缺点:创建的是一个空值,只能存储固定长度的数据,一旦存满了,

分类:Collection:----接口(clear,:刚开始是空的,没有东西,clear!=null,回到了刚创建对象的状态)

集合遍历的方法:

迭代器

hasnext():判断当前位置是否有元素,有就是true,没有就是false

next()将当前位置的值取出来,并且将指针向后移动一个位置

原理:开始指针指向第一个位置,使用hasnext方法判断,返回true,再使用next()方法获取当前的值,并将指针后移一个位置,依次进行,直到指针指向了

集合的末尾,再次调用hasnext方法,结束遍历

创建(迭代器)对象---->绑定集合和迭代器(系统优化)---------》集合中的iterator()方法.返回一个迭代器(当前集合绑定的)对象,从左到右,执行完之后,指针不动,在末尾。

public class Demo1 {public static void main(String[] args) {//Collection中定义是整个集合体系最共性的方法:Collection collection = new ArrayList<>();//1.添加://boolean add(Object o) collection.add("java");collection.add("python");System.out.println(collection);//重写了toString()//boolean addAll(Collection<? extends E> c)  一次添加多个元素Collection collection1 = new ArrayList<>();collection1.add("BigData");collection1.add("hadoop");collection.addAll(collection1);System.out.println(collection);//2.删除://boolean remove(Object o)  //boolean removeAll(Collection<?> c)//void clear()  clear != null//collection.remove("java");//collection.clear();System.out.println(collection);//3.判断://boolean contains(Object o) System.out.println(collection.contains("java"));//boolean containsAll(Collection<?> c)  在判断当前集合是否包含子集合中的元素时,必须包含所有的元素才会返回truecollection.remove("BigData");System.out.println("haha:"+collection.containsAll(collection1));//boolean isEmpty() //判断集合是否为空   空 != nullSystem.out.println(collection.isEmpty());//boolean equals(Object o) //4.获取://Iterator<E> iterator() //获取集合中的对象test(collection);//int size() //获取集合中对象的个数System.out.println(collection.size());//5.集合变数组:当不希望别人改变集合长度的时候.//Object[] toArray()Object[] objects =  collection.toArray();for (int i = 0; i < objects.length; i++) {System.out.println(objects[i]);}}//原理:开始指针指向第一个位置,使用hasnext方法判断,返回true,再使用next方法获取当前的值,//并将指针后移一个位置,依次进行,直到指针指向了集合的末尾,再次调用hasnext方法,返回false,结束遍历.public static  void test(Collection collection) {//4.获取://Iterator<E> iterator() //获取集合中的对象/** Iterator:叫迭代器* hasnext():判断当前位置是否有值,有返回true,没有false* next():取出当前位置的值,并将指针指向下一个位置*/Iterator iterator = collection.iterator();while (iterator.hasNext()) {String value = (String) iterator.next();System.out.println("iterator:"+value);}//注意点://1.直接再次使用第一次的iterator进行遍历,遍历失败.因为当前的指针已经指向了集合的最后.//再次使用hasnext会直接返回false.所以如果想再次遍历,要重新获取迭代器对象.while (iterator.hasNext()) {String value = (String) iterator.next();System.out.println("iterator1:"+value);}//2.注意:集合可以存储引用数据类型.可以存储不同的数据类型collection.add(2);//3.再次遍历--当集合中同时存在不同类型的数据时,需要进行容错处理和向下转型.Iterator iterator1 = collection.iterator();while (iterator1.hasNext()) {Object object = iterator1.next();if (object instanceof String) {System.out.println("iterator2:"+object);}}}
}

List:有序的(元素的存储顺序与添加元素的顺序一致),可以重复

Collection:

List:存储的数据是有序的(元素的存储顺序与添加元素的顺序一致),可以重复的.

Arraylist:底层的数据结构是数组,线程不安全的.特点:查找速度快,添加删除速度慢

Vector:底层的数据结构是数组,线程安全的.特点:查找速度快,添加删除速度慢

LinkedList:底层是链表,线程不安全的.特点:查找速度慢,添加删除速度快.

Set:存储的数据是无序的,不可以重复

不建议在迭代器循环过程中,调用集合的手段增删改(使用迭代器里的方法)。

public class Demo2 {public static void main(String[] args) {//* List:特有方法,可以操作下标List list = new ArrayList<>();//1.增://void add(int index, E element) //boolean addAll(int index, Collection<? extends E> c) list.add("java");list.add("html");list.add(1, "hadoop");System.out.println(list);//2.删   //E remove(int index) //System.out.println(list.remove(0));//3.改//E set(int index, E element)System.out.println(list.set(0, "haha"));//4.查//ListIterator<E> listIterator() test(list);//返回此列表元素的列表迭代器(按适当顺序)。 //ListIterator<E> listIterator(int index) //List<E> subList(int fromIndex, int toIndex)   左闭右开List list2 = list.subList(1, 2);System.out.println(list2);//E get(int index) System.out.println(list.get(0));System.out.println(list);//5.删除全部对象list.clear();}//4.查//ListIterator<E> listIterator() public static void test(List list) {//先获取迭代器对象ListIterator listIterator = list.listIterator();//从左到右while (listIterator.hasNext()) {String object = (String) listIterator.next();System.out.println("从左到右:"+object);}//从右到左while (listIterator.hasPrevious()) {String object = (String)listIterator.previous();System.out.println("从右到左:"+object);}//内部有add,set,remove方法,可以直接对当前位置的元素进行操作//从左到右while (listIterator.hasNext()) {String object = (String) listIterator.next();if (object.equals("haha")) {//在使用迭代器期间,使用list的删除方法直接删除元素,有可能发生错误,所以不要这样做//list.remove(object);//使用迭代器自带的remove方法//用迭代器提供的方法,注意:remove,add,set不要同时使用listIterator.remove();}System.out.println("从左到右:"+object);}}
}

vector:

遍历的时候用的是枚举器。枚举器(elements)方法,和迭代一样,调用方法就可以获得这个对象。

public class Demo3 {public static void main(String[] args) {Vector vector = new Vector<>();vector.add("java");vector.add("html");vector.add("hadoop");vector.add("spark");vector.add("spark");System.out.println(vector);//遍历//获取一个枚举器Enumeration enumeration = vector.elements();while (enumeration.hasMoreElements()) {Object object = (Object) enumeration.nextElement();System.out.println(object);}}
}

linkedList:

public class Demo4 {public static void main(String[] args) {//      LindedList
//      特有的方法:LinkedList linkedList = new LinkedList<>();
//      addFirst()//始终在首位添加
//      addLast()//始终在末尾添加linkedList.addFirst("java");linkedList.addLast("html");linkedList.add("hadoop");linkedList.add(1, "spark");linkedList.addFirst("BigData");System.out.println(linkedList);
//      getFirst()//获取的对象不存在会发生异常
//      getLast()linkedList.clear();String value =  (String)linkedList.getFirst();//NoSuchElementException  没有这个元素异常System.out.println(value);
//      removeFirst()//删除的对象不存在会发生异常
//      removeLast()//      从jdk1.6开始出现以下方法
//      offerFirst()
//      offerLast()//       peekFirst()//获取的对象不存在会返回null
//      peekLast()System.out.println(linkedList.peekFirst());
//      pollFirst()//删除的对象不存在会返回null
//      pollLast()}
}

add get remove;

1.6以后出现了

offerFirst() offerLast() 增加

peekFirst() peekLast()获取

pollFirst() pollLast();删除

想要List不重复,用contains判断元素是否存在,然后确定存不存进List.

/** List的注意点:* 例题:List是有序可重复的* 要求:使用List存储数据,但是数据不能重复----利用Contains*/
public class Demo5 {public static void main(String[] args) {ArrayList list = new ArrayList<>();list.add("java");list.add("php");list.add("php");list.add("html");list.add("python");System.out.println(list);//创建一个新的集合,作为装不重复数据的地方ArrayList list1 = new ArrayList<>();Iterator iterator = list.iterator();while (iterator.hasNext()) {String object = (String) iterator.next();//如果list1中不包含当前的元素,就进行添加if (!list1.contains(object)) {//当list1中不包换object时,将它添加进来/** 如果判断成立,说明list1中不包含当前的元素* 工作原理:当添加元素时,会让当前的元素与集合中已有的元素通过equels方法进行一一比较.过程中* 只要有一次返回true,停止比较.让整个的contains方法返回true.只有所有的比较都返回false,最终* 才会返回false* * 实例:添加第三个元素的时候,调用equals方法的过程* 第三元素.equals("java") = false     第三元素.equals("php") = true  停止比较*/list1.add(object);}}System.out.println(list1);}
}

set :不重复的,无序的

HashSet:底层是哈希表,线程不安全的

TreeSet:底层是二叉树,线程不安全

HashSet:

原理:是通过调用元素的hashCode和equals方法实现去重,首先调用hashCode方法,拿到当前对象的哈希码值,去让两个对象的哈希码值进行比较,如果不同,直接认为是两个对象,不再去调用equals,如果相同,再继续调用equals方法,返回true认为是一个对象,返回false认为是两个对象

public class Demo7 {public static void main(String[] args) {Set set = new HashSet<>();//说明Set本身的add方法内部实现的去重功能,默认调用的是元素的hashCode和equals方法//String类已经默认重写了hashCode和equals方法set.add("java");set.add("hadoop");set.add("spark");set.add("HDFS");set.add("HDFS");set.add("Mapreduce");System.out.println("result1:"+set);Set set1 = new HashSet<>();//* 实例:使用HashSet实现Person对象的存储// * 比较规则自己指定:按照年龄和姓名比,相同认为是一个人//* 分析:要重写hashCode和equals方法set1.add(new Person1("bingbing",18));set1.add(new Person1("zhangsan",28));set1.add(new Person1("chenchen",98));set1.add(new Person1("bingbing",18));System.out.println(set1);}
}class Person1{String name;int age;public Person1() {super();// TODO Auto-generated constructor stub}public Person1(String name, int age) {super();this.name = name;this.age = age;}@Overridepublic String toString() {return "Person1 [name=" + name + ", age=" + age + "]";}//重写hashCode方法@Overridepublic int hashCode() {return name.hashCode()+age*1000;}//重写equals方法@Overridepublic boolean equals(Object obj) {//自己制定比较规则:根据年龄和姓名比较//容错处理if (!(obj instanceof Person1)) {throw new ClassCastException("当前的对象不是Person1类型的");}//向下转型Person1 person = (Person1)obj;return this.name.equals(person.name)  && this.age==person.age;}}

实现排序和去重;

实例:

第一种实现comparable接口,

/** TreeSet:可以实现排序和去重* *实例:在TreeSet中存入Person1类的对象*并按照年龄和姓名实现排序,去重*分析:需要实现Comparable接口的compareTo方法* */
public class Demo8 {public static void main(String[] args) {Set set = new TreeSet<>();/** TreeSet的add方法实现的排序,去重.通过调用元素的compareTo方法* String类已经实现了Comparable接口*/set.add("java");set.add("hadoop");set.add("spark");set.add("HDFS");set.add("HDFS");set.add("Mapreduce");System.out.println("result1:"+set);//没有实现Compareble接口的对象不能当做TreeSet的元素,否则报异常ClassCastExceptionSet set1 = new TreeSet<>();set1.add(new Person2("bingbing",18));set1.add(new Person2("zhangsan",28));set1.add(new Person2("chenchen",98));set1.add(new Person2("bingbing",18));System.out.println(set1);}
}class Person2 implements Comparable{String name;int age;public Person2() {super();// TODO Auto-generated constructor stub}public Person2(String name, int age) {super();this.name = name;this.age = age;}@Overridepublic String toString() {return "Person2 [name=" + name + ", age=" + age + "]";}@Overridepublic int compareTo(Object o) {//根据自己制定的规则比较//根据年龄和姓名比较//容错处理if (!(o instanceof Person2)) {throw new ClassCastException("类型转换错误");}//向下转型Person2 person2 = (Person2)o;//先比姓名int num = this.name.compareTo(person2.name);//再按照年龄比return num==0?this.age-person2.age:num;}
}

第二种创建实现了Comparator接口的比较器----人工排序

创建一个比较器(自定义类实现接口),

把比较器交给TreeSet

Set set = new TreeSet<>(comStrWithLength);

注意:人工排序的优先级高于默认排序

练习:将人存入TreeSet,通过Comparator实现Person2的排序和比较
规则:只要姓名和年龄相同认为是一个人

public class Demo9 {public static void main(String[] args) {//创建比较器对象ComStrWithLength comStrWithLength = new  ComStrWithLength();//将比较器对象交给TreeSetSet set = new TreeSet<>(comStrWithLength);/** TreeSet的add方法实现的排序,去重.通过调用元素的compareTo方法* String类已经实现了Comparable接口*/set.add("java");set.add("hadoop");set.add("spark");set.add("HDFS");set.add("HDFS");+set.add("Mapreduce");System.out.println(set);}
}//比较字符串的长度if (!(o1 instanceof String)) {throw new ClassCastException("类型转换错误");}if (!(o2 instanceof String)) {throw new ClassCastException("类型转换错误");}//向下转型String s1 = (String)o1;String s2 = (String)o2;//先按照长度比int num = s1.length()-s2.length();//长度相同,再按照字典顺序比return num==0?s1.compareTo(s2):num;}

泛型

泛型:通过<数据类型>接收一种数据类型,在编译的时候会使用这种数据类型检测集合中的元素,如果元素不是<>中规定的类型,

就不允许添加到当前的集合中(编译失败)

泛型作用:1.使用了泛型不再需要进行容错处理,向下转型,强制类型转换----简化代码

2.将运行阶段的问题提前到编译阶段检查,提高了代码的安全性和编程效率

泛型可以修饰的地方:类,方法,接口

当泛型没有指定时默认是Object

泛型用在类上

public class Demo11 {public static void main(String[] args) {Phone phone = new Phone("华为");Computer computer = new Computer("苹果");//使用泛型前Student student = new Student();student.setTools(phone);// tools = phone   多态Tools tools = student.getTools();  //多态Phone phone2 = (Phone)tools;//向下转型phone2.callPhone();Computer computer2 = (Computer)tools;//使用泛型后//当泛型确定后,类型就确定了.Student1<Phone> student1 = new Student1<>();student1.setTools(phone);//student1.setTools(computer);student1.getTools();}
}
//使用泛型后
/** 给Student类加泛型,方式:在类的后面直接添加<E>,E代表任意一种数据类型,注意:这里不一定是E,任意字母都可以* 这就是在类上使用泛型* 在类上确定的泛型可以直接在方法上使用* * 当泛型没有指定时默认是Object*/
class Student1<E>{E tools;public E getTools() {return tools;}public void setTools(E tools) {this.tools = tools;}
}//使用泛型前
class Student{Tools tools;public Tools getTools() {return tools;}public void setTools(Tools tools) {this.tools = tools;}
}class Tools{String name;
}class Computer extends Tools{public Computer() {}public Computer(String name){this.name = name;}
}class Phone extends Tools{public Phone() {}public Phone(String name) {this.name = name;}public void callPhone() {System.out.println("打电话");}
}

泛型用在方法上

1方法的泛型与类上的泛型保持一致

2方法上独立使用泛型

3静态方法上使用泛型

public class Demo12 {public static void main(String[] args) {Dog<String> dog = new Dog<>();//1.方法上的泛型与类上的泛型保持一致dog.eat("吃");//2.方法上独立使用泛型dog.song("吃");//3.静态方法上使用泛型}
}class Dog<F>{//1.方法上的泛型与类上的泛型保持一致public void eat(F f) {System.out.println(f);}//2.方法上独立使用泛型/** 注意:泛型在使用之前一定要先进行定义* 定义的方式:在当前方法的最前面添加<泛型>* 作用:让方法与方法内的泛型保持一致*/public <E> void song(E e) {ArrayList<E> arrayList = new ArrayList<>();System.out.println(e);}//3.静态方法上使用泛型/** 必须独立使用* 方式:在static 后面定义泛型  <泛型> */public static <W> void show(W w) {}
}

必须独立使用

方式在:在static后面定义泛型,自己用

泛型用在接口上

1接口用泛型,实现类和接口一样用泛型

2实现类直接不用泛型。要在接口的<>中写上具体类型

public class Demo13 {public static void main(String[] args) {//1.子类上的泛型与接口上的一致Pig<String> pig = new Pig<>();pig.show("哈哈");//2.接口上使用泛型,子类上不用泛型Bird bird = new Bird();bird.show("haha");Class<?> class1 =  bird.getClass();}
}interface Inte<E>{public void show(E e);
}
//1.子类上的泛型与接口上的一致
/* 类上的泛型确定了,接口上的泛型就确定了,方法上的泛型就确定了
*/
class Pig<E> implements Inte<E>{@Overridepublic void show(E e) {System.out.println(e);}
}//2.接口上使用泛型,子类上不用泛型
/*在实现的接口位置必须指定一个具体的泛型* * 方法使用泛型的情况:* 1.如果是重写的方法,泛型与接口一致* 2.如果是子类自己的方法,可以与接口一致,也可以有自己的泛型*/
class Bird  implements Inte<String>{public void show(String e) {};
}

?的使用

关于java中?的使用

1)用于?: 这里是算目运算符一部分,前面是判断条件,后面是两个分支结果

2)用于数据库的sql语句 select * from emp where name=? :表示占位符

3)用于泛型,表示任意一种数据类型.

//这里的Object与前面的?没有关系
Class<?> class1 = Object.class;
//如果类的泛型使用时写成?是可以的.作为返回值时,会默认成Object类型,但是作为参数不行.所以在给对象指定泛型时要写具体类型
Test<?> test = new Test();
//test.run(new Object());
class Test<T>{T e;public T run(T a) {T t = null;return t;}
}

限制上限

限制下限

/ * ?:通配符,可以表示一种或几种数据类型* 限制上限:<? extends E>:限制的是整个的<>可以取的泛型类型的上限是E,<>中可以取的类型是E及E的子类* 限制下限:<? super E>::限制的是整个的<>可以取的泛型类型的下限是E,<>中可以取的类型是E及E的父类* * * 讲的是限制上限:<? extends E>*/
public class Demo14
{public static void main(String[] args) {//ArrayList<Student2> list1 = new ArrayList<>();list1.add(new Student2("bingbing", 1));//可以传参:因为Student2是Person1的子类,可以实现遍历bianli(list1);ArrayList<Teacher> list2 = new ArrayList<>();list2.add(new Teacher("bingbing", 1));//可以传参:因为Teacher1是Person1的子类,可以实现遍历bianli(list2);ArrayList<Person4> list3 = new ArrayList<>();list3.add(new Person4("bingbing", 1));//可以传参bianli(list3);ArrayList<Object> list4 = new ArrayList<>();list4.add(new Object());//可以传参:因为Object是Person1的父类,不可以实现遍历//bianli(list4);}public static void bianli(Collection<? extends Person4>  e){System.out.println("遍历了");}
}class Person4{String name;int age;public Person4() {super();// TODO Auto-generated constructor stub}public Person4(String name, int age) {super();this.name = name;this.age = age;}@Overridepublic String toString() {return "Person4 [name=" + name + ", age=" + age + "]";}
}class Teacher extends Person4{public  Teacher(String name, int age) {super(name, age);}
}class Student2 extends Person4 {public  Student2(String name, int age) {super(name, age);}
}

map接口

​ HashMap:底层是哈希表,线程是不安全的

​ TreeMap: 底层是二叉树,线程是不安全的

Collection:直接存储的数据

Map:本身是接口,存储的是键值对,一个元素就是一个键(key)值(value)对,key必须是惟一的,值随意,即可以重复。

public class Demo1 {public static void main(String[] args) {//介绍Map接口的方法Map<String, String> map = new HashMap<>();//1.增加//V put(K key,V value)  增加一个键值对String value1 = map.put("01", "java");//返回nullString value2 = map.put("02", "html");//返回nullString value3 = map.put("02", "iOS");//返回htmlmap.put("03", "BigData");map.put("04", "iOS");//关于返回值,如果当前的key之前没有添加过,返回null.如果当前的key之前已经存在了,这里返回之前的值System.out.println(value3);System.out.println(map);//void putAll(Map<? extends K,? extends V> map)  增加多个//2.删除//V remove(Object key)  根据key删除元素//返回值就是被删掉的值//System.out.println(map.remove("01"));//void clear()  删除全部  != null//3.获取//V get(Object key)  根据key查找元素System.out.println(map.get("02"));//int size()  获取键值对的个数//Set<K> keySet()   遍历方法一//Set<Map.Entry<K,V>> entrySet() 遍历方法二//4.常用的判断//boolean isEmpty()  //空map!=null//map = null;System.out.println(map.isEmpty());//boolean containsKey(K key) 是否包含当前的key//boolean containsVal+ue(V value) 是否包含当前的value}
}

​ 遍历方法:map本身没有遍历的方法,先把map(key)装换成set(key)利用set的迭代器,然后利用去取value值。

​ 遍历方法一:Set keyset

原理:先得到所有的key,放入一个Set中,利用Set的迭代器进行遍历得到key,在利用Key获取value;

​ Set<Map.Entry<K,V>> entrySet() 遍历方法二

原理:先得到所有的entry,放入一个Set中,利用Set的迭代器进行遍历得到entry实体,在利用entry的方法获取key和value

//方法一public static void test1(Map<String, String> map) {//第一步:先得到装着key的setSet<String> set = map.keySet();//第二步:遍历set,得到key,再根据key获取valueIterator<String> iterator = set.iterator();while (iterator.hasNext()) {String key = iterator.next();System.out.println("key:"+key+"   value:"+map.get(key));}}//方法二public static void test2(Map<String, String> map) {//第一步:先得到装着Entry实体的setSet<Map.Entry<String,String>> set =  map.entrySet();//第二步:遍历set,得到entry实体,再调用entry实体对象的方法获取key和valueIterator<Map.Entry<String,String>> iterator = set.iterator();while (iterator.hasNext()) {Map.Entry<String, String> entry = iterator.next();//通过setValue可以将map的原始值改变,但是一般在使用entrySet的时候,是进行遍历.不进行值的改变.//entry.setValue("bingbing");System.out.println("key1:"+entry.getKey()+"    value1:"+entry.getValue());}}
}

HashMap:(线程不安全的,key可以为null)

HashMap的底层实现,put(调用了以上两个方法其中一个,)

HashMap:去重,因为HashMap的底层与HashSet的底层实现一样,只是对HashMap去重的时候,操作的是key

注意:HashMap可以实现排序:因为他的底层数据结构是由数组+链表+二叉树共同实现的.所以可以排序.同时这样做的目的是提高数据存储的效率.

HashMap经常出的相关面试题

1.HashMap的底层实现

2.HashMap与HashTable的区别

HashTable:是线程安全的(key不能为null),

TreeMap:必须实现了Comparable接口的CompareTo()方法 2.实现Comparator接口的Compare()方法。put(调用了以上两个方法其中一个,)

TreeMap的注意点:

1.什么类型的数据类型可以作为key?

a:实现了Comparable接口的compareTo()方法 b:实现了Comparator接口的compare()方法

可以的代表:String,包装类,自定义的实现了要求的类

不可以的代表:数组,ArrayList,LinkedList(如果给他们建立的比较器也可以比较,但是不建议使用)

2.元素可不可以作为key,跟元素内部的成员有没有关系

元素可不可以作为key,跟元素内部的成员有没有关系

可变参数:

可变参数:参数的个数可以改变

作用:简化代码,简化操作

可变参数的特点
1.给可变参数传值的实参可以直接写,个数不限制,内部会自动的将他们放入可变数组中.
sum1(5,6,7,8,9,3,3,4);
2.当包括可变参数在内有多个参数时,可变参数必须放在最后面,并且一个方法中最多只能有一个可变参数
3.当可变参数的方法与固定参数的方法是重载关系时,调用的顺序,固定参数的优先于可变参数的.
sum3(2,3);

固定参数的优先级优先于可变参数。

//构成:数据类型+...    实际上就是数据类型[]  即:int[]public static int sum1(int... a) {int sum = 0;for (int i = 0; i < a.length; i++) {sum+=a[i];}return sum;}//2.当包括可变参数在内有多个参数时,可变参数必须放在最后面,并且一个方法中最多只能有一个可变参数public static int sum2(float b,int... a) {int sum = 0;for (int i = 0; i < a.length; i++) {sum+=a[i];}return sum;}//3.当可变参数的方法与固定参数的方法是重载关系时,调用的顺序,固定参数的优先于可变参数的.public static int sum3(int a, int b) {System.out.println("a");int sum = 0;return sum;}public static int sum3(int... a) {System.out.println("b");int sum = 0;return sum;}
}

增强for循环

for(元素:数组、Collection){

​ 内容

}

增强for循环

可以遍历的内容有:数组,Collection,Map.但是Map不能直接遍历

结构:

for(元素:数组/Collection){

内容

}

原理:每次遍历开始后,会自动从数组中依次取出一个元素放入前面的变量中,当次循环的操作使用的就是这个元素.遍历完成之后,会自动进行第二次遍历.一直到数组的末尾.所有元素遍历结束.循环停止.

Collections:

封装了大量操作的Collections:封装了大量的操作Colletion的工具

注意:

public class Demo8 {public static void main(String[] args) {//要求:存储多个数据,可排序,可重复ArrayList<String> list = new ArrayList<>();list.add("java");list.add("java1haha");list.add("java2BigData");list.add("java3ok");list.add("java2");System.out.println(list);//使用Collections排序//第一种排序:默认按照字典进行排序//注意:要想list中的元素可以按照字典排序,元素必须实现Comparable接口Collections.sort(list);System.out.println("默认的字典排序:"+list);//按照从短到长排序//使用比较器ComStrWithLength1 comStrWithLength = new ComStrWithLength1();Collections.sort(list, comStrWithLength);System.out.println("从短到长排序:"+list);//按照从长到短排序Comparator<String> comparator1 =  Collections.reverseOrder(comStrWithLength);Collections.sort(list, comparator1);System.out.println("从长到短排序:"+list);//倒叙字典排序Comparator<String> comparator2 =  Collections.reverseOrder();        Collections.sort(list, comparator2);System.out.println("倒叙字典排序:"+list);Collections.reverse(list);System.out.println("现有顺序的反转排序:"+list);//求最大值String max = Collections.max(list, comStrWithLength);System.out.println(max);}
}class ComStrWithLength1 implements Comparator<String>{@Overridepublic int compare(String o1, String o2) {int num = o1.length()-o2.length();return num==0?o1.compareTo(o2):num;}
}

数据结构:

数组:

内存是连续的。查找速度快,增加和删除的速度慢

链表:

树:

二叉树:

Hash表的实现

1.什么是Hash表

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,它是基于快速存取的角度设计的,也是一种典型的“空间换时间”的做法。顾名思义,该数据结构可以理解为一个线性表,但是其中的元素不是紧密排列的,而是可能存在空隙。

​ 散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。比如我们存储70个元素,但我们可能为这70个元素申请了100个元素的空间。70/100=0.7,这个数字称为负载(加载)因子。我们之所以这样做,也 是为了“快速存取”的目的。我们基于一种结果尽可能随机平均分布的固定函数H为每个元素安排存储位置,以达到快速存取。但是由于此随机性,也必然导致一个问题就是冲突。所谓冲突,即两个元素通过散列函数H得到的地址相同,那么这两个元素称为“同义词”。这类似于70个人去一个有100个椅子的饭店吃饭。散列函数的计算结果是一个存储单位地址,每个存储单位称为“桶”。设一个散列表有m个桶,则散列函数的值域应为[0,m-1]。

这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置

2.hash表扩容的理解

可是当哈希表接近装满时,因为数组的扩容问题,性能较低(转移到更大的哈希表中).

Java默认的散列单元大小全部都是2的幂,初始值为16(2的4次幂)。假如16条链表中的75%链接有数据的时候,则认为加载因子达到默认的0.75。HahSet开始重新散列,也就是将原来的散列结构全部抛弃,重新开辟一个散列单元大小为32(2的5次幂)的散列结果,并重新计算各个数据的存储位置。以此类推下去…

负载(加载)因子:0.75.–>hash表提供的空间是16 也就是说当到达12的时候就扩容

3.排重机制的实现

假如我们有一个数据(散列码76268),而此时的HashSet有128个散列单元,那么这个数据将有可能插入到数组的第108个链表中(76268%128=108)。但这只是有可能,如果在第108号链表中发现有一个老数据与新数据equals()=true的话,这个新数据将被视为已经加入,而不再重复丢入链表。

4.优点

哈希表的插入和查找是很优秀的.

对于查找:直接根据数据的散列码和散列表的数组大小计算除余后,就得到了所在数组的位置,然后再查找链表中是否有这个数据即可。因为数组本身查找速度快,所以查找的效率高低体现在链表中,但是真实情况下在一条链表中的数据又很少,有的甚至没有,所以几乎没有什么迭代的代价。所以散列表的查找效率建立在散列单元所指向的链表中数据的多少上.

对于插入:数组的插入速度慢,而链表的插入速度快.当我们使用哈希表时,不需要更改数组的结构,只需要在找到对应的数组下标后,进入对应的链表,操作链表即可.所以hash表的整体插入速度也很快.

数组加链表

推荐使用Treemap

HashMap和ConcurrenMap

HashMap是一个散列桶(数组和链表),它存储的内容是键值对(key-value)映射HashMap采用了数组和链表的数据结构,能在查询和修改方便继承了数组的线性查找和链表的寻址修改HashMap是非synchronized,所以HashMap很快HashMap可以接受null键和值,而Hashtable则不能(原因就是equlas()方法需要对象,因为HashMap是后出的API经过处理才可以)

以下是HashMap初始化 ,简单模拟数据结构Node[] table=new Node[16] 散列桶初始化,tableclass Node {hash;//hash值key;//键 value;//值 node next;//用于指向链表的下一层(产生冲突,用拉链法)}

以下是具体的put过程(JDK1.8版)

1、对Key求Hash值,然后再计算下标

2、如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)

3、如果碰撞了,以链表的方式链接到后面

4、如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表

5、如果节点已经存在就替换旧值

6、如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)

以下是具体get过程(考虑特殊情况如果两个键的hashcode相同,你如何获取值对象?)当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

3.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置

​ HashMap和ConcurrentHashMap的区别

首先Map是接口,一般而言concurrentHashMap是线程安全的,具体实现

在1.7采取的segment分段锁,有点类似于16个线程安全的hashtable组合成了一个concurrenthashmap,不同分段操作不需要上锁,同一个分段才需要上锁,读不上锁,写上锁。锁的粒度更加精细,而在1.8中而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,而原有的Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本,反观HashMap是传统Map接口中所使用的实现了,操作速度快,但是线程是不安全的

segment分段锁

1、线程不安全的HashMap
因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap。

2、效率低下的HashTable容器
HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

3、ConcurrentHashMap分段锁技术

ConcurrentHashMap是Java 5中支持高并发、高吞吐量的线程安全HashMap实现。

我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在哪一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

红黑树

红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。

1、每个节点非红即黑

2、根节点总是黑色的

3、如果节点是红色的,则它的子节点必须是黑色的(反之不一定)

4、每个叶子节点都是黑色的空节点(NIL节点)

5、从根节点到叶子节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

大O表达式(计算时间和空间复杂度的表示方法)

数据结构中的一些基本理念,可以自己查找,对了解java底层的集合有好处

线程的概念

程序:可执行的文件

进程:一个正在运行的程序,也可以理解成在内存中开辟了一块空间

线程:负责程序的运行,可以看做一条执行的通道或执行单元,所以我们通常将进程的工作理解成线程的工作.

进程中可不可以没有线程? 必须有线程,至少有一个.当有一个线程的时候我们称为单线程(唯一的线程就是主线程).

当有一个以上的线程同时存在的时候我们称为多线程.

多线程的作用:并发(为了实现同一时间干多件事情).

任务区:我们将线程工作的地方称为任务区.

每一个线程都有一个任务区,任务区通过对应的方法产生作用.

方法的开始就是任务区的开始,方法的结束就是任务区的结束。

线程的开始就是任务区的开始,线程的结束就是任务区的结束。

JVM默认是多线程吗?

至少要有两个线程:

主线程:任务区:main函数

垃圾回收线程:任务区:finalize函数

Thread.currentThread():获取的是当前的线程

Thread.currentThread().getName():线程的名字

主线程的名字:main

子线程的名字默认是:从Thread-0开始命名

创建线程

默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成自己的功能----创建自己的线程对象

java将线程面向对象了,形成的类就是Thread,在Thread类内部执行任务的方法叫run()

注意:如果想让run作为任务区,必须让他去被自动调用.我们通过执行start()方法,来开启线程,继而实现run方法的自动调用.

实现多线程的方式两种:

第一种方式:通过创建Thread子类的方式实现功能----线程与任务绑定在了一起,操作不方法

第二种:将任务从线程中分离出来,哪个线程需要工作,就将任务交给谁,操作方便

1.直接使用Thread创建线程对象

2.通过继承Thread重写run方法。(单个线程绑定一个任务区)

3.需求:多个线程一个任务区

Runnable:只有一个run接口;实现runnable接口,(即任务区和线程分离)

+Tread(不用加继承,参数传递Runablede 类)

外部传入的优先级(run)>Tread 的run优先级。

public class Demo2 {public static void main(String[] args) {//创建自己的线程对象
//      //1.直接使用Thread创建线程对象
//      //分析:由于我们实现的实际功能Thread类是决定不了的,所以没有办法将我们的功能放入Thread的run方法里
//      //所以Thread的run 方法是一个空方法.如果我们想实现自己的功能,可以写Thread类的子类,重写run方法
//      Thread thread1 = new Thread();
//      Thread thread2 = new Thread();
//      thread1.start();
//      thread2.start();//手动调用,不能让run充当任务区
//      thread1.run();
//      thread2.run();//2.使用子类创建对象MyThread t0 = new MyThread("t0");MyThread t1 = new MyThread("t1");t0.start();//开启线程t1.start();//开启线程
//
//      //当我们手动调用run的时候,他失去了任务区的功能,变成了一个普通的方法.
//      //当run作为一个普通方法时,内部对应的线程跟调用他的位置保持一致.
//      t0.run();
//      t1.run();
//      for (int i = 0; i < 10; i++) {System.out.println("  main   "+Thread.currentThread().getName()+"   i:"+i);//有三个线程(暂时忽略垃圾回收线程)}}
}class MyThread extends Thread{String name1;//重写run方法,实现我们的功能.run就是我们的任务区/** Thread.currentThread():获取的是当前的线程* Thread.currentThread().getName():线程的名字*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(this.name1+"  MyThread   "+Thread.currentThread().getName()+"   i:"+i);}}public MyThread(String name1) {this.name1 = name1;}
}
//当线程与任务分离后
//这里Thread内部默认有一个run,又通过ticket传入一个run,为什么优先调用的是传入的run
//如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
public class Demo3 {public static void main(String[] args) {//任务对象Ticket ticket = new Ticket();//将任务与线程绑定Thread t0 = new Thread(ticket);Thread t1 = new Thread(ticket);Thread t2 = new Thread(ticket);Thread t3 = new Thread(ticket);//开启线程t0.start();t1.start();t2.start();t3.start();}
}//任务类class Ticket implements Runnable{int sum = 20;boolean flag = true;public void run() {while (flag) {//让当前的线程睡100毫秒//作用:让他暂时让出cpu try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}if (sum > 0) {System.out.println("剩余 票数:"+ --sum);}else {flag = ! flag;}}}
}

线程安全

线程安全问题:

分析(上面案例,四个售货员卖票的问题):4个线程共用了一个数据,出现了-1,-2,-3等错误的数据

具体分析:1.共用了一个数据

2.共享语句有多条,一个线程使用cpu,没有使用完,cpu被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生.

解决:

在代码中使用同步代码块儿(同步锁)

解释:在某一段任务中,同一时间只允许一个线程执行任务,其他的线程即使抢到了cpu,也无法进入当前的任务区间,只有当当前的线程将任务执行完后,

其他的线程才能有资格进入

同步代码块儿的构成:

synchronized(锁(对象)){

同步的代码

}

对作为锁的对象的要求:1.必须是对象 2.必须保证被多个线程共享

可以充当锁的:1.一个普通的对象 2.当前对象的引用–this 3.类的字节码文件

同步代码块儿的特点:1.可以保证线程的安全 2.由于每次都要进行判断处理,所以降低了执行效率

总结:什么时候使用同步代码块儿

1.多个线程共享一个数据

2.至少有两个线程

public class Demo4 {public static void main(String[] args) {//任务对象Ticket1 ticket = new Ticket1();//将任务与线程绑定Thread t0 = new Thread(ticket);Thread t1 = new Thread(ticket);Thread t2 = new Thread(ticket);Thread t3 = new Thread(ticket);//开启线程t0.start();t1.start();t2.start();t3.start();}
}//任务类class Ticket1 implements Runnable{int sum = 20;boolean flag = true;//让Object类型的对象临时充当锁Object object = new Object();public void run() {while (flag) {//让当前的线程睡100毫秒//作用:让他暂时让出cpu try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}/** 锁的条件:* 1.锁必须是对象      普通的对象/this/字节码文件* 2.要被所有的线程共享* * 注意:字节码文件的使用范围太大,一般不建议使用.*/synchronized (this) {//线程互斥if (sum > 0) {System.out.println("剩余 票数:"+ --sum);}else {flag = ! flag;}}}}
}

锁的共性:1锁必须是对象(普通的对象/this/字节码文件);2要被所有的线程共享(不建议使用字节码文件的使用范围太大)。

比较同步函数和同步代码块

同步代码块儿使用更加的灵活,只给需要同步的部分代码同步即可,而同步函数是给这个函数内的所有代码同步.

由于处于同步的代码越少越好,所以最好使用同步代码块儿

注意:1.当在一个类中同时存在多个synchronized修饰的代码块儿或函数时,要想安全,就必须让他们后面的对象一致。因为只有同一把锁才能安全。

同步默认(规定)函数的锁:this

2静态同步函数在进内存的时候不会创建对象,但是存在其所属类的字节码文件对象,属于class类型的对象,所以静态同步函数的锁是其所属类的字节码文件对象

理解synchronized关键字

1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。
这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问
这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,
表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}
在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

public class Demo5 {public static void main(String[] args) {//1.创建任务类对象SaveMoney saveMoney = new SaveMoney();//2.创建两个线程当做两个人Thread t0 = new Thread(saveMoney);Thread t1 = new Thread(saveMoney);//3.开启线程t0.start();t1.start();}
}class Bank {int money;//使用同步代码块儿
//  public void addMoney(int money) {//      synchronized (Bank.class) {//          this.money += money;
//          System.out.println(this.money);
//      }
//  }//使用同步函数//非静态的同步函数//在synchronized后面默认有一个thispublic synchronized void addMoney(int money) {this.money += money;System.out.println(this.money);}//静态的同步函数//在synchronized后面默认有一个当前类的字节码文件-----Bank.class
//  public synchronized static void addMoney(int money) {//      this.money += money;
//      System.out.println(this.money);
//  }
}class SaveMoney implements Runnable{Bank bank = new Bank();@Overridepublic void run() {for (int i = 0; i < 3; i++) {bank.addMoney(100);}}
}

同一把锁的作用:

package com.qf.test;
/** 两个线程的通信* * 实例:打印机打印----不断输入不断输出* 两个线程:输入线程和输出线程* 两个任务区:输入任务,输出任务* 一份数据*/
public class Demo6 {public static void main(String[] args) {//0.创建数据对象Des des = new Des();//1.创建输入任务对象和输出任务对象Input input = new Input(des);Output output = new Output(des);//2.创建线程Thread in = new Thread(input);Thread out = new Thread(output);//3.开启线程in.start();out.start();}
}//数据类
class Des{String name;String sex;
}//输入任务
class Input implements Runnable{Des des;public Input(Des des) {this.des = des;}public void run() {int i=0;while (true) {/** 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的* 给两个任务加一把锁:可以是des或者Object.class* 分析:* 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.* 使用des最合适,因为他只被当前的两个任务共享.* *注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.*/synchronized (des) {if (i==0) {des.name = "万表哥";des.sex = "男";}else {des.name = "蔡表妹";des.sex = "女";}i=(i+1)%2;}}}
}
//输出任务
class Output implements Runnable{Des des;public Output(Des des) {this.des = des;}public void run() {while (true) {synchronized (des) {System.out.println("姓名:"+des.name+"   性别:"+des.sex);}}}
}

使用唤醒等待机制

wait:必须是在同步环境下使用,wait必须是在锁使用,当执行wait的时候,对应哪个线程,就操作哪个线程。(线程等待,立刻释放cpu,停在wait的那行命令,等待唤醒)wait():让当前的线程变成等待的状态,放入一个池子(线程容器),失去了抢cpu的能力,.等待唤醒(锁相当于给当前的线程做了一个标记)

notify():让当前的线程从等待状态唤醒,相当于从池子中取出线程.(唤醒的是同一把锁下的任意一个线程) 唤醒,允许空唤醒.

notifyAll():唤醒的是同一把锁下的所有线程

public class Demo7 {public static void main(String[] args) {//0.创建数据对象Des1 des = new Des1();//1.创建输入任务对象和输出任务对象Input1 input = new Input1(des);Output1 output = new Output1(des);//2.创建线程Thread in = new Thread(input);Thread out = new Thread(output);//3.开启线程in.start();out.start();}
}//数据类
class Des1{String name;String sex;boolean flag = false;//用于唤醒和等待之间的切换
}//输入任务
class Input1 implements Runnable{Des1 des;public Input1(Des1 des) {this.des = des;}public void run() {int i=0;while (true) {/** 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的* 给两个任务加一把锁:可以是dies或者Object.class* 分析:* 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.* 使用des最合适,因为他只被当前的两个任务共享.* *注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.*/synchronized (des) {if (des.flag == true) {//如果是true,让当前的线程处于等待状态try {des.wait();//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if (i==0) {des.name = "万表哥";des.sex = "男";}else {des.name = "蔡表妹";des.sex = "女";}i=(i+1)%2;des.flag = ! des.flag;des.notify();//唤醒的是同一把锁下的线程,因为现在只有一个输入线程,一个输出线程.所以这里唤醒的是输出线程//当线程池中没有被当前的锁标记的线程可唤醒时,我们成为空唤醒,空唤醒不影响程序的执行.}}}
}
//输出任务
class Output1 implements Runnable{Des1 des;public Output1(Des1 des) {this.des = des;}public void run() {while (true) {synchronized (des) {if (des.flag == false) {//让输出线程等待try {des.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("姓名:"+des.name+"   性别:"+des.sex);des.flag = ! des.flag;des.notify();//唤醒输入线程}}}
}

面向对象思想优化代码(单消费者单生产者)

public class Demo8 {public static void main(String[] args) {//两个线程:输入线程和输出线程//1.准备数据Des2 des = new Des2();//2.准备两个任务Input2 input = new Input2(des);Output2 output = new Output2(des);//3.准备两个线程Thread in = new Thread(input);Thread out = new Thread(output);//4.开启线程in.start();out.start();}
}
//创建数据类
class Des2{String name;String sex;boolean flag;//控制唤醒和等待状态的切换//负责输入public void setData(String name,String sex) {if (flag == true) {//当flag值为true,就让当前的线程处于等待状态try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//当执行这行代码的时候,这里对应的是哪个线程,就操作的是哪个线程}this.name = name;this.sex = sex;flag = !flag;notify();//唤醒的是通一把锁下的线程,因为现在只有一个输入线程,一个输出线程.所以这里唤醒的是输出线程//当线程池中没有被当前的锁标记的线程可唤醒时,我们成为空唤醒,空唤醒不影响程序的执行.}//负责输出public void getData() {if (flag == false) {//让输出线程等待try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println("姓名:"+name+"    性别:"+sex);flag = ! flag;notify();//唤醒的是输入线程}
}//两个任务区:输入任务,输出任务
class Input2 implements Runnable{Des2 des = null;public Input2(Des2 des) {super();this.des = des;}public void run() {int i=0;while (true) {/** 需要给输入任务和输出任务同时加一把锁,保证两个任务之间是同步的* 给两个任务加一把锁:可以是des或者Object.class* 分析:* 不建议使用Object.class:由于Object的使用范围太大,可能造成不必要的错误.* 使用des最合适,因为他只被当前的两个任务共享.* *注意:对于当前的情况只给一个线程加锁,无法实现两个线程的同步.*/synchronized (des) {if (i == 0) {des.setData("万表哥", "男");}else {des.setData("蔡表妹", "女");}i=(i+1)%2;}}}
}class Output2 implements Runnable{Des2 des = null;public Output2(Des2 des) {super();this.des = des;}public void run() {while (true) {synchronized (des) {des.getData();}}}
}
public class Demo9 {public static void main(String[] args) {//准备数据Product product = new Product();//准备任务Producer producer = new Producer(product);Consumer consumer = new Consumer(product);//准备线程Thread proThread = new Thread(producer);Thread conThread = new Thread(consumer);//开启线程proThread.start();conThread.start();    }
}//创建产品
class Product{String name;//产品的名字double price;//产品的价格int count;//生产的产品数量//标识boolean flag = false;//准备生产public synchronized void setProduce(String name,double price){if (flag == true) {try {wait();//让生产线程等待} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}this.name = name;this.price = price;System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);count++;flag = ! flag;notify();//唤醒消费线程}//准备消费public  synchronized void getConsume() {if (flag == false) {try {wait();//让消费线程等待} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);//唤醒生产线程flag = ! flag;notify();}
}
//创建生产任务
class Producer implements Runnable{Product product;public Producer(Product product) {super();this.product = product;}public void run() {while (true) {product.setProduce("bingbing", 1000);}}
}
//创建消费任务
class Consumer implements Runnable{Product product;public Consumer(Product product) {super();this.product = product;}public void run() {while (true) {product.getConsume();}}
}

学习多生产者多消费者

需要的线程:四个—两个生产线程两个消费线程

需要的任务:两个—一个生产任务一个消费任务

需要数据:一份—产品

生产任务与消费任务共用一个数据–产品类

要求:最终也要实现一次生产一次消费
*错误描述:当有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
*原因:当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码
*
*解决办法:将标记处的if改成while
*
*问题描述:继续运行程序,会出现死锁的情况(4个线程同时处于等待状态)
*原因:唤醒的是本方的线程,最后导致所有的线程都处于等待状态.
*
*解决办法:将notify改成notifyAll.保证将对方的线程唤醒
*
*死锁:出现的情况有两种
*1.所有的线程处于等待状态
*2.锁之间进行嵌套调用

public class Demo10 {public static void main(String[] args) {//准备数据Product1 product = new Product1();//准备任务Producer1 producer = new Producer1(product);Consumer1 consumer = new Consumer1(product);//准备线程Thread proThread1 = new Thread(producer);Thread proThread2 = new Thread(producer);Thread conThread1 = new Thread(consumer);Thread conThread2 = new Thread(consumer);//开启线程proThread1.start();conThread1.start(); proThread2.start();conThread2.start();  }
}//创建产品
class Product1{String name;//产品的名字double price;//产品的价格int count;//生产的产品数量//标识boolean flag = false;//准备生产public synchronized void setProduce(String name,double price){while (flag == true) {try {wait();//让生产线程等待} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}this.name = name;this.price = price;System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);count++;flag = ! flag;//notify();//唤醒消费线程notifyAll();}//准备消费public  synchronized void getConsume() {while (flag == false) {try {wait();//让消费线程等待} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);//唤醒生产线程flag = ! flag;//notify();notifyAll();}
}
//创建生产任务
class Producer1 implements Runnable{Product1 product;public Producer1(Product1 product) {super();this.product = product;}public void run() {while (true) {product.setProduce("bingbing", 10);}}
}
//创建消费任务
class Consumer1 implements Runnable{Product1 product;public Consumer1(Product1 product) {super();this.product = product;}public void run() {while (true) {product.getConsume();}}
}

synchronized 隐式锁

lock显示锁

生产者消费者:

研究:研究Lock

比较synchronized和Lock

1.synchronized:从jdk1.0就开始使用的同步方法-称为隐式同步

synchronized(锁对象){//获取锁 我们将锁还可以称为锁旗舰或者监听器

同步的代码

}//释放锁

2.Lock:从jdk1.5开始使用的同步方法-称为显示同步

原理:Lock本身是接口,要通过他的子类创建对象干活儿

使用过程:

首先调用lock()方法获取锁

进行同步的代码块儿

使用unlock()方法释放锁

使用的场景:

当进行多生产者多消费者的功能时,使用Lock,其他的都使用synchronized

使用效率上:Lock高于synchronized

public class Demo11 {public static void main(String[] args) {//准备数据Product2 product = new Product2();//准备任务Producer2 producer = new Producer2(product);Consumer2 consumer = new Consumer2(product);//准备线程Thread proThread1 = new Thread(producer);Thread proThread2 = new Thread(producer);Thread conThread1 = new Thread(consumer);Thread conThread2 = new Thread(consumer);//开启线程proThread1.start();conThread1.start(); proThread2.start();conThread2.start();  }
}//创建产品
class Product2{String name;//产品的名字double price;//产品的价格int count;//生产的产品数量//标识boolean flag = false;//创建锁对象Lock lock = new ReentrantLock();//用于生产任务的ConditionCondition proCon = lock.newCondition();//用于消费任务的ConditionCondition conCon = lock.newCondition();//准备生产public  void setProduce(String name,double price){try {lock.lock();//获取锁while (flag == true) {try {//wait();//让生产线程等待proCon.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}this.name = name;this.price = price;System.out.println(Thread.currentThread().getName()+"   生产了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);count++;flag = ! flag;//notify();//唤醒消费线程//notifyAll();conCon.signal();}finally {lock.unlock();//释放锁}}//准备消费public   void getConsume() {try {lock.lock();while (flag == false) {try {//wait();//让消费线程等待conCon.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"   消费了:"+this.name+"   产品的数量:"+this.count+"   价格:"+this.price);//唤醒生产线程flag = ! flag;//notify();//notifyAll();proCon.signal();}finally {lock.unlock();}}
}
//创建生产任务
class Producer2 implements Runnable{Product2 product;public Producer2(Product2 product) {super();this.product = product;}public void run() {while (true) {product.setProduce("bingbing", 10);}}
}
//创建消费任务
class Consumer2 implements Runnable{Product2 product;public Consumer2(Product2 product) {super();this.product = product;}public void run() {while (true) {product.getConsume();}}
}

线程单例

package com.qf.test;public class Demo13 {public static void main(String[] args) {Test1 test = new Test1();Thread t0 = new Thread(test);Thread t1 = new Thread(test);t0.start();t1.start();}
}class Test1 implements Runnable{public void run() {SingleInstance2 singleInstance2 = SingleInstance2.getInstance();}
}
//饿汉式,由于公共方法中只有一行公共的代码,所以不会产生线程安全问题
class SingleInstance1{private static final SingleInstance1 s = new SingleInstance1();private SingleInstance1() {}public static SingleInstance1 getInstance() {return s;}
}//懒汉式,
class SingleInstance2{private static  SingleInstance2 s = null;private SingleInstance2() {}public  static SingleInstance2 getInstance() {if (s == null) {//尽量减少线程安全代码的判断次数,提高效率synchronized (SingleInstance2.class) {//使用同步代码块儿实现了线程安全if (s == null) {s = new  SingleInstance2();}}}return s;}
}

线程的停止

线程的停止:3种

1.通过一个标识结束线程

2.调用stop方法—因为有固有的安全问题,所以系统不建议使用.

3.调用interrupt方法----如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。

1.通过一个标识结束线程

public class Demo10 {public static void main(String[] args) {MyTest myTest = new MyTest();Thread thread  = new Thread(myTest);thread.start();try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}int i =0;while (true) {if (++i == 10) {//当i==10的时候,我就让子线程结束,让flag=falsemyTest.flag = false;break;//主线程结束}}}
}class MyTest implements Runnable{boolean flag = true;public void run() {while (flag) {System.out.println(Thread.currentThread().getName()+"   明天吃西瓜");}    }
}

3.调用interrupt方法----如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。

:3种

public class Demo14 {public static void main(String[] args) {MyTest myTest = new MyTest();Thread thread  = new Thread(myTest);thread.start();try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}int i =0;while (true) {if (++i == 10) {//当i==10的时候,我就让子线程结束,直接调用interrupt方法thread.interrupt();break;//主线程结束}}}
}class MyTest implements Runnable{boolean flag = true;public synchronized void run() {while (flag) {try {this.wait();} catch (InterruptedException e) {flag = false;}System.out.println(Thread.currentThread().getName()+"   明天吃西瓜");} }
}

守护线程

JVM默认是多线程吗?

至少要有两个线程:

主线程:任务区:main函数

垃圾回收线程:任务区:finalize函数

垃圾回收就是典型的守护线程。任务区finalize方法

程序结束(例主线程结束),守护线程也停止

public class Demo15 {public static void main(String[] args) {MyTest1 myTest1 = new MyTest1();Thread t0 = new Thread(myTest1);/** 当程序调用setDaemon方法时,并且将参数设置成true.当前线程就变成了守护线层.* 注意:这个方法一定要在start方法之前调用*/t0.setDaemon(true);t0.start();int i=0;while (true) {if (++i == 10) {System.out.println(Thread.currentThread().getName()+"   主线程");break;}}}
}class MyTest1 implements Runnable{public void run() {while (true) {System.out.println(Thread.currentThread().getName()+"   子线程");}}
}

join()方法:

原理:线程一旦调用了join方法,他的优先级会高于主线程.主线程会等当前的线程执行完后再去执行.

注意点:优先级只比main线程的高.对其他的线程没有影响.

public class Demo16 {public static void main(String[] args) {MyTest2 myTest1 = new MyTest2();Thread t0 = new Thread(myTest1);Thread t1 = new Thread(myTest1);t0.start();t1.start();/** 当线程开始工作后,让t0调用join方法,让他的优先级高于main线程* 注意:join方法必须在线程开始工作后,执行.*/try {t0.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"   主线程"+i);}}
}class MyTest2 implements Runnable{public void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"   子线程"+i);}}
}

java Application 的若干类中(),有且仅有一个主类,只有主类中含有主方法main()

io流(stream):作用:实现俩个设备之间数据的传递。

设备:磁盘(硬盘),内存,键盘,文件,网络,控制台

网络:当前主机之外的网上资源

分类:
根据操作的方式:输入流和输出流。

根据数据的类型:字节流和字符流。

(io流) 内存出(o) 从内存进(i)

磁盘 ----------------(io流以内存为中心)内存 ------------------ cpu:
内存(短期存储),磁盘(长期存储),存储数据

Tcp (类似于流)面向连接的 Udp(类似于包)面向无连接的,丢包。UDP速度快(传输小文件,速度快),Tcp 速度慢(点对点通信)。

字节流:传输的是字节,可以操作任意类型的数据 ------音频,视频,文件,图片等

字符流:传输的字节,不同点是在传输过程中加入了编码的操作。----文本

字节输入流:inputStrem

字节输出流outputStrem

字符输入流:Reader

字符输出流:writer(从内存写出)

实例:研究字符流

以磁盘的数据存储为例,将数据写入文件

分析:因为操作的是文本,所以使用字符流

写入文件-----写出流------FileWriter

fileinputStream:

fileoutputStream:

FileWrite:

1.创建对象,绑定文件。(目录写对。FileNotFountException(默认异常,编译时需要处理)IoException io流异常总类。 )

注意点:
一:如果只写文件的名字,不写具体路径,默认路径是当前的工程
二:对于关联的文件,如果之前不存在,程序会自动创建一个,如果存在,会将原来的内容覆盖
三:可以自己指定路径,但是必须保证路径是真实存在的,否则报异常—FileNotFountException(系统找不到指定的路径。)

2.写
注意点四:在执行write方法时,数据被临时放到了流对象的内部数组中,这个数组是一个字节数组,会默认去查编码表

写 .write()默认写不到文件,先写到缓存(字符集->二进制 .flush() 刷新 (从缓存到磁盘)) fileWriter.write(“chenchen”);
3.刷新—将临时数组中的数据放入磁盘
fileWriter.flush();
4.关闭流–两个功能:a关闭流 b:刷新
第五个注意点:流对象使用完后必须关闭
fileWriter.close();
第六个注意点:当流对象关闭之后,不能再进行操作,否则会报异常:Stream closed
fileWriter.write(“haha”);

文件的续写:

FileWrite(String file,boolean value)

当value 为true的时候,不会将文件覆盖。会接着写。

public class Demo2 {public static void main(String[] args) {//1.FileWriter fileWriter = null;try {fileWriter = new FileWriter("test2.txt",true);fileWriter.write("haha");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if (fileWriter != null) {try {fileWriter.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}
}

FileReader:

1.创建FileReader的对象
FileReader fileReader = new FileReader(“test2.txt”);
2.读
read():一个字符一个字符的读,每次读出一个字符
read(数组):一次可以读出多个字符 ,数组的作用:每次会将读出的字符临时放到这个数组中
read():一个字符一个字符的读,每次读出一个字符

public class Demo3 {public static void main(String[] args) throws IOException {//1.创建FileReader的对象FileReader fileReader = new FileReader("test2.txt");//2.读/** read():一个字符一个字符的读,每次读出一个字符* read(数组):一次可以读出多个字符   ,数组的作用:每次会将读出的字符临时放到这个数组中*///read():一个字符一个字符的读,每次读出一个字符int  num = 0;
//      num = fileReader.read();//返回的是ASCII码
//      System.out.println((char)num);
//      num = fileReader.read();
//      System.out.println((char)num);
//      num = fileReader.read();
//      System.out.println((char)num);
//      num = fileReader.read();
//      System.out.println((char)num);
//      num = fileReader.read();
//      System.out.println((char)num);
//      num = fileReader.read();
//      System.out.println(num);while ((num = fileReader.read())!= -1) {System.out.println((char)num);}//3.关闭资源fileReader.close();}
}

创建对象,绑定文件.read():一个字符一个字符的读,每次读出一个字符(返回是字符,指针加一,读到最后的时候放返回-1;默认读到是Acsall码);

读的是一个字符(!字节)(包括汉字,也是一个一个读的)

//经典写法

while ((num = fileReader.read())!= -1) {
System.out.println((char)num);
}

read(数组):一次可以读出多个字符 ,数组的作用:每次会将读出的字符临时放到这个数组中

read(数组):一次可以读出多个字符 ,数组的作用:每次会将读出的字符临时放到这个数组中
数组是临时存放数据的地方,我们会将读到的字符放到临时数组中,数组的大小决定了我们一次可以读到的字符个数.

一般这个数组的大小<=1kB

返回值代表本次读到的真实的字符个数,如果返回值是-1代表读完了.

​ char[] arr = new char[2];
​ int num = 0;
// num = fileReader.read(arr);
// System.out.println(Arrays.toString(arr)+" num:"+num);
// num = fileReader.read(arr);
// System.out.println(Arrays.toString(arr)+" num:"+num);
// num = fileReader.read(arr);
// System.out.println(new String(arr,0,num)+" num:"+num);
// num = fileReader.read(arr);
// System.out.println(Arrays.toString(arr)+" num:"+num);

​ while ((num = fileReader.read(arr)) != -1) {
​ System.out.println(new String(arr,0,num)+" num:"+num);
​ }
​ fileReader.close();
​ }

相对路径:从路径中间的某个部位开始一直到当前的文件

绝对路径:一个文件的完整路径,从根目录开始到当前的文件

补充知识:

\代表转义字符 \t:制表符 \n换行符 \:表示普通的\

在代表路径的时候,\与/是一个意思.

1.创建读入流和写出流

字符缓冲流

(可以将缓冲流看做现小车或催化剂)

字符缓冲流(字符缓冲区)

定义:为了提高读写的能力,本身没有读写的能力,要想进行读写就必须借助于字符流实现.

可以将缓冲流类比于催化剂或者高速的小车

BufferedReader 没有读的能力

BufferedWriter 没有写的能力

public class Demo6 {public static void main(String[] args) throws IOException {//0.创建字符写出流对象//1.创建字符缓冲写出流对象BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("text3.txt"));//2.写bufferedWriter.write("bingbing");//windows系统   换行符   \r\n    linux/unix  换行符   \n//bufferedWriter.write("\r\n");//换行bufferedWriter.newLine();//换行---支持跨平台bufferedWriter.write("chenchen");//3.关闭资源 a.关闭内部的写出流     b.关闭自己   c.刷新bufferedWriter.close();}
}

字符缓冲流换行

windows系统 换行符 \r\n linux/unix 换行符 \n

bufferedWriter.write("\r\n");//换行

bufferedWriter.newLine();//换行—支持跨平台

字符缓冲流读行

一次读一行 readline()
原理:一个字符一个字符的读,直到读到换行符为止.然后将所有读到的字符返回
注意点:不会将当前的换行符返回 ;返回值就是我们读到的内容.如果读完了,返回null

public class Demo7 {public static void main(String[] args) throws IOException {//1.创建字符缓冲读入流BufferedReader bufferedReader = new BufferedReader(new FileReader("text3.txt"));//读//一次读一个字符int num = 0;
//      while ((num = bufferedReader.read()) != -1) {//          System.out.print((char)num);
//      }//一次读多个字符
//      char[] arr = new char[5];
//      while ((num = bufferedReader.read(arr)) != -1) {//          System.out.print(new String(arr,0,num));
//      }//一次读一行  readline()//原理:一个字符一个字符的读,直到读到换行符为止.然后将所有读到的字符返回//注意点:不会将当前的换行符返回 ;返回值就是我们读到的内容.如果读完了,返回nullString data = null;while ((data = bufferedReader.readLine()) != null) {System.out.print(data);//System.out.println();//换行System.out.print("\r\n");}bufferedReader.close();}
}

字符流读行典型

while ((data = bufferedReader.readLine()) != null) {
System.out.print(data);
//System.out.println();//换行
System.out.print("\r\n");
}

java从入门到放弃(二)相关推荐

  1. Java从入门到放弃-序言

    Java从入门到放弃 前言 本人希望由浅及深的探讨java的底层原理,和编程思想,与大家一起学习提升对程序语言的认知.由于自己是理工科出身,所以对底层原理往往非常感兴趣.那么就跟我一起学习Java吧. ...

  2. java从入门到精通二十四(三层架构完成增删改查)

    java从入门到精通二十四(三层架构完成增删改查) 前言 环境准备 创建web项目结构 导入依赖和配置文件 创建层次模型 实现查询 实现添加 实现修改 完成删除 做一个用户登录验证 会话技术 cook ...

  3. java从入门到精通二十三(Servlet)

    java从入门到精通二十三(Servlet) Servlet 说明 Servlet初步入门尝试 Servlet生命周期 Servlet方法说明和体系结构 方法说明 体系结构说明 一些优化封装 urlP ...

  4. Java从入门到放弃09---多态/向上转型/向下转型/多态内存图/抽象类/关键字abstract不能和哪些关键字共存/接口/类与类,类与接口,接口与接口的关系/抽象类与接口的区别

    Java从入门到放弃09-多态/向上转型/向下转型/多态内存图/抽象类/关键字abstract不能和哪些关键字共存/接口/类与类,类与接口,接口与接口的关系/抽象类与接口的区别 01 多态 多态指的是 ...

  5. 《Java从入门到放弃》框架入门篇:hibernate中的多表对应关系(二)

    前一篇讲完了一对多的关系,通过与JDBC对比应该能发现,是不是比JDBC简单了很多? 我们只需要把对象只间的包含或对应关系理清楚,完全不用我们自己来写SQL语句.所以使用hibernate框架后,我们 ...

  6. 《Java从入门到放弃》JavaSE入门篇:面向对象语法二(入门版)

    想了半天,发现单独的封装和多态没什么好讲的,我们就简单说说Java里面对应的语法吧. 相关内容如下: 一.访问修饰符 二.getter/setter方法 三.构造方法 四.super和this 五.s ...

  7. 面向对象java语法_《Java从入门到放弃》JavaSE入门篇:面向对象语法二(入门版)...

    想了半天,发现单独的封装和多态没什么好讲的,我们就简单说说Java里面对应的语法吧. 相关内容如下: 一.访问修饰符 二.getter/setter方法 三.构造方法 四.super和this 五.s ...

  8. 《Java从入门到放弃》JavaSE入门篇:文件操作

    Java中的文件操作还有点小复杂··· 不过没关系,我会把它讲得很简单,嘿嘿嘿!!! 在讲Java中的文件操作前,先了解一个概念--"流",比如我们把一个杯子的水倒到另一个同样大小 ...

  9. javascript java html_JS入门篇(二):在html中如何使用Javascript

    原标题:JS入门篇(二):在html中如何使用Javascript (1)java的使用 HTML 中的脚本必须位于 <> 与 > 标签之间.脚本可被放置在 HTML 页面的 和 部 ...

最新文章

  1. VS2010 MFC中 单独添加ODBC数据库记录集类(CRecordset)方法
  2. 二数和,三数和,四数和的优化总结
  3. java aop 切面判断对象是否是一个类的实例
  4. 通用权限管理系统组件 (GPM - General Permissions Manager) 给信息管理系统加一个初始化的功能,调用存储过程...
  5. jupyter问题: failed to create cublas handle: CUBLAS_STATUS_ALLOC_FAILED
  6. itextpdf 实现html转pdf中中文及图片base64的解决方法
  7. 机器学习中的损失函数(交叉熵损失、Hinge loss)
  8. nginx 端口转发_Knative Service 是如何指定端口和协议的
  9. 只用两行代码,我让Transformer推理加速了10倍
  10. hashmap相同的key会覆盖吗_【简单了解系列】从基础的使用来深挖HashMap
  11. [bzoj2594][Wc2006]水管局长数据加强版
  12. Android, App常用图标尺寸规范
  13. 用画图工具制作背景透明的图片总结
  14. 四月一个晴朗的早晨,遇见一个百分之百的女孩
  15. iOS-关于微信支付
  16. 【空间转录组】MIA分析
  17. 安徽计算机在职研究生学校,安徽在职研究生可以考哪几个学校
  18. SWE_Browser编译
  19. 劳动合同的主要内容:劳动合同必备条款(工作时间 、年休假)、劳动报酬 (劳动报酬与支付)
  20. 打表+dp思维+博弈

热门文章

  1. 给h264帧增加start code和sps/pps
  2. Python 中MNE库去伪迹(ICA)案例的逐句解析
  3. 本周AI热点回顾:何恺明RegNet超越EfficientNet、数学难题“abc猜想”封印终被开启、微软麻将 AI 论文发布
  4. Linux的虚拟内存
  5. 有用的Linux操作系统的基础命令总结
  6. 重磅!欧盟委员会正式提议:禁止强迫劳动产品进入欧洲市场
  7. 关于xamarin汉字转换成拼音
  8. 3Q中的AQ是什么意思?
  9. css3实现各种角度的三角形
  10. 睢宁县微服务平台_微服务(Weifuwu)国内微信公众服务平台