java基础第二周

第二周笔记

day01

Date(Demo06)

  1. Data: 数据类,装的是二进制数据(和Date区分开)

  2. Date: 日期类

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

  4. 数组转过来的集合长度是固定的,所以不能执行增加,删除,但是可以执行修改,更改,遍历

  5. 二分查找: Arrays.binarySearch(arr,a);

  6. 日历类:

    //日历类
    		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);
    		
    		//获取属性的最大值,最小值
    		int value1 = calendar.getMaximum(Calendar.YEAR);
    		int value2 = calendar.getMinimum(Calendar.YEAR);
    		System.out.println(value1+"    "+value2);
    

String

  • 字符串的基础
  1. java将字符串相关的功能面向对象。形成了对象的类–字符串类
  2. 字符串不是对象(没new ),但是把他当作对象来用,来提高使用效率
  3. 常量存在常量区里面,堆区里面有个特殊常量区(专门存放字符串)
  4. String默认将Object中的.equals重写,比较的时候取new出的内存里面的内容,
  5. -------字符串的比较用.equal方法去比较---------
  6. s1=1000; s2=1000; s3= new String(“1000”); s4= new String(“1000”); s1=s2; true(地址相同) s1=s3; flase s1.equals(s3) ; true (比较内容) s3=s4; false (地址不同)
  • String的常用方法

    String s = “1000phone”;

    1. 是否包含字符串(s.contains(“1000”));

    2. 字符串是否相同(s.equals(“1000”));

    3. 忽略大小写判断字符串的内容是否相同(s.equalsIgnoreCase(“1000”));

    4. 判断是否以某字符串开头(s.startWith(“1000”));

    5. 判断以某字符串结尾(s.endsWith(“phone”));

    6. 将字符串数组转换成字符串

               char[] arr = {'p','h','o','n','e'};
      		String string1 = new String(arr);
      		System.out.println(string1);//phone
      
    7. 将字符串转换为字符数组

             String s= "1000phone";
    		char[] arr =s.toCharArray();
    		System.out.println(arr);
    
    1. 将字节数组转成字符串
            byte[] bytes = {97,98,99,100};
    		String string3 = new String(bytes);
    		System.out.println(string3);
    
    1. 将字符串转成字节数组

             String s="abbcc";
             byte[] bytes1 = s.getBytes();
      		System.out.println(bytes1[0]);//97
      
    2. 替换

              String   s = "1000phone";
             //String replace(char oldChar, char newChar) 
      		String string5 = s.replace("1000", "haha");
      		System.out.println(string5+"    s:"+s);
      
      
    3. 截取字符串

      //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);
      
    4. 大小写转换

              //String toLowerCase()   转成小写
      	    //String toUpperCase()   转成大写
      
    5. 去除空格,只能将字符串俩边的空格去掉

               String string7 = "    1000  phone     ";
      		System.out.println(string7.trim()+"haha");
      
    6. 按字典顺序比较两个字符串

              String   s = "1000phone";
              int value = s.compareTo("1000PHone");
      		System.out.println(value);
      
    7. 切割(//被作为刀的子字符串不会再被作为内容.)

      	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]);
      		}
      

  • 不可变字符串(string)的常用方法(前面finfal修饰)

  1. 字符串本身[例如:(wq11)]不能发生改变,与指向字符串的引用无关 String a1=“wq11”;
  • 可变字符串的常用方法
  1. StringBuilder(线程不安全)jdk1.5开启的 / StringBuffer.(线程安全)jdk1.0开启的 字符串本身可以发生变化, StringBuffer sbuffer=new StringBuffer(“wq11”);
  2. 在不考虑线程安全问题时,尽量使用StringBuilder,因为速度快,效率高
  3. 存储:StringBuffer append(boolean b) 从最后插入
  4. StringBuffer insert(int offset, boolean b) 从指定位置插入
  5. 删除:
    StringBuffer delete(int start, int end) 删除一部分字符串
    StringBuffer deleteCharAt(int index) 删除一个字符
  6. 修改:
    StringBuffer replace(int start, int end, String str) 替换指定的子字符串
  7. 获取:
    char charAt(int index)

正则表达式(Demo11)

验证qq号 qq.matches

  1. :转义字符
  2. \d:表示数字
  3. {4,12}表示位数最少4位,最多12位
  4. []:表示限定某一位

网址(Demo04)

  1. 网址: 协议+域名(ip地址)+端口+资源在服务器上的路径+查询条件
  2. 功能:实现客户端和服务器端的通信
  3. 协议:是制定一个统一的规范
  4. 服务器:中转站,数据处理,数据存储

[外链图片转存失败(img-W0ioO6d3-1567949517211)(E:\web开发项目文件\Typora\1564385721379.png)]

[外链图片转存失败(img-KC9cSJZP-1567949517213)(C:\Users\12811\AppData\Local\Temp\1564385910017.png)]

day02

一般类中代码执行顺序如下:

  • ​ 静态成员变量 > 实例成员变量 > 构造代码块 > 构造方法 ;
  • ​ 静态代码块 > 构造代码块 > 构造方法;
  • ​ 静态成员变量和静态代码块在类初始化阶段被初始化,且只初始化一次,两者顺 序按照代码从上到下执行;

静态代码块(父) > 静态代码块(子) > 实例成员变量(父) > 构造代码块(父) > 构造方法(父) > 实例成员变量(子) > 构造代码块(子) > 构造方法(子)

lambda表达式

  1. lambda 是只有一个抽象方法的接口,是简单的匿名内部类,是java8中的新特性
  2. 使得java可以函数式编程,在并发性能上迈出了实质性的一步
  3. 基本语法:(参数)->表达式 或 (参数)->{方法体} 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(被作为常量,类似于局部变量在局部内部类中使用的情况)
  4. 比较lambda表达式和匿名内部类:
    总结:lambda表达式就是简单的匿名内部类
    1.匿名内部类可以为任意接口创建实例,不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法
    即可; 但Lambda表达式只能为函数式接口创建实例(即只能有一个抽象方法)
    2.匿名内部类可以为抽象类甚至是普通类创建实例;但Lambda表达式只能为函数式接口创建实例
    3.匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认(default)方法;但Lambda表达式的代码块
    不允许调用接口中的默认(default)方法

集合

  • 区分集合和数组

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

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

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

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

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

  • 集合分类
Collection:—接口
List—接口:有序的,可重复的

​ ArrayList–类:底层的数据结构是数组,线程不安全的
Vector —类:底层的数据结构是数组,线程安全的
LinkedList–类:底层是链表,线程不安全的

​ 实现去重的方法是list.contains()来实现 ,不包含则添加,包含则不添加

Set—接口 :不可重复的,无序的

​ HashSet–类:底层是哈希表,线程不安全的,不可排序,可以去重
TreeSet–类:底层是二叉树,线程不安全的,可以***实现***排序和去重

遍历set集合

//Set keySet() 遍历方法一

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

public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		//1.增加
	    map.put("01", "java");
		map.put("02", "html");
		map.put("05", "iOS");
		map.put("03", "BigData");
		map.put("04", "iOS");
		//方法一
		test1(map);
		//方法二
		test2(map);
		
		
	}
//方法一
	public static void test1(Map<String, String> map) {
		//第一步:先得到装着key的set
		Set<String> set = map.keySet();
		//第二步:遍历set,得到key,再根据key获取value
		Iterator<String> iterator = set.iterator();
		while (iterator.hasNext()) {
			String key = iterator.next();
			System.out.println("key:"+key+"   value:"+map.get(key));
		}
	}

//Set<Map.Entry<K,V>> entrySet() 遍历方法二
//原理:先得到所有的entry,放入一个Set中,利用Set的迭代器进行遍历得到entry实体,在利用entry的方法获取key和value

//方法二
	public static void test2(Map<String, String> map) {
		//第一步:先得到装着Entry实体的set
		Set<Map.Entry<String,String>> set =  map.entrySet();
		//第二步:遍历set,得到entry实体,再调用entry实体对象的方法获取key和value
		Iterator<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());
		}
Map:–接口

​ HashMap–类 底层是哈希表,线程不安全
TreeMap–类 底层是二叉树,线程不安全

TreeMap的注意点:

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

  a:实现了Comparable接口的compareTo()方法   b:实现了Comparator接口的compare()方法
 可以的代表:String,包装类,自定义的实现了要求的类
 不可以的代表:数组,ArrayList,LinkedList(如果给他们建立的比较器也可以比较,但是不建议使用)
  1. 存储的键值对,key 唯一,value不唯一

  2. 增加: put

  3. 遍历方法一:keySet(),

  4. 遍历方法二:entrySet(),entry是一个接口,先将键值对放入entry对象中,再调用set进行遍历,得到entry实体,再调用entry实体对象获取key和value,

  5. entry接口作为map接口的内接口,而且是静态的

  6. HashMap去重的时候是对key进行操作的

  7. HashMap的底层实现:HashMap里面实现一个静态内部类Entry,其重要的属性有 hash,key,value,next。

  8. HashMap与HashTable的区别:HashTable线程安全,key值不能为空,HashMap线程不安全,key值可为空

  9. 可变参数(demo6):本身是一个数组,参数的个数可以可变 , 构成:数据类型+… 可变参数的特点:

  10. 增强for循环:for(元素:数组/Collectiom){ 内容 }

  11. 常用的判断

    	//boolean isEmpty()  //空map!=null
    	//boolean containsKey(K key) 是否包含当前的key
    	//boolean containsValue(V value) 是否包含当前的value
    
  • Collection接口
  1. ArrayList的父类是abstractList

  2. ArrayList通过迭代器进行遍历,Iterator iterator() //获取集合中的对象

  • 迭代器

Iterator:叫迭代器

hasnext():判断当前位置是否有值,有返回true,没有false

next():取出当前位置的值,并将指针指向下一个位置

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);
			}
			
		}
	}
}
Collection:
  • List:存储的数据是有序的(元素的存储顺序与添加元素的顺序一致),可以重复的.

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

  • Set:存储的数据是无序的,不可以重复
  1. vector底层遍历使用的是枚举器
        Vector vector = new Vector<>();
        vector.add("java");
		vector.add("html");
		vector.add("hadoop");
		//遍历
		//获取一个枚举器
		Enumeration enumeration = vector.elements();
		while (enumeration.hasMoreElements()) {
			Object object = (Object) enumeration.nextElement();
			System.out.println(object);
		}
	}

LinkedList特有的方法:(Demo04)

​ addFirst()//始终在首位添加
addLast()//始终在末尾添加

​ getFirst()//获取的对象不存在会发生异常 getLast()

​ removeFirst()//删除的对象不存在会发生异常 removeLast()

作业:用LinkedList模拟一下栈和队列

/**
 * addFirst   堆栈   先进后出
 * addLast   队列 先进先出
 * @author 12811
 *
 */
public class Tools {

    LinkedList<String> ll = new LinkedList<String>();
	
	
	public void add(String str){
		ll.addLast(str);
	}
	public void remove(){
		for (int i = 0; i < ll.size(); i++) {
			String content = ll.get(i);
			System.out.println(content);
		}
	}
	
}
public class 堆栈队列 {
/**
 * 使用LinkedList模拟一个堆栈或者队列数据结构
 * 堆栈:先进后出
 * 队列:先进先出
 * 用LinkedList中的addFirst(),addLast()方法
 */
	public static void main(String[] args) {
		Tools tools = new Tools();
		tools.add("a");
		tools.add("b");
		tools.add("c");
		tools.add("d");
		
		tools.remove();
	}
	
}
TreeSet的排序去重方法
  1. TreeSet的add方法实现的排序,去重.通过调用元素的compareTo方法
    String类已经实现了Comparable接口
  2. 使用实现了Comparator接口的compare()方法的比较器对象进行比较
比较器

treeSet里面有比较器这个成员对象

//创建一个比较器类
class ComStrWithLength implements Comparator{

	@Override
	public int compare(Object o1, Object o2) {
		//比较字符串的长度
		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;
	}
	
}
public static void main(String[] args) {
		//创建比较器对象
		ComStrWithLength comStrWithLength = new  ComStrWithLength();
		
		//将比较器对象交给TreeSet
		Set 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);
	}

泛型

​ 泛型:通过<数据类型>接收一种数据类型,在编译的时候会使用这种数据类型检测集合中的元素,如果元素不是<>中规定的类型,就不允许添加到当前的集合中(编译失败)

泛型作用:

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

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

泛型可以修饰的地方: 类, 方法, 接口
泛型应用在方法上

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

​ 2.方法上独立使用泛型 注意:泛型在使用之前一定要先进行定义
定义的方式:在当前方法的最前面添加<泛型>
作用:让方法与方法内的泛型保持一致

​ 3.静态方法上使用泛型 必须独立使用
方式:在static 后面定义泛型 <泛型>

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) {
		
	}
}

泛型应用在接口上(Demo13)

/*
 * 泛型应用在接口上
 *   我们主要研究的是实现接口的子类上的泛型是如何使用的
 */
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>{
	@Override
	public void show(E e) {
		System.out.println(e);
	}
}

//2.接口上使用泛型,子类上不用泛型
/*在实现的接口位置必须指定一个具体的泛型
 * 
 * 方法使用泛型的情况:
 * 1.如果是重写的方法,泛型与接口一致
 * 2.如果是子类自己的方法,可以与接口一致,也可以有自己的泛型
 */
class Bird  implements Inte<String>{
	public void show(String e) {};
}
泛型应用在类上

​ 方式:在类的后面直接添加,E代表任意一种数据类型,注意:这里不一定是E,任意字母都可以

  • 这就是在类上使用泛型
  • 在类上确定的泛型可以直接在方法上使用
通配符

​ ?:通配符,可以表示一种或几种数据类型

  • 限制上限:<? extends E>:限制的是整个的<>可以取的泛型类型的上限是E,<>中可以取的类型是E及E的子类
  • 限制下限:<? super E>::限制的是整个的<>可以取的泛型类型的下限是E,<>中可以取的类型是E及E的父类

day03

map部分写在day02

数据结构讲解(浅过)

节点:一个节点包含俩部分:数据域和指针域,数据域内部放的是数据,指针域内部放的是下一个节点的地址

1.为什么用HashMap?

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

2.HashMap的工作原理是什么?

HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,计算并返回的hashCode是用于找到Map数组的bucket位置来储存Node 对象。这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,作为Map.Node 。

[外链图片转存失败(img-soaTVgDr-1567949517214)(E:/%E5%8D%83%E5%B3%B0%E5%9F%B9%E8%AE%AD/week2-03%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/hashMap%E5%92%8CConcurrentMap/HashMap%E5%92%8CConcurrentMap.assets/%E5%9B%BE1.png)]

以下是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()方法去找到链表中正确的节点,最终找到要找的值对象。

[外链图片转存失败(img-hHJgEhcg-1567949517214)(E:/%E5%8D%83%E5%B3%B0%E5%9F%B9%E8%AE%AD/week2-03%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/hashMap%E5%92%8CConcurrentMap/HashMap%E5%92%8CConcurrentMap.assets/%E5%9B%BE2.png)]

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

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

4. HashMap和ConcurrentHashMap的区别

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

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

5. 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全局信息的时候,就需要获取所有的分段锁才能统计。

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

6.红黑树

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

[外链图片转存失败(img-GBmeTbge-1567949517216)(E:/%E5%8D%83%E5%B3%B0%E5%9F%B9%E8%AE%AD/week2-03%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/%E8%B5%84%E6%96%99/hashMap%E5%92%8CConcurrentMap/HashMap%E5%92%8CConcurrentMap.assets/%E5%9B%BE3.png)]

[外链图片转存失败(img-fqm8EyHq-1567949517216)(C:\Users\bihai\Desktop\BigData1924\day08\资料\hashMap和ConcurrentMap\HashMap和ConcurrentMap.assets\临时1.png)]

1、每个节点非红即黑

2、根节点总是黑色的

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

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

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

7.大 O 表示法

什么是大 O 表示法?
我们常常会听到有人说,“这个算法在最糟情况下的运行时间是 O(n^2) 而且占用了 O(n) 大小的空间”,他的意思是这个算法有点慢,不过没占多大空间。
这里的 O(n^2)O(n) 就是我们通常用来描述算法复杂度的大 O 表示法。
大 O 表示法能让你对一个算法的运行时间占用空间有个大概概念。

大 O 表示法怎么看?怎么用?
假设一个算法的时间复杂度是 O(n),n 在这里代表的意思就是数据的个数。举个例子,如果你的代码用一个循环遍历 100 个元素,那么这个算法就是 O(n),n 为 100,所以这里的算法在执行时就要做 100 次工作。

大O符号是关于一个算法的最坏情况的。比如说,你要从一个大小为 100 的数组(数组中存的都是数字)中找出一个数值等于 10 的元素,我们可以从头到尾扫描一遍,这个复杂度就是 O(n),这里 n 等于 100,实际上呢,有可能第 1 次就找到了,也有可能是第 100 次才找到,但是大 O 表示法考虑的是最坏的情况,也就是一个算法理论上要执行多久才能覆盖所有的情况。

常见的时间复杂度有:
常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3),…, k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。

说明:

大部分情况下你用直觉就可以知道一个算法的大 O 表示法

大 O 表示法只是一种估算,当数据量大的时候才有用

这种东西仅仅在比较两种算法哪种更好的时候才有点用。但归根结底,你还是要实际测试之后才能得出结论。而且如果数据量相对较小,哪怕算法比较慢,在实际使用也不会造成太大的问题。

要知道一个算法的大 O 表示法通常要通过数学分析。在这里我们不会涉及具体的数学,不过知道不同的值意味着什么会很有用。所以这里有一张方便的表。n 在这里代表的意思是数据的个数。举个例子,当对一个有 100 个元素的数组进行排序时,n = 100

Big-O名字描述
O(1)常数级最好的。不论输入数据量有多大,这个算法的运行时间总是一样的。例子: 基于索引取出数组中对应的元素。
O(log n)对数级相当好。这种算法每次循环时会把需要处理的数据量减半。如果你有 100 个元素,则只需要七步就可以找到答案。1000 个元素只要十步。100,0000 元素只要二十步。即便数据量很大这种算法也非常快。例子:二分查找。
O(n)线性级还不错。如果你有 100 个元素,这种算法就要做 100 次工作。数据量翻倍那么运行时间也翻倍。例子:线性查找。
O(n log n)线性对数级还可以。比线性级差了一些,不过也没那么差劲。例子:最快的通用排序算法。
O(n^2)二次方级有点慢。如果你有 100 个元素,这种算法需要做 100^2 = 10000 次工作。数据量 x 2 会导致运行时间 x 4 (因为 2 的 2 次方等于 4)。例子:循环套循环的算法,比如插入排序。
O(n^3)三次方级特别慢。如果你有 100 个元素,那么这种算法就要做 100^3 = 100,0000 次工作。数据量 x 2 会导致运行时间 x 8。例子:矩阵乘法。
O(2^n)指数级超级慢。这种算法你要想方设法避免,但有时候你就是没得选。加一点点数据就会把运行时间成倍的加长。例子:旅行商问题。
O(n!)阶乘级比蜗牛还慢!不管干什么都要跑个 N 年才能得到结果。

大部分情况下你用直觉就可以知道一个算法的大 O 表示法。比如说,如果你的代码用一个循环遍历你输入的每个元素,那么这个算法就是 O(n)。如果是循环套循环,那就是 O(n^2)。如果 3 个循环套在一起就是 O(n^3),以此类推。

注意,大 O 表示法只是一种估算,当数据量大的时候才有用。

例如冒泡排序:

假设需要比较的数组中有N个元素,也就意味着外层循环会执行N次即O(n),而内层循环也是要执行N,所以此时冒泡排序的O(n^2)

最好情况下的时间复杂度:如果元素本来就是有序的,那么一趟冒泡排序既可以完成排序工作,比较和移动元素的次数分别是n-1和0,因此最好情况的时间复杂度为O(n)。

最差情况的时间复杂度:如果数据元素本来就是逆序的,进行n-1趟排序,所需比较和移动次数分别为n(n-1)/2和3n(n-1)/2。因此最坏情况子下的时间复杂度为O(n^2)。

day04

多线程

程序:一个可执行的文件

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

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

多线程的作用:同一时间干多件事情

为什么调用start(),来开启线程? 因为cpu有随机性(不可控)

通过start()开启线程的时候JVM自动的调用了run()方法,

this只能在非静态方法中使用

wait(demo07)

创建线程对象
  • 默认情况下,主线程和垃圾回收线程都是由系统创建的,但是我们需要完成自己的功能----创建自己的线程对象
  • java将线程面向对象了,形成的类就是Thread,在Thread类内部执行任务的方法叫run()
  • 注意:如果想让run作为任务区,必须让他去被自动调用.我们通过执行start()方法,来开启线程,继而实现run方法的自动调用.
一:直接使用Thread创建线程对象

​ 分析:由于我们实现的实际功能Thread类是决定不了的,所以没有办法将我们的功能放入Thread的run方法里
所以Thread的run 方法是一个空方法.如果我们想实现自己的功能,可以写Thread类的子类,重写run方法

	//2.使用子类创建对象
		MyThread t0 = new MyThread("t0");
		MyThread t1 = new MyThread("t1");
		t0.start();//开启线程
		t1.start();//开启线程

​ 重写run方法,实现我们的功能.run就是我们的任务区

​ Thread.currentThread():获取的是当前的线程
Thread.currentThread().getName():线程的名字

二:实现多线程的方式两种:
  • 第一种方式:通过创建Thread子类的方式实现功能----线程与任务绑定在了一起,操作不方法
  • 第二种:将任务从线程中分离出来,哪个线程需要工作,就将任务交给谁,操作方便
三:线程的停止
  1. 通过一个标识结束线程 false
  2. 调用stop方法-----因为有固态的安全问题,所以系统不建议使用
  3. 调用interrupt方法-------如果目标线程等待很长时间(例如基于一个条件变量),则应该使用interrupt 方法来中断该等待。
thread.interrupt();
四:守护
  1. 守护线程:相当于后台线程,依赖于前台线程,正常情况下,当前台线程结束的时候,不管守护线程有没有结束,都会立刻结束 t0.setDaemon(true);

  2. 典型的守护线程:垃圾回收线程

    MyTest1 myTest1 = new MyTest1();
    		Thread t0 = new Thread(myTest1);
    		/*
    		 * 当程序调用setDaemon方法时,并且将参数设置成true.当前线程就变成了守护线层.
    		 * 注意:这个方法一定要在start方法之前调用
    		 */
    		t0.setDaemon(true);
    		t0.start();
    

五:高于主线程的方法 join()方法
  1. 原理:线程一旦调用了join方法,他的优先级会高于主线程,主线程会等当前的线程执行完后再去执行
  2. 注意点: 优先级只比main线程高,对其他的线程没有影响
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 block
			e.printStackTrace();
		}
		
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"   主线程"+i);
		}
六:单例线程
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;
	}
}
七:生产者消费者
  1. 单生产者单消费者

    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 block
    				e.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 block
    				e.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", 10);
    		}
    		
    	}
    }
    //创建消费任务
    class Consumer implements Runnable{
    	Product product;
    	public Consumer(Product product) {
    		super();
    		this.product = product;
    	}
    	public void run() {
    		while (true) {
    			product.getConsume();
    		}
    	}
    }
    

  2. 多生产者多消费者(使用synchronized)

    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 block
    				e.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 block
    				e.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();
    		}
    	}
    }
    

  3. 多生产者多消费者(同步使用Lock,唤醒等待使用Condition)

ConditionObject 监视器方法(waitnotifynotifyAll)分解成截然不同的对象,其中,Lock 替代了 synchronized
方法和语句的使用,Condition 替代了 Object 监视器方法的使用。

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();
	//用于生产任务的Condition
	Condition proCon = lock.newCondition();
	//用于消费任务的Condition
	Condition 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 block
					e.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 block
					e.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();
		}
	}
}

  1. 死锁:出现的情况有两种
    1.所有的线程处于等待状态
    2.锁之间进行嵌套调用

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

    要求:最终也要实现一次生产一次消费
    错误描述:当有两个生产线程,两个消费线程同时存在的时候,有可能出现生产一次,消费多次或者生产多次消费一次的情况.
    原因:当线程被重新唤醒之后,没有判断标记,直接执行了下面的代码

    ​ 解决办法:将标记处的if改成while

八:线程通信
  1. 面向对象的精髓:谁的活儿谁干,不是你的活儿不要干,将数据准备的活儿从输入任务输出任务提出来,放入数据类
  2. 实例:打印机打印----一次输入一次输出 两个线程:输入线程和输出线程 两个任务区:输入任务,输出任务 一份数据
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 block
				e.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 block
				e.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();
			}
		}
	}
}


九:线程同步
  1. 原理:线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的!
十:比较synchronized和Lock

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

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

同步的代码

}//释放锁

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

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

使用过程:

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

进行同步的代码块儿

使用unlock()方法释放锁

使用的场景:

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

使用效率上:Lock高于synchronized

day05

IO流
一:简介
  1. 作用:实现俩个设备之间数据的传递
  2. 设备:磁盘,内存,键盘,文件,网络,控制台
  3. [外链图片转存失败(img-rt0zmwpE-1567949517218)(E:\千锋uml课设\1564798504189.png)]
  4. 分类: 根据操作的方式:输入流和输出流 根据数据的类型:字节流和字符流
  5. 字节流:传输的是字节,可以操作任何类型的数据 字符流: 传输的是字节,不同点是在传输过程中加入了编码的操作,让我们的操作更加方便
  6. 字节输入流:InputStream 字节输出流:OutputStream 字符写出流:Writer 字符读入流:Reader
二:文件读入写出流
  1. 写入文件----------写出流---------- FileWrite
  2. 保证路径真实,写write内容完了,刷新,关闭流
  3. 文件的续写FileWriter(String string, Boolean value),当value的值为true的时候,文件内容不会被覆盖,会在后面续写
  4. 读出流:返回的 是ASCII码值[外链图片转存失败(img-FDylgZMc-1567949517218)(E:\千锋uml课设\1564801579171.png)]

[外链图片转存失败(img-1zNq37Vm-1567949517220)(E:\千锋uml课设\1564888257512.png)]

三:字符缓冲流(demo07,08)
  1. 定义:为了提高读写的能力,本身没有读写的能力,借助与字符流来实现

  2. 换行:newLine 一次读一行 readLine 返回值为null

  3. 读入写出配合使用

  4. 字符缓冲流分类: 字符缓冲读入流: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();
    	}
    }
    
    

  5. 相对路径:从路径中间的某个部位开始一直到当前的文件 绝对路径:一个文件的完整路径,从根目录开始到当前的文件

  6. 补充知识:

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

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

  7. 完成文件的复制(使用读入流和写出流)

    public class Demo5 {
    	public static void main(String[] args) throws IOException {
    		//1.创建读入流和写出流
    		FileReader fileReader = new FileReader("D:\\workspace/BigData1923N19\\src\\com\\qf\\test\\Demo1.java");
    		FileWriter fileWriter = new FileWriter("Demo1copy.java");
    		
    		//读写
    		//使用一次读一个
    		int num =0 ;
    //		while ((num = fileReader.read()) != -1) {
    //			fileWriter.write(num);//可以实现自动转换
    //			fileWriter.flush();
    //		}
    		
    		//使用一次读多行
    		char[] arr = new char[10];
    		while ((num = fileReader.read(arr)) != -1) {
    			fileWriter.write(arr,0,num);//可以实现自动转换
    			fileWriter.flush();
    		}
    		
    		fileReader.close();
    		fileWriter.close();
    	}
    }
    

  8. 完成文件的复制(使用缓冲流实现文件的复制)

    public class Demo8 {
    	public static void main(String[] args) throws IOException {
    		//1.创建读入流和写出流
    		FileReader fileReader = new FileReader("D:\\workspace/BigData1923N19\\src\\com\\qf\\test\\Demo1.java");
    		FileWriter fileWriter = new FileWriter("Demo1copy.java");
    		BufferedReader bufferedReader = new BufferedReader(fileReader);
    		BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
    		//读写
    		//使用一次读一个
    		int num =0 ;
    		while ((num = bufferedReader.read()) != -1) {
    			bufferedWriter.write(num);//可以实现自动转换
    			fileWriter.flush();
    		}
    		
    		//使用一次读多行
    //		char[] arr = new char[10];
    //		while ((num = bufferedReader.read(arr)) != -1) {
    //			bufferedWriter.write(arr,0,num);//可以实现自动转换
    //			fileWriter.flush();
    //		}
    		
    		//使用一次读一行
    		String data = null;
    		while ((data = bufferedReader.readLine()) != null) {
    			bufferedWriter.write(data);
    			bufferedWriter.newLine();
    			bufferedWriter.flush();
    		}
    		
    		bufferedReader.close();
    		bufferedWriter.close();
    
    	}
    }
    
    

Writer(“Demo1copy.java”);

	//读写
	//使用一次读一个
	int num =0 ;

// while ((num = fileReader.read()) != -1) {
// fileWriter.write(num);//可以实现自动转换
// fileWriter.flush();
// }

	//使用一次读多行
	char[] arr = new char[10];
	while ((num = fileReader.read(arr)) != -1) {
		fileWriter.write(arr,0,num);//可以实现自动转换
		fileWriter.flush();
	}
	
	fileReader.close();
	fileWriter.close();
}

}


​

8. 完成文件的复制(使用缓冲流实现文件的复制)

public class Demo8 {
public static void main(String[] args) throws IOException {
//1.创建读入流和写出流
FileReader fileReader = new FileReader(“D:\workspace/BigData1923N19\src\com\qf\test\Demo1.java”);
FileWriter fileWriter = new FileWriter(“Demo1copy.java”);
BufferedReader bufferedReader = new BufferedReader(fileReader);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
//读写
//使用一次读一个
int num =0 ;
while ((num = bufferedReader.read()) != -1) {
bufferedWriter.write(num);//可以实现自动转换
fileWriter.flush();
}

	//使用一次读多行

// char[] arr = new char[10];
// while ((num = bufferedReader.read(arr)) != -1) {
// bufferedWriter.write(arr,0,num);//可以实现自动转换
// fileWriter.flush();
// }

	//使用一次读一行
	String data = null;
	while ((data = bufferedReader.readLine()) != null) {
		bufferedWriter.write(data);
		bufferedWriter.newLine();
		bufferedWriter.flush();
	}
	
	bufferedReader.close();
	bufferedWriter.close();

}

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值