admin 发布的文章

Mac开发

1、Chrome-开发者最友好的浏览器

2、Alfred效率工具

配置

  • 设置快捷键启动:option + command + enter
  • 设置主题Appearance:自选

3、iterm命令行工具

  • oh-my-zsh:github链接 curl安装
  • vim ~/.zshrc:设置主题等
  • command + “,” 呼出设置:
  • General: Closing:去除勾勾 confirm command + “Q” 确认提示键,直接关闭
  • General: Window:去除勾勾 Native full screen window 直接呼出iterm2 command + enter
  • Keys:Hotkey: esc
  • Profiles:文字更改:text:大小、PT Mono 、行间距、竖间距
  • 去除每次的login:touch .hushlogin

4、homebrew文件的包管理

  • brew -h 查看相关命令
  • brew install php71 安装
  • brew update 升级
  • brew search php71 查找

5、node

  • brew install yarn
  • yarn
  • npm run dev 编译文件

6、composer

  • mac 安装好之后:PHP composer.phar执行命令
  • mv composer.phar /usr/local/bin/composer
  • composer == php composer.phar
  • composer包都可以在https://packagist.org看到

7、Valet开发laravel

  • valet需要上方的前置条件
  • composer 安装即可
  • valet install 就可以安装所需依赖
  • valet park

8、数据库图形化管理-sequel pro

9、homestead+vagrant+virtulbox

phpExcel的安装与使用

1、安装命令:composer require phpoffice/phpexcel

2、基本使用步骤

`
//实例化phpexcel对象
$objPHPExcel = new PHPExcel();

//设置Excel属性
$objPHPExcel->getProperties()

        ->setCreator("Maarten Balliauw")                    //创建人
        ->setLastModifiedBy("Maarten Balliauw")                //最后修改人
        ->setTitle("Office 2007 XLSX Test Document")        //设置标题
        ->setSubject("Office 2007 XLSX Test Document")        //设置主题
        ->setDescription("Test document ")                    //设置备注
        ->setKeywords( "office 2007 openxml php")            //设置关键字
        ->setCategory( "Test result file");                    //设置类别

// 给表格添加数据
$objPHPExcel->setActiveSheetIndex(0) //设置第一个内置表(一个xls文件里可以有多个表)为活动的

        ->setCellValue( 'A1', 'Hello' )         //给表的单元格设置数据
        ->setCellValue( 'B2', 'world!' )      //数据格式可以为字符串
        ->setCellValue( 'C1', 12)            //数字型
        ->setCellValue( 'D2', 12)            //
        ->setCellValue( 'D3', **true** )           //布尔型
        ->setCellValue( 'D4', '=SUM(C1:D2)' );//公式

//激活当前表
$objPHPExcel->setActiveSheetIndex(0);
ob_end_clean();//清除缓冲区,避免乱码

//最后只需要生成Excel或者提示下载即可
//生成Excel,并自定义保存路径
//"Excel2007"生成2007版本的xlsx,"Excel5"生成2003版本的xls
$objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel,'Excel5');
$objWriter->save($path);

//弹出提示下载文件
header('pragma:public');
header("Content-Disposition:attachment;filename=$expFileName");
header('Cache-Control: max-age=0');
$objWriter = PHPExcel\_IOFactory:: *createWriter*($objPHPExcel, 'Excel2007');
$objWriter->save( 'php://output');
`

3、设置Excel样式

`

//设置宽度
$objPHPExcel->getActiveSheet()->getColumnDimension('A')->setWidth(200);   //设置单元格宽度
$objPHPExcel->getActiveSheet()->getColumnDimension('A')->setAutoSize(true);   //内容自适应
//设置align(需要引入PHPExcel_Style_Alignment)
$objPHPExcel->getActiveSheet()->getStyle('A18')->getAlignment()->setHorizontal(PHPExcel_Style_Alignment::HORIZONTAL_JUSTIFY);//水平方向上两端对齐
$objPHPExcel->getActiveSheet()->getStyle( 'A18')->getAlignment()->setVertical(PHPExcel_Style_Alignment::VERTICAL_CENTER);    //垂直方向上中间居中
//合并拆分单元格
$objPHPExcel->getActiveSheet()->mergeCells('A28:B28');      // A28:B28合并
$objPHPExcel->getActiveSheet()->unmergeCells('A28:B28');    // A28:B28再拆分
//字体大小、粗体、字体、下划线、字体颜色(需引入PHPExcel_Style_Font、PHPExcel_Style_Color)
$objPHPExcel->getActiveSheet()->getStyle('A1')->getFont()->setSize(20);
$objPHPExcel->getActiveSheet()->getStyle('A1')->getFont()->setBold(true);
$objPHPExcel->getActiveSheet()->getStyle('B1')->getFont()->setName('Candara');
$objPHPExcel->getActiveSheet()->getStyle('B1')->getFont()->setUnderline(PHPExcel_Style_Font::UNDERLINE_SINGLE);
$objPHPExcel->getActiveSheet()->getStyle('B1')->getFont()->getColor()->setARGB(PHPExcel_Style_Color::COLOR_WHITE);
//默认字体、大小
$objPHPExcel->getDefaultStyle()->getFont()->setName( 'Arial');
$objPHPExcel->getDefaultStyle()->getFont()->setSize(20);
//背景填充
$objPHPExcel->getActiveSheet()->getStyle( 'A3:E3')->getFill()->setFillType(PHPExcel_Style_Fill::FILL_SOLID);
$objPHPExcel->getActiveSheet()->getStyle( 'A4:E4')->getFill()->getStartColor()->setARGB('FFC125');
// 单元格密码保护不让修改
$objPHPExcel->getActiveSheet()->getProtection()->setSheet( **true**);  // 为了使任何表保护,需设置为真
$objPHPExcel->getActiveSheet()->protectCells( 'A3:E13', 'PHPExcel' ); // 将A3到E13保护 加密密码是 PHPExcel
$objPHPExcel->getActiveSheet()->getStyle( 'B1')->getProtection()->setLocked(PHPExcel_Style_Protection::PROTECTION_UNPROTECTED); //去掉保护
//给单元格内容设置url超链接
$objActSheet->getCell('E26')->getHyperlink()->setUrl( 'http://www.phpexcel.net');    //超链接url地址
$objActSheet->getCell('E26')->getHyperlink()->setTooltip( 'Navigate to website');  //鼠标移上去连接提示信息

`
参考:
[https://www.kancloud.cn/chunyu/php_basic_knowledge/1041088]
[https://blog.csdn.net/jackbon8/article/details/107914628]
[https://blog.csdn.net/jackbon8/article/details/107915006]
[https://blog.csdn.net/jackbon8/article/details/107940638]

Golang-缓冲通道&定向通道

1.非缓冲通道:发送和接收到一个未缓冲的通道是阻塞的;一次发送操作对应一次接收操作;对于一个goroutine来讲,它的一次发送,在另一个goroutine接受之前都是阻塞的,同样的,对于接收来讲,在另一个goroutine发送之前,它也是阻塞的

2.缓冲通道:指一个通道,带有一个缓冲区。发送到一个缓冲通道只有在缓冲区满时才被阻塞。类似地,从缓冲通道接收的信息只有在缓冲区为空时才会被阻塞

  • 语法:ch := make(chan type, capactity);capactity应该大于零,以便通道具有缓冲区;无缓冲通道的容量为0,因此之前创建通道时省略了容量参数
func main() {
    ch3 := make(chan string, 4)
    go sendData(ch3)

    for{
        v, ok := <-ch3
        if !ok{
            fmt.Println("读完了。。。", ok)
            break
        }
        fmt.Println("读取的数据是:", v)

    }

    fmt.Println("main...over...")
}

func sendData(ch chan string)  {
    for i:= 0; i<10; i++ {
        ch <- "数据" + strconv.Itoa(i)
        fmt.Printf("子goroutine中发送的第 %d 个数据\n", i)
    }
    close(ch)
}

3.定向通道

  • 双向通道:一个goroutine可以向通道发送数据,也可以接收数据
双向通道:
        chan
            chan <- data,发送数据,写出
            data <- chan,获取数据,读取
单向通道:定向通道
        chan <- T, 只支持写
        <- chan T, 只支持读
ch1 := make(chan string)

    done := make(chan bool)
    go sendData(ch1, done)

    data := <- ch1 //读取
    fmt.Println("子goroutine传来的数据是:", data)

    ch1 <- "我是main的数据" //发送数据

    <- done//因为阻塞,所以会等待子goroutine的 通道done发送数据
    fmt.Println("main...over...")

    //time.Sleep(1)

}

func sendData(ch1 chan string, done chan bool)  {
    ch1 <- "我是lzc" //发送

    data := <- ch1 //读取数据
    fmt.Println("main goroutine传来的数据:", data)

    done <- true
}
  • 定向通道也叫单向通道
单向:单向
            chan <- T, 只支持写
            <- chan T, 只读
ch2 := make(chan <- int)//单向,只能写,不能读
ch2 <- 100
data := <- ch2
invalid operation: <-ch2 (receive from send-only type chan<- int)


ch3 := make(<- chan int)//单向,只能读,不能写
ch3 <- 100
invalid operation: ch3 <- 100 (send to receive-only type <-chan int)

Golang-channel通道初识

1.通道可以被认为是Goroutines通信的管道;当多个goroutines想共享数据的时候,虽然也提供了传统的同步机制(同步等待组、同步锁),但是Go语言强烈建议的是使用Channel通道来实现goroutine之间的通信;

2.通道的声明和定义一个变量的语法一样; 通道是引用型数据

//声明通道
var 通道名 chan 数据类型
//创建通道:如果通道为nil(就是不存在),就需要先创建通道
通道名 = make(chan 数据类型)

func main(){
    var a chan int
    if a == nil {
        fmt.Println("channel 是nil的,不能使用,需要先创建通道。")
    }
    a = make(chan int)
    fmt.Printf("数据类型:%T", a)

}

//也可以简短声明:
a := make(chan int)

3.通道的注意点

  • 用于goroutine,传递消息
  • 每个通道都有相关联的数据类型
  • 使用通道传递数据 <-
  • chan <-data,发送数据到通道。向通道中写数据
  • data <-chan,从通道中获取数据。从通道中读取数据
  • 阻塞:发送数据 chan <- data,阻塞的,直到另一条goroutine,读取数据来解除阻塞;读取数据,data <- chan,也是阻塞的。直到另一条goroutine,写出数据解除阻塞;
  • 本身channel就是同步的,意味着同一时间,只能有一条goroutine来操作
func main() {
    var ch1 chan bool
    ch1 = make(chan bool)

    go func() {
        for i:=0; i<10; i++ {
            fmt.Println("子goroutine中,i:", i)
        }
        //循环结束,向通道中写数据,表示要结束了。。。
        ch1 <- true
        fmt.Println("结束")
    }()


    //如果主进程先抢占资源,会去chan中读数据,如果没有数据,会阻塞下去..
    data := <- ch1
    fmt.Println("main...data-->:", data)
    fmt.Println("main主进程结束...")
}
//阻塞式通道
func main() {
    ch1 := make(chan int)

    go func() {
        fmt.Println("子goroutine开始执行")

        //time.Sleep(2*time.Second)
        data := <- ch1 //从ch1中读取数据
        fmt.Println("data:", data)
    }()

    //time.Sleep(5*time.Second)
    ch1 <- 10
    fmt.Println("main...over...")
}
//死锁:使用通道的时候要考虑的一个重要因素;只有发送数据,没有接受数据;或者只有接受数据,没有发送数据

func main()  {
    ch1 := make(chan int)
    ch1 <- 5
}
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()

3.关闭通道和通道上范围循环

· 关闭通道,发送者可以关闭通道,来通知接受方不会有更多的数据发送到channel上: close(ch);接受者可以在接收来自通道的数据时使用额外的变量来检查通道是否已经关闭:v,ok := <-ch, 如果ok的值是true,表示成功的读取了一个数据value。如果为false,意味着正在从一个封闭的通道读取数据,从封闭通道读取的值为零值(根据ch的类型的零值)

关闭通道:close(ch)
    子goroutine:写出10个数据
        每写一个,阻塞一次,主goroutine读取一次,解除阻塞

    主goroutine,读取数据
        每次读取数据,阻塞一次,子goroutine,写出一个,解除阻塞

func main() {
    
    ch1 := make(chan int)
    go sendData(ch1)

    for{
        time.Sleep(1*time.Second)
        v, ok := <- ch1
        if !ok {
            fmt.Println("已经读取了所有的数据。。。", ok)
            break
        }
        fmt.Println("读取的数据:", v, ok)
    }
    fmt.Println("main..over...")

}

func sendData(ch1 chan int)  {
    //发送方:10条数据
    for i:=0; i <10; i++ {
        ch1 <- i
    }
    close(ch1)
}

读取的数据: 0 true
读取的数据: 1 true
读取的数据: 2 true
读取的数据: 3 true
读取的数据: 4 true
读取的数据: 5 true
读取的数据: 6 true
读取的数据: 7 true
读取的数据: 8 true
读取的数据: 9 true
已经读取了所有的数据。。。 false
main..over...
  • 通道上的范围循环
func main() {

    ch1 := make(chan int)
    go sendData(ch1)

    //for循环的 for range形式可用于从通道接收值,直到它关闭为止,不需要单独判断
    for v := range ch1{//从通道读取数据,如果没值,则阻塞
        fmt.Println("读取数据:", v)
    }
    fmt.Println("main..over.....")

}

func sendData(ch1 chan int)  {
    //发送方:10条数据
    for i:=0; i <10; i++ {
        time.Sleep(1*time.Second)
        ch1 <- i
    }
    close(ch1)//通知对方,通道关闭;否则会发生死锁
}

Golang-临界资源

001#1. 临界资源:指并发环境中多个进程、线程、协程共享的资源;但是在并发编程中对临界资源的处理不当,往往会导致数据不一致的问题

func main() {
    /*
    临界资源
    */
    a := 1
    go func() {
        a = 2
        fmt.Println("goroutine...", a)
    }()


    a = 3
        
    fmt.Println("main goroutine", a)
    
}
因为主的协程执行完之后,关闭了资源,所以子协程没有来的及执行:
main goroutine 3
func main() {
    /*
    临界资源
    */
    a := 1
    go func() {
        a = 2//14行
        fmt.Println("goroutine...", a)
    }()

    a = 3//18行
    time.Sleep(1)
    fmt.Println("main goroutine", a)
    
}
主协程先把赋值为3,在要打印还没打印的时候,睡去之后,子协程持有资源又将其赋值为2
goroutine... 2
main goroutine 2
通过命令行来看一下:
$ go run -race demo10_race.go
==================
//警告:有临界资源
WARNING: DATA RACE
//通过goroutine 6在0x00c00000a0e0处写入:
Write at 0x00c00000a0e0 by goroutine 6:
  main.main.func1()
      E:/GoPath/src/Go_Advanced/demo10_race.go:14 +0x43

//通过主goroutine在0x00c00000a0e0上一次写入:
Previous write at 0x00c00000a0e0 by main goroutine:
  main.main()
      E:/GoPath/src/Go_Advanced/demo10_race.go:18 +0x8f

Goroutine 6 (running) created at:
  main.main()
      E:/GoPath/src/Go_Advanced/demo10_race.go:13 +0x81
==================
main goroutine 2
goroutine... 2
Found 1 data race(s)
exit status 66

2. 并发本身并不复杂,但是因为有了资源竞争的问题,就会有许多莫名其妙的事情

var ticket = 10
func main() {

    go sale("售票口1")
    go sale("售票口2")
    go sale("售票口3")
    go sale("售票口4")

    time.Sleep(5*time.Second)
}

func sale(name string){
    rand.Seed(time.Now().UnixNano())
    for  {
        if ticket > 0  {
            time.Sleep(time.Duration(rand.Intn(1000))*time.Microsecond)
            fmt.Println(name,"售出:", ticket)
            ticket--
        } else {
            fmt.Println("售完票了")
            break
        }
    }

}

售票口4 售出: 10
售票口2 售出: 10
售票口1 售出: 10
售票口3 售出: 10
售票口4 售出: 6
售票口1 售出: 6
售票口2 售出: 5
售票口3 售出: 5
售票口1 售出: 2
售票口2 售出: 1
售完票了
售票口4 售出: 1
售完票了
售票口3 售出: 0
售完票了
售票口1 售出: -2
售完票了

3. 解决:很多其他语言都是同步方式,通过上锁的方式,及某一段时间段,只能允许一个协程来访问这个共享数据, sync同步包;

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {

    /**
    WaitGroup: 同步等待组
        Add(), 设置等待组中要执行的子 goroutine的数量

        Wait(), 让主goroutine处于等待


        Done(),让等待组中的计数器减一,=在子goroutine中结束等待
     */

    wg.Add(2)

    go fun1()
    go fun2()

    fmt.Println("main 进入阻塞状态,等待wg子goroutine结束")

    wg.Wait() //表示goroutine进入等待,意味着阻塞

    fmt.Println("main..结束阻塞")
}

func fun1()  {
    for i:= 1; i<10; i++ {
        fmt.Println("fun1()函数中打印...A", i)
    }

    wg.Done()//执行完会后,将计数器减1 wg.Add(-1)
}
func fun2()  {

    defer wg.Done()
    for j:= 1; j<10; j++ {
        fmt.Println("\tfun2()函数中打印...A", j)
    }
}

4. go并不鼓励用锁来保护临界资源,而是鼓励通过channel将共享状态的变化在各个协程之间传递;要以通信的方式去共享内存;

  • 互斥锁(同步):最简单的,也是最暴力的;降低了性能
var ticket = 10

var mutex sync.Mutex

var wg sync.WaitGroup
func main() {


    wg.Add(4)
    go sale("售票口1")
    go sale("售票口2")
    go sale("售票口3")
    go sale("售票口4")

    wg.Wait()//main要等待
    fmt.Println("程序结束了")
    //time.Sleep(5*time.Second)
}

func sale(name string){
    rand.Seed(time.Now().UnixNano())
    defer wg.Done()
    for  {

        mutex.Lock()
        if ticket > 0  {
            time.Sleep(time.Duration(rand.Intn(1000))*time.Microsecond)
            fmt.Println(name,"售出:", ticket)
            ticket--
        } else {
            mutex.Unlock()
            fmt.Println("售完票了")
            break
        }

        mutex.Unlock()
    }

}
  • 读写锁:针对读或写的互斥锁,锁可以由任意数量的读取器或单个编写器持有;基本遵循两大原则:可以随便读,多个goroutine同时读;写的时候啥也不能干,不能读也不能写。
var rwMutex *sync.RWMutex
var wg *sync.WaitGroup
func main() {
    rwMutex = new(sync.RWMutex)
    wg = new(sync.WaitGroup)

    wg.Add(2)

    go readData(1)
    go readData(2)

    wg.Wait()
    fmt.Println("main over...")

}

func readData(i int){
    defer wg.Done()
    fmt.Println(i, "开始读: read start...")

    rwMutex.RLock()//读操作上锁
    fmt.Println(i, "正在读取数据:reading...")
    time.Sleep(1*time.Second)
    rwMutex.RUnlock()//读操作解锁
    fmt.Println(i, "读结束:read over...")

}
2 开始读: read start...
2 正在读取数据:reading...
1 开始读: read start...
1 正在读取数据:reading...
2 读结束:read over...
1 读结束:read over...
main over...

读操作可以同时进行
var rwMutex *sync.RWMutex
var wg *sync.WaitGroup
func main() {
    rwMutex = new(sync.RWMutex)
    wg = new(sync.WaitGroup)

    wg.Add(3)

    go writeData(1)
    go readData(2)
    go writeData(3)

    wg.Wait()
    fmt.Println("main over...")

}

func writeData(i int)  {
    defer wg.Done()
    fmt.Println(i, "开始写: write start...")

    rwMutex.Lock()//读操作上锁
    fmt.Println(i, "正在写数据:writing...")
    time.Sleep(3*time.Second)
    rwMutex.Unlock()//读操作解锁
    fmt.Println(i, "写结束:write over...")

}
3 开始写: write start...
3 正在写数据:writing...
2 开始读: read start...
1 开始写: write start...
明显卡顿:必须等3写完,解锁之后才可以;这个时候其他两个协程(1,2)是不能操作的
3 写结束:write over...
2 正在读取数据:reading...
2 读结束:read over...
1 正在写数据:writing...
1 写结束:write over...
main over...

Golang进阶-runtime包

1. 包含了go运行时系统交互的操作,比如控制goroutines的函数,还包括了反射包的使用;类似java和.net的虚拟机,管理包括内存分配,垃圾回收等

func main() {
    //获取goroot的目录
    fmt.Println("GOROOT:", runtime.GOROOT())
    //获取操作系统
    fmt.Println("os:",runtime.GOOS)
    //获取cpu的数量
    fmt.Println("逻辑cpu的数量:",runtime.NumCPU())

    //设置go程序执行的最大cpu的数量:[1,256]
    n := runtime.GOMAXPROCS(runtime.NumCPU());
    fmt.Println(n)

    //gosched, 让出cpu
    go func() {
        for i := 0; i < 5; i++ {
            fmt.Println("go1...")
        }
    }()

    for i:= 0; i<4; i++ {
        //让出时间片,先让别的goroutine执行
        runtime.Gosched()
        fmt.Println("main...")
    }

    runtime.Goexit()
    //终止当前的goroutine
}

Golang进阶-goroutine

1. go中使用goroutine来实现concurrently

  • goroutines非常便捷便宜,只有堆栈大小的几个kb,可以根据应用程序的需要增长和收缩;而在线程的情况下,堆栈大小必须指定并且是固定的

2. 主goroutine:封装main函数的goroutine称为主goroutine;

  • 主goroutine结束了,子goroutine也会都结束;不管运行完没有

3. 在函数或方法调用前面加上关键字go;没有返回值

package main

import (
    "fmt"
    "time"
)

func main() {
    go hello()
    fmt.Println("mafin function")
}

func hello()  {
    fmt.Println("hello world goroutine")
}

输出:main function

Golang进阶-断点续传

1. 主要借助io包里的Seeker接口

type Seeker interface {
    Seek(offset int64, whence int) (int64, error)

    //设置指针光标的位置:
        第一个参数:偏移量,
        第二个参数:如何设置:
            const (
            SeekStart   = 0 // 相对于光标开头
            SeekCurrent = 1 // 相对于光标当前
            SeekEnd     = 2 // 相对于光标结尾
            )
}

fileName := "E:/GoPath/src/a/aa.txt"
    //打开文件,注意是否有此文件

    //file, err := os.Open(fileName)
    file, err := os.OpenFile(fileName, os.O_RDWR, os.ModePerm)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    bs := []byte{0}
    file.Read(bs)
    fmt.Println(string(bs))

    file.Seek(4, io.SeekStart)
    file.Read(bs)
    fmt.Println(string(bs))

    file.Seek(4, io.SeekCurrent)
    file.Read(bs)
    fmt.Println(string(bs))

    file.Seek(0, io.SeekEnd)
    file.WriteString("ABC")

2. 断点续传应用场景(文件的传递:文件的复制)

  • 文件比较大,如何缩短耗时?
  • 如果文件传递过程中,程序被迫中断,再次重启是否需要重头开始?
  • 传递过程中,是否支持暂停和恢复?

3. 原理:通过一个临时文件记录已传递文件的大小,字节的位置; 思路:边复制,边记录复制的总量

func main() {

    srcFile := "E:/GoPath/src/a/1.png"
    //获取文件名
    destFile := srcFile[strings.LastIndex(srcFile, "/")+1: ]
    fmt.Println(destFile)
    temFile := destFile + "temp.txt"
    fmt.Println(temFile)

    file1,err := os.Open(srcFile)
    HandleErr(err)
    file2,err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm)
    HandleErr(err)
    file3,err := os.OpenFile(temFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
    HandleErr(err)

    defer file1.Close()
    defer file2.Close()

    //1: 先读取临时文件的数据,再seek
    file3.Seek(0, io.SeekStart)//从0开始读
    bs := make([]byte, 100,100)//一个文件的长度
    n1,err := file3.Read(bs)
    //HandleErr(err)
    countStr := string(bs[:n1])
    count,err := strconv.ParseInt(countStr, 10, 64)
    //HandleErr(err)
    fmt.Println(count)

    //2:设置偏移量;设置读,写的位置
    file1.Seek(count, io.SeekStart)
    file2.Seek(count, io.SeekStart)
    data := make([]byte, 1024, 1024)//用来复制文件
    n2 := -1 //读取的数据量
    n3 := -1 //写出的数据量
    total := int(count) //读取的总量

    //3: 复制文件
    for{
        n2,err = file1.Read(data)
        if err == io.EOF || n2 == 0 {
            fmt.Println("复制完毕", total)
            file3.Close()
            os.Remove(temFile)
            break
        }

        n3,err = file2.Write(data[:n2])
        total += n3

        //将复制的总量,存储到临时文件中
        file3.Seek(0, io.SeekStart)
        file3.WriteString(strconv.Itoa(total))

        fmt.Print("total:%d\n", total)


        //假装断电
        if total > 8000{
            //panic("假装断电看看")
        }
    }




}

func HandleErr(err error){
    if err != nil {
        log.Fatal(err)
    }
}

Golang进阶-复制文件

1. 文件复制原理:读取源文件的数据,把读到的数据再写到目标文件中;如果文件比较小,一次读写就可以了;如果文件比较大,则便边读边写

2. 使用io.read和io.write来实现

func main() {
    srcFile := "E:/GoPath/src/a/1.png"
    desFile := "E:/GoPath/src/a/2.png"
    total,err := CopyFile1(srcFile, desFile)
    fmt.Println(total, err)
}

func CopyFile1(srcFile, destFile string)(int, error)  {
    filel,err := os.Open(srcFile)
    if err != nil {
        return 0, err
    }

    file2,err := os.OpenFile(destFile,os.O_WRONLY|os.O_CREATE,os.ModePerm)
    if err != nil {
        return 0, err
    }

    defer filel.Close()
    defer file2.Close()

    //读写
    bs := make([]byte, 1024, 1024)
    n := -1 //读取的数据量
    total := 0
    for{
        n,err = filel.Read(bs)
        if err == io.EOF || n == 0 {
            fmt.Println("拷贝完毕")
            break
        } else if err != nil {
            fmt.Println("报错了:", err)
            return total, err
        }
        total += n
        file2.Write(bs[:n])
    }

    return total,nil
}

如果文件比较大,可以把切片bs的长度写大一些

3. 使用io包的copy方法

func CopyFile2(srcFile, destFile string)(int64, error)  {
    file1,err := os.Open(srcFile)
    if err != nil {
        return 0, err
    }

    file2,err := os.OpenFile(destFile,os.O_WRONLY|os.O_CREATE,os.ModePerm)
    if err != nil {
        return 0, err
    }

    defer file1.Close()
    defer file2.Close()

    return io.Copy(file2, file1)
}

4. 使用ioutil的读写,一次性读取,一次性写入;不适合较大的文件复制,容易内存溢出

func CopyFile3(srcFile, destFile string)(int, error)  {
    bs,err := ioutil.ReadFile(srcFile)
    if err != nil {
        return 0,err
    }
    err = ioutil.WriteFile(destFile,bs,0777)
    if err != nil {
        return 0,err
    }
    return len(bs), nil
}

5. 总结,后两种方法的性能还是不错的;扩展:CopyN(), CopyBuffer();无论哪个copy方法最终都是有copyBuffer这个私有方法实现的

Golang进阶-io操作

1. I/O操作也叫输入输出操作。I指Input,O指Output;用于读写数据的,有些语言也叫流操作,是指数据通信的通道;

2. io包里最重要的是两个接口:Reader和Writer接口

3. Reader接口

type Reader interface {
    Read(p []byte) (n int, err error)
}
它返回读取的字节数(0 <= n <= len(p))和遇到的任何错误

4. 读取本地aa.txt文件的数据 (asdfgryuio)

  • 打开文件
fileName := "E:/GoPath/src/a/aa.txt"
    file, err := os.Open(fileName)
    if err != nil {
        fmt.Println("err: ", err)
        return
    }
  • 读取数据
//读取数据
    bs := make([]byte, 4, 4)
    //第一次读取
    n, err := file.Read(bs)
    fmt.Println(err)//<nil>
    fmt.Println(n)//4
    fmt.Println(bs)//[97 115 100 102]返回的是每个元素的字节编码数值
    fmt.Println(string(bs))//asdf

    //第二次读取
    n, err = file.Read(bs)
    fmt.Println(err)//<nil>
    fmt.Println(n)//4
    fmt.Println(bs)//[103 114 121 117]返回的是每个元素的字节编码数值
    fmt.Println(string(bs))//gryu

    //第三次读取
    n, err = file.Read(bs)
    fmt.Println(err)//<nil>
    fmt.Println(n)//2
    fmt.Println(bs)//[105 111 121 117]返回的是每个元素的字节编码数值
    fmt.Println(string(bs))//ioyu


    //第四次读取
    n, err = file.Read(bs)
    fmt.Println(err)//EOF
    fmt.Println(n)//0

//读取数据
    bs := make([]byte, 4, 4)
    n := -1
    for  {
        n, err = file.Read(bs)
        if  n == 0 || err == io.EOF{
            fmt.Println("到了文件末尾,结束读取...")
            break
        }
        fmt.Println(string(bs[:n]))

    }
  • 关闭文件
//可以用defer先关闭掉,以防漏写,造成资源泄露
    defer file.Close()

5. Writer接口

type Writer interface {
    Write(p []byte) (n int, err error)
}

6. 写入到本地文件ab.txt

  • 打开文件(已存在的文件用open,不存在的用openFile)
//file, err := os.Open(fileName)
    file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm)


    if err != nil {
        fmt.Println("err: ", err)
        return
    }
  • 关闭文件 (以防忘掉,造成资源泄露)
defer file.Close()
  • 写入内容
//写入数据
    //bs := []byte{65,66,67,68,69,70}
    bs := []byte{97,98,99,100}
    //n,err := file.Write(bs)
    n,err := file.Write(bs[:2])
    fmt.Println(n)
    HandleErr(err)
    file.WriteString("\n")

    //直接写出字符串
    n,err = file.WriteString("Hello world")
    fmt.Println(n)
    HandleErr(err)
    file.WriteString("\n")

    //切片也可以直接写入字符串
    n,err = file.Write([]byte("totay"))
    fmt.Println(n)
    HandleErr(err)

func HandleErr(err error){
    if err != nil {
        log.Fatal(err)
    }
}

7. writeAt方法指定从什么位置开始写,对应readAt从什么位置开始读