JAVA笔记(十三)
-
package cn.itcast.dao.impl; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import cn.itcast.dao.UserDao; import cn.itcast.pojo.User; /** * 这是用户操作的具体实现类(IO版) * * @author 风清扬 * @version V1.1 */ public class UserDaoImpl implements UserDao { // 为了保证文件一加载就创建 private static File file = new File("user.txt"); static { try { file.createNewFile(); } catch (IOException e) { System.out.println("创建文件失败"); // e.printStackTrace(); } } @Override public boolean isLogin(String username, String password) { boolean flag = false; BufferedReader br = null; try { // br = new BufferedReader(new FileReader("user.txt")); br = new BufferedReader(new FileReader(file)); String line = null; while ((line = br.readLine()) != null) { // 用户名=密码 String[] datas = line.split("="); if (datas[0].equals(username) && datas[1].equals(password)) { flag = true; break; } } } catch (FileNotFoundException e) { System.out.println("用户登录找不到信息所在的文件"); // e.printStackTrace(); } catch (IOException e) { System.out.println("用户登录失败"); // e.printStackTrace(); } finally { if (br != null) { try { br.close(); } catch (IOException e) { System.out.println("用户登录释放资源失败"); // e.printStackTrace(); } } } return flag; } @Override public void regist(User user) { /* * 为了让注册的数据能够有一定的规则,我就自己定义了一个规则: 用户名=密码 */ BufferedWriter bw = null; try { // bw = new BufferedWriter(new FileWriter("user.txt")); // bw = new BufferedWriter(new FileWriter(file)); // 为了保证数据是追加写入,必须加true bw = new BufferedWriter(new FileWriter(file, true)); bw.write(user.getUsername() + "=" + user.getPassword()); bw.newLine(); bw.flush(); } catch (IOException e) { System.out.println("用户注册失败"); // e.printStackTrace(); } finally { if (bw != null) { try { bw.close(); } catch (IOException e) { System.out.println("用户注册释放资源失败"); // e.printStackTrace(); } } } } }
- 其他的流(了解),每种颜色代表一种大的流。
操作基本数据类型
(操作基本数据的流)
DataInputStream
DataOutputStream
内存操作流
操作字节数组
ByteArrayInputStream
ByteArrayOutputStream
操作字符数组
CharArrayReader
CharArrayWrite
操作字符串
StringReader
StringWriter
内存操作流一般用于处理临时信息,因为临时信息不需要保存,使用后就可以删除。
2:以第一个举例即可
ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write("helloworld".getBytes()); baos.close(); byte[] bys = baos.toByteArray(); ByteArrayInputStream bais = new ByteArrayInputStream(bys); int by = 0; while ((by = bais.read()) != -1) { System.out.println((char) by); } baos.close(); bais.close();
最后的close()可以不写,通过看源码可以知道这里什么都没有做。
打印流概述
:只有写,没有读。
字节流打印流
字符打印流
打印流特点
只能操作目的地,不能操作数据源。
可以操作任意类型的数据。
如果启动了自动刷新,能够自动刷新。
可以操作文件的流
打印流复制文本文件
System类中的字段:in,out。(标准的输入输出流)
它们各代表了系统标准的输入和输出设备。
默认输入设备是键盘,输出设备是显示器。
System.in的类型是InputStream.
(字节流)System.out的类型是PrintStream(字符打印流)
是OutputStream的子类FilterOutputStream 的子类.
RandomAccessFile概述
RandomAccessFile类不属于流,是Object类的子类。但它融合了InputStream和OutputStream的功能。支持对随机访问文件的读取和写入。【该类的构造方法的第二个参数就是读写文件的模式,一共有四种,用得最多的是rw】utf对应汉字的是3个字节,想读取就可以读取那个位置,是通过设置指针位置来实现的。
SequenceInputStream概述
(合并流)SequenceInputStream类可以将多个输入流串流在一起,合并为一个输入流,因此,该流也被称为合并流。
SequenceInputStream的构造方法
SequenceInputStream(InputStream s1, InputStream s2)
SequenceInputStream(Enumeration<? extends InputStream> e)
把多个文件的内容写入到一个文本文件
合并流的一个构造可以合并两个文件,有一个构造可以合并多个文件,但是需要注意的是,该多个流的合并需要用到vector集合的方法,所以就得创建该集合。
- 打印流如果启动了自动刷新,对于某些方法就可以不用刷新和换行了。(比如println())。 该流的底层也高效。
- 在jdk1.5之前,没有scanner这个类,为了使键盘输入数据就通过字符缓冲流包装标准输入流实现【BufferedReader br=new BufferedReader(new InputStream(System.in)).】
- 注意一点:字符流如果不能显示在控制台或者文件中,看看是否刷新缓冲区了。
- 序列化流:
序列化流
ObjectOutputStream
(写)反序列化流
ObjectInputStream
(读)序列化操作问题
为什么要实现序列化?
如何实现序列化?
序列化数据后,再次修改类文件,读取数据会出问题,如何解决呢?
使用transient关键字声明不需要序列化的成员变量
对象序列化是将对象状态转换为可保持或传输的过程。一般的格式是与平台无关的二进制流,可以将这种二进制流持久保存在磁盘上,也可以通过网络将这种二进制流传输到另一个网络结点。
对象反序列化,是指把这种二进制流数据还原成对象。
Properties概述
Properties作为Map集合的使用
,(但是不能有泛型)Properties的特殊功能
public Object setProperty(String key,String value)
【添加元素】public String getProperty(String key)
【获取元素】public Set<String> stringPropertyNames()
【获取所有键的集合】Properties和IO流的结合使用
public void load(Reader reader)
【把文件的数据读取到集合中】public void store(Writer writer,String comments)
【把集合中的数据存储到文件】注意这里的集合只能是Priperties.这个文件的数据也必须是键值对形式。该集合可以应用于单机游戏状态的存贮。
NIO其实就是新IO的意思。
JDK4出现NIO。新IO和传统的IO有相同的目的,都是用于进行输入输出的,但新IO使用了不同的方式来处理输入输出,采用内存映射文件的方式,将文件或者文件的一段区域映射到内存中,就可以像访问内存一样的来访问文件了,这种方式效率比旧IO要高很多,但是目前好多地方我们看到的还是旧IO的引用,所以我们仍以旧IO为主,知道NIO即可。Buffer(缓冲),Channer(通道)
JDK7要了解的新IO类
Path:与平台无关的路径。
Paths:包含了返回Path的静态方法。
public static Path get(URI uri):根据给定的URI来确定文件路径。
Files:操作文件的工具类。提供了大量的方法,简单了解如下方法
public static long copy(Path source, OutputStream out) :复制文件
public static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options):
把集合的数据写到文件。
//复制文件
Files.copy(Paths.get("Demo.java"), newFileOutputStream("Copy.Java"));
//把集合中的数据写到文件
List<String> list = new ArrayList<String>(); list.add("hello"); list.add("world"); list.add("java"); Files.write(Paths.get("list.txt"), list, Charset.forName("gbk"));
- 要想实现序列化和反序列化就必须实现serializable接口,该接口中没有任何方法,在java中,没有任何方法的接口称为标记接口。
- 看到类实现了序列化接口的时候,要想解决黄色警告线的问题,就可以产生一个序列化id值,而且产生这个值以后,我们对类进行任何改动,它读取以前的数据是没有问题的。
- 打印流复制文件代码
BufferedReader br = new BufferedReader(new FileReader("a.txt")); PrintWriter pw = new PrintWriter(new FileWriter("b.txt"),true); String line = null; while((line=br.readLine())!=null) { pw.println(line); } pw.close(); br.close();
- Jdk1.7新特性的代码【了解使用】
package cn.itcast_09; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; /* * nio包在JDK4出现,提供了IO流的操作效率。但是目前还不是大范围的使用。 * 有空的话了解下,有问题再问我。 * JDK7的之后的nio: * Path:路径 * Paths:有一个静态方法返回一个路径 * public static Path get(URI uri) * Files:提供了静态方法供我们使用 * public static long copy(Path source,OutputStream out):复制文件 * public static Path write(Path path,Iterable<? extends CharSequence> lines,Charset cs,OpenOption... options) */ public class NIODemo { public static void main(String[] args) throws IOException { // public static long copy(Path source,OutputStream out) // Files.copy(Paths.get("ByteArrayStreamDemo.java"), new // FileOutputStream( // "Copy.java")); ArrayList<String> array = new ArrayList<String>(); array.add("hello"); array.add("world"); array.add("java"); Files.write(Paths.get("array.txt"), array, Charset.forName("GBK")); } } Properties的经典代码 package cn.itcast_08; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Properties; /* * 这里的集合必须是Properties集合: * public void load(Reader reader):把文件中的数据读取到集合中 * public void store(Writer writer,String comments):把集合中的数据存储到文件 * 单机版游戏: * 进度保存和加载。 * 三国群英传,三国志,仙剑奇侠传... * * 吕布=1 * 方天画戟=1 */ public class PropertiesDemo3 { public static void main(String[] args) throws IOException { // myLoad(); myStore(); } private static void myStore() throws IOException { // 创建集合对象 Properties prop = new Properties(); prop.setProperty("林青霞", "27"); prop.setProperty("武鑫", "30"); prop.setProperty("刘晓曲", "18"); //public void store(Writer writer,String comments):把集合中的数据存储到文件 Writer w = new FileWriter("name.txt"); prop.store(w, "helloworld"); w.close(); } private static void myLoad() throws IOException { Properties prop = new Properties(); // public void load(Reader reader):把文件中的数据读取到集合中 // 注意:这个文件的数据必须是键值对形式 Reader r = new FileReader("prop.txt"); prop.load(r); r.close(); System.out.println("prop:" + prop); } }
- 游戏设置次数的代码
package cn.itcast_08; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Properties; /* * 我有一个猜数字小游戏的程序,请写一个程序实现在测试类中只能用5次,超过5次提示:游戏试玩已结束,请付费。 */ public class PropertiesTest2 { public static void main(String[] args) throws IOException { // 读取某个地方的数据,如果次数不大于5,可以继续玩。否则就提示"游戏试玩已结束,请付费。" // 创建一个文件 // File file = new File("count.txt"); // if (!file.exists()) { // file.createNewFile(); // } // 把数据加载到集合中 Properties prop = new Properties(); Reader r = new FileReader("count.txt"); prop.load(r); r.close(); // 我自己的程序,我当然知道里面的键是谁 String value = prop.getProperty("count"); int number = Integer.parseInt(value); if (number > 5) { System.out.println("游戏试玩已结束,请付费。"); System.exit(0); } else { number++; prop.setProperty("count", String.valueOf(number)); Writer w = new FileWriter("count.txt"); prop.store(w, null); w.close(); GuessNumber.start(); } } }
- 如果程序只有一条执行路径,那么该程序就是单线程程序,如果程序有多条【大于等于2】执行路径,那么该程序就是多线程程序。
- 进程就是正在运行的程序,多进程的意义就是提高cpu的使用率,在同一时间段同时运行,但是同一时间点是不可能的。
- 在同一个进程中又可以执行多个任务,而这每一个任务就可以看做是一个线程,多线程可以是进程拥有更大的几率抢到cpu资源,从而提高了程序的使用率。
- 我们不敢保证哪一个线程在某一时刻抢到资源,所以线程的执行是随机的。
- 并行(同一时间段)并发(同一时间点)
- Jvm的启动其实是多线程的,除了主线程之外,垃圾回收线程也会先启动,否则容易造成内存溢出,所以java虚拟机最低启动了两个线程。
- 实现多线程的2个方法【其实是3个】1.继承thread类,重写该类中的run()方法。2. 实现Runnable接口3.(了解)、使用ExecutorService、Callable、Future实现有返回结果的多线程
- 不是类中的所有代码都需要被(多)线程执行,这个时候,为了区分哪些代码能够被(多)线程执行,java就提供了thread类中的run()用来包含哪些被(多)线程执行的代码。 一般来说,被线程执行的代码都是比较耗时的,所以比较简单的就没有必要开创线程。
- Run()和start()方法的区别:run()仅仅是封装了线程执行的代码,直接调用跟普通方法一样。Start()方法首先启动了线程,然后再有jvm去调用该线程的run() 方法。
- 针对不是thread类的子类如何获取线程对象的名称?public static thread currenthread():返回当前线程的对象,然后在getname()即可。
- Java使用的是抢占模型:优先级高的获得cpu的时间片就多,同一优先级就随便获取一个。
- 线程的默认优先级是5.线程的优先级的范围是1~10,最小的是1,最大的是10.
- 线程的优先级高仅仅表示线程获取的cpu的时间片的几率高一些,但是要多次运行比较后才能得到比较好的效果,否则一两次有随机性,并不能很好的展现出结果。
- 线程的一些控制方法
线程休眠
public static void sleep(long millis)
【以毫秒为单位】
线程加入
【等待该线程终止,别的线程才可以抢】
public final void join()
线程礼让
【暂停当前正在执行的线程对象,并执行其他线程,它可以让多个线程的执行更和谐,但是不能保证一人一次】
public static void yield()
后台线程
【将该线程记为守护线程或用户线程,当正在运行的线程是守护线程时,java虚拟机退出,该方法必须在启动线程前调用】
public final void setDaemon(boolean on)
中断线程
public final void stop()
【让线程停止,已经过时了,不过还可以用,太过暴力,直接停止了,后面的代码就不能运行】
public void interrupt()
【中断线程,把线程的状态终止,并抛出一个异常,好让的代码运行】
- 线程的生命周期
- 实现runnable接口也同样要重写run()方法。然后创建实现类的对象,在创建thread类的对象,将实现类作为参数传递。
- 实现runnable接口的好处
实现接口方式的好处
可以避免由于Java单继承带来的局限性。
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
- 为了实现共享一种资源,可以将这个资源定义为静态的。
- 加入延时后买票出现的问题
相同的票出现多次:
CPU的一次操作必须是原子性的
还出现了负数的票:
随机性和延迟导致的
package cn.itcast_08; public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; @Override // public void run() { // while (true) { // // t1,t2,t3三个线程 // // 这一次的tickets = 100; // if (tickets > 0) { // // 为了模拟更真实的场景,我们稍作休息 // try { // Thread.sleep(100); // t1就稍作休息,t2就稍作休息 // } catch (InterruptedException e) { // e.printStackTrace(); // } // // System.out.println(Thread.currentThread().getName() + "正在出售第" // + (tickets--) + "张票"); // // 理想状态: // // 窗口1正在出售第100张票 // // 窗口2正在出售第99张票 // // 但是呢? // // CPU的每一次执行必须是一个原子性(最简单基本的)的操作。 // // 先记录以前的值 // // 接着把ticket-- // // 然后输出以前的值(t2来了) // // ticket的值就变成了99 // // 窗口1正在出售第100张票 // // 窗口2正在出售第100张票 // // } // } // } @Override public void run() { while (true) { // t1,t2,t3三个线程 // 这一次的tickets = 1; if (tickets > 0) { // 为了模拟更真实的场景,我们稍作休息 try { Thread.sleep(100); //t1进来了并休息,t2进来了并休息,t3进来了并休息, } catch (InterruptedException e) { e.printStackTrace(); }System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票"); //窗口1正在出售第1张票,tickets=0 //窗口2正在出售第0张票,tickets=-1 //窗口3正在出售第-1张票,tickets=-2 } } } }
- 解决线程安全问题的基本思想
首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。
- 同步代码块(可以解决线程安全问题)
同步代码块
格式:
synchronized(对象){需要同步的代码;}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。(这个对象要形成唯一的一把”锁”,定义成成员变量为佳)
- 买票的完美代码:
package cn.itcast_10; public class SellTicket implements Runnable { // 定义100张票 private int tickets = 100; // 定义同一把锁 private Object obj = new Object(); @Override public void run() { while (true) { // t1,t2,t3都能走到这里 // 假设t1抢到CPU的执行权,t1就要进来 // 假设t2抢到CPU的执行权,t2就要进来,发现门是关着的,进不去。所以就等着。 // 门(开,关) synchronized (obj) { // 发现这里的代码将来是会被锁上的,所以t1进来后,就锁了。(关) if (tickets > 0) { try { Thread.sleep(100); // t1就睡眠了 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 "); //窗口1正在出售第100张票 } } //t1就出来可,然后就开门。(开) } } } package cn.itcast_10; /* * 举例: * 火车上厕所。 * 同步的特点: * 前提: * 多个线程 * 解决问题的时候要注意: * 多个线程使用的是同一个锁对象 * 同步的好处 * 同步的出现解决了多线程的安全问题。 * 同步的弊端 * 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。 */ public class SellTicketDemo { public static void main(String[] args) { // 创建资源对象 SellTicket st = new SellTicket(); // 创建三个线程对象 Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); // 启动线程 t1.start(); t2.start(); t3.start(); } }
- 同步方法的格式以及锁对象。格式:将同步关键字写到方法上,它的锁对象是this(表示当前对象),同步代码块的锁对象是任意对象。同步静态方法的对象是当前类的class文件【类的字节码文件对象】,因为只有该class文件才会在静态方法之前。
如何把一个线程不安全的集合类变成一个线程安全的集合类
用Collections工具类的方法即可。
- Lock是一个接口。(子类实现然后调用lock()和unlock()方法,此功能就如同synchronized一样,这是jdk1.5的新特性,最好用try…finally语句,因为这样不管怎样都会释放锁)
- 同步可能产生死锁。
死锁是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
- 线程间通信问题:不同种类的线程间针对同一个资源的操作。
- 消费者和生产者问题要用到等待唤醒机制。【这些方法在object类中,这些方法的调用必须通过锁对象调用】------->>待续